[hamradio-commits] [chirp] 01/03: New upstream version 20170714
Iain R. Learmonth
irl at moszumanska.debian.org
Sat Jul 15 23:46:05 UTC 2017
This is an automated email from the git hooks/post-receive script.
irl pushed a commit to branch master
in repository chirp.
commit 13e802fb673ce97dd3cca5b5af67fff0ccebd75e
Author: Iain R. Learmonth <irl at fsfe.org>
Date: Sun Jul 16 00:34:18 2017 +0100
New upstream version 20170714
---
PKG-INFO | 2 +-
chirp/__init__.py | 2 +-
chirp/bandplan_na.py | 20 +-
chirp/drivers/alinco.py | 41 +-
chirp/drivers/baofeng_wp970i.py | 5 +
chirp/drivers/btech.py | 5498 ++++++++++++++++++-------------
chirp/drivers/ft1d.py | 380 ++-
chirp/drivers/ft2d.py | 128 +
chirp/drivers/ft857.py | 4 +-
chirp/drivers/ftm3200d.py | 201 ++
chirp/drivers/generic_xml.py | 5 +-
chirp/drivers/ic2300.py | 385 +++
chirp/drivers/icf.py | 3 +
chirp/drivers/icp7.py | 243 ++
chirp/drivers/icx8x.py | 3 +-
chirp/drivers/id880.py | 40 +-
chirp/drivers/kguv8d.py | 53 +-
chirp/drivers/kyd.py | 49 +-
chirp/drivers/kyd_IP620.py | 55 +-
chirp/drivers/lt725uv.py | 2 +-
chirp/drivers/{kyd.py => radtel_t18.py} | 425 ++-
chirp/drivers/retevis_rt21.py | 49 +-
chirp/drivers/retevis_rt22.py | 54 +-
chirp/drivers/retevis_rt23.py | 867 +++++
chirp/drivers/rh5r_v2.py | 290 ++
chirp/drivers/tdxone_tdq8a.py | 1154 +++++++
chirp/drivers/th9000.py | 16 +-
chirp/drivers/th_uv3r.py | 2 +-
chirp/drivers/thd72.py | 47 +-
chirp/drivers/tk270.py | 46 +-
chirp/drivers/tk760.py | 46 +-
chirp/drivers/tk760g.py | 167 +-
chirp/drivers/tk8102.py | 49 +-
chirp/drivers/uv5r.py | 169 +-
chirp/drivers/uvb5.py | 24 +-
chirp/drivers/vx3.py | 21 +-
chirp/drivers/vx5.py | 31 +-
chirp/drivers/vx7.py | 6 +-
chirp/drivers/vx8.py | 498 +--
chirp/drivers/wouxun.py | 49 +-
chirp/platform.py | 17 +
chirp/ui/mainapp.py | 35 +-
chirp/ui/memedit.py | 8 +-
chirp/ui/radiobrowser.py | 2 +-
chirp/ui/settingsedit.py | 3 +-
chirpw | 5 +-
46 files changed, 8028 insertions(+), 3171 deletions(-)
diff --git a/PKG-INFO b/PKG-INFO
index 95f827c..215ab77 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.0
Name: chirp
-Version: daily-20170124
+Version: daily-20170714
Summary: UNKNOWN
Home-page: UNKNOWN
Author: UNKNOWN
diff --git a/chirp/__init__.py b/chirp/__init__.py
index 6f4f7bd..1e08ef5 100644
--- a/chirp/__init__.py
+++ b/chirp/__init__.py
@@ -17,7 +17,7 @@ import os
import sys
from glob import glob
-CHIRP_VERSION="daily-20170124"
+CHIRP_VERSION="daily-20170714"
module_dir = os.path.dirname(sys.modules["chirp"].__file__)
__all__ = []
diff --git a/chirp/bandplan_na.py b/chirp/bandplan_na.py
index 50bbb27..0ddc781 100644
--- a/chirp/bandplan_na.py
+++ b/chirp/bandplan_na.py
@@ -82,9 +82,9 @@ BANDS_10M = (
bandplan.Band((29000000, 29200000), "AM", mode="AM"),
bandplan.Band((29300000, 29510000), "Satellite Downlinks"),
bandplan.Band((29520000, 29590000), "Repeater Inputs",
- step_khz=10, mode="NFM"),
+ step_khz=10, mode="FM"),
bandplan.Band((29610000, 29700000), "Repeater Outputs",
- step_khz=10, mode="NFM", input_offset=-890000),
+ step_khz=10, mode="FM", input_offset=-890000),
)
BANDS_6M = (
@@ -103,10 +103,10 @@ BANDS_6M = (
input_offset=-500000),
bandplan.Band((51620000, 51680000), "Digital repeater outputs",
input_offset=-500000),
- bandplan.Band((52020000, 52040000), "FM simplex", mode="NFM"),
+ bandplan.Band((52020000, 52040000), "FM simplex", mode="FM"),
bandplan.Band((52500000, 52980000), "Repeater outputs B",
- input_offset=-500000, step_khz=20, mode="NFM"),
- bandplan.Band((53000000, 53100000), "FM simplex", mode="NFM"),
+ input_offset=-500000, step_khz=20, mode="FM"),
+ bandplan.Band((53000000, 53100000), "FM simplex", mode="FM"),
bandplan.Band((53100000, 53400000), "Radio remote control", step_khz=100),
bandplan.Band((53500000, 53980000), "Repeater outputs C",
input_offset=-500000),
@@ -125,13 +125,13 @@ BANDS_2M = (
mode="USB"),
bandplan.Band((144275000, 144300000), "Propagation beacons", mode="CW"),
bandplan.Band((144300000, 144500000), "OSCAR subband"),
- bandplan.Band((144600000, 144900000), "FM repeater inputs", mode="NFM"),
+ bandplan.Band((144600000, 144900000), "FM repeater inputs", mode="FM"),
bandplan.Band((144900000, 145100000), "Weak signal and FM simplex",
- mode="NFM", step_khz=10),
+ mode="FM", step_khz=10),
bandplan.Band((145100000, 145200000), "Linear translator outputs",
input_offset=-600000),
bandplan.Band((145200000, 145500000), "FM repeater outputs",
- input_offset=-600000, mode="NFM",),
+ input_offset=-600000, mode="FM",),
bandplan.Band((145500000, 145800000), "Misc and experimental modes"),
bandplan.Band((145800000, 146000000), "OSCAR subband"),
bandplan.Band((146400000, 146580000), "Simplex"),
@@ -149,12 +149,12 @@ BANDS_1_25M = (
bandplan.Band((222050000, 222060000), "Propagation beacons"),
bandplan.Band((222100000, 222150000), "Weak-signal CW & SSB"),
bandplan.Band((222150000, 222250000), "Local coordinator's option"),
- bandplan.Band((223400000, 223520000), "FM simplex", mode="NFM"),
+ bandplan.Band((223400000, 223520000), "FM simplex", mode="FM"),
bandplan.Band((223520000, 223640000), "Digital, packet"),
bandplan.Band((223640000, 223700000), "Links, control"),
bandplan.Band((223710000, 223850000), "Local coordinator's option"),
bandplan.Band((223850000, 224980000), "Repeater outputs only",
- mode="NFM", input_offset=-1600000),
+ mode="FM", input_offset=-1600000),
)
BANDS_70CM = (
diff --git a/chirp/drivers/alinco.py b/chirp/drivers/alinco.py
index f1b4225..9ff6425 100644
--- a/chirp/drivers/alinco.py
+++ b/chirp/drivers/alinco.py
@@ -18,6 +18,8 @@ from chirp import chirp_common, bitwise, memmap, errors, directory, util
from chirp.settings import RadioSettingGroup, RadioSetting
from chirp.settings import RadioSettingValueBoolean, RadioSettings
+from textwrap import dedent
+
import time
import logging
@@ -612,6 +614,31 @@ class AlincoDJG7EG(AlincoStyleRadio):
_memsize = 0x1a7c0
_range = [(500000, 1300000000)]
+ @classmethod
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.pre_download = _(dedent("""\
+ 1. Ensure your firmware version is 4_10 or higher
+ 2. Turn radio off
+ 3. Connect your interface cable
+ 4. Turn radio on
+ 5. Press and release PTT 3 times while holding MONI key
+ 6. Supported baud rates: 57600 (default) and 19200
+ (rotate dial while holding MONI to change)
+ 7. Click OK
+ """))
+ rp.pre_upload = _(dedent("""\
+ 1. Ensure your firmware version is 4_10 or higher
+ 2. Turn radio off
+ 3. Connect your interface cable
+ 4. Turn radio on
+ 5. Press and release PTT 3 times while holding MONI key
+ 6. Supported baud rates: 57600 (default) and 19200
+ (rotate dial while holding MONI to change)
+ 7. Click OK
+ """))
+ return rp
+
def get_features(self):
rf = chirp_common.RadioFeatures()
rf.has_dtcs_polarity = False
@@ -655,8 +682,18 @@ class AlincoDJG7EG(AlincoStyleRadio):
return data
+ def _detect_baudrate_and_identify(self):
+ if self._identify():
+ return True
+ else:
+ # Apparenly Alinco support suggests to try again at a lower baud
+ # rate if their cable fails with the default rate. See #4355.
+ LOG.info("Could not talk to radio. Trying again at 19200 baud")
+ self.pipe.baudrate = 19200
+ return self._identify()
+
def _download(self, limit):
- self._identify()
+ self._detect_baudrate_and_identify()
data = "\x00"*0x200
@@ -688,7 +725,7 @@ class AlincoDJG7EG(AlincoStyleRadio):
raise Exception("Unexpected response from radio: %s" % resp)
def _upload(self, limit):
- if not self._identify():
+ if not self._detect_baudrate_and_identify():
raise Exception("I can't talk to this model")
for addr in range(0x200, self._memsize, 0x40):
diff --git a/chirp/drivers/baofeng_wp970i.py b/chirp/drivers/baofeng_wp970i.py
index 76c8d92..74b25ef 100644
--- a/chirp/drivers/baofeng_wp970i.py
+++ b/chirp/drivers/baofeng_wp970i.py
@@ -840,11 +840,16 @@ class WP970I(baofeng_common.BaofengCommonHT):
else:
return False
+class RH5XAlias(chirp_common.Alias):
+ VENDOR = "Rugged"
+ MODEL = "RH5X"
+
@directory.register
class BFA58(WP970I):
"""Baofeng BF-A58"""
VENDOR = "Baofeng"
MODEL = "BF-A58"
+ ALIASES = [RH5XAlias]
_fileid = ["BFT515 ", "BFT517 "]
diff --git a/chirp/drivers/btech.py b/chirp/drivers/btech.py
index 0d62ebd..b5e1155 100644
--- a/chirp/drivers/btech.py
+++ b/chirp/drivers/btech.py
@@ -1,4 +1,4 @@
-# Copyright 2016:
+# Copyright 2016-2017:
# * Pavel Milanes CO7WT, <pavelmc at gmail.com>
# * Jim Unroe KC9HI, <rock.unroe at gmail.com>
#
@@ -30,2693 +30,3645 @@ from chirp.settings import RadioSettingGroup, RadioSetting, \
RadioSettingValueFloat, RadioSettings, InvalidValueError
from textwrap import dedent
-MEM_FORMAT = """
-#seekto 0x0000;
-struct {
- lbcd rxfreq[4];
- lbcd txfreq[4];
- ul16 rxtone;
- ul16 txtone;
- u8 unknown0:4,
- scode:4;
- u8 unknown1:2,
- spmute:1,
- unknown2:3,
- optsig:2;
- u8 unknown3:3,
- scramble:1,
- unknown4:3,
- power:1;
- u8 unknown5:1,
- wide:1,
- unknown6:2,
- bcl:1,
- add:1,
- pttid:2;
-} memory[200];
+# A note about the memmory in these radios
+#
+# The real memory of these radios extends to 0x4000
+# On read the factory software only uses up to 0x3200
+# On write it just uploads the contents up to 0x3100
+#
+# The mem beyond 0x3200 holds the ID data
-#seekto 0x0E00;
-struct {
- u8 tdr;
- u8 unknown1;
- u8 sql;
- u8 unknown2[2];
- u8 tot;
- u8 apo; // BTech radios use this as the Auto Power Off time
- // other radios use this as pre-Time Out Alert
- u8 unknown3;
- u8 abr;
- u8 beep;
- u8 unknown4[4];
- u8 dtmfst;
- u8 unknown5[2];
- u8 prisc;
- u8 prich;
- u8 screv;
- u8 unknown6[2];
- u8 pttid;
- u8 pttlt;
- u8 unknown7;
- u8 emctp;
- u8 emcch;
- u8 ringt;
- u8 unknown8;
- u8 camdf;
- u8 cbmdf;
- u8 sync; // BTech radios use this as the display sync setting
- // other radios use this as the auto keypad lock setting
- u8 ponmsg;
- u8 wtled;
- u8 rxled;
- u8 txled;
- u8 unknown9[5];
- u8 anil;
- u8 reps;
- u8 repm;
- u8 tdrab;
- u8 ste;
- u8 rpste;
- u8 rptdl;
- u8 mgain;
- u8 dtmfg;
-} settings;
+MEM_SIZE = 0x4000
+BLOCK_SIZE = 0x40
+TX_BLOCK_SIZE = 0x10
+ACK_CMD = "\x06"
+MODES = ["FM", "NFM"]
+SKIP_VALUES = ["S", ""]
+TONES = chirp_common.TONES
+DTCS = sorted(chirp_common.DTCS_CODES + [645])
+
+# lists related to "extra" settings
+PTTID_LIST = ["OFF", "BOT", "EOT", "BOTH"]
+PTTIDCODE_LIST = ["%s" % x for x in range(1, 16)]
+OPTSIG_LIST = ["OFF", "DTMF", "2TONE", "5TONE"]
+SPMUTE_LIST = ["Tone/DTCS", "Tone/DTCS and Optsig", "Tone/DTCS or Optsig"]
+
+# lists
+LIST_AB = ["A", "B"]
+LIST_ABCD = LIST_AB + ["C", "D"]
+LIST_ANIL = ["3", "4", "5"]
+LIST_APO = ["Off"] + ["%s minutes" % x for x in range(30, 330, 30)]
+LIST_COLOR4 = ["Off", "Blue", "Orange", "Purple"]
+LIST_COLOR8 = ["Black", "White", "Red", "Blue", "Green", "Yellow", "Indego",
+ "Purple", "Gray"]
+LIST_DTMFST = ["OFF", "Keyboard", "ANI", "Keyboad + ANI"]
+LIST_EMCTP = ["TX alarm sound", "TX ANI", "Both"]
+LIST_EMCTPX = ["Off"] + LIST_EMCTP
+LIST_LANGUA = ["English", "Chinese"]
+LIST_MDF = ["Frequency", "Channel", "Name"]
+LIST_OFF1TO9 = ["Off"] + ["%s seconds" % x for x in range(1, 10)]
+LIST_OFF1TO10 = ["Off"] + ["%s seconds" % x for x in range(1, 11)]
+LIST_OFF1TO50 = ["Off"] + ["%s seconds" % x for x in range(1, 51)]
+LIST_PONMSG = ["Full", "Message", "Battery voltage"]
+LIST_REPM = ["Off", "Carrier", "CTCSS or DCS", "Tone", "DTMF"]
+LIST_REPS = ["1000 Hz", "1450 Hz", "1750 Hz", "2100Hz"]
+LIST_RPTDL = ["Off"] + ["%s ms" % x for x in range(1, 10)]
+LIST_SCMODE = ["Off", "PTT-SC", "MEM-SC", "PON-SC"]
+LIST_SHIFT = ["Off", "+", "-"]
+LIST_SKIPTX = ["Off", "Skip 1", "Skip 2"]
+STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0]
+LIST_STEP = [str(x) for x in STEPS]
+LIST_SYNC = ["Off", "AB", "CD", "AB+CD"]
+LIST_TMR = ["OFF", "M+A", "M+B", "M+C", "M+D", "M+A+B", "M+A+C", "M+A+D",
+ "M+B+C", "M+B+D", "M+C+D", "M+A+B+C", "M+A+B+D", "M+A+C+D",
+ "M+B+C+D", "A+B+C+D"]
+LIST_TOT = ["%s sec" % x for x in range(15, 615, 15)]
+LIST_TXDISP = ["Power", "Mic Volume"]
+LIST_TXP = ["High", "Low"]
+LIST_SCREV = ["TO (timeout)", "CO (carrier operated)", "SE (search)"]
+LIST_VFOMR = ["Frequency", "Channel"]
+LIST_WIDE = ["Wide", "Narrow"]
+
+# lists related to DTMF, 2TONE and 5TONE settings
+LIST_5TONE_STANDARDS = ["CCIR1", "CCIR2", "PCCIR", "ZVEI1", "ZVEI2", "ZVEI3",
+ "PZVEI", "DZVEI", "PDZVEI", "EEA", "EIA", "EURO",
+ "CCITT", "NATEL", "MODAT", "none"]
+LIST_5TONE_STANDARDS_without_none = ["CCIR1", "CCIR2", "PCCIR", "ZVEI1",
+ "ZVEI2", "ZVEI3",
+ "PZVEI", "DZVEI", "PDZVEI", "EEA", "EIA",
+ "EURO", "CCITT", "NATEL", "MODAT"]
+LIST_5TONE_STANDARD_PERIODS = ["20", "30", "40", "50", "60", "70", "80", "90",
+ "100", "110", "120", "130", "140", "150", "160",
+ "170", "180", "190", "200"]
+LIST_5TONE_DIGITS = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A",
+ "B", "C", "D", "E", "F"]
+LIST_5TONE_DELAY = ["%s ms" % x for x in range(0, 1010, 10)]
+LIST_5TONE_RESET = ["%s ms" % x for x in range(100, 8100, 100)]
+LIST_5TONE_RESET_COLOR = ["%s ms" % x for x in range(100, 20100, 100)]
+LIST_DTMF_SPEED = ["%s ms" % x for x in range(50, 2010, 10)]
+LIST_DTMF_DIGITS = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B",
+ "C", "D", "#", "*"]
+LIST_DTMF_VALUES = [0x0A, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x0D, 0x0E, 0x0F, 0x00, 0x0C, 0x0B ]
+LIST_DTMF_SPECIAL_DIGITS = [ "*", "#", "A", "B", "C", "D"]
+LIST_DTMF_SPECIAL_VALUES = [ 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00]
+LIST_DTMF_DELAY = ["%s ms" % x for x in range(100, 4100, 100)]
+CHARSET_DTMF_DIGITS = "0123456789AaBbCcDd#*"
+LIST_2TONE_DEC = ["A-B", "A-C", "A-D",
+ "B-A", "B-C", "B-D",
+ "C-A", "C-B", "C-D",
+ "D-A", "D-B", "D-C"]
+LIST_2TONE_RESPONSE = ["None", "Alert", "Transpond", "Alert+Transpond"]
+
+# This is a general serial timeout for all serial read functions.
+# Practice has show that about 0.7 sec will be enough to cover all radios.
+STIMEOUT = 0.7
+
+# this var controls the verbosity in the debug and by default it's low (False)
+# make it True and you will to get a very verbose debug.log
+debug = False
+
+# valid chars on the LCD, Note that " " (space) is stored as "\xFF"
+VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \
+ "`{|}!\"#$%&'()*+,-./:;<=>?@[]^_"
+
+
+##### ID strings #####################################################
+
+# BTECH UV2501 pre-production units
+UV2501pp_fp = "M2C294"
+# BTECH UV2501 pre-production units 2 + and 1st Gen radios
+UV2501pp2_fp = "M29204"
+# B-TECH UV-2501 second generation (2G) radios
+UV2501G2_fp = "BTG214"
+# B-TECH UV-2501 third generation (3G) radios
+UV2501G3_fp = "BTG324"
+
+# B-TECH UV-2501+220 pre-production units
+UV2501_220pp_fp = "M3C281"
+# extra block read for the 2501+220 pre-production units
+# the same for all of this radios so far
+UV2501_220pp_id = " 280528"
+# B-TECH UV-2501+220
+UV2501_220_fp = "M3G201"
+# new variant, let's call it Generation 2
+UV2501_220G2_fp = "BTG211"
+# B-TECH UV-2501+220 third generation (3G)
+UV2501_220G3_fp = "BTG311"
+
+# B-TECH UV-5001 pre-production units + 1st Gen radios
+UV5001pp_fp = "V19204"
+# B-TECH UV-5001 alpha units
+UV5001alpha_fp = "V28204"
+# B-TECH UV-5001 second generation (2G) radios
+UV5001G2_fp = "BTG214"
+# B-TECH UV-5001 second generation (2G2)
+UV5001G22_fp = "V2G204"
+# B-TECH UV-5001 third generation (3G)
+UV5001G3_fp = "BTG304"
+
+# B-TECH UV-25X2
+UV25X2_fp = "UC2012"
+
+# B-TECH UV-25X4
+UV25X4_fp = "UC4014"
+
+# B-TECH UV-50X2
+UV50X2_fp = "UC2M12"
+
+# special var to know when we found a BTECH Gen 3
+BTECH3 = [UV2501G3_fp, UV2501_220G3_fp, UV5001G3_fp]
+
+
+# WACCOM Mini-8900
+MINI8900_fp = "M28854"
+
+
+# QYT KT-UV980
+KTUV980_fp = "H28854"
+
+# QYT KT8900
+KT8900_fp = "M29154"
+# New generations KT8900
+KT8900_fp1 = "M2C234"
+KT8900_fp2 = "M2G1F4"
+KT8900_fp3 = "M2G2F4"
+KT8900_fp4 = "M2G304"
+KT8900_fp5 = "M2G314"
+# this radio has an extra ID
+KT8900_id = "303688"
+
+# KT8900R
+KT8900R_fp = "M3G1F4"
+# Second Generation
+KT8900R_fp1 = "M3G214"
+# another model
+KT8900R_fp2 = "M3C234"
+# another model G4?
+KT8900R_fp3 = "M39164"
+# another model
+KT8900R_fp4 = "M3G314"
+# this radio has an extra ID
+KT8900R_id = "280528"
+
+# KT7900D (quad band)
+KT7900D_fp = "VC4004"
+
+# KT8900D (dual band)
+KT8900D_fp = "VC2002"
+
+# LUITON LT-588UV
+LT588UV_fp = "V2G1F4"
+# Added by rstrickoff gen 2 id
+LT588UV_fp1 = "V2G214"
+
+
+#### MAGICS
+# for the Waccom Mini-8900
+MSTRING_MINI8900 = "\x55\xA5\xB5\x45\x55\x45\x4d\x02"
+# for the B-TECH UV-2501+220 (including pre production ones)
+MSTRING_220 = "\x55\x20\x15\x12\x12\x01\x4d\x02"
+# for the QYT KT8900 & R
+MSTRING_KT8900 = "\x55\x20\x15\x09\x16\x45\x4D\x02"
+MSTRING_KT8900R = "\x55\x20\x15\x09\x25\x01\x4D\x02"
+# magic string for all other models
+MSTRING = "\x55\x20\x15\x09\x20\x45\x4d\x02"
+# for the QYT KT7900D & KT8900D
+MSTRING_KT8900D = "\x55\x20\x16\x08\x01\xFF\xDC\x02"
+# for the BTECH UV-25X2 and UV-50X2
+MSTRING_UV25X2 = "\x55\x20\x16\x12\x28\xFF\xDC\x02"
+# for the BTECH UV-25X4
+MSTRING_UV25X4 = "\x55\x20\x16\x11\x18\xFF\xDC\x02"
+
+
+def _clean_buffer(radio):
+ """Cleaning the read serial buffer, hard timeout to survive an infinite
+ data stream"""
+
+ # touching the serial timeout to optimize the flushing
+ # restored at the end to the default value
+ radio.pipe.timeout = 0.1
+ dump = "1"
+ datacount = 0
+
+ try:
+ while len(dump) > 0:
+ dump = radio.pipe.read(100)
+ datacount += len(dump)
+ # hard limit to survive a infinite serial data stream
+ # 5 times bigger than a normal rx block (69 bytes)
+ if datacount > 345:
+ seriale = "Please check your serial port selection."
+ raise errors.RadioError(seriale)
+
+ # restore the default serial timeout
+ radio.pipe.timeout = STIMEOUT
+
+ except Exception:
+ raise errors.RadioError("Unknown error cleaning the serial buffer")
+
+
+def _rawrecv(radio, amount):
+ """Raw read from the radio device, less intensive way"""
+
+ data = ""
+
+ try:
+ data = radio.pipe.read(amount)
+
+ # DEBUG
+ if debug is True:
+ LOG.debug("<== (%d) bytes:\n\n%s" %
+ (len(data), util.hexprint(data)))
+
+ # fail if no data is received
+ if len(data) == 0:
+ raise errors.RadioError("No data received from radio")
+
+ # notice on the logs if short
+ if len(data) < amount:
+ LOG.warn("Short reading %d bytes from the %d requested." %
+ (len(data), amount))
+
+ except:
+ raise errors.RadioError("Error reading data from radio")
+
+ return data
+
+
+def _send(radio, data):
+ """Send data to the radio device"""
+
+ try:
+ for byte in data:
+ radio.pipe.write(byte)
+ # Some OS (mainly Linux ones) are too fast on the serial and
+ # get the MCU inside the radio stuck in the early stages, this
+ # hits some models more than others.
+ #
+ # To cope with that we introduce a delay on the writes.
+ # Many option have been tested (delaying only after error occures,
+ # after short reads, only for linux, ...)
+ # Finally, a static delay was chosen as simplest of all solutions
+ # (Michael Wagner, OE4AMW)
+ # (for details, see issue 3993)
+ sleep(0.002)
+
+ # DEBUG
+ if debug is True:
+ LOG.debug("==> (%d) bytes:\n\n%s" %
+ (len(data), util.hexprint(data)))
+ except:
+ raise errors.RadioError("Error sending data to radio")
+
+
+def _make_frame(cmd, addr, length, data=""):
+ """Pack the info in the headder format"""
+ frame = "\x06" + struct.pack(">BHB", ord(cmd), addr, length)
+ # add the data if set
+ if len(data) != 0:
+ frame += data
+
+ return frame
+
+
+def _recv(radio, addr):
+ """Get data from the radio all at once to lower syscalls load"""
+
+ # Get the full 69 bytes at a time to reduce load
+ # 1 byte ACK + 4 bytes header + 64 bytes of data (BLOCK_SIZE)
+
+ # get the whole block
+ block = _rawrecv(radio, BLOCK_SIZE + 5)
+
+ # basic check
+ if len(block) < (BLOCK_SIZE + 5):
+ raise errors.RadioError("Short read of the block 0x%04x" % addr)
+
+ # checking for the ack
+ if block[0] != ACK_CMD:
+ raise errors.RadioError("Bad ack from radio in block 0x%04x" % addr)
+
+ # header validation
+ c, a, l = struct.unpack(">BHB", block[1:5])
+ if a != addr or l != BLOCK_SIZE or c != ord("X"):
+ LOG.debug("Invalid header for block 0x%04x" % addr)
+ LOG.debug("CMD: %s ADDR: %04x SIZE: %02x" % (c, a, l))
+ raise errors.RadioError("Invalid header for block 0x%04x:" % addr)
+
+ # return the data
+ return block[5:]
+
+
+def _start_clone_mode(radio, status):
+ """Put the radio in clone mode and get the ident string, 3 tries"""
+
+ # cleaning the serial buffer
+ _clean_buffer(radio)
+
+ # prep the data to show in the UI
+ status.cur = 0
+ status.msg = "Identifying the radio..."
+ status.max = 3
+ radio.status_fn(status)
+
+ try:
+ for a in range(0, status.max):
+ # Update the UI
+ status.cur = a + 1
+ radio.status_fn(status)
+
+ # send the magic word
+ _send(radio, radio._magic)
+
+ # Now you get a x06 of ACK if all goes well
+ ack = radio.pipe.read(1)
+
+ if ack == "\x06":
+ # DEBUG
+ LOG.info("Magic ACK received")
+ status.cur = status.max
+ radio.status_fn(status)
+
+ return True
-#seekto 0x0E80;
-struct {
- u8 unknown1;
- u8 vfomr;
- u8 keylock;
- u8 unknown2;
- u8 unknown3:4,
- vfomren:1,
- unknown4:1,
- reseten:1,
- menuen:1;
- u8 unknown5[11];
- u8 dispab;
- u8 mrcha;
- u8 mrchb;
- u8 menu;
-} settings2;
+ return False
-#seekto 0x0EC0;
-struct {
- char line1[6];
- char line2[6];
-} poweron_msg;
+ except errors.RadioError:
+ raise
+ except Exception, e:
+ raise errors.RadioError("Error sending Magic to radio:\n%s" % e)
-struct settings_vfo {
- u8 freq[8];
- u8 unknown1;
- u8 offset[4];
- u8 unknown2[3];
- ul16 rxtone;
- ul16 txtone;
- u8 scode;
- u8 spmute;
- u8 optsig;
- u8 scramble;
- u8 wide;
- u8 power;
- u8 shiftd;
- u8 step;
- u8 unknown3[4];
-};
-#seekto 0x0F00;
-struct {
- struct settings_vfo a;
- struct settings_vfo b;
-} vfo;
+def _do_ident(radio, status, upload=False):
+ """Put the radio in PROGRAM mode & identify it"""
+ # set the serial discipline
+ radio.pipe.baudrate = 9600
+ radio.pipe.parity = "N"
-#seekto 0x1000;
-struct {
- char name[6];
- u8 unknown1[10];
-} names[200];
+ # open the radio into program mode
+ if _start_clone_mode(radio, status) is False:
+ msg = "Radio did not enter clone mode"
+ # warning about old versions of QYT KT8900
+ if radio.MODEL == "KT8900":
+ msg += ". You may want to try it as a WACCOM MINI-8900, there is a"
+ msg += " known variant of this radios that is a clone of it."
+ raise errors.RadioError(msg)
-#seekto 0x2400;
-struct {
- u8 period; // one out of LIST_5TONE_STANDARD_PERIODS
- u8 group_tone;
- u8 repeat_tone;
- u8 unused[13];
-} _5tone_std_settings[15];
+ # Ok, get the ident string
+ ident = _rawrecv(radio, 49)
-#seekto 0x2500;
-struct {
- u8 frame1[5];
- u8 frame2[5];
- u8 frame3[5];
- u8 standard; // one out of LIST_5TONE_STANDARDS
-} _5tone_codes[15];
+ # basic check for the ident
+ if len(ident) != 49:
+ raise errors.RadioError("Radio send a short ident block.")
-#seekto 0x25F0;
-struct {
- u8 _5tone_delay1; // * 10ms
- u8 _5tone_delay2; // * 10ms
- u8 _5tone_delay3; // * 10ms
- u8 _5tone_first_digit_ext_length;
- u8 unknown1;
- u8 unknown2;
- u8 unknown3;
- u8 unknown4;
- u8 decode_standard;
- u8 unknown5:5,
- _5tone_decode_call_frame3:1,
- _5tone_decode_call_frame2:1,
- _5tone_decode_call_frame1:1;
- u8 unknown6:5,
- _5tone_decode_disp_frame3:1,
- _5tone_decode_disp_frame2:1,
- _5tone_decode_disp_frame1:1;
- u8 decode_reset_time; // * 100 + 100ms
-} _5tone_settings;
+ # check if ident is OK
+ itis = False
+ for fp in radio._fileid:
+ if fp in ident:
+ # got it!
+ itis = True
+ # checking if we are dealing with a Gen 3 BTECH
+ if radio.VENDOR == "BTECH" and fp in BTECH3:
+ radio.btech3 = True
-#seekto 0x2900;
-struct {
- u8 code[16]; // 0=x0A, A=0x0D, B=0x0E, C=0x0F, D=0x00, #=0x0C *=0x0B
-} dtmf_codes[15];
+ break
-#seekto 0x29F0;
-struct {
- u8 dtmfspeed_on; //list with 50..2000ms in steps of 10
- u8 dtmfspeed_off; //list with 50..2000ms in steps of 10
- u8 unknown0[14];
- u8 inspection[16];
- u8 monitor[16];
- u8 alarmcode[16];
- u8 stun[16];
- u8 kill[16];
- u8 revive[16];
- u8 unknown1[16];
- u8 unknown2[16];
- u8 unknown3[16];
- u8 unknown4[16];
- u8 unknown5[16];
- u8 unknown6[16];
- u8 unknown7[16];
- u8 masterid[16];
- u8 viceid[16];
- u8 unused01:7,
- mastervice:1;
- u8 unused02:3,
- mrevive:1,
- mkill:1,
- mstun:1,
- mmonitor:1,
- minspection:1;
- u8 unused03:3,
- vrevive:1,
- vkill:1,
- vstun:1,
- vmonitor:1,
- vinspection:1;
- u8 unused04:6,
- txdisable:1,
- rxdisable:1;
- u8 groupcode;
- u8 spacecode;
- u8 delayproctime; // * 100 + 100ms
- u8 resettime; // * 100 + 100ms
-} dtmf_settings;
+ if itis is False:
+ LOG.debug("Incorrect model ID, got this:\n\n" + util.hexprint(ident))
+ raise errors.RadioError("Radio identification failed.")
-#seekto 0x2D00;
-struct {
- struct {
- ul16 freq1;
- u8 unused01[6];
- ul16 freq2;
- u8 unused02[6];
- } _2tone_encode[15];
- u8 duration_1st_tone; // *10ms
- u8 duration_2nd_tone; // *10ms
- u8 duration_gap; // *10ms
- u8 unused03[13];
- struct {
- struct {
- u8 dec; // one out of LIST_2TONE_DEC
- u8 response; // one out of LIST_2TONE_RESPONSE
- u8 alert; // 1-16
- } decs[4];
- u8 unused04[4];
- } _2tone_decode[15];
- u8 unused05[16];
+ # some radios needs a extra read and check for a code on it, this ones
+ # has the check value in the _id2 var, others simply False
+ if radio._id2 is not False:
+ # lower the timeout here as this radios are reseting due to timeout
+ radio.pipe.timeout = 0.05
- struct {
- ul16 freqA;
- ul16 freqB;
- ul16 freqC;
- ul16 freqD;
- // unknown what those values mean, but they are
- // derived from configured frequencies
- ul16 derived_from_freqA; // 2304000/freqA
- ul16 derived_from_freqB; // 2304000/freqB
- ul16 derived_from_freqC; // 2304000/freqC
- ul16 derived_from_freqD; // 2304000/freqD
- }freqs[15];
- u8 reset_time; // * 100 + 100ms - 100-8000ms
-} _2tone;
+ # query & receive the extra ID
+ _send(radio, _make_frame("S", 0x3DF0, 16))
+ id2 = _rawrecv(radio, 21)
+
+ # WARNING !!!!!!
+ # different radios send a response with a different amount of data
+ # it seems that it's padded with \xff, \x20 and some times with \x00
+ # we just care about the first 16, our magic string is in there
+ if len(id2) < 16:
+ raise errors.RadioError("The extra ID is short, aborting.")
+
+ # ok, the correct string must be in the received data
+ if radio._id2 not in id2:
+ LOG.debug("Full *BAD* extra ID on the %s is: \n%s" %
+ (radio.MODEL, util.hexprint(id2)))
+ raise errors.RadioError("The extra ID is wrong, aborting.")
+
+ # this radios need a extra request/answer here on the upload
+ # the amount of data received depends of the radio type
+ #
+ # also the first block of TX must no have the ACK at the beginning
+ # see _upload for this.
+ if upload is True:
+ # send an ACK
+ _send(radio, ACK_CMD)
+
+ # the amount of data depend on the radio, so far we have two radios
+ # reading two bytes with an ACK at the end and just ONE with just
+ # one byte (QYT KT8900)
+ # the JT-6188 appears a clone of the last, but reads TWO bytes.
+ #
+ # we will read two bytes with a custom timeout to not penalize the
+ # users for this.
+ #
+ # we just check for a response and last byte being a ACK, that is
+ # the common stone for all radios (3 so far)
+ ack = _rawrecv(radio, 2)
+
+ # checking
+ if len(ack) == 0 or ack[-1:] != ACK_CMD:
+ raise errors.RadioError("Radio didn't ACK the upload")
+
+ # restore the default serial timeout
+ radio.pipe.timeout = STIMEOUT
+
+ # DEBUG
+ LOG.info("Positive ident, this is a %s %s" % (radio.VENDOR, radio.MODEL))
+
+ return True
+
+
+def _download(radio):
+ """Get the memory map"""
-#seekto 0x3000;
-struct {
- u8 freq[8];
- char broadcast_station_name[6];
- u8 unknown[2];
-} fm_radio_preset[16];
+ # UI progress
+ status = chirp_common.Status()
-#seekto 0x3C90;
-struct {
- u8 vhf_low[3];
- u8 vhf_high[3];
- u8 uhf_low[3];
- u8 uhf_high[3];
-} ranges;
+ # put radio in program mode and identify it
+ _do_ident(radio, status)
-// the UV-2501+220 & KT8900R has different zones for storing ranges
+ # the models that doesn't have the extra ID have to make a dummy read here
+ if radio._id2 is False:
+ _send(radio, _make_frame("S", 0, BLOCK_SIZE))
+ discard = _rawrecv(radio, BLOCK_SIZE + 5)
-#seekto 0x3CD0;
-struct {
- u8 vhf_low[3];
- u8 vhf_high[3];
- u8 unknown1[4];
- u8 unknown2[6];
- u8 vhf2_low[3];
- u8 vhf2_high[3];
- u8 unknown3[4];
- u8 unknown4[6];
- u8 uhf_low[3];
- u8 uhf_high[3];
-} ranges220;
+ if debug is True:
+ LOG.info("Dummy first block read done, got this:\n\n %s",
+ util.hexprint(discard))
-#seekto 0x3F70;
-struct {
- char fp[6];
-} fingerprint;
+ # reset the progress bar in the UI
+ status.max = MEM_SIZE / BLOCK_SIZE
+ status.msg = "Cloning from radio..."
+ status.cur = 0
+ radio.status_fn(status)
-"""
+ # cleaning the serial buffer
+ _clean_buffer(radio)
-# A note about the memmory in these radios
-#
-# The real memory of these radios extends to 0x4000
-# On read the factory software only uses up to 0x3200
-# On write it just uploads the contents up to 0x3100
-#
-# The mem beyond 0x3200 holds the ID data
+ data = ""
+ for addr in range(0, MEM_SIZE, BLOCK_SIZE):
+ # sending the read request
+ _send(radio, _make_frame("S", addr, BLOCK_SIZE))
-MEM_SIZE = 0x4000
-BLOCK_SIZE = 0x40
-TX_BLOCK_SIZE = 0x10
-ACK_CMD = "\x06"
-MODES = ["FM", "NFM"]
-SKIP_VALUES = ["S", ""]
-TONES = chirp_common.TONES
-DTCS = sorted(chirp_common.DTCS_CODES + [645])
-NAME_LENGTH = 6
-PTTID_LIST = ["OFF", "BOT", "EOT", "BOTH"]
-PTTIDCODE_LIST = ["%s" % x for x in range(1, 16)]
-OPTSIG_LIST = ["OFF", "DTMF", "2TONE", "5TONE"]
-SPMUTE_LIST = ["Tone/DTCS", "Tone/DTCS and Optsig", "Tone/DTCS or Optsig"]
+ # read
+ d = _recv(radio, addr)
-LIST_TOT = ["%s sec" % x for x in range(15, 615, 15)]
-LIST_TOA = ["Off"] + ["%s seconds" % x for x in range(1, 11)]
-LIST_APO = ["Off"] + ["%s minutes" % x for x in range(30, 330, 30)]
-LIST_ABR = ["Off"] + ["%s seconds" % x for x in range(1, 51)]
-LIST_DTMFST = ["OFF", "Keyboard", "ANI", "Keyboad + ANI"]
-LIST_SCREV = ["TO (timeout)", "CO (carrier operated)", "SE (search)"]
-LIST_EMCTP = ["TX alarm sound", "TX ANI", "Both"]
-LIST_RINGT = ["Off"] + ["%s seconds" % x for x in range(1, 10)]
-LIST_MDF = ["Frequency", "Channel", "Name"]
-LIST_PONMSG = ["Full", "Message", "Battery voltage"]
-LIST_COLOR = ["Off", "Blue", "Orange", "Purple"]
-LIST_REPS = ["1000 Hz", "1450 Hz", "1750 Hz", "2100Hz"]
-LIST_REPM = ["Off", "Carrier", "CTCSS or DCS", "Tone", "DTMF"]
-LIST_RPTDL = ["Off"] + ["%s ms" % x for x in range(1, 10)]
-LIST_ANIL = ["3", "4", "5"]
-LIST_AB = ["A", "B"]
-LIST_VFOMR = ["Frequency", "Channel"]
-LIST_SHIFT = ["Off", "+", "-"]
-LIST_TXP = ["High", "Low"]
-LIST_WIDE = ["Wide", "Narrow"]
-STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0]
-LIST_STEP = [str(x) for x in STEPS]
-LIST_5TONE_STANDARDS = ["CCIR1", "CCIR2", "PCCIR", "ZVEI1", "ZVEI2", "ZVEI3",
- "PZVEI", "DZVEI", "PDZVEI", "EEA", "EIA", "EURO",
- "CCITT", "NATEL", "MODAT", "none"]
-LIST_5TONE_STANDARDS_without_none = ["CCIR1", "CCIR2", "PCCIR", "ZVEI1",
- "ZVEI2", "ZVEI3",
- "PZVEI", "DZVEI", "PDZVEI", "EEA", "EIA",
- "EURO", "CCITT", "NATEL", "MODAT"]
-LIST_5TONE_STANDARD_PERIODS = ["20", "30", "40", "50", "60", "70", "80", "90",
- "100", "110", "120", "130", "140", "150", "160",
- "170", "180", "190", "200"]
-LIST_5TONE_DIGITS = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A",
- "B", "C", "D", "E", "F"]
-LIST_5TONE_DELAY = ["%s ms" % x for x in range(0, 1010, 10)]
-LIST_5TONE_RESET = ["%s ms" % x for x in range(100, 8100, 100)]
-LIST_DTMF_SPEED = ["%s ms" % x for x in range(50, 2010, 10)]
-LIST_DTMF_DIGITS = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B",
- "C", "D", "#", "*"]
-LIST_DTMF_VALUES = [0x0A, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
- 0x0D, 0x0E, 0x0F, 0x00, 0x0C, 0x0B ]
-LIST_DTMF_SPECIAL_DIGITS = [ "*", "#", "A", "B", "C", "D"]
-LIST_DTMF_SPECIAL_VALUES = [ 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00]
-LIST_DTMF_DELAY = ["%s ms" % x for x in range(100, 4100, 100)]
-CHARSET_DTMF_DIGITS = "0123456789AaBbCcDd#*"
-LIST_2TONE_DEC = ["A-B", "A-C", "A-D",
- "B-A", "B-C", "B-D",
- "C-A", "C-B", "C-D",
- "D-A", "D-B", "D-C"]
-LIST_2TONE_RESPONSE = ["None", "Alert", "Transpond", "Alert+Transpond"]
+ # aggregate the data
+ data += d
-# This is a general serial timeout for all serial read functions.
-# Practice has show that about 0.7 sec will be enough to cover all radios.
-STIMEOUT = 0.7
+ # UI Update
+ status.cur = addr / BLOCK_SIZE
+ status.msg = "Cloning from radio..."
+ radio.status_fn(status)
-# this var controls the verbosity in the debug and by default it's low (False)
-# make it True and you will to get a very verbose debug.log
-debug = False
+ return data
-# Power Levels
-NORMAL_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=25),
- chirp_common.PowerLevel("Low", watts=10)]
-UV5001_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=50),
- chirp_common.PowerLevel("Low", watts=10)]
-# this must be defined globaly
-POWER_LEVELS = None
+def _upload(radio):
+ """Upload procedure"""
-# valid chars on the LCD, Note that " " (space) is stored as "\xFF"
-VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \
- "`{|}!\"#$%&'()*+,-./:;<=>?@[]^_"
+ # The UPLOAD mem is restricted to lower than 0x3100,
+ # so we will overide that here localy
+ MEM_SIZE = 0x3100
+ # UI progress
+ status = chirp_common.Status()
-##### ID strings #####################################################
+ # put radio in program mode and identify it
+ _do_ident(radio, status, True)
-# BTECH UV2501 pre-production units
-UV2501pp_fp = "M2C294"
-# BTECH UV2501 pre-production units 2 + and 1st Gen radios
-UV2501pp2_fp = "M29204"
-# B-TECH UV-2501 second generation (2G) radios
-UV2501G2_fp = "BTG214"
-# B-TECH UV-2501 third generation (3G) radios
-UV2501G3_fp = "BTG324"
+ # get the data to upload to radio
+ data = radio.get_mmap()
-# B-TECH UV-2501+220 pre-production units
-UV2501_220pp_fp = "M3C281"
-# extra block read for the 2501+220 pre-production units
-# the same for all of this radios so far
-UV2501_220pp_id = " 280528"
-# B-TECH UV-2501+220
-UV2501_220_fp = "M3G201"
-# new variant, let's call it Generation 2
-UV2501_220G2_fp = "BTG211"
-# B-TECH UV-2501+220 third generation (3G)
-UV2501_220G3_fp = "BTG311"
+ # Reset the UI progress
+ status.max = MEM_SIZE / TX_BLOCK_SIZE
+ status.cur = 0
+ status.msg = "Cloning to radio..."
+ radio.status_fn(status)
-# B-TECH UV-5001 pre-production units + 1st Gen radios
-UV5001pp_fp = "V19204"
-# B-TECH UV-5001 alpha units
-UV5001alpha_fp = "V28204"
-# B-TECH UV-5001 second generation (2G) radios
-UV5001G2_fp = "BTG214"
-# B-TECH UV-5001 second generation (2G2)
-UV5001G22_fp = "V2G204"
-# B-TECH UV-5001 third generation (3G)
-UV5001G3_fp = "BTG304"
+ # the radios that doesn't have the extra ID 'may' do a dummy write, I found
+ # that leveraging the bad ACK and NOT doing the dummy write is ok, as the
+ # dummy write is accepted (it actually writes to the mem!) by the radio.
-# special var to know when we found a BTECH Gen 3
-BTECH3 = [UV2501G3_fp, UV2501_220G3_fp, UV5001G3_fp]
+ # cleaning the serial buffer
+ _clean_buffer(radio)
+ # the fun start here
+ for addr in range(0, MEM_SIZE, TX_BLOCK_SIZE):
+ # getting the block of data to send
+ d = data[addr:addr + TX_BLOCK_SIZE]
-# WACCOM Mini-8900
-MINI8900_fp = "M28854"
+ # build the frame to send
+ frame = _make_frame("X", addr, TX_BLOCK_SIZE, d)
+ # first block must not send the ACK at the beginning for the
+ # ones that has the extra id, since this have to do a extra step
+ if addr == 0 and radio._id2 is not False:
+ frame = frame[1:]
-# QYT KT-UV980
-KTUV980_fp = "H28854"
+ # send the frame
+ _send(radio, frame)
-# QYT KT8900
-KT8900_fp = "M29154"
-# New generations KT8900
-KT8900_fp1 = "M2C234"
-KT8900_fp2 = "M2G1F4"
-KT8900_fp3 = "M2G2F4"
-KT8900_fp4 = "M2G304"
-KT8900_fp5 = "M2G314"
-# this radio has an extra ID
-KT8900_id = " 303688"
+ # receiving the response
+ ack = _rawrecv(radio, 1)
-# KT8900R
-KT8900R_fp = "M3G1F4"
-# Second Generation
-KT8900R_fp1 = "M3G214"
-# another model
-KT8900R_fp2 = "M3C234"
-# another model G4?
-KT8900R_fp3 = "M39164"
-# another model
-KT8900R_fp4 = "M3G314"
-# this radio has an extra ID
-KT8900R_id = "280528"
+ # basic check
+ if len(ack) != 1:
+ raise errors.RadioError("No ACK when writing block 0x%04x" % addr)
+ if not ack in "\x06\x05":
+ raise errors.RadioError("Bad ACK writing block 0x%04x:" % addr)
-# LUITON LT-588UV
-LT588UV_fp = "V2G1F4"
-# Added by rstrickoff gen 2 id
-LT588UV_fp1 = "V2G214"
+ # UI Update
+ status.cur = addr / TX_BLOCK_SIZE
+ status.msg = "Cloning to radio..."
+ radio.status_fn(status)
-#### MAGICS
-# for the Waccom Mini-8900
-MSTRING_MINI8900 = "\x55\xA5\xB5\x45\x55\x45\x4d\x02"
-# for the B-TECH UV-2501+220 (including pre production ones)
-MSTRING_220 = "\x55\x20\x15\x12\x12\x01\x4d\x02"
-# for the QYT KT8900 & R
-MSTRING_KT8900 = "\x55\x20\x15\x09\x16\x45\x4D\x02"
-MSTRING_KT8900R = "\x55\x20\x15\x09\x25\x01\x4D\x02"
-# magic string for all other models
-MSTRING = "\x55\x20\x15\x09\x20\x45\x4d\x02"
+def model_match(cls, data):
+ """Match the opened/downloaded image to the correct version"""
+ rid = data[0x3f70:0x3f76]
+ if rid in cls._fileid:
+ return True
-def _clean_buffer(radio):
- """Cleaning the read serial buffer, hard timeout to survive an infinite
- data stream"""
+ return False
- # touching the serial timeout to optimize the flushing
- # restored at the end to the default value
- radio.pipe.timeout = 0.1
- dump = "1"
- datacount = 0
- try:
- while len(dump) > 0:
- dump = radio.pipe.read(100)
- datacount += len(dump)
- # hard limit to survive a infinite serial data stream
- # 5 times bigger than a normal rx block (69 bytes)
- if datacount > 345:
- seriale = "Please check your serial port selection."
- raise errors.RadioError(seriale)
+def _decode_ranges(low, high):
+ """Unpack the data in the ranges zones in the memmap and return
+ a tuple with the integer corresponding to the Mhz it means"""
+ ilow = int(low[0]) * 100 + int(low[1]) * 10 + int(low[2])
+ ihigh = int(high[0]) * 100 + int(high[1]) * 10 + int(high[2])
+ ilow *= 1000000
+ ihigh *= 1000000
- # restore the default serial timeout
- radio.pipe.timeout = STIMEOUT
+ return (ilow, ihigh)
- except Exception:
- raise errors.RadioError("Unknown error cleaning the serial buffer")
+def _split(rf, f1, f2):
+ """Returns False if the two freqs are in the same band (no split)
+ or True otherwise"""
-def _rawrecv(radio, amount):
- """Raw read from the radio device, less intensive way"""
+ # determine if the two freqs are in the same band
+ for low, high in rf.valid_bands:
+ if f1 >= low and f1 <= high and \
+ f2 >= low and f2 <= high:
+ # if the two freqs are on the same Band this is not a split
+ return False
- data = ""
+ # if you get here is because the freq pairs are split
+ return True
- try:
- data = radio.pipe.read(amount)
- # DEBUG
- if debug is True:
- LOG.debug("<== (%d) bytes:\n\n%s" %
- (len(data), util.hexprint(data)))
+class BTechMobileCommon(chirp_common.CloneModeRadio,
+ chirp_common.ExperimentalRadio):
+ """BTECH's UV-5001 and alike radios"""
+ VENDOR = "BTECH"
+ MODEL = ""
+ IDENT = ""
+ BANDS = 2
+ COLOR_LCD = False
+ NAME_LENGTH = 6
+ _power_levels = [chirp_common.PowerLevel("High", watts=25),
+ chirp_common.PowerLevel("Low", watts=10)]
+ _vhf_range = (130000000, 180000000)
+ _220_range = (200000000, 271000000)
+ _uhf_range = (400000000, 521000000)
+ _350_range = (350000000, 391000000)
+ _upper = 199
+ _magic = MSTRING
+ _fileid = None
+ _id2 = False
+ btech3 = False
- # fail if no data is received
- if len(data) == 0:
- raise errors.RadioError("No data received from radio")
+ @classmethod
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.experimental = \
+ ('This driver is experimental.\n'
+ '\n'
+ 'Please keep a copy of your memories with the original software '
+ 'if you treasure them, this driver is new and may contain'
+ ' bugs.\n'
+ '\n'
+ )
+ rp.pre_download = _(dedent("""\
+ Follow these instructions to download your info:
- # notice on the logs if short
- if len(data) < amount:
- LOG.warn("Short reading %d bytes from the %d requested." %
- (len(data), amount))
+ 1 - Turn off your radio
+ 2 - Connect your interface cable
+ 3 - Turn on your radio
+ 4 - Do the download of your radio data
- except:
- raise errors.RadioError("Error reading data from radio")
+ """))
+ rp.pre_upload = _(dedent("""\
+ Follow these instructions to upload your info:
- return data
+ 1 - Turn off your radio
+ 2 - Connect your interface cable
+ 3 - Turn on your radio
+ 4 - Do the upload of your radio data
+ """))
+ return rp
-def _send(radio, data):
- """Send data to the radio device"""
+ def get_features(self):
+ """Get the radio's features"""
- try:
- for byte in data:
- radio.pipe.write(byte)
- # Some OS (mainly Linux ones) are too fast on the serial and
- # get the MCU inside the radio stuck in the early stages, this
- # hits some models more than others.
- #
- # To cope with that we introduce a delay on the writes.
- # Many option have been tested (delaying only after error occures, after short reads, only for linux, ...)
- # Finally, a static delay was chosen as simplest of all solutions (Michael Wagner, OE4AMW)
- # (for details, see issue 3993)
- sleep(0.002)
+ # we will use the following var as global
+ global POWER_LEVELS
- # DEBUG
- if debug is True:
- LOG.debug("==> (%d) bytes:\n\n%s" %
- (len(data), util.hexprint(data)))
- except:
- raise errors.RadioError("Error sending data to radio")
+ rf = chirp_common.RadioFeatures()
+ rf.has_settings = True
+ rf.has_bank = False
+ rf.has_tuning_step = False
+ rf.can_odd_split = True
+ 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_characters = VALID_CHARS
+ rf.valid_name_length = self.NAME_LENGTH
+ rf.valid_duplexes = ["", "-", "+", "split", "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_skips = SKIP_VALUES
+ rf.valid_dtcs_codes = DTCS
+ rf.memory_bounds = (0, self._upper)
+ # power levels
+ POWER_LEVELS = self._power_levels
+ rf.valid_power_levels = POWER_LEVELS
-def _make_frame(cmd, addr, length, data=""):
- """Pack the info in the headder format"""
- frame = "\x06" + struct.pack(">BHB", ord(cmd), addr, length)
- # add the data if set
- if len(data) != 0:
- frame += data
+ # normal dual bands
+ rf.valid_bands = [self._vhf_range, self._uhf_range]
- return frame
+ # 220 band
+ if self.BANDS == 3 or self.BANDS == 4:
+ rf.valid_bands.append(self._220_range)
+ # 350 band
+ if self.BANDS == 4:
+ rf.valid_bands.append(self._350_range)
-def _recv(radio, addr):
- """Get data from the radio all at once to lower syscalls load"""
+ return rf
- # Get the full 69 bytes at a time to reduce load
- # 1 byte ACK + 4 bytes header + 64 bytes of data (BLOCK_SIZE)
+ def sync_in(self):
+ """Download from radio"""
+ data = _download(self)
+ self._mmap = memmap.MemoryMap(data)
+ self.process_mmap()
- # get the whole block
- block = _rawrecv(radio, BLOCK_SIZE + 5)
+ def sync_out(self):
+ """Upload to radio"""
+ try:
+ _upload(self)
+ except errors.RadioError:
+ raise
+ except Exception, e:
+ raise errors.RadioError("Error: %s" % e)
- # basic check
- if len(block) < (BLOCK_SIZE + 5):
- raise errors.RadioError("Short read of the block 0x%04x" % addr)
+ def get_raw_memory(self, number):
+ return repr(self._memobj.memory[number])
- # checking for the ack
- if block[0] != ACK_CMD:
- raise errors.RadioError("Bad ack from radio in block 0x%04x" % addr)
+ def _decode_tone(self, val):
+ """Parse the tone data to decode from mem, it returns:
+ Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
+ pol = None
- # header validation
- c, a, l = struct.unpack(">BHB", block[1:5])
- if a != addr or l != BLOCK_SIZE or c != ord("X"):
- LOG.debug("Invalid header for block 0x%04x" % addr)
- LOG.debug("CMD: %s ADDR: %04x SIZE: %02x" % (c, a, l))
- raise errors.RadioError("Invalid header for block 0x%04x:" % addr)
+ if val in [0, 65535]:
+ return '', None, None
+ elif val > 0x0258:
+ a = val / 10.0
+ return 'Tone', a, pol
+ else:
+ if val > 0x69:
+ index = val - 0x6A
+ pol = "R"
+ else:
+ index = val - 1
+ pol = "N"
- # return the data
- return block[5:]
+ tone = DTCS[index]
+ return 'DTCS', tone, pol
+
+ def _encode_tone(self, memval, mode, val, pol):
+ """Parse the tone data to encode from UI to mem"""
+ if mode == '' or mode is None:
+ memval.set_raw("\x00\x00")
+ elif mode == 'Tone':
+ memval.set_value(val * 10)
+ elif mode == 'DTCS':
+ # detect the index in the DTCS list
+ try:
+ index = DTCS.index(val)
+ if pol == "N":
+ index += 1
+ else:
+ index += 0x6A
+ memval.set_value(index)
+ except:
+ msg = "Digital Tone '%d' is not supported" % value
+ LOG.error(msg)
+ raise errors.RadioError(msg)
+ else:
+ msg = "Internal error: invalid mode '%s'" % mode
+ LOG.error(msg)
+ raise errors.InvalidDataError(msg)
+ def get_memory(self, number):
+ """Get the mem representation from the radio image"""
+ _mem = self._memobj.memory[number]
+ _names = self._memobj.names[number]
-def _start_clone_mode(radio, status):
- """Put the radio in clone mode and get the ident string, 3 tries"""
+ # Create a high-level memory object to return to the UI
+ mem = chirp_common.Memory()
- # cleaning the serial buffer
- _clean_buffer(radio)
+ # Memory number
+ mem.number = number
- # prep the data to show in the UI
- status.cur = 0
- status.msg = "Identifying the radio..."
- status.max = 3
- radio.status_fn(status)
+ if _mem.get_raw()[0] == "\xFF":
+ mem.empty = True
+ return mem
- try:
- for a in range(0, status.max):
- # Update the UI
- status.cur = a + 1
- radio.status_fn(status)
+ # 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 freq set
+ offset = (int(_mem.txfreq) * 10) - mem.freq
+ if offset != 0:
+ if _split(self.get_features(), mem.freq, int(_mem.txfreq) * 10):
+ mem.duplex = "split"
+ mem.offset = int(_mem.txfreq) * 10
+ elif offset < 0:
+ mem.offset = abs(offset)
+ mem.duplex = "-"
+ elif offset > 0:
+ mem.offset = offset
+ mem.duplex = "+"
+ else:
+ mem.offset = 0
- # send the magic word
- _send(radio, radio._magic)
+ # name TAG of the channel
+ mem.name = str(_names.name).rstrip("\xFF").replace("\xFF", " ")
- # Now you get a x06 of ACK if all goes well
- ack = radio.pipe.read(1)
+ # power
+ mem.power = POWER_LEVELS[int(_mem.power)]
- if ack == "\x06":
- # DEBUG
- LOG.info("Magic ACK received")
- status.cur = status.max
- radio.status_fn(status)
+ # wide/narrow
+ mem.mode = MODES[int(_mem.wide)]
- return True
+ # skip
+ mem.skip = SKIP_VALUES[_mem.add]
- return False
+ # tone data
+ rxtone = txtone = None
+ txtone = self._decode_tone(_mem.txtone)
+ rxtone = self._decode_tone(_mem.rxtone)
+ chirp_common.split_tone_decode(mem, txtone, rxtone)
- except errors.RadioError:
- raise
- except Exception, e:
- raise errors.RadioError("Error sending Magic to radio:\n%s" % e)
+ # Extra
+ mem.extra = RadioSettingGroup("extra", "Extra")
+ if not self.COLOR_LCD or \
+ (self.COLOR_LCD and not self.VENDOR == "BTECH"):
+ scramble = RadioSetting("scramble", "Scramble",
+ RadioSettingValueBoolean(bool(
+ _mem.scramble)))
+ mem.extra.append(scramble)
-def _do_ident(radio, status, upload=False):
- """Put the radio in PROGRAM mode & identify it"""
- # set the serial discipline
- radio.pipe.baudrate = 9600
- radio.pipe.parity = "N"
+ bcl = RadioSetting("bcl", "Busy channel lockout",
+ RadioSettingValueBoolean(bool(_mem.bcl)))
+ mem.extra.append(bcl)
- # open the radio into program mode
- if _start_clone_mode(radio, status) is False:
- msg = "Radio did not enter clone mode"
- # warning about old versions of QYT KT8900
- if radio.MODEL == "KT8900":
- msg += ". You may want to try it as a WACCOM MINI-8900, there is a"
- msg += " known variant of this radios that is a clone of it."
- raise errors.RadioError(msg)
+ pttid = RadioSetting("pttid", "PTT ID",
+ RadioSettingValueList(PTTID_LIST,
+ PTTID_LIST[_mem.pttid]))
+ mem.extra.append(pttid)
- # Ok, get the ident string
- ident = _rawrecv(radio, 49)
+ # validating scode
+ scode = _mem.scode if _mem.scode != 15 else 0
+ pttidcode = RadioSetting("scode", "PTT ID signal code",
+ RadioSettingValueList(
+ PTTIDCODE_LIST,
+ PTTIDCODE_LIST[scode]))
+ mem.extra.append(pttidcode)
- # basic check for the ident
- if len(ident) != 49:
- raise errors.RadioError("Radio send a short ident block.")
+ optsig = RadioSetting("optsig", "Optional signaling",
+ RadioSettingValueList(
+ OPTSIG_LIST,
+ OPTSIG_LIST[_mem.optsig]))
+ mem.extra.append(optsig)
- # check if ident is OK
- itis = False
- for fp in radio._fileid:
- if fp in ident:
- # got it!
- itis = True
- # checking if we are dealing with a Gen 3 BTECH
- if radio.VENDOR == "BTECH" and fp in BTECH3:
- radio.btech3 = True
+ spmute = RadioSetting("spmute", "Speaker mute",
+ RadioSettingValueList(
+ SPMUTE_LIST,
+ SPMUTE_LIST[_mem.spmute]))
+ mem.extra.append(spmute)
- break
+ return mem
- if itis is False:
- LOG.debug("Incorrect model ID, got this:\n\n" + util.hexprint(ident))
- raise errors.RadioError("Radio identification failed.")
+ def set_memory(self, mem):
+ """Set the memory data in the eeprom img from the UI"""
+ # get the eprom representation of this channel
+ _mem = self._memobj.memory[mem.number]
+ _names = self._memobj.names[mem.number]
- # some radios needs a extra read and check for a code on it, this ones
- # has the check value in the _id2 var, others simply False
- if radio._id2 is not False:
- # lower the timeout here as this radios are reseting due to timeout
- radio.pipe.timeout = 0.05
+ mem_was_empty = False
+ # same method as used in get_memory for determining if mem is empty
+ # doing this BEFORE overwriting it with new values ...
+ if _mem.get_raw()[0] == "\xFF":
+ LOG.debug("This mem was empty before")
+ mem_was_empty = True
+
+ # if empty memmory
+ if mem.empty:
+ # the channel itself
+ _mem.set_raw("\xFF" * 16)
+ # the name tag
+ _names.set_raw("\xFF" * 16)
+ return
- # query & receive the extra ID
- _send(radio, _make_frame("S", 0x3DF0, 16))
- id2 = _rawrecv(radio, 21)
+ # frequency
+ _mem.rxfreq = mem.freq / 10
- # WARNING !!!!!!
- # different radios send a response with a different amount of data
- # it seems that it's padded with \xff, \x20 and some times with \x00
- # we just care about the first 16, our magic string is in there
- if len(id2) < 16:
- raise errors.RadioError("The extra ID is short, aborting.")
+ # duplex
+ 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 _mem.txfreq:
+ i.set_raw("\xFF")
+ elif mem.duplex == "split":
+ _mem.txfreq = mem.offset / 10
+ else:
+ _mem.txfreq = mem.freq / 10
- # ok, the correct string must be in the received data
- if radio._id2 not in id2:
- LOG.debug("Full *BAD* extra ID on the %s is: \n%s" %
- (radio.MODEL, util.hexprint(id2)))
- raise errors.RadioError("The extra ID is wrong, aborting.")
+ # tone data
+ ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
+ chirp_common.split_tone_encode(mem)
+ self._encode_tone(_mem.txtone, txmode, txtone, txpol)
+ self._encode_tone(_mem.rxtone, rxmode, rxtone, rxpol)
- # this radios need a extra request/answer here on the upload
- # the amount of data received depends of the radio type
- #
- # also the first block of TX must no have the ACK at the beginning
- # see _upload for this.
- if upload is True:
- # send an ACK
- _send(radio, ACK_CMD)
+ # name TAG of the channel
+ if len(mem.name) < self.NAME_LENGTH:
+ # we must pad to self.NAME_LENGTH chars, " " = "\xFF"
+ mem.name = str(mem.name).ljust(self.NAME_LENGTH, " ")
+ _names.name = str(mem.name).replace(" ", "\xFF")
- # the amount of data depend on the radio, so far we have two radios
- # reading two bytes with an ACK at the end and just ONE with just
- # one byte (QYT KT8900)
- # the JT-6188 appears a clone of the last, but reads TWO bytes.
- #
- # we will read two bytes with a custom timeout to not penalize the
- # users for this.
- #
- # we just check for a response and last byte being a ACK, that is
- # the common stone for all radios (3 so far)
- ack = _rawrecv(radio, 2)
+ # power, # default power level is high
+ _mem.power = 0 if mem.power is None else POWER_LEVELS.index(mem.power)
- # checking
- if len(ack) == 0 or ack[-1:] != ACK_CMD:
- raise errors.RadioError("Radio didn't ACK the upload")
+ # wide/narrow
+ _mem.wide = MODES.index(mem.mode)
- # restore the default serial timeout
- radio.pipe.timeout = STIMEOUT
+ # scan add property
+ _mem.add = SKIP_VALUES.index(mem.skip)
- # DEBUG
- LOG.info("Positive ident, this is a %s %s" % (radio.VENDOR, radio.MODEL))
+ # reseting unknowns, this have to be set by hand
+ _mem.unknown0 = 0
+ _mem.unknown1 = 0
+ _mem.unknown2 = 0
+ _mem.unknown3 = 0
+ _mem.unknown4 = 0
+ _mem.unknown5 = 0
+ _mem.unknown6 = 0
- return True
+ # extra settings
+ if len(mem.extra) > 0:
+ # there are setting, parse
+ LOG.debug("Extra-Setting supplied. Setting them.")
+ for setting in mem.extra:
+ setattr(_mem, setting.get_name(), setting.value)
+ else:
+ if mem.empty:
+ LOG.debug("New mem is empty.")
+ else:
+ LOG.debug("New mem is NOT empty")
+ # set extra-settings to default ONLY when apreviously empty or
+ # deleted memory was edited to prevent errors such as #4121
+ if mem_was_empty :
+ LOG.debug("old mem was empty. Setting default for extras.")
+ _mem.spmute = 0
+ _mem.optsig = 0
+ _mem.scramble = 0
+ _mem.bcl = 0
+ _mem.pttid = 0
+ _mem.scode = 0
+ return mem
-def _download(radio):
- """Get the memory map"""
+ def get_settings(self):
+ """Translate the bit in the mem_struct into settings in the UI"""
+ _mem = self._memobj
+ basic = RadioSettingGroup("basic", "Basic Settings")
+ advanced = RadioSettingGroup("advanced", "Advanced Settings")
+ other = RadioSettingGroup("other", "Other Settings")
+ work = RadioSettingGroup("work", "Work Mode Settings")
+ top = RadioSettings(basic, advanced, other, work)
- # UI progress
- status = chirp_common.Status()
+ # Basic
+ if self.COLOR_LCD:
+ tmr = RadioSetting("settings.tmr", "Transceiver multi-receive",
+ RadioSettingValueList(
+ LIST_TMR,
+ LIST_TMR[_mem.settings.tmr]))
+ basic.append(tmr)
+ else:
+ tdr = RadioSetting("settings.tdr", "Transceiver dual receive",
+ RadioSettingValueBoolean(_mem.settings.tdr))
+ basic.append(tdr)
- # put radio in program mode and identify it
- _do_ident(radio, status)
+ sql = RadioSetting("settings.sql", "Squelch level",
+ RadioSettingValueInteger(0, 9, _mem.settings.sql))
+ basic.append(sql)
- # the models that doesn't have the extra ID have to make a dummy read here
- if radio._id2 is False:
- _send(radio, _make_frame("S", 0, BLOCK_SIZE))
- discard = _rawrecv(radio, BLOCK_SIZE + 5)
+ tot = RadioSetting("settings.tot", "Time out timer",
+ RadioSettingValueList(
+ LIST_TOT,
+ LIST_TOT[_mem.settings.tot]))
+ basic.append(tot)
- if debug is True:
- LOG.info("Dummy first block read done, got this:\n\n %s",
- util.hexprint(discard))
+ if self.VENDOR == "BTECH" or self.COLOR_LCD:
+ apo = RadioSetting("settings.apo", "Auto power off timer",
+ RadioSettingValueList(
+ LIST_APO,
+ LIST_APO[_mem.settings.apo]))
+ basic.append(apo)
+ else:
+ toa = RadioSetting("settings.apo", "Time out alert timer",
+ RadioSettingValueList(
+ LIST_OFF1TO10,
+ LIST_OFF1TO10[_mem.settings.apo]))
+ basic.append(toa)
- # reset the progress bar in the UI
- status.max = MEM_SIZE / BLOCK_SIZE
- status.msg = "Cloning from radio..."
- status.cur = 0
- radio.status_fn(status)
+ abr = RadioSetting("settings.abr", "Backlight timer",
+ RadioSettingValueList(
+ LIST_OFF1TO50,
+ LIST_OFF1TO50[_mem.settings.abr]))
+ basic.append(abr)
- # cleaning the serial buffer
- _clean_buffer(radio)
+ beep = RadioSetting("settings.beep", "Key beep",
+ RadioSettingValueBoolean(_mem.settings.beep))
+ basic.append(beep)
- data = ""
- for addr in range(0, MEM_SIZE, BLOCK_SIZE):
- # sending the read request
- _send(radio, _make_frame("S", addr, BLOCK_SIZE))
+ dtmfst = RadioSetting("settings.dtmfst", "DTMF side tone",
+ RadioSettingValueList(
+ LIST_DTMFST,
+ LIST_DTMFST[_mem.settings.dtmfst]))
+ basic.append(dtmfst)
- # read
- d = _recv(radio, addr)
+ if not self.COLOR_LCD:
+ prisc = RadioSetting("settings.prisc", "Priority scan",
+ RadioSettingValueBoolean(
+ _mem.settings.prisc))
+ basic.append(prisc)
- # aggregate the data
- data += d
+ prich = RadioSetting("settings.prich", "Priority channel",
+ RadioSettingValueInteger(0, 199,
+ _mem.settings.prich))
+ basic.append(prich)
- # UI Update
- status.cur = addr / BLOCK_SIZE
- status.msg = "Cloning from radio..."
- radio.status_fn(status)
+ screv = RadioSetting("settings.screv", "Scan resume method",
+ RadioSettingValueList(
+ LIST_SCREV,
+ LIST_SCREV[_mem.settings.screv]))
+ basic.append(screv)
- return data
+ pttlt = RadioSetting("settings.pttlt", "PTT transmit delay",
+ RadioSettingValueInteger(0, 30,
+ _mem.settings.pttlt))
+ basic.append(pttlt)
+ if self.VENDOR == "BTECH" and self.COLOR_LCD:
+ emctp = RadioSetting("settings.emctp", "Alarm mode",
+ RadioSettingValueList(
+ LIST_EMCTPX,
+ LIST_EMCTPX[_mem.settings.emctp]))
+ basic.append(emctp)
+ else:
+ emctp = RadioSetting("settings.emctp", "Alarm mode",
+ RadioSettingValueList(
+ LIST_EMCTP,
+ LIST_EMCTP[_mem.settings.emctp]))
+ basic.append(emctp)
-def _upload(radio):
- """Upload procedure"""
+ emcch = RadioSetting("settings.emcch", "Alarm channel",
+ RadioSettingValueInteger(0, 199,
+ _mem.settings.emcch))
+ basic.append(emcch)
- # The UPLOAD mem is restricted to lower than 0x3100,
- # so we will overide that here localy
- MEM_SIZE = 0x3100
+ if self.COLOR_LCD:
+ if _mem.settings.sigbp > 0x01:
+ val = 0x00
+ else:
+ val = _mem.settings.sigbp
+ sigbp = RadioSetting("settings.sigbp", "Roger beep",
+ RadioSettingValueBoolean(val))
+ basic.append(sigbp)
+ else:
+ ringt = RadioSetting("settings.ringt", "Ring time",
+ RadioSettingValueList(
+ LIST_OFF1TO9,
+ LIST_OFF1TO9[_mem.settings.ringt]))
+ basic.append(ringt)
- # UI progress
- status = chirp_common.Status()
+ camdf = RadioSetting("settings.camdf", "Display mode A",
+ RadioSettingValueList(
+ LIST_MDF,
+ LIST_MDF[_mem.settings.camdf]))
+ basic.append(camdf)
- # put radio in program mode and identify it
- _do_ident(radio, status, True)
+ cbmdf = RadioSetting("settings.cbmdf", "Display mode B",
+ RadioSettingValueList(
+ LIST_MDF,
+ LIST_MDF[_mem.settings.cbmdf]))
+ basic.append(cbmdf)
- # get the data to upload to radio
- data = radio.get_mmap()
+ if self.COLOR_LCD:
+ ccmdf = RadioSetting("settings.ccmdf", "Display mode C",
+ RadioSettingValueList(
+ LIST_MDF,
+ LIST_MDF[_mem.settings.ccmdf]))
+ basic.append(ccmdf)
- # Reset the UI progress
- status.max = MEM_SIZE / TX_BLOCK_SIZE
- status.cur = 0
- status.msg = "Cloning to radio..."
- radio.status_fn(status)
+ cdmdf = RadioSetting("settings.cdmdf", "Display mode D",
+ RadioSettingValueList(
+ LIST_MDF,
+ LIST_MDF[_mem.settings.cdmdf]))
+ basic.append(cdmdf)
+
+ langua = RadioSetting("settings.langua", "Language",
+ RadioSettingValueList(
+ LIST_LANGUA,
+ LIST_LANGUA[_mem.settings.langua]))
+ basic.append(langua)
+
+ if self.VENDOR == "BTECH":
+ if self.COLOR_LCD:
+ sync = RadioSetting("settings.sync", "Channel display sync",
+ RadioSettingValueList(
+ LIST_SYNC,
+ LIST_SYNC[_mem.settings.sync]))
+ basic.append(sync)
+ else:
+ sync = RadioSetting("settings.sync", "A/B channel sync",
+ RadioSettingValueBoolean(
+ _mem.settings.sync))
+ basic.append(sync)
+ else:
+ autolk = RadioSetting("settings.sync", "Auto keylock",
+ RadioSettingValueBoolean(
+ _mem.settings.sync))
+ basic.append(autolk)
+
+ if not self.COLOR_LCD:
+ ponmsg = RadioSetting("settings.ponmsg", "Power-on message",
+ RadioSettingValueList(
+ LIST_PONMSG,
+ LIST_PONMSG[_mem.settings.ponmsg]))
+ basic.append(ponmsg)
+
+ if self.COLOR_LCD:
+ mainfc = RadioSetting("settings.mainfc",
+ "Main LCD foreground color",
+ RadioSettingValueList(
+ LIST_COLOR8,
+ LIST_COLOR8[_mem.settings.mainfc]))
+ basic.append(mainfc)
+
+ mainbc = RadioSetting("settings.mainbc",
+ "Main LCD background color",
+ RadioSettingValueList(
+ LIST_COLOR8,
+ LIST_COLOR8[_mem.settings.mainbc]))
+ basic.append(mainbc)
+
+ menufc = RadioSetting("settings.menufc", "Menu foreground color",
+ RadioSettingValueList(
+ LIST_COLOR8,
+ LIST_COLOR8[_mem.settings.menufc]))
+ basic.append(menufc)
+
+ menubc = RadioSetting("settings.menubc", "Menu background color",
+ RadioSettingValueList(
+ LIST_COLOR8,
+ LIST_COLOR8[_mem.settings.menubc]))
+ basic.append(menubc)
+
+ stafc = RadioSetting("settings.stafc",
+ "Top status foreground color",
+ RadioSettingValueList(
+ LIST_COLOR8,
+ LIST_COLOR8[_mem.settings.stafc]))
+ basic.append(stafc)
+
+ stabc = RadioSetting("settings.stabc",
+ "Top status background color",
+ RadioSettingValueList(
+ LIST_COLOR8,
+ LIST_COLOR8[_mem.settings.stabc]))
+ basic.append(stabc)
+
+ sigfc = RadioSetting("settings.sigfc",
+ "Bottom status foreground color",
+ RadioSettingValueList(
+ LIST_COLOR8,
+ LIST_COLOR8[_mem.settings.sigfc]))
+ basic.append(sigfc)
+
+ sigbc = RadioSetting("settings.sigbc",
+ "Bottom status background color",
+ RadioSettingValueList(
+ LIST_COLOR8,
+ LIST_COLOR8[_mem.settings.sigbc]))
+ basic.append(sigbc)
+
+ rxfc = RadioSetting("settings.rxfc", "Receiving character color",
+ RadioSettingValueList(
+ LIST_COLOR8,
+ LIST_COLOR8[_mem.settings.rxfc]))
+ basic.append(rxfc)
+
+ txfc = RadioSetting("settings.txfc",
+ "Transmitting character color",
+ RadioSettingValueList(
+ LIST_COLOR8,
+ LIST_COLOR8[_mem.settings.txfc]))
+ basic.append(txfc)
+
+ txdisp = RadioSetting("settings.txdisp",
+ "Transmitting status display",
+ RadioSettingValueList(
+ LIST_TXDISP,
+ LIST_TXDISP[_mem.settings.txdisp]))
+ basic.append(txdisp)
+ else:
+ wtled = RadioSetting("settings.wtled", "Standby backlight Color",
+ RadioSettingValueList(
+ LIST_COLOR4,
+ LIST_COLOR4[_mem.settings.wtled]))
+ basic.append(wtled)
- # the radios that doesn't have the extra ID 'may' do a dummy write, I found
- # that leveraging the bad ACK and NOT doing the dummy write is ok, as the
- # dummy write is accepted (it actually writes to the mem!) by the radio.
+ rxled = RadioSetting("settings.rxled", "RX backlight Color",
+ RadioSettingValueList(
+ LIST_COLOR4,
+ LIST_COLOR4[_mem.settings.rxled]))
+ basic.append(rxled)
- # cleaning the serial buffer
- _clean_buffer(radio)
+ txled = RadioSetting("settings.txled", "TX backlight Color",
+ RadioSettingValueList(
+ LIST_COLOR4,
+ LIST_COLOR4[_mem.settings.txled]))
+ basic.append(txled)
- # the fun start here
- for addr in range(0, MEM_SIZE, TX_BLOCK_SIZE):
- # getting the block of data to send
- d = data[addr:addr + TX_BLOCK_SIZE]
+ anil = RadioSetting("settings.anil", "ANI length",
+ RadioSettingValueList(
+ LIST_ANIL,
+ LIST_ANIL[_mem.settings.anil]))
+ basic.append(anil)
- # build the frame to send
- frame = _make_frame("X", addr, TX_BLOCK_SIZE, d)
+ reps = RadioSetting("settings.reps", "Relay signal (tone burst)",
+ RadioSettingValueList(
+ LIST_REPS,
+ LIST_REPS[_mem.settings.reps]))
+ basic.append(reps)
- # first block must not send the ACK at the beginning for the
- # ones that has the extra id, since this have to do a extra step
- if addr == 0 and radio._id2 is not False:
- frame = frame[1:]
+ repm = RadioSetting("settings.repm", "Relay condition",
+ RadioSettingValueList(
+ LIST_REPM,
+ LIST_REPM[_mem.settings.repm]))
+ basic.append(repm)
- # send the frame
- _send(radio, frame)
+ if self.VENDOR == "BTECH" or self.COLOR_LCD:
+ if self.COLOR_LCD:
+ tmrmr = RadioSetting("settings.tmrmr", "TMR return time",
+ RadioSettingValueList(
+ LIST_OFF1TO50,
+ LIST_OFF1TO50[_mem.settings.tmrmr]))
+ basic.append(tmrmr)
+ else:
+ tdrab = RadioSetting("settings.tdrab", "TDR return time",
+ RadioSettingValueList(
+ LIST_OFF1TO50,
+ LIST_OFF1TO50[_mem.settings.tdrab]))
+ basic.append(tdrab)
- # receiving the response
- ack = _rawrecv(radio, 1)
+ ste = RadioSetting("settings.ste", "Squelch tail eliminate",
+ RadioSettingValueBoolean(_mem.settings.ste))
+ basic.append(ste)
- # basic check
- if len(ack) != 1:
- raise errors.RadioError("No ACK when writing block 0x%04x" % addr)
+ rpste = RadioSetting("settings.rpste", "Repeater STE",
+ RadioSettingValueList(
+ LIST_OFF1TO9,
+ LIST_OFF1TO9[_mem.settings.rpste]))
+ basic.append(rpste)
- if not ack in "\x06\x05":
- raise errors.RadioError("Bad ACK writing block 0x%04x:" % addr)
+ rptdl = RadioSetting("settings.rptdl", "Repeater STE delay",
+ RadioSettingValueList(
+ LIST_RPTDL,
+ LIST_RPTDL[_mem.settings.rptdl]))
+ basic.append(rptdl)
- # UI Update
- status.cur = addr / TX_BLOCK_SIZE
- status.msg = "Cloning to radio..."
- radio.status_fn(status)
+ if str(_mem.fingerprint.fp) in BTECH3:
+ mgain = RadioSetting("settings.mgain", "Mic gain",
+ RadioSettingValueInteger(0, 120,
+ _mem.settings.mgain))
+ basic.append(mgain)
+ if str(_mem.fingerprint.fp) in BTECH3 or self.COLOR_LCD:
+ dtmfg = RadioSetting("settings.dtmfg", "DTMF gain",
+ RadioSettingValueInteger(0, 60,
+ _mem.settings.dtmfg))
+ basic.append(dtmfg)
-def model_match(cls, data):
- """Match the opened/downloaded image to the correct version"""
- rid = data[0x3f70:0x3f76]
+ if self.VENDOR == "BTECH" and self.COLOR_LCD:
+ mgain = RadioSetting("settings.mgain", "Mic gain",
+ RadioSettingValueInteger(0, 120,
+ _mem.settings.mgain))
+ basic.append(mgain)
- if rid in cls._fileid:
- return True
+ skiptx = RadioSetting("settings.skiptx", "Skip TX",
+ RadioSettingValueList(
+ LIST_SKIPTX,
+ LIST_SKIPTX[_mem.settings.skiptx]))
+ basic.append(skiptx)
- return False
+ scmode = RadioSetting("settings.scmode", "Scan mode",
+ RadioSettingValueList(
+ LIST_SCMODE,
+ LIST_SCMODE[_mem.settings.scmode]))
+ basic.append(scmode)
+ # Advanced
+ def _filter(name):
+ filtered = ""
+ for char in str(name):
+ if char in VALID_CHARS:
+ filtered += char
+ else:
+ filtered += " "
+ return filtered
-def _decode_ranges(low, high):
- """Unpack the data in the ranges zones in the memmap and return
- a tuple with the integer corresponding to the Mhz it means"""
- ilow = int(low[0]) * 100 + int(low[1]) * 10 + int(low[2])
- ihigh = int(high[0]) * 100 + int(high[1]) * 10 + int(high[2])
- ilow *= 1000000
- ihigh *= 1000000
+ _msg = self._memobj.poweron_msg
+ if self.COLOR_LCD:
+ line1 = RadioSetting("poweron_msg.line1",
+ "Power-on message line 1",
+ RadioSettingValueString(0, 8, _filter(
+ _msg.line1)))
+ advanced.append(line1)
+ line2 = RadioSetting("poweron_msg.line2",
+ "Power-on message line 2",
+ RadioSettingValueString(0, 8, _filter(
+ _msg.line2)))
+ advanced.append(line2)
+ line3 = RadioSetting("poweron_msg.line3",
+ "Power-on message line 3",
+ RadioSettingValueString(0, 8, _filter(
+ _msg.line3)))
+ advanced.append(line3)
+ line4 = RadioSetting("poweron_msg.line4",
+ "Power-on message line 4",
+ RadioSettingValueString(0, 8, _filter(
+ _msg.line4)))
+ advanced.append(line4)
+ line5 = RadioSetting("poweron_msg.line5",
+ "Power-on message line 5",
+ RadioSettingValueString(0, 8, _filter(
+ _msg.line5)))
+ advanced.append(line5)
+ line6 = RadioSetting("poweron_msg.line6",
+ "Power-on message line 6",
+ RadioSettingValueString(0, 8, _filter(
+ _msg.line6)))
+ advanced.append(line6)
+ line7 = RadioSetting("poweron_msg.line7",
+ "Power-on message line 7",
+ RadioSettingValueString(0, 8, _filter(
+ _msg.line7)))
+ advanced.append(line7)
+ line8 = RadioSetting("poweron_msg.line8", "Static message",
+ RadioSettingValueString(0, 8, _filter(
+ _msg.line8)))
+ advanced.append(line8)
+ else:
+ line1 = RadioSetting("poweron_msg.line1",
+ "Power-on message line 1",
+ RadioSettingValueString(0, 6, _filter(
+ _msg.line1)))
+ advanced.append(line1)
+ line2 = RadioSetting("poweron_msg.line2",
+ "Power-on message line 2",
+ RadioSettingValueString(0, 6, _filter(
+ _msg.line2)))
+ advanced.append(line2)
- return (ilow, ihigh)
+ if self.MODEL in ("UV-2501", "UV-5001"):
+ vfomren = RadioSetting("settings2.vfomren", "VFO/MR switching",
+ RadioSettingValueBoolean(
+ _mem.settings2.vfomren))
+ advanced.append(vfomren)
+ reseten = RadioSetting("settings2.reseten", "RESET",
+ RadioSettingValueBoolean(
+ _mem.settings2.reseten))
+ advanced.append(reseten)
-def _split(rf, f1, f2):
- """Returns False if the two freqs are in the same band (no split)
- or True otherwise"""
+ menuen = RadioSetting("settings2.menuen", "Menu",
+ RadioSettingValueBoolean(
+ _mem.settings2.menuen))
+ advanced.append(menuen)
- # determine if the two freqs are in the same band
- for low, high in rf.valid_bands:
- if f1 >= low and f1 <= high and \
- f2 >= low and f2 <= high:
- # if the two freqs are on the same Band this is not a split
- return False
+ # Other
+ def convert_bytes_to_limit(bytes):
+ limit = ""
+ for byte in bytes:
+ if byte < 10:
+ limit += chr(byte + 0x30)
+ else:
+ break
+ return limit
- # if you get here is because the freq pairs are split
- return False
+ if self.MODEL in ["UV-2501+220", "KT8900R"]:
+ _ranges = self._memobj.ranges220
+ ranges = "ranges220"
+ else:
+ _ranges = self._memobj.ranges
+ ranges = "ranges"
+ _limit = convert_bytes_to_limit(_ranges.vhf_low)
+ val = RadioSettingValueString(0, 3, _limit)
+ val.set_mutable(False)
+ vhf_low = RadioSetting("%s.vhf_low" % ranges, "VHF low", val)
+ other.append(vhf_low)
-class BTech(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
- """BTECH's UV-5001 and alike radios"""
- VENDOR = "BTECH"
- MODEL = ""
- IDENT = ""
- _vhf_range = (130000000, 180000000)
- _220_range = (210000000, 231000000)
- _uhf_range = (400000000, 521000000)
- _upper = 199
- _magic = MSTRING
- _fileid = None
- _id2 = False
- btech3 = False
+ _limit = convert_bytes_to_limit(_ranges.vhf_high)
+ val = RadioSettingValueString(0, 3, _limit)
+ val.set_mutable(False)
+ vhf_high = RadioSetting("%s.vhf_high" % ranges, "VHF high", val)
+ other.append(vhf_high)
- @classmethod
- def get_prompts(cls):
- rp = chirp_common.RadioPrompts()
- rp.experimental = \
- ('This driver is experimental.\n'
- '\n'
- 'Please keep a copy of your memories with the original software '
- 'if you treasure them, this driver is new and may contain'
- ' bugs.\n'
- '\n'
- )
- rp.pre_download = _(dedent("""\
- Follow these instructions to download your info:
+ if self.BANDS == 3 or self.BANDS == 4:
+ _limit = convert_bytes_to_limit(_ranges.vhf2_low)
+ val = RadioSettingValueString(0, 3, _limit)
+ val.set_mutable(False)
+ vhf2_low = RadioSetting("%s.vhf2_low" % ranges, "VHF2 low", val)
+ other.append(vhf2_low)
- 1 - Turn off your radio
- 2 - Connect your interface cable
- 3 - Turn on your radio
- 4 - Do the download of your radio data
+ _limit = convert_bytes_to_limit(_ranges.vhf2_high)
+ val = RadioSettingValueString(0, 3, _limit)
+ val.set_mutable(False)
+ vhf2_high = RadioSetting("%s.vhf2_high" % ranges, "VHF2 high", val)
+ other.append(vhf2_high)
- """))
- rp.pre_upload = _(dedent("""\
- Follow these instructions to upload your info:
+ _limit = convert_bytes_to_limit(_ranges.uhf_low)
+ val = RadioSettingValueString(0, 3, _limit)
+ val.set_mutable(False)
+ uhf_low = RadioSetting("%s.uhf_low" % ranges, "UHF low", val)
+ other.append(uhf_low)
- 1 - Turn off your radio
- 2 - Connect your interface cable
- 3 - Turn on your radio
- 4 - Do the upload of your radio data
+ _limit = convert_bytes_to_limit(_ranges.uhf_high)
+ val = RadioSettingValueString(0, 3, _limit)
+ val.set_mutable(False)
+ uhf_high = RadioSetting("%s.uhf_high" % ranges, "UHF high", val)
+ other.append(uhf_high)
- """))
- return rp
+ if self.BANDS == 4:
+ _limit = convert_bytes_to_limit(_ranges.uhf2_low)
+ val = RadioSettingValueString(0, 3, _limit)
+ val.set_mutable(False)
+ uhf2_low = RadioSetting("%s.uhf2_low" % ranges, "UHF2 low", val)
+ other.append(uhf2_low)
- def get_features(self):
- """Get the radio's features"""
+ _limit = convert_bytes_to_limit(_ranges.uhf2_high)
+ val = RadioSettingValueString(0, 3, _limit)
+ val.set_mutable(False)
+ uhf2_high = RadioSetting("%s.uhf2_high" % ranges, "UHF2 high", val)
+ other.append(uhf2_high)
- # we will use the following var as global
- global POWER_LEVELS
+ val = RadioSettingValueString(0, 6, _filter(_mem.fingerprint.fp))
+ val.set_mutable(False)
+ fp = RadioSetting("fingerprint.fp", "Fingerprint", val)
+ other.append(fp)
- rf = chirp_common.RadioFeatures()
- rf.has_settings = True
- rf.has_bank = False
- rf.has_tuning_step = False
- rf.can_odd_split = True
- 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_characters = VALID_CHARS
- rf.valid_name_length = NAME_LENGTH
- rf.valid_duplexes = ["", "-", "+", "split", "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_skips = SKIP_VALUES
- rf.valid_dtcs_codes = DTCS
- rf.memory_bounds = (0, self._upper)
- # power levels
- if self.MODEL == "UV-5001":
- POWER_LEVELS = UV5001_POWER_LEVELS # Higher power (50W)
+ # Work
+ if self.COLOR_LCD:
+ dispab = RadioSetting("settings2.dispab", "Display",
+ RadioSettingValueList(
+ LIST_ABCD,
+ LIST_ABCD[_mem.settings2.dispab]))
+ work.append(dispab)
+ else:
+ dispab = RadioSetting("settings2.dispab", "Display",
+ RadioSettingValueList(
+ LIST_AB,
+ LIST_AB[_mem.settings2.dispab]))
+ work.append(dispab)
+
+ if self.COLOR_LCD:
+ vfomra = RadioSetting("settings2.vfomra", "VFO/MR A mode",
+ RadioSettingValueList(
+ LIST_VFOMR,
+ LIST_VFOMR[_mem.settings2.vfomra]))
+ work.append(vfomra)
+
+ vfomrb = RadioSetting("settings2.vfomrb", "VFO/MR B mode",
+ RadioSettingValueList(
+ LIST_VFOMR,
+ LIST_VFOMR[_mem.settings2.vfomrb]))
+ work.append(vfomrb)
+
+ vfomrc = RadioSetting("settings2.vfomrc", "VFO/MR C mode",
+ RadioSettingValueList(
+ LIST_VFOMR,
+ LIST_VFOMR[_mem.settings2.vfomrc]))
+ work.append(vfomrc)
+
+ vfomrd = RadioSetting("settings2.vfomrd", "VFO/MR D mode",
+ RadioSettingValueList(
+ LIST_VFOMR,
+ LIST_VFOMR[_mem.settings2.vfomrd]))
+ work.append(vfomrd)
else:
- POWER_LEVELS = NORMAL_POWER_LEVELS # Lower power (25W)
+ vfomr = RadioSetting("settings2.vfomr", "VFO/MR mode",
+ RadioSettingValueList(
+ LIST_VFOMR,
+ LIST_VFOMR[_mem.settings2.vfomr]))
+ work.append(vfomr)
- rf.valid_power_levels = POWER_LEVELS
- # bands
- rf.valid_bands = [self._vhf_range, self._uhf_range]
+ keylock = RadioSetting("settings2.keylock", "Keypad lock",
+ RadioSettingValueBoolean(_mem.settings2.keylock))
+ work.append(keylock)
- # 2501+220 & KT8900R
- if self.MODEL in ["UV-2501+220", "KT8900R"]:
- rf.valid_bands.append(self._220_range)
+ mrcha = RadioSetting("settings2.mrcha", "MR A channel",
+ RadioSettingValueInteger(0, 199,
+ _mem.settings2.mrcha))
+ work.append(mrcha)
- return rf
+ mrchb = RadioSetting("settings2.mrchb", "MR B channel",
+ RadioSettingValueInteger(0, 199,
+ _mem.settings2.mrchb))
+ work.append(mrchb)
- def sync_in(self):
- """Download from radio"""
- data = _download(self)
- self._mmap = memmap.MemoryMap(data)
- self.process_mmap()
+ if self.COLOR_LCD:
+ mrchc = RadioSetting("settings2.mrchc", "MR C channel",
+ RadioSettingValueInteger(0, 199,
+ _mem.settings2.mrchc))
+ work.append(mrchc)
- def sync_out(self):
- """Upload to radio"""
- try:
- _upload(self)
- except errors.RadioError:
- raise
- except Exception, e:
- raise errors.RadioError("Error: %s" % e)
+ mrchd = RadioSetting("settings2.mrchd", "MR D channel",
+ RadioSettingValueInteger(0, 199,
+ _mem.settings2.mrchd))
+ work.append(mrchd)
- def set_options(self):
- """This is to read the options from the image and set it in the
- environment, for now just the limits of the freqs in the VHF/UHF
- ranges"""
+ def convert_bytes_to_freq(bytes):
+ real_freq = 0
+ for byte in bytes:
+ real_freq = (real_freq * 10) + byte
+ return chirp_common.format_freq(real_freq * 10)
- # setting the correct ranges for each radio type
- if self.MODEL in ["UV-2501+220", "KT8900R"]:
- # the model 2501+220 has a segment in 220
- # and a different position in the memmap
- # also the QYT KT8900R
- ranges = self._memobj.ranges220
- else:
- ranges = self._memobj.ranges
+ def my_validate(value):
+ _vhf_lower = int(convert_bytes_to_limit(_ranges.vhf_low))
+ _vhf_upper = int(convert_bytes_to_limit(_ranges.vhf_high))
+ _uhf_lower = int(convert_bytes_to_limit(_ranges.uhf_low))
+ _uhf_upper = int(convert_bytes_to_limit(_ranges.uhf_high))
+ if self.BANDS == 3 or self.BANDS == 4:
+ _vhf2_lower = int(convert_bytes_to_limit(_ranges.vhf2_low))
+ _vhf2_upper = int(convert_bytes_to_limit(_ranges.vhf2_high))
+ if self.BANDS == 4:
+ _uhf2_lower = int(convert_bytes_to_limit(_ranges.uhf2_low))
+ _uhf2_upper = int(convert_bytes_to_limit(_ranges.uhf2_high))
- # the normal dual bands
- vhf = _decode_ranges(ranges.vhf_low, ranges.vhf_high)
- uhf = _decode_ranges(ranges.uhf_low, ranges.uhf_high)
+ value = chirp_common.parse_freq(value)
+ msg = ("Can't be less then %i.0000")
+ if value > 99000000 and value < _vhf_lower * 1000000:
+ raise InvalidValueError(msg % (_vhf_lower))
+ msg = ("Can't be betweeb %i.9975-%i.0000")
+ if self.BANDS == 2:
+ if (_vhf_upper + 1) * 1000000 <= value and \
+ value < _uhf_lower * 1000000:
+ raise InvalidValueError(msg % (_vhf_upper, _uhf_lower))
+ if self.BANDS == 3:
+ if (_vhf_upper + 1) * 1000000 <= value and \
+ value < _vhf2_lower * 1000000:
+ raise InvalidValueError(msg % (_vhf_upper, _vhf2_lower))
+ if (_vhf2_upper + 1) * 1000000 <= value and \
+ value < _uhf_lower * 1000000:
+ raise InvalidValueError(msg % (_vhf2_upper, _uhf_lower))
+ if self.BANDS == 4:
+ if (_vhf_upper + 1) * 1000000 <= value and \
+ value < _vhf2_lower * 1000000:
+ raise InvalidValueError(msg % (_vhf_upper, _vhf2_lower))
+ if (_vhf2_upper + 1) * 1000000 <= value and \
+ value < _uhf2_lower * 1000000:
+ raise InvalidValueError(msg % (_vhf2_upper, _uhf2_lower))
+ if (_uhf2_upper + 1) * 1000000 <= value and \
+ value < _uhf_lower * 1000000:
+ raise InvalidValueError(msg % (_uhf2_upper, _uhf_lower))
+ msg = ("Can't be greater then %i.9975")
+ if value > 99000000 and value >= _uhf_upper * 1000000:
+ raise InvalidValueError(msg % (_uhf_upper))
+ return chirp_common.format_freq(value)
- # DEBUG
- LOG.info("Radio ranges: VHF %d to %d" % vhf)
- LOG.info("Radio ranges: UHF %d to %d" % uhf)
+ def apply_freq(setting, obj):
+ value = chirp_common.parse_freq(str(setting.value)) / 10
+ for i in range(7, -1, -1):
+ obj.freq[i] = value % 10
+ value /= 10
- # 220Mhz radios case
- if self.MODEL in ["UV-2501+220", "KT8900R"]:
- vhf2 = _decode_ranges(ranges.vhf2_low, ranges.vhf2_high)
- LOG.info("Radio ranges: VHF(220) %d to %d" % vhf2)
- self._220_range = vhf2
+ val1a = RadioSettingValueString(0, 10, convert_bytes_to_freq(
+ _mem.vfo.a.freq))
+ val1a.set_validate_callback(my_validate)
+ vfoafreq = RadioSetting("vfo.a.freq", "VFO A frequency", val1a)
+ vfoafreq.set_apply_callback(apply_freq, _mem.vfo.a)
+ work.append(vfoafreq)
- # set the class with the real data
- self._vhf_range = vhf
- self._uhf_range = uhf
+ val1b = RadioSettingValueString(0, 10, convert_bytes_to_freq(
+ _mem.vfo.b.freq))
+ val1b.set_validate_callback(my_validate)
+ vfobfreq = RadioSetting("vfo.b.freq", "VFO B frequency", val1b)
+ vfobfreq.set_apply_callback(apply_freq, _mem.vfo.b)
+ work.append(vfobfreq)
- def process_mmap(self):
- """Process the mem map into the mem object"""
+ if self.COLOR_LCD:
+ val1c = RadioSettingValueString(0, 10, convert_bytes_to_freq(
+ _mem.vfo.c.freq))
+ val1c.set_validate_callback(my_validate)
+ vfocfreq = RadioSetting("vfo.c.freq", "VFO C frequency", val1c)
+ vfocfreq.set_apply_callback(apply_freq, _mem.vfo.c)
+ work.append(vfocfreq)
+
+ val1d = RadioSettingValueString(0, 10, convert_bytes_to_freq(
+ _mem.vfo.d.freq))
+ val1d.set_validate_callback(my_validate)
+ vfodfreq = RadioSetting("vfo.d.freq", "VFO D frequency", val1d)
+ vfodfreq.set_apply_callback(apply_freq, _mem.vfo.d)
+ work.append(vfodfreq)
- # Get it
- self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+ vfoashiftd = RadioSetting("vfo.a.shiftd", "VFO A shift",
+ RadioSettingValueList(
+ LIST_SHIFT,
+ LIST_SHIFT[_mem.vfo.a.shiftd]))
+ work.append(vfoashiftd)
- # load specific parameters from the radio image
- self.set_options()
+ vfobshiftd = RadioSetting("vfo.b.shiftd", "VFO B shift",
+ RadioSettingValueList(
+ LIST_SHIFT,
+ LIST_SHIFT[_mem.vfo.b.shiftd]))
+ work.append(vfobshiftd)
- def get_raw_memory(self, number):
- return repr(self._memobj.memory[number])
+ if self.COLOR_LCD:
+ vfocshiftd = RadioSetting("vfo.c.shiftd", "VFO C shift",
+ RadioSettingValueList(
+ LIST_SHIFT,
+ LIST_SHIFT[_mem.vfo.c.shiftd]))
+ work.append(vfocshiftd)
- def _decode_tone(self, val):
- """Parse the tone data to decode from mem, it returns:
- Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
- pol = None
+ vfodshiftd = RadioSetting("vfo.d.shiftd", "VFO D shift",
+ RadioSettingValueList(
+ LIST_SHIFT,
+ LIST_SHIFT[_mem.vfo.d.shiftd]))
+ work.append(vfodshiftd)
- if val in [0, 65535]:
- return '', None, None
- elif val > 0x0258:
- a = val / 10.0
- return 'Tone', a, pol
- else:
- if val > 0x69:
- index = val - 0x6A
- pol = "R"
- else:
- index = val - 1
- pol = "N"
+ def convert_bytes_to_offset(bytes):
+ real_offset = 0
+ for byte in bytes:
+ real_offset = (real_offset * 10) + byte
+ return chirp_common.format_freq(real_offset * 1000)
- tone = DTCS[index]
- return 'DTCS', tone, pol
+ def apply_offset(setting, obj):
+ value = chirp_common.parse_freq(str(setting.value)) / 1000
+ for i in range(5, -1, -1):
+ obj.offset[i] = value % 10
+ value /= 10
- def _encode_tone(self, memval, mode, val, pol):
- """Parse the tone data to encode from UI to mem"""
- if mode == '' or mode is None:
- memval.set_raw("\x00\x00")
- elif mode == 'Tone':
- memval.set_value(val * 10)
- elif mode == 'DTCS':
- # detect the index in the DTCS list
- try:
- index = DTCS.index(val)
- if pol == "N":
- index += 1
- else:
- index += 0x6A
- memval.set_value(index)
- except:
- msg = "Digital Tone '%d' is not supported" % value
- LOG.error(msg)
- raise errors.RadioError(msg)
+ if self.COLOR_LCD:
+ val1a = RadioSettingValueString(0, 10, convert_bytes_to_offset(
+ _mem.vfo.a.offset))
+ vfoaoffset = RadioSetting("vfo.a.offset",
+ "VFO A offset (0.000-999.999)", val1a)
+ vfoaoffset.set_apply_callback(apply_offset, _mem.vfo.a)
+ work.append(vfoaoffset)
+
+ val1b = RadioSettingValueString(0, 10, convert_bytes_to_offset(
+ _mem.vfo.b.offset))
+ vfoboffset = RadioSetting("vfo.b.offset",
+ "VFO B offset (0.000-999.999)", val1b)
+ vfoboffset.set_apply_callback(apply_offset, _mem.vfo.b)
+ work.append(vfoboffset)
+
+ val1c = RadioSettingValueString(0, 10, convert_bytes_to_offset(
+ _mem.vfo.c.offset))
+ vfocoffset = RadioSetting("vfo.c.offset",
+ "VFO C offset (0.000-999.999)", val1c)
+ vfocoffset.set_apply_callback(apply_offset, _mem.vfo.c)
+ work.append(vfocoffset)
+
+ val1d = RadioSettingValueString(0, 10, convert_bytes_to_offset(
+ _mem.vfo.d.offset))
+ vfodoffset = RadioSetting("vfo.d.offset",
+ "VFO D offset (0.000-999.999)", val1d)
+ vfodoffset.set_apply_callback(apply_offset, _mem.vfo.d)
+ work.append(vfodoffset)
else:
- msg = "Internal error: invalid mode '%s'" % mode
- LOG.error(msg)
- raise errors.InvalidDataError(msg)
+ val1a = RadioSettingValueString(0, 10, convert_bytes_to_offset(
+ _mem.vfo.a.offset))
+ vfoaoffset = RadioSetting("vfo.a.offset",
+ "VFO A offset (0.000-99.999)", val1a)
+ vfoaoffset.set_apply_callback(apply_offset, _mem.vfo.a)
+ work.append(vfoaoffset)
- def get_memory(self, number):
- """Get the mem representation from the radio image"""
- _mem = self._memobj.memory[number]
- _names = self._memobj.names[number]
+ val1b = RadioSettingValueString(0, 10, convert_bytes_to_offset(
+ _mem.vfo.b.offset))
+ vfoboffset = RadioSetting("vfo.b.offset",
+ "VFO B offset (0.000-99.999)", val1b)
+ vfoboffset.set_apply_callback(apply_offset, _mem.vfo.b)
+ work.append(vfoboffset)
- # Create a high-level memory object to return to the UI
- mem = chirp_common.Memory()
- # Memory number
- mem.number = number
+ vfoatxp = RadioSetting("vfo.a.power", "VFO A power",
+ RadioSettingValueList(
+ LIST_TXP,
+ LIST_TXP[_mem.vfo.a.power]))
+ work.append(vfoatxp)
- if _mem.get_raw()[0] == "\xFF":
- mem.empty = True
- return mem
+ vfobtxp = RadioSetting("vfo.b.power", "VFO B power",
+ RadioSettingValueList(
+ LIST_TXP,
+ LIST_TXP[_mem.vfo.b.power]))
+ work.append(vfobtxp)
- # 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 freq set
- offset = (int(_mem.txfreq) * 10) - mem.freq
- if offset != 0:
- if _split(self.get_features(), mem.freq, int(_mem.txfreq) * 10):
- mem.duplex = "split"
- mem.offset = int(_mem.txfreq) * 10
- elif offset < 0:
- mem.offset = abs(offset)
- mem.duplex = "-"
- elif offset > 0:
- mem.offset = offset
- mem.duplex = "+"
- else:
- mem.offset = 0
+ if self.COLOR_LCD:
+ vfoctxp = RadioSetting("vfo.c.power", "VFO C power",
+ RadioSettingValueList(
+ LIST_TXP,
+ LIST_TXP[_mem.vfo.c.power]))
+ work.append(vfoctxp)
- # name TAG of the channel
- mem.name = str(_names.name).rstrip("\xFF").replace("\xFF", " ")
+ vfodtxp = RadioSetting("vfo.d.power", "VFO D power",
+ RadioSettingValueList(
+ LIST_TXP,
+ LIST_TXP[_mem.vfo.d.power]))
+ work.append(vfodtxp)
- # power
- mem.power = POWER_LEVELS[int(_mem.power)]
+ vfoawide = RadioSetting("vfo.a.wide", "VFO A bandwidth",
+ RadioSettingValueList(
+ LIST_WIDE,
+ LIST_WIDE[_mem.vfo.a.wide]))
+ work.append(vfoawide)
- # wide/narrow
- mem.mode = MODES[int(_mem.wide)]
+ vfobwide = RadioSetting("vfo.b.wide", "VFO B bandwidth",
+ RadioSettingValueList(
+ LIST_WIDE,
+ LIST_WIDE[_mem.vfo.b.wide]))
+ work.append(vfobwide)
+
+ if self.COLOR_LCD:
+ vfocwide = RadioSetting("vfo.c.wide", "VFO C bandwidth",
+ RadioSettingValueList(
+ LIST_WIDE,
+ LIST_WIDE[_mem.vfo.c.wide]))
+ work.append(vfocwide)
+
+ vfodwide = RadioSetting("vfo.d.wide", "VFO D bandwidth",
+ RadioSettingValueList(
+ LIST_WIDE,
+ LIST_WIDE[_mem.vfo.d.wide]))
+ work.append(vfodwide)
+
+ vfoastep = RadioSetting("vfo.a.step", "VFO A step",
+ RadioSettingValueList(
+ LIST_STEP,
+ LIST_STEP[_mem.vfo.a.step]))
+ work.append(vfoastep)
- # skip
- mem.skip = SKIP_VALUES[_mem.add]
+ vfobstep = RadioSetting("vfo.b.step", "VFO B step",
+ RadioSettingValueList(
+ LIST_STEP,
+ LIST_STEP[_mem.vfo.b.step]))
+ work.append(vfobstep)
- # tone data
- rxtone = txtone = None
- txtone = self._decode_tone(_mem.txtone)
- rxtone = self._decode_tone(_mem.rxtone)
- chirp_common.split_tone_decode(mem, txtone, rxtone)
+ if self.COLOR_LCD:
+ vfocstep = RadioSetting("vfo.c.step", "VFO C step",
+ RadioSettingValueList(
+ LIST_STEP,
+ LIST_STEP[_mem.vfo.c.step]))
+ work.append(vfocstep)
- # Extra
- mem.extra = RadioSettingGroup("extra", "Extra")
+ vfodstep = RadioSetting("vfo.d.step", "VFO D step",
+ RadioSettingValueList(
+ LIST_STEP,
+ LIST_STEP[_mem.vfo.d.step]))
+ work.append(vfodstep)
- scramble = RadioSetting("scramble", "Scramble",
- RadioSettingValueBoolean(bool(_mem.scramble)))
- mem.extra.append(scramble)
+ vfoaoptsig = RadioSetting("vfo.a.optsig", "VFO A optional signal",
+ RadioSettingValueList(
+ OPTSIG_LIST,
+ OPTSIG_LIST[_mem.vfo.a.optsig]))
+ work.append(vfoaoptsig)
- bcl = RadioSetting("bcl", "Busy channel lockout",
- RadioSettingValueBoolean(bool(_mem.bcl)))
- mem.extra.append(bcl)
+ vfoboptsig = RadioSetting("vfo.b.optsig", "VFO B optional signal",
+ RadioSettingValueList(
+ OPTSIG_LIST,
+ OPTSIG_LIST[_mem.vfo.b.optsig]))
+ work.append(vfoboptsig)
- pttid = RadioSetting("pttid", "PTT ID",
- RadioSettingValueList(PTTID_LIST,
- PTTID_LIST[_mem.pttid]))
- mem.extra.append(pttid)
+ if self.COLOR_LCD:
+ vfocoptsig = RadioSetting("vfo.c.optsig", "VFO C optional signal",
+ RadioSettingValueList(
+ OPTSIG_LIST,
+ OPTSIG_LIST[_mem.vfo.c.optsig]))
+ work.append(vfocoptsig)
- # validating scode
- scode = _mem.scode if _mem.scode != 15 else 0
- pttidcode = RadioSetting("scode", "PTT ID signal code",
- RadioSettingValueList(
- PTTIDCODE_LIST,
- PTTIDCODE_LIST[scode]))
- mem.extra.append(pttidcode)
+ vfodoptsig = RadioSetting("vfo.d.optsig", "VFO D optional signal",
+ RadioSettingValueList(
+ OPTSIG_LIST,
+ OPTSIG_LIST[_mem.vfo.d.optsig]))
+ work.append(vfodoptsig)
- optsig = RadioSetting("optsig", "Optional signaling",
- RadioSettingValueList(
- OPTSIG_LIST,
- OPTSIG_LIST[_mem.optsig]))
- mem.extra.append(optsig)
+ vfoaspmute = RadioSetting("vfo.a.spmute", "VFO A speaker mute",
+ RadioSettingValueList(
+ SPMUTE_LIST,
+ SPMUTE_LIST[_mem.vfo.a.spmute]))
+ work.append(vfoaspmute)
- spmute = RadioSetting("spmute", "Speaker mute",
- RadioSettingValueList(
- SPMUTE_LIST,
- SPMUTE_LIST[_mem.spmute]))
- mem.extra.append(spmute)
+ vfobspmute = RadioSetting("vfo.b.spmute", "VFO B speaker mute",
+ RadioSettingValueList(
+ SPMUTE_LIST,
+ SPMUTE_LIST[_mem.vfo.b.spmute]))
+ work.append(vfobspmute)
- return mem
+ if self.COLOR_LCD:
+ vfocspmute = RadioSetting("vfo.c.spmute", "VFO C speaker mute",
+ RadioSettingValueList(
+ SPMUTE_LIST,
+ SPMUTE_LIST[_mem.vfo.c.spmute]))
+ work.append(vfocspmute)
+
+ vfodspmute = RadioSetting("vfo.d.spmute", "VFO D speaker mute",
+ RadioSettingValueList(
+ SPMUTE_LIST,
+ SPMUTE_LIST[_mem.vfo.d.spmute]))
+ work.append(vfodspmute)
+
+ if not self.COLOR_LCD or \
+ (self.COLOR_LCD and not self.VENDOR == "BTECH"):
+ vfoascr = RadioSetting("vfo.a.scramble", "VFO A scramble",
+ RadioSettingValueBoolean(
+ _mem.vfo.a.scramble))
+ work.append(vfoascr)
- def set_memory(self, mem):
- """Set the memory data in the eeprom img from the UI"""
- # get the eprom representation of this channel
- _mem = self._memobj.memory[mem.number]
- _names = self._memobj.names[mem.number]
+ vfobscr = RadioSetting("vfo.b.scramble", "VFO B scramble",
+ RadioSettingValueBoolean(
+ _mem.vfo.b.scramble))
+ work.append(vfobscr)
- mem_was_empty = False
- # same method as used in get_memory for determining if mem is empty
- # doing this BEFORE overwriting it with new values ...
- if _mem.get_raw()[0] == "\xFF":
- LOG.debug("This mem was empty before")
- mem_was_empty = True
-
- # if empty memmory
- if mem.empty:
- # the channel itself
- _mem.set_raw("\xFF" * 16)
- # the name tag
- _names.set_raw("\xFF" * 16)
- return
+ if self.COLOR_LCD and not self.VENDOR == "BTECH":
+ vfocscr = RadioSetting("vfo.c.scramble", "VFO C scramble",
+ RadioSettingValueBoolean(
+ _mem.vfo.c.scramble))
+ work.append(vfocscr)
- # frequency
- _mem.rxfreq = mem.freq / 10
+ vfodscr = RadioSetting("vfo.d.scramble", "VFO D scramble",
+ RadioSettingValueBoolean(
+ _mem.vfo.d.scramble))
+ work.append(vfodscr)
- # duplex
- 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 _mem.txfreq:
- i.set_raw("\xFF")
- elif mem.duplex == "split":
- _mem.txfreq = mem.offset / 10
- else:
- _mem.txfreq = mem.freq / 10
+ vfoascode = RadioSetting("vfo.a.scode", "VFO A PTT-ID",
+ RadioSettingValueList(
+ PTTIDCODE_LIST,
+ PTTIDCODE_LIST[_mem.vfo.a.scode]))
+ work.append(vfoascode)
- # tone data
- ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
- chirp_common.split_tone_encode(mem)
- self._encode_tone(_mem.txtone, txmode, txtone, txpol)
- self._encode_tone(_mem.rxtone, rxmode, rxtone, rxpol)
+ vfobscode = RadioSetting("vfo.b.scode", "VFO B PTT-ID",
+ RadioSettingValueList(
+ PTTIDCODE_LIST,
+ PTTIDCODE_LIST[_mem.vfo.b.scode]))
+ work.append(vfobscode)
- # name TAG of the channel
- if len(mem.name) < NAME_LENGTH:
- # we must pad to NAME_LENGTH chars, " " = "\xFF"
- mem.name = str(mem.name).ljust(NAME_LENGTH, " ")
- _names.name = str(mem.name).replace(" ", "\xFF")
+ if self.COLOR_LCD:
+ vfocscode = RadioSetting("vfo.c.scode", "VFO C PTT-ID",
+ RadioSettingValueList(
+ PTTIDCODE_LIST,
+ PTTIDCODE_LIST[_mem.vfo.c.scode]))
+ work.append(vfocscode)
- # power, # default power level is high
- _mem.power = 0 if mem.power is None else POWER_LEVELS.index(mem.power)
+ vfodscode = RadioSetting("vfo.d.scode", "VFO D PTT-ID",
+ RadioSettingValueList(
+ PTTIDCODE_LIST,
+ PTTIDCODE_LIST[_mem.vfo.d.scode]))
+ work.append(vfodscode)
- # wide/narrow
- _mem.wide = MODES.index(mem.mode)
+ pttid = RadioSetting("settings.pttid", "PTT ID",
+ RadioSettingValueList(
+ PTTID_LIST,
+ PTTID_LIST[_mem.settings.pttid]))
+ work.append(pttid)
- # scan add property
- _mem.add = SKIP_VALUES.index(mem.skip)
+ if not self.COLOR_LCD:
+ #FM presets
+ fm_presets = RadioSettingGroup("fm_presets", "FM Presets")
+ top.append(fm_presets)
- # reseting unknowns, this have to be set by hand
- _mem.unknown0 = 0
- _mem.unknown1 = 0
- _mem.unknown2 = 0
- _mem.unknown3 = 0
- _mem.unknown4 = 0
- _mem.unknown5 = 0
- _mem.unknown6 = 0
+ def fm_validate(value):
+ if value == 0:
+ return chirp_common.format_freq(value)
+ if not (87.5 <= value and value <= 108.0): # 87.5-108MHz
+ msg = ("FM-Preset-Frequency: Must be between 87.5 and 108 MHz")
+ raise InvalidValueError(msg)
+ return value
- # extra settings
- if len(mem.extra) > 0:
- # there are setting, parse
- LOG.debug("Extra-Setting supplied. Setting them.")
- for setting in mem.extra:
- setattr(_mem, setting.get_name(), setting.value)
- else:
- if mem.empty:
- LOG.debug("New mem is empty.")
- else:
- LOG.debug("New mem is NOT empty")
- # set extra-settings to default ONLY when apreviously empty or
- # deleted memory was edited to prevent errors such as #4121
- if mem_was_empty :
- LOG.debug("old mem was empty. Setting default for extras.")
- _mem.spmute = 0
- _mem.optsig = 0
- _mem.scramble = 0
- _mem.bcl = 0
- _mem.pttid = 0
- _mem.scode = 0
+ def apply_fm_preset_name(setting, obj):
+ valstring = str (setting.value)
+ for i in range(0,6):
+ if valstring[i] in VALID_CHARS:
+ obj[i] = valstring[i]
+ else:
+ obj[i] = '0xff'
- return mem
+ def apply_fm_freq(setting, obj):
+ value = chirp_common.parse_freq(str(setting.value)) / 10
+ for i in range(7, -1, -1):
+ obj.freq[i] = value % 10
+ value /= 10
+
+ _presets = self._memobj.fm_radio_preset
+ i = 1
+ for preset in _presets:
+ line = RadioSetting("fm_presets_"+ str(i),
+ "Station name " + str(i),
+ RadioSettingValueString(0, 6, _filter(
+ preset.broadcast_station_name)))
+ line.set_apply_callback(apply_fm_preset_name,
+ preset.broadcast_station_name)
+
+ val = RadioSettingValueFloat(0, 108,
+ convert_bytes_to_freq(
+ preset.freq))
+ fmfreq = RadioSetting("fm_presets_"+ str(i) + "_freq",
+ "Frequency "+ str(i), val)
+ val.set_validate_callback(fm_validate)
+ fmfreq.set_apply_callback(apply_fm_freq, preset)
+ fm_presets.append(line)
+ fm_presets.append(fmfreq)
+
+ i = i + 1
- def get_settings(self):
- """Translate the bit in the mem_struct into settings in the UI"""
- _mem = self._memobj
- basic = RadioSettingGroup("basic", "Basic Settings")
- advanced = RadioSettingGroup("advanced", "Advanced Settings")
- other = RadioSettingGroup("other", "Other Settings")
- work = RadioSettingGroup("work", "Work Mode Settings")
- fm_presets = RadioSettingGroup("fm_presets", "FM Presets")
- top = RadioSettings(basic, advanced, other, work, fm_presets)
+ # DTMF-Setting
+ dtmf_enc_settings = RadioSettingGroup ("dtmf_enc_settings",
+ "DTMF Encoding Settings")
+ dtmf_dec_settings = RadioSettingGroup ("dtmf_dec_settings",
+ "DTMF Decoding Settings")
+ top.append(dtmf_enc_settings)
+ top.append(dtmf_dec_settings)
+ txdisable = RadioSetting("dtmf_settings.txdisable",
+ "TX-Disable",
+ RadioSettingValueBoolean(
+ _mem.dtmf_settings.txdisable))
+ dtmf_enc_settings.append(txdisable)
- # Basic
- tdr = RadioSetting("settings.tdr", "Transceiver dual receive",
- RadioSettingValueBoolean(_mem.settings.tdr))
- basic.append(tdr)
+ rxdisable = RadioSetting("dtmf_settings.rxdisable",
+ "RX-Disable",
+ RadioSettingValueBoolean(
+ _mem.dtmf_settings.rxdisable))
+ dtmf_enc_settings.append(rxdisable)
- sql = RadioSetting("settings.sql", "Squelch level",
- RadioSettingValueInteger(0, 9, _mem.settings.sql))
- basic.append(sql)
+ dtmfspeed_on = RadioSetting(
+ "dtmf_settings.dtmfspeed_on",
+ "DTMF Speed (On Time)",
+ RadioSettingValueList(LIST_DTMF_SPEED,
+ LIST_DTMF_SPEED[
+ _mem.dtmf_settings.dtmfspeed_on]))
+ dtmf_enc_settings.append(dtmfspeed_on)
- tot = RadioSetting("settings.tot", "Time out timer",
- RadioSettingValueList(LIST_TOT, LIST_TOT[
- _mem.settings.tot]))
- basic.append(tot)
+ dtmfspeed_off = RadioSetting(
+ "dtmf_settings.dtmfspeed_off",
+ "DTMF Speed (Off Time)",
+ RadioSettingValueList(LIST_DTMF_SPEED,
+ LIST_DTMF_SPEED[
+ _mem.dtmf_settings.dtmfspeed_off]))
+ dtmf_enc_settings.append(dtmfspeed_off)
- if self.MODEL in ("UV-2501", "UV-2501+220", "UV-5001"):
- apo = RadioSetting("settings.apo", "Auto power off timer",
- RadioSettingValueList(LIST_APO, LIST_APO[
- _mem.settings.apo]))
- basic.append(apo)
- else:
- toa = RadioSetting("settings.apo", "Time out alert timer",
- RadioSettingValueList(LIST_TOA, LIST_TOA[
- _mem.settings.apo]))
- basic.append(toa)
+ def memory2string(dmtf_mem):
+ dtmf_string = ""
+ for digit in dmtf_mem:
+ if digit != 255:
+ index = LIST_DTMF_VALUES.index(digit)
+ dtmf_string = dtmf_string + LIST_DTMF_DIGITS[index]
+ return dtmf_string
- abr = RadioSetting("settings.abr", "Backlight timer",
- RadioSettingValueList(LIST_ABR, LIST_ABR[
- _mem.settings.abr]))
- basic.append(abr)
+ def apply_dmtf_frame(setting, obj):
+ LOG.debug("Setting DTMF-Code: " + str(setting.value) )
+ val_string = str(setting.value)
+ for i in range(0,16):
+ obj[i] = 255
+ i = 0
+ for current_char in val_string:
+ current_char = current_char.upper()
+ index = LIST_DTMF_DIGITS.index(current_char)
+ obj[i] = LIST_DTMF_VALUES[ index ]
+ i = i + 1
- beep = RadioSetting("settings.beep", "Key beep",
- RadioSettingValueBoolean(_mem.settings.beep))
- basic.append(beep)
+ codes = self._memobj.dtmf_codes
+ i = 1
+ for dtmfcode in codes:
+ val = RadioSettingValueString(0, 16,
+ memory2string(dtmfcode.code),
+ False, CHARSET_DTMF_DIGITS)
+ line = RadioSetting("dtmf_code_" + str(i) + "_code",
+ "DMTF Code " + str(i), val)
+ line.set_apply_callback(apply_dmtf_frame, dtmfcode.code)
+ dtmf_enc_settings.append(line)
+ i = i + 1
- dtmfst = RadioSetting("settings.dtmfst", "DTMF side tone",
- RadioSettingValueList(LIST_DTMFST, LIST_DTMFST[
- _mem.settings.dtmfst]))
- basic.append(dtmfst)
+ line = RadioSetting("dtmf_settings.mastervice",
+ "Master and Vice ID",
+ RadioSettingValueBoolean(
+ _mem.dtmf_settings.mastervice))
+ dtmf_dec_settings.append(line)
- prisc = RadioSetting("settings.prisc", "Priority scan",
- RadioSettingValueBoolean(_mem.settings.prisc))
- basic.append(prisc)
+ val = RadioSettingValueString(0, 16,
+ memory2string(
+ _mem.dtmf_settings.masterid),
+ False, CHARSET_DTMF_DIGITS)
+ line = RadioSetting("dtmf_settings.masterid",
+ "Master Control ID ", val)
+ line.set_apply_callback(apply_dmtf_frame,
+ _mem.dtmf_settings.masterid)
+ dtmf_dec_settings.append(line)
- prich = RadioSetting("settings.prich", "Priority channel",
- RadioSettingValueInteger(0, 199,
- _mem.settings.prich))
- basic.append(prich)
+ line = RadioSetting("dtmf_settings.minspection",
+ "Master Inspection",
+ RadioSettingValueBoolean(
+ _mem.dtmf_settings.minspection))
+ dtmf_dec_settings.append(line)
- screv = RadioSetting("settings.screv", "Scan resume method",
- RadioSettingValueList(LIST_SCREV, LIST_SCREV[
- _mem.settings.screv]))
- basic.append(screv)
+ line = RadioSetting("dtmf_settings.mmonitor",
+ "Master Monitor",
+ RadioSettingValueBoolean(
+ _mem.dtmf_settings.mmonitor))
+ dtmf_dec_settings.append(line)
- pttlt = RadioSetting("settings.pttlt", "PTT transmit delay",
- RadioSettingValueInteger(0, 30,
- _mem.settings.pttlt))
- basic.append(pttlt)
+ line = RadioSetting("dtmf_settings.mstun",
+ "Master Stun",
+ RadioSettingValueBoolean(
+ _mem.dtmf_settings.mstun))
+ dtmf_dec_settings.append(line)
- emctp = RadioSetting("settings.emctp", "Alarm mode",
- RadioSettingValueList(LIST_EMCTP, LIST_EMCTP[
- _mem.settings.emctp]))
- basic.append(emctp)
+ line = RadioSetting("dtmf_settings.mkill",
+ "Master Kill",
+ RadioSettingValueBoolean(
+ _mem.dtmf_settings.mkill))
+ dtmf_dec_settings.append(line)
- emcch = RadioSetting("settings.emcch", "Alarm channel",
- RadioSettingValueInteger(0, 199,
- _mem.settings.emcch))
- basic.append(emcch)
+ line = RadioSetting("dtmf_settings.mrevive",
+ "Master Revive",
+ RadioSettingValueBoolean(
+ _mem.dtmf_settings.mrevive))
+ dtmf_dec_settings.append(line)
- ringt = RadioSetting("settings.ringt", "Ring time",
- RadioSettingValueList(LIST_RINGT, LIST_RINGT[
- _mem.settings.ringt]))
- basic.append(ringt)
+ val = RadioSettingValueString(0, 16,
+ memory2string(
+ _mem.dtmf_settings.viceid),
+ False, CHARSET_DTMF_DIGITS)
+ line = RadioSetting("dtmf_settings.viceid",
+ "Vice Control ID ", val)
+ line.set_apply_callback(apply_dmtf_frame,
+ _mem.dtmf_settings.viceid)
+ dtmf_dec_settings.append(line)
- camdf = RadioSetting("settings.camdf", "Display mode A",
- RadioSettingValueList(LIST_MDF, LIST_MDF[
- _mem.settings.camdf]))
- basic.append(camdf)
+ line = RadioSetting("dtmf_settings.vinspection",
+ "Vice Inspection",
+ RadioSettingValueBoolean(
+ _mem.dtmf_settings.vinspection))
+ dtmf_dec_settings.append(line)
- cbmdf = RadioSetting("settings.cbmdf", "Display mode B",
- RadioSettingValueList(LIST_MDF, LIST_MDF[
- _mem.settings.cbmdf]))
- basic.append(cbmdf)
+ line = RadioSetting("dtmf_settings.vmonitor",
+ "Vice Monitor",
+ RadioSettingValueBoolean(
+ _mem.dtmf_settings.vmonitor))
+ dtmf_dec_settings.append(line)
- if self.MODEL in ("UV-2501", "UV-2501+220", "UV-5001"):
- sync = RadioSetting("settings.sync", "A/B channel sync",
- RadioSettingValueBoolean(_mem.settings.sync))
- basic.append(sync)
- else:
- autolk = RadioSetting("settings.sync", "Auto keylock",
- RadioSettingValueBoolean(_mem.settings.sync))
- basic.append(autolk)
-
- ponmsg = RadioSetting("settings.ponmsg", "Power-on message",
- RadioSettingValueList(LIST_PONMSG, LIST_PONMSG[
- _mem.settings.ponmsg]))
- basic.append(ponmsg)
-
- wtled = RadioSetting("settings.wtled", "Standby backlight Color",
- RadioSettingValueList(LIST_COLOR, LIST_COLOR[
- _mem.settings.wtled]))
- basic.append(wtled)
-
- rxled = RadioSetting("settings.rxled", "RX backlight Color",
- RadioSettingValueList(LIST_COLOR, LIST_COLOR[
- _mem.settings.rxled]))
- basic.append(rxled)
-
- txled = RadioSetting("settings.txled", "TX backlight Color",
- RadioSettingValueList(LIST_COLOR, LIST_COLOR[
- _mem.settings.txled]))
- basic.append(txled)
+ line = RadioSetting("dtmf_settings.vstun",
+ "Vice Stun",
+ RadioSettingValueBoolean(
+ _mem.dtmf_settings.vstun))
+ dtmf_dec_settings.append(line)
- anil = RadioSetting("settings.anil", "ANI length",
- RadioSettingValueList(LIST_ANIL, LIST_ANIL[
- _mem.settings.anil]))
- basic.append(anil)
+ line = RadioSetting("dtmf_settings.vkill",
+ "Vice Kill",
+ RadioSettingValueBoolean(
+ _mem.dtmf_settings.vkill))
+ dtmf_dec_settings.append(line)
- reps = RadioSetting("settings.reps", "Relay signal (tone burst)",
- RadioSettingValueList(LIST_REPS, LIST_REPS[
- _mem.settings.reps]))
- basic.append(reps)
+ line = RadioSetting("dtmf_settings.vrevive",
+ "Vice Revive",
+ RadioSettingValueBoolean(
+ _mem.dtmf_settings.vrevive))
+ dtmf_dec_settings.append(line)
- repm = RadioSetting("settings.repm", "Relay condition",
- RadioSettingValueList(LIST_REPM, LIST_REPM[
- _mem.settings.repm]))
- basic.append(repm)
+ val = RadioSettingValueString(0, 16,
+ memory2string(
+ _mem.dtmf_settings.inspection),
+ False, CHARSET_DTMF_DIGITS)
+ line = RadioSetting("dtmf_settings.inspection",
+ "Inspection", val)
+ line.set_apply_callback(apply_dmtf_frame,
+ _mem.dtmf_settings.inspection)
+ dtmf_dec_settings.append(line)
- if self.MODEL in ("UV-2501", "UV-2501+220", "UV-5001"):
- tdrab = RadioSetting("settings.tdrab", "TDR return time",
- RadioSettingValueList(LIST_ABR, LIST_ABR[
- _mem.settings.tdrab]))
- basic.append(tdrab)
+ val = RadioSettingValueString(0, 16,
+ memory2string(
+ _mem.dtmf_settings.alarmcode),
+ False, CHARSET_DTMF_DIGITS)
+ line = RadioSetting("dtmf_settings.alarmcode",
+ "Alarm", val)
+ line.set_apply_callback(apply_dmtf_frame,
+ _mem.dtmf_settings.alarmcode)
+ dtmf_dec_settings.append(line)
- ste = RadioSetting("settings.ste", "Squelch tail eliminate",
- RadioSettingValueBoolean(_mem.settings.ste))
- basic.append(ste)
+ val = RadioSettingValueString(0, 16,
+ memory2string(
+ _mem.dtmf_settings.kill),
+ False, CHARSET_DTMF_DIGITS)
+ line = RadioSetting("dtmf_settings.kill",
+ "Kill", val)
+ line.set_apply_callback(apply_dmtf_frame,
+ _mem.dtmf_settings.kill)
+ dtmf_dec_settings.append(line)
- rpste = RadioSetting("settings.rpste", "Repeater STE",
- RadioSettingValueList(LIST_RINGT, LIST_RINGT[
- _mem.settings.rpste]))
- basic.append(rpste)
+ val = RadioSettingValueString(0, 16,
+ memory2string(
+ _mem.dtmf_settings.monitor),
+ False, CHARSET_DTMF_DIGITS)
+ line = RadioSetting("dtmf_settings.monitor",
+ "Monitor", val)
+ line.set_apply_callback(apply_dmtf_frame,
+ _mem.dtmf_settings.monitor)
+ dtmf_dec_settings.append(line)
- rptdl = RadioSetting("settings.rptdl", "Repeater STE delay",
- RadioSettingValueList(LIST_RPTDL, LIST_RPTDL[
- _mem.settings.rptdl]))
- basic.append(rptdl)
+ val = RadioSettingValueString(0, 16,
+ memory2string(
+ _mem.dtmf_settings.stun),
+ False, CHARSET_DTMF_DIGITS)
+ line = RadioSetting("dtmf_settings.stun",
+ "Stun", val)
+ line.set_apply_callback(apply_dmtf_frame,
+ _mem.dtmf_settings.stun)
+ dtmf_dec_settings.append(line)
- if str(_mem.fingerprint.fp) in BTECH3:
+ val = RadioSettingValueString(0, 16,
+ memory2string(
+ _mem.dtmf_settings.revive),
+ False, CHARSET_DTMF_DIGITS)
+ line = RadioSetting("dtmf_settings.revive",
+ "Revive", val)
+ line.set_apply_callback(apply_dmtf_frame,
+ _mem.dtmf_settings.revive)
+ dtmf_dec_settings.append(line)
- mgain = RadioSetting("settings.mgain", "Mic gain",
- RadioSettingValueInteger(0, 120,
- _mem.settings.mgain))
- basic.append(mgain)
+ def apply_dmtf_listvalue(setting, obj):
+ LOG.debug("Setting value: "+ str(setting.value) + " from list")
+ val = str(setting.value)
+ index = LIST_DTMF_SPECIAL_DIGITS.index(val)
+ val = LIST_DTMF_SPECIAL_VALUES[index]
+ obj.set_value(val)
- dtmfg = RadioSetting("settings.dtmfg", "DTMF gain",
- RadioSettingValueInteger(0, 60,
- _mem.settings.dtmfg))
- basic.append(dtmfg)
+ idx = LIST_DTMF_SPECIAL_VALUES.index(_mem.dtmf_settings.groupcode)
+ line = RadioSetting(
+ "dtmf_settings.groupcode",
+ "Group Code",
+ RadioSettingValueList(LIST_DTMF_SPECIAL_DIGITS,
+ LIST_DTMF_SPECIAL_DIGITS[idx]))
+ line.set_apply_callback(apply_dmtf_listvalue,
+ _mem.dtmf_settings.groupcode)
+ dtmf_dec_settings.append(line)
- # Advanced
- def _filter(name):
- filtered = ""
- for char in str(name):
- if char in VALID_CHARS:
- filtered += char
- else:
- filtered += " "
- return filtered
+ idx = LIST_DTMF_SPECIAL_VALUES.index(_mem.dtmf_settings.spacecode)
+ line = RadioSetting(
+ "dtmf_settings.spacecode",
+ "Space Code",
+ RadioSettingValueList(LIST_DTMF_SPECIAL_DIGITS,
+ LIST_DTMF_SPECIAL_DIGITS[idx]))
+ line.set_apply_callback(apply_dmtf_listvalue,
+ _mem.dtmf_settings.spacecode)
+ dtmf_dec_settings.append(line)
- _msg = self._memobj.poweron_msg
- line1 = RadioSetting("poweron_msg.line1", "Power-on message line 1",
- RadioSettingValueString(0, 6, _filter(
- _msg.line1)))
- advanced.append(line1)
- line2 = RadioSetting("poweron_msg.line2", "Power-on message line 2",
- RadioSettingValueString(0, 6, _filter(
- _msg.line2)))
- advanced.append(line2)
+ if self.COLOR_LCD:
+ line = RadioSetting(
+ "dtmf_settings.resettime",
+ "Reset time",
+ RadioSettingValueList(LIST_5TONE_RESET_COLOR,
+ LIST_5TONE_RESET_COLOR[
+ _mem.dtmf_settings.resettime]))
+ dtmf_dec_settings.append(line)
+ else:
+ line = RadioSetting(
+ "dtmf_settings.resettime",
+ "Reset time",
+ RadioSettingValueList(LIST_5TONE_RESET,
+ LIST_5TONE_RESET[
+ _mem.dtmf_settings.resettime]))
+ dtmf_dec_settings.append(line)
- if self.MODEL in ("UV-2501", "UV-5001"):
- vfomren = RadioSetting("settings2.vfomren", "VFO/MR switching",
- RadioSettingValueBoolean(
- _mem.settings2.vfomren))
- advanced.append(vfomren)
+ line = RadioSetting(
+ "dtmf_settings.delayproctime",
+ "Delay processing time",
+ RadioSettingValueList(LIST_DTMF_DELAY,
+ LIST_DTMF_DELAY[
+ _mem.dtmf_settings.delayproctime]))
+ dtmf_dec_settings.append(line)
- reseten = RadioSetting("settings2.reseten", "RESET",
- RadioSettingValueBoolean(
- _mem.settings2.reseten))
- advanced.append(reseten)
- menuen = RadioSetting("settings2.menuen", "Menu",
- RadioSettingValueBoolean(
- _mem.settings2.menuen))
- advanced.append(menuen)
+ # 5 Tone Settings
+ stds_5tone = RadioSettingGroup ("stds_5tone", "Standards")
+ codes_5tone = RadioSettingGroup ("codes_5tone", "Codes")
- # Other
- def convert_bytes_to_limit(bytes):
- limit = ""
- for byte in bytes:
- if byte < 10:
- limit += chr(byte + 0x30)
- else:
- break
- return limit
+ group_5tone = RadioSettingGroup ("group_5tone", "5 Tone Settings")
+ group_5tone.append(stds_5tone)
+ group_5tone.append(codes_5tone)
- if self.MODEL in ["UV-2501+220", "KT8900R"]:
- _ranges = self._memobj.ranges220
- ranges = "ranges220"
- else:
- _ranges = self._memobj.ranges
- ranges = "ranges"
+ top.append(group_5tone)
- _limit = convert_bytes_to_limit(_ranges.vhf_low)
- val = RadioSettingValueString(0, 3, _limit)
- val.set_mutable(False)
- vhf_low = RadioSetting("%s.vhf_low" % ranges, "VHF low", val)
- other.append(vhf_low)
+ def apply_list_value(setting, obj):
+ options = setting.value.get_options()
+ obj.set_value ( options.index(str(setting.value)) )
- _limit = convert_bytes_to_limit(_ranges.vhf_high)
- val = RadioSettingValueString(0, 3, _limit)
- val.set_mutable(False)
- vhf_high = RadioSetting("%s.vhf_high" % ranges, "VHF high", val)
- other.append(vhf_high)
+ _5tone_standards = self._memobj._5tone_std_settings
+ i = 0
+ for standard in _5tone_standards:
+ std_5tone = RadioSettingGroup ("std_5tone_" + str(i),
+ LIST_5TONE_STANDARDS[i])
+ stds_5tone.append(std_5tone)
+
+ period = standard.period
+ if period == 255:
+ LOG.debug("Period for " + LIST_5TONE_STANDARDS[i] +
+ " is not yet configured. Setting to 70ms.")
+ period = 5
- if self.MODEL in ["UV-2501+220", "KT8900R"]:
- _limit = convert_bytes_to_limit(_ranges.vhf2_low)
- val = RadioSettingValueString(0, 3, _limit)
- val.set_mutable(False)
- vhf2_low = RadioSetting("%s.vhf2_low" % ranges, "VHF2 low", val)
- other.append(vhf2_low)
+ if period <= len( LIST_5TONE_STANDARD_PERIODS ):
+ line = RadioSetting(
+ "_5tone_std_settings_" + str(i) + "_period",
+ "Period (ms)", RadioSettingValueList
+ (LIST_5TONE_STANDARD_PERIODS,
+ LIST_5TONE_STANDARD_PERIODS[period]))
+ line.set_apply_callback(apply_list_value, standard.period)
+ std_5tone.append(line)
+ else:
+ LOG.debug("Invalid value for 5tone period! Disabling.")
- _limit = convert_bytes_to_limit(_ranges.vhf2_high)
- val = RadioSettingValueString(0, 3, _limit)
- val.set_mutable(False)
- vhf2_high = RadioSetting("%s.vhf2_high" % ranges, "VHF2 high", val)
- other.append(vhf2_high)
+ group_tone = standard.group_tone
+ if group_tone == 255:
+ LOG.debug("Group-Tone for " + LIST_5TONE_STANDARDS[i] +
+ " is not yet configured. Setting to A.")
+ group_tone = 10
- _limit = convert_bytes_to_limit(_ranges.uhf_low)
- val = RadioSettingValueString(0, 3, _limit)
- val.set_mutable(False)
- uhf_low = RadioSetting("%s.uhf_low" % ranges, "UHF low", val)
- other.append(uhf_low)
+ if group_tone <= len( LIST_5TONE_DIGITS ):
+ line = RadioSetting(
+ "_5tone_std_settings_" + str(i) + "_grouptone",
+ "Group Tone",
+ RadioSettingValueList(LIST_5TONE_DIGITS,
+ LIST_5TONE_DIGITS[
+ group_tone]))
+ line.set_apply_callback(apply_list_value,
+ standard.group_tone)
+ std_5tone.append(line)
+ else:
+ LOG.debug("Invalid value for 5tone digit! Disabling.")
- _limit = convert_bytes_to_limit(_ranges.uhf_high)
- val = RadioSettingValueString(0, 3, _limit)
- val.set_mutable(False)
- uhf_high = RadioSetting("%s.uhf_high" % ranges, "UHF high", val)
- other.append(uhf_high)
+ repeat_tone = standard.repeat_tone
+ if repeat_tone == 255:
+ LOG.debug("Repeat-Tone for " + LIST_5TONE_STANDARDS[i] +
+ " is not yet configured. Setting to E.")
+ repeat_tone = 14
- val = RadioSettingValueString(0, 6, _filter(_mem.fingerprint.fp))
- val.set_mutable(False)
- fp = RadioSetting("fingerprint.fp", "Fingerprint", val)
- other.append(fp)
+ if repeat_tone <= len( LIST_5TONE_DIGITS ):
+ line = RadioSetting(
+ "_5tone_std_settings_" + str(i) + "_repttone",
+ "Repeat Tone",
+ RadioSettingValueList(LIST_5TONE_DIGITS,
+ LIST_5TONE_DIGITS[
+ repeat_tone]))
+ line.set_apply_callback(apply_list_value,
+ standard.repeat_tone)
+ std_5tone.append(line)
+ else:
+ LOG.debug("Invalid value for 5tone digit! Disabling.")
+ i = i + 1
- # Work
- dispab = RadioSetting("settings2.dispab", "Display",
- RadioSettingValueList(LIST_AB,LIST_AB[
- _mem.settings2.dispab]))
- work.append(dispab)
+ def my_apply_5tonestdlist_value(setting, obj):
+ if LIST_5TONE_STANDARDS.index(str(setting.value)) == 15:
+ obj.set_value(0xFF)
+ else:
+ obj.set_value( LIST_5TONE_STANDARDS.
+ index(str(setting.value)) )
- vfomr = RadioSetting("settings2.vfomr", "VFO/MR mode",
- RadioSettingValueList(LIST_VFOMR,LIST_VFOMR[
- _mem.settings2.vfomr]))
- work.append(vfomr)
+ def apply_5tone_frame(setting, obj):
+ LOG.debug("Setting 5 Tone: " + str(setting.value) )
+ valstring = str(setting.value)
+ if len(valstring) == 0:
+ for i in range(0,5):
+ obj[i] = 255
+ else:
+ validFrame = True
+ for i in range(0,5):
+ currentChar = valstring[i].upper()
+ if currentChar in LIST_5TONE_DIGITS:
+ obj[i] = LIST_5TONE_DIGITS.index(currentChar)
+ else:
+ validFrame = False
+ LOG.debug("invalid char: " + str(currentChar))
+ if not validFrame:
+ LOG.debug("setting whole frame to FF" )
+ for i in range(0,5):
+ obj[i] = 255
- keylock = RadioSetting("settings2.keylock", "Keypad lock",
- RadioSettingValueBoolean(_mem.settings2.keylock))
- work.append(keylock)
+ def validate_5tone_frame(value):
+ if (len(str(value)) != 5) and (len(str(value)) != 0) :
+ msg = ("5 Tone must have 5 digits or 0 digits")
+ raise InvalidValueError(msg)
+ for digit in str(value):
+ if digit.upper() not in LIST_5TONE_DIGITS:
+ msg = (str(digit) + " is not a valid digit for 5tones")
+ raise InvalidValueError(msg)
+ return value
- mrcha = RadioSetting("settings2.mrcha", "MR A channel",
- RadioSettingValueInteger(0, 199,
- _mem.settings2.mrcha))
- work.append(mrcha)
+ def frame2string(frame):
+ frameString = ""
+ for digit in frame:
+ if digit != 255:
+ frameString = frameString + LIST_5TONE_DIGITS[digit]
+ return frameString
- mrchb = RadioSetting("settings2.mrchb", "MR B channel",
- RadioSettingValueInteger(0, 199,
- _mem.settings2.mrchb))
- work.append(mrchb)
+ _5tone_codes = self._memobj._5tone_codes
+ i = 1
+ for code in _5tone_codes:
+ code_5tone = RadioSettingGroup ("code_5tone_" + str(i),
+ "5 Tone code " + str(i))
+ codes_5tone.append(code_5tone)
+ if (code.standard == 255 ):
+ currentVal = 15
+ else:
+ currentVal = code.standard
+ line = RadioSetting("_5tone_code_" + str(i) + "_std",
+ " Standard",
+ RadioSettingValueList(LIST_5TONE_STANDARDS,
+ LIST_5TONE_STANDARDS[
+ currentVal]) )
+ line.set_apply_callback(my_apply_5tonestdlist_value,
+ code.standard)
+ code_5tone.append(line)
- def convert_bytes_to_freq(bytes):
- real_freq = 0
- for byte in bytes:
- real_freq = (real_freq * 10) + byte
- return chirp_common.format_freq(real_freq * 10)
+ val = RadioSettingValueString(0, 6,
+ frame2string(code.frame1), False)
+ line = RadioSetting("_5tone_code_" + str(i) + "_frame1",
+ " Frame 1", val)
+ val.set_validate_callback(validate_5tone_frame)
+ line.set_apply_callback(apply_5tone_frame, code.frame1)
+ code_5tone.append(line)
- def my_validate(value):
- value = chirp_common.parse_freq(value)
- if "+220" in self.MODEL:
- if 180000000 <= value and value < 210000000:
- msg = ("Can't be between 180.00000-210.00000")
- raise InvalidValueError(msg)
- elif 231000000 <= value and value < 400000000:
- msg = ("Can't be between 231.00000-400.00000")
- raise InvalidValueError(msg)
- elif "8900R" in self.MODEL:
- if 180000000 <= value and value < 240000000:
- msg = ("Can't be between 180.00000-240.00000")
- raise InvalidValueError(msg)
- elif 271000000 <= value and value < 400000000:
- msg = ("Can't be between 271.00000-400.00000")
- raise InvalidValueError(msg)
- elif 180000000 <= value and value < 400000000:
- msg = ("Can't be between 180.00000-400.00000")
- raise InvalidValueError(msg)
- return chirp_common.format_freq(value)
+ val = RadioSettingValueString(0, 6,
+ frame2string(code.frame2), False)
+ line = RadioSetting("_5tone_code_" + str(i) + "_frame2",
+ " Frame 2", val)
+ val.set_validate_callback(validate_5tone_frame)
+ line.set_apply_callback(apply_5tone_frame, code.frame2)
+ code_5tone.append(line)
- def apply_freq(setting, obj):
- value = chirp_common.parse_freq(str(setting.value)) / 10
- for i in range(7, -1, -1):
- obj.freq[i] = value % 10
- value /= 10
+ val = RadioSettingValueString(0, 6,
+ frame2string(code.frame3), False)
+ line = RadioSetting("_5tone_code_" + str(i) + "_frame3",
+ " Frame 3", val)
+ val.set_validate_callback(validate_5tone_frame)
+ line.set_apply_callback(apply_5tone_frame, code.frame3)
+ code_5tone.append(line)
+ i = i + 1
- val1a = RadioSettingValueString(0, 10, convert_bytes_to_freq(
- _mem.vfo.a.freq))
- val1a.set_validate_callback(my_validate)
- vfoafreq = RadioSetting("vfo.a.freq", "VFO A frequency", val1a)
- vfoafreq.set_apply_callback(apply_freq, _mem.vfo.a)
- work.append(vfoafreq)
+ _5_tone_decode1 = RadioSetting(
+ "_5tone_settings._5tone_decode_call_frame1",
+ "5 Tone decode call Frame 1",
+ RadioSettingValueBoolean(
+ _mem._5tone_settings._5tone_decode_call_frame1))
+ group_5tone.append(_5_tone_decode1)
- val1b = RadioSettingValueString(0, 10, convert_bytes_to_freq(
- _mem.vfo.b.freq))
- val1b.set_validate_callback(my_validate)
- vfobfreq = RadioSetting("vfo.b.freq", "VFO B frequency", val1b)
- vfobfreq.set_apply_callback(apply_freq, _mem.vfo.b)
- work.append(vfobfreq)
+ _5_tone_decode2 = RadioSetting(
+ "_5tone_settings._5tone_decode_call_frame2",
+ "5 Tone decode call Frame 2",
+ RadioSettingValueBoolean(
+ _mem._5tone_settings._5tone_decode_call_frame2))
+ group_5tone.append(_5_tone_decode2)
- vfoashiftd = RadioSetting("vfo.a.shiftd", "VFO A shift",
- RadioSettingValueList(LIST_SHIFT, LIST_SHIFT[
- _mem.vfo.a.shiftd]))
- work.append(vfoashiftd)
+ _5_tone_decode3 = RadioSetting(
+ "_5tone_settings._5tone_decode_call_frame3",
+ "5 Tone decode call Frame 3",
+ RadioSettingValueBoolean(
+ _mem._5tone_settings._5tone_decode_call_frame3))
+ group_5tone.append(_5_tone_decode3)
- vfobshiftd = RadioSetting("vfo.b.shiftd", "VFO B shift",
- RadioSettingValueList(LIST_SHIFT, LIST_SHIFT[
- _mem.vfo.b.shiftd]))
- work.append(vfobshiftd)
+ _5_tone_decode_disp1 = RadioSetting(
+ "_5tone_settings._5tone_decode_disp_frame1",
+ "5 Tone decode disp Frame 1",
+ RadioSettingValueBoolean(
+ _mem._5tone_settings._5tone_decode_disp_frame1))
+ group_5tone.append(_5_tone_decode_disp1)
- def convert_bytes_to_offset(bytes):
- real_offset = 0
- for byte in bytes:
- real_offset = (real_offset * 10) + byte
- return chirp_common.format_freq(real_offset * 10000)
+ _5_tone_decode_disp2 = RadioSetting(
+ "_5tone_settings._5tone_decode_disp_frame2",
+ "5 Tone decode disp Frame 2",
+ RadioSettingValueBoolean(
+ _mem._5tone_settings._5tone_decode_disp_frame2))
+ group_5tone.append(_5_tone_decode_disp2)
- def apply_offset(setting, obj):
- value = chirp_common.parse_freq(str(setting.value)) / 10000
- for i in range(3, -1, -1):
- obj.offset[i] = value % 10
- value /= 10
+ _5_tone_decode_disp3 = RadioSetting(
+ "_5tone_settings._5tone_decode_disp_frame3",
+ "5 Tone decode disp Frame 3",
+ RadioSettingValueBoolean(
+ _mem._5tone_settings._5tone_decode_disp_frame3))
+ group_5tone.append(_5_tone_decode_disp3)
- val1a = RadioSettingValueString(0, 10, convert_bytes_to_offset(
- _mem.vfo.a.offset))
- vfoaoffset = RadioSetting("vfo.a.offset",
- "VFO A offset (0.00-99.95)", val1a)
- vfoaoffset.set_apply_callback(apply_offset, _mem.vfo.a)
- work.append(vfoaoffset)
+ decode_standard = _mem._5tone_settings.decode_standard
+ if decode_standard == 255:
+ decode_standard = 0
+ if decode_standard <= len (LIST_5TONE_STANDARDS_without_none) :
+ line = RadioSetting("_5tone_settings.decode_standard",
+ "5 Tone-decode Standard",
+ RadioSettingValueList(
+ LIST_5TONE_STANDARDS_without_none,
+ LIST_5TONE_STANDARDS_without_none[
+ decode_standard]))
+ group_5tone.append(line)
+ else:
+ LOG.debug("Invalid decode std...")
+
+ _5tone_delay1 = _mem._5tone_settings._5tone_delay1
+ if _5tone_delay1 == 255:
+ _5tone_delay1 = 20
- val1b = RadioSettingValueString(0, 10, convert_bytes_to_offset(
- _mem.vfo.b.offset))
- vfoboffset = RadioSetting("vfo.b.offset",
- "VFO B offset (0.00-99.95)", val1b)
- vfoboffset.set_apply_callback(apply_offset, _mem.vfo.b)
- work.append(vfoboffset)
+ if _5tone_delay1 <= len( LIST_5TONE_DELAY ):
+ list = RadioSettingValueList(LIST_5TONE_DELAY,
+ LIST_5TONE_DELAY[
+ _5tone_delay1])
+ line = RadioSetting("_5tone_settings._5tone_delay1",
+ "5 Tone Delay Frame 1", list)
+ group_5tone.append(line)
+ else:
+ LOG.debug("Invalid value for 5tone delay (frame1) ! Disabling.")
- vfoatxp = RadioSetting("vfo.a.power", "VFO A power",
- RadioSettingValueList(LIST_TXP,LIST_TXP[
- _mem.vfo.a.power]))
- work.append(vfoatxp)
+ _5tone_delay2 = _mem._5tone_settings._5tone_delay2
+ if _5tone_delay2 == 255:
+ _5tone_delay2 = 20
+ LOG.debug("5 Tone delay unconfigured! Resetting to 200ms.")
- vfobtxp = RadioSetting("vfo.b.power", "VFO B power",
- RadioSettingValueList(LIST_TXP,LIST_TXP[
- _mem.vfo.b.power]))
- work.append(vfobtxp)
+ if _5tone_delay2 <= len( LIST_5TONE_DELAY ):
+ list = RadioSettingValueList(LIST_5TONE_DELAY,
+ LIST_5TONE_DELAY[
+ _5tone_delay2])
+ line = RadioSetting("_5tone_settings._5tone_delay2",
+ "5 Tone Delay Frame 2", list)
+ group_5tone.append(line)
+ else:
+ LOG.debug("Invalid value for 5tone delay (frame2)! Disabling.")
- vfoawide = RadioSetting("vfo.a.wide", "VFO A bandwidth",
- RadioSettingValueList(LIST_WIDE,LIST_WIDE[
- _mem.vfo.a.wide]))
- work.append(vfoawide)
+ _5tone_delay3 = _mem._5tone_settings._5tone_delay3
+ if _5tone_delay3 == 255:
+ _5tone_delay3 = 20
+ LOG.debug("5 Tone delay unconfigured! Resetting to 200ms.")
- vfobwide = RadioSetting("vfo.b.wide", "VFO B bandwidth",
- RadioSettingValueList(LIST_WIDE,LIST_WIDE[
- _mem.vfo.b.wide]))
- work.append(vfobwide)
+ if _5tone_delay3 <= len( LIST_5TONE_DELAY ):
+ list = RadioSettingValueList(LIST_5TONE_DELAY,
+ LIST_5TONE_DELAY[
+ _5tone_delay3])
+ line = RadioSetting("_5tone_settings._5tone_delay3",
+ "5 Tone Delay Frame 3", list )
+ group_5tone.append(line)
+ else:
+ LOG.debug("Invalid value for 5tone delay (frame3)! Disabling.")
- vfoastep = RadioSetting("vfo.a.step", "VFO A step",
- RadioSettingValueList(LIST_STEP,LIST_STEP[
- _mem.vfo.a.step]))
- work.append(vfoastep)
+ ext_length = _mem._5tone_settings._5tone_first_digit_ext_length
+ if ext_length == 255:
+ ext_length = 0
+ LOG.debug("1st Tone ext lenght unconfigured! Resetting to 0")
- vfobstep = RadioSetting("vfo.b.step", "VFO B step",
- RadioSettingValueList(LIST_STEP,LIST_STEP[
- _mem.vfo.b.step]))
- work.append(vfobstep)
+ if ext_length <= len(
+ LIST_5TONE_DELAY ):
+ list = RadioSettingValueList(
+ LIST_5TONE_DELAY,
+ LIST_5TONE_DELAY[
+ ext_length])
+ line = RadioSetting(
+ "_5tone_settings._5tone_first_digit_ext_length",
+ "First digit extend length", list)
+ group_5tone.append(line)
+ else:
+ LOG.debug("Invalid value for 5tone ext length! Disabling.")
- vfoaoptsig = RadioSetting("vfo.a.optsig", "VFO A optional signal",
- RadioSettingValueList(OPTSIG_LIST,
- OPTSIG_LIST[_mem.vfo.a.optsig]))
- work.append(vfoaoptsig)
+ decode_reset_time = _mem._5tone_settings.decode_reset_time
+ if decode_reset_time == 255:
+ decode_reset_time = 59
+ LOG.debug("Decode reset time unconfigured. resetting.")
+ if decode_reset_time <= len(LIST_5TONE_RESET):
+ list = RadioSettingValueList(
+ LIST_5TONE_RESET,
+ LIST_5TONE_RESET[
+ decode_reset_time])
+ line = RadioSetting("_5tone_settings.decode_reset_time",
+ "Decode reset time", list)
+ group_5tone.append(line)
+ else:
+ LOG.debug("Invalid value decode reset time! Disabling.")
- vfoboptsig = RadioSetting("vfo.b.optsig", "VFO B optional signal",
- RadioSettingValueList(OPTSIG_LIST,
- OPTSIG_LIST[_mem.vfo.b.optsig]))
- work.append(vfoboptsig)
+ # 2 Tone
+ encode_2tone = RadioSettingGroup ("encode_2tone", "2 Tone Encode")
+ decode_2tone = RadioSettingGroup ("decode_2tone", "2 Code Decode")
- vfoaspmute = RadioSetting("vfo.a.spmute", "VFO A speaker mute",
- RadioSettingValueList(SPMUTE_LIST,
- SPMUTE_LIST[_mem.vfo.a.spmute]))
- work.append(vfoaspmute)
+ top.append(encode_2tone)
+ top.append(decode_2tone)
- vfobspmute = RadioSetting("vfo.b.spmute", "VFO B speaker mute",
- RadioSettingValueList(SPMUTE_LIST,
- SPMUTE_LIST[_mem.vfo.b.spmute]))
- work.append(vfobspmute)
+ duration_1st_tone = self._memobj._2tone.duration_1st_tone
+ if duration_1st_tone == 255:
+ LOG.debug("Duration of first 2 Tone digit is not yet " +
+ "configured. Setting to 600ms")
+ duration_1st_tone = 60
- vfoascr = RadioSetting("vfo.a.scramble", "VFO A scramble",
- RadioSettingValueBoolean(_mem.vfo.a.scramble))
- work.append(vfoascr)
+ if duration_1st_tone <= len( LIST_5TONE_DELAY ):
+ line = RadioSetting("_2tone.duration_1st_tone",
+ "Duration 1st Tone",
+ RadioSettingValueList(LIST_5TONE_DELAY,
+ LIST_5TONE_DELAY[
+ duration_1st_tone]))
+ encode_2tone.append(line)
- vfobscr = RadioSetting("vfo.b.scramble", "VFO B scramble",
- RadioSettingValueBoolean(_mem.vfo.b.scramble))
- work.append(vfobscr)
+ duration_2nd_tone = self._memobj._2tone.duration_2nd_tone
+ if duration_2nd_tone == 255:
+ LOG.debug("Duration of second 2 Tone digit is not yet " +
+ "configured. Setting to 600ms")
+ duration_2nd_tone = 60
- vfoascode = RadioSetting("vfo.a.scode", "VFO A PTT-ID",
- RadioSettingValueList(PTTIDCODE_LIST,
- PTTIDCODE_LIST[_mem.vfo.a.scode]))
- work.append(vfoascode)
+ if duration_2nd_tone <= len( LIST_5TONE_DELAY ):
+ line = RadioSetting("_2tone.duration_2nd_tone",
+ "Duration 2nd Tone",
+ RadioSettingValueList(LIST_5TONE_DELAY,
+ LIST_5TONE_DELAY[
+ duration_2nd_tone]))
+ encode_2tone.append(line)
- vfobscode = RadioSetting("vfo.b.scode", "VFO B PTT-ID",
- RadioSettingValueList(PTTIDCODE_LIST,
- PTTIDCODE_LIST[_mem.vfo.b.scode]))
- work.append(vfobscode)
+ duration_gap = self._memobj._2tone.duration_gap
+ if duration_gap == 255:
+ LOG.debug("Duration of gap is not yet " +
+ "configured. Setting to 300ms")
+ duration_gap = 30
- pttid = RadioSetting("settings.pttid", "PTT ID",
- RadioSettingValueList(PTTID_LIST,
- PTTID_LIST[_mem.settings.pttid]))
- work.append(pttid)
+ if duration_gap <= len( LIST_5TONE_DELAY ):
+ line = RadioSetting("_2tone.duration_gap", "Duration of gap",
+ RadioSettingValueList(LIST_5TONE_DELAY,
+ LIST_5TONE_DELAY[
+ duration_gap]))
+ encode_2tone.append(line)
- #FM presets
- def fm_validate(value):
+ def _2tone_validate(value):
if value == 0:
- return chirp_common.format_freq(value)
- if not (87.5 <= value and value <= 108.0): # 87.5-108MHz
- msg = ("FM-Preset-Frequency: Must be between 87.5 and 108 MHz")
+ return 65535
+ if value == 65535:
+ return value
+ if not (300 <= value and value <= 3000):
+ msg = ("2 Tone Frequency: Must be between 300 and 3000 Hz")
raise InvalidValueError(msg)
return value
- def apply_fm_preset_name(setting, obj):
- valstring = str (setting.value)
- for i in range(0,6):
- if valstring[i] in VALID_CHARS:
- obj[i] = valstring[i]
- else:
- obj[i] = '0xff'
+ def apply_2tone_freq(setting, obj):
+ val = int(setting.value)
+ if (val == 0) or (val == 65535):
+ obj.set_value(65535)
+ else:
+ obj.set_value(val)
- def apply_fm_freq(setting, obj):
- value = chirp_common.parse_freq(str(setting.value)) / 10
- for i in range(7, -1, -1):
- obj.freq[i] = value % 10
- value /= 10
-
- _presets = self._memobj.fm_radio_preset
i = 1
- for preset in _presets:
- line = RadioSetting("fm_presets_"+ str(i), "Station name " + str(i),
- RadioSettingValueString(0, 6, _filter(
- preset.broadcast_station_name)))
- line.set_apply_callback(apply_fm_preset_name,
- preset.broadcast_station_name)
-
- val = RadioSettingValueFloat(0, 108, convert_bytes_to_freq(preset.freq))
- fmfreq = RadioSetting("fm_presets_"+ str(i) + "_freq", "Frequency "+ str(i), val)
- val.set_validate_callback(fm_validate)
- fmfreq.set_apply_callback(apply_fm_freq, preset)
- fm_presets.append(line)
- fm_presets.append(fmfreq)
-
- i = i + 1
-
- # DTMF-Setting
- dtmf_enc_settings = RadioSettingGroup ("dtmf_enc_settings",
- "DTMF Encoding Settings")
- dtmf_dec_settings = RadioSettingGroup ("dtmf_dec_settings",
- "DTMF Decoding Settings")
- top.append(dtmf_enc_settings)
- top.append(dtmf_dec_settings)
- txdisable = RadioSetting("dtmf_settings.txdisable",
- "TX-Disable",
- RadioSettingValueBoolean(
- _mem.dtmf_settings.txdisable))
- dtmf_enc_settings.append(txdisable)
-
- rxdisable = RadioSetting("dtmf_settings.rxdisable",
- "RX-Disable",
- RadioSettingValueBoolean(
- _mem.dtmf_settings.rxdisable))
- dtmf_enc_settings.append(rxdisable)
+ for code in self._memobj._2tone._2tone_encode:
+ code_2tone = RadioSettingGroup ("code_2tone_" + str(i),
+ "Encode Code " + str(i))
+ encode_2tone.append(code_2tone)
- dtmfspeed_on = RadioSetting(
- "dtmf_settings.dtmfspeed_on",
- "DTMF Speed (On Time)",
- RadioSettingValueList(LIST_DTMF_SPEED,
- LIST_DTMF_SPEED[
- _mem.dtmf_settings.dtmfspeed_on]))
- dtmf_enc_settings.append(dtmfspeed_on)
+ tmp = code.freq1
+ if tmp == 65535:
+ tmp = 0
+ val1 = RadioSettingValueInteger(0, 65535, tmp)
+ freq1 = RadioSetting("2tone_code_"+ str(i) + "_freq1",
+ "Frequency 1", val1)
+ val1.set_validate_callback(_2tone_validate)
+ freq1.set_apply_callback(apply_2tone_freq, code.freq1)
+ code_2tone.append(freq1)
- dtmfspeed_off = RadioSetting(
- "dtmf_settings.dtmfspeed_off",
- "DTMF Speed (Off Time)",
- RadioSettingValueList(LIST_DTMF_SPEED,
- LIST_DTMF_SPEED[
- _mem.dtmf_settings.dtmfspeed_off]))
- dtmf_enc_settings.append(dtmfspeed_off)
+ tmp = code.freq2
+ if tmp == 65535:
+ tmp = 0
+ val2 = RadioSettingValueInteger(0, 65535, tmp)
+ freq2 = RadioSetting("2tone_code_"+ str(i) + "_freq2",
+ "Frequency 2", val2)
+ val2.set_validate_callback(_2tone_validate)
+ freq2.set_apply_callback(apply_2tone_freq, code.freq2)
+ code_2tone.append(freq2)
- def memory2string(dmtf_mem):
- dtmf_string = ""
- for digit in dmtf_mem:
- if digit != 255:
- index = LIST_DTMF_VALUES.index(digit)
- dtmf_string = dtmf_string + LIST_DTMF_DIGITS[index]
- return dtmf_string
+ i = i + 1
- def apply_dmtf_frame(setting, obj):
- LOG.debug("Setting DTMF-Code: " + str(setting.value) )
- val_string = str(setting.value)
- for i in range(0,16):
- obj[i] = 255
- i = 0
- for current_char in val_string:
- current_char = current_char.upper()
- index = LIST_DTMF_DIGITS.index(current_char)
- obj[i] = LIST_DTMF_VALUES[ index ]
- i = i + 1
+ decode_reset_time = _mem._2tone.reset_time
+ if decode_reset_time == 255:
+ decode_reset_time = 59
+ LOG.debug("Decode reset time unconfigured. resetting.")
+ if decode_reset_time <= len(LIST_5TONE_RESET):
+ list = RadioSettingValueList(
+ LIST_5TONE_RESET,
+ LIST_5TONE_RESET[
+ decode_reset_time])
+ line = RadioSetting("_2tone.reset_time",
+ "Decode reset time", list)
+ decode_2tone.append(line)
+ else:
+ LOG.debug("Invalid value decode reset time! Disabling.")
- codes = self._memobj.dtmf_codes
- i = 1
- for dtmfcode in codes:
- val = RadioSettingValueString(0, 16,
- memory2string(dtmfcode.code),
- False, CHARSET_DTMF_DIGITS)
- line = RadioSetting("dtmf_code_" + str(i) + "_code",
- "DMTF Code " + str(i), val)
- line.set_apply_callback(apply_dmtf_frame, dtmfcode.code)
- dtmf_enc_settings.append(line)
- i = i + 1
+ def apply_2tone_freq_pair(setting, obj):
+ val = int(setting.value)
+ derived_val = 65535
+ frqname = str(setting._name[-5:])
+ derivedname = "derived_from_" + frqname
- line = RadioSetting("dtmf_settings.mastervice",
- "Master and Vice ID",
- RadioSettingValueBoolean(
- _mem.dtmf_settings.mastervice))
- dtmf_dec_settings.append(line)
+ if (val == 0):
+ val = 65535
+ derived_val = 65535
+ else:
+ derived_val = int(round(2304000.0/val))
- val = RadioSettingValueString(0, 16,
- memory2string(
- _mem.dtmf_settings.masterid),
- False, CHARSET_DTMF_DIGITS)
- line = RadioSetting("dtmf_settings.masterid",
- "Master Control ID ", val)
- line.set_apply_callback(apply_dmtf_frame,
- _mem.dtmf_settings.masterid)
- dtmf_dec_settings.append(line)
+ obj[frqname].set_value( val )
+ obj[derivedname].set_value( derived_val )
- line = RadioSetting("dtmf_settings.minspection",
- "Master Inspection",
- RadioSettingValueBoolean(
- _mem.dtmf_settings.minspection))
- dtmf_dec_settings.append(line)
+ LOG.debug("Apply " + frqname + ": " + str(val) + " | "
+ + derivedname + ": " + str(derived_val))
- line = RadioSetting("dtmf_settings.mmonitor",
- "Master Monitor",
- RadioSettingValueBoolean(
- _mem.dtmf_settings.mmonitor))
- dtmf_dec_settings.append(line)
+ i = 1
+ for decode_code in self._memobj._2tone._2tone_decode:
+ _2tone_dec_code = RadioSettingGroup ("code_2tone_" + str(i),
+ "Decode Code " + str(i))
+ decode_2tone.append(_2tone_dec_code)
- line = RadioSetting("dtmf_settings.mstun",
- "Master Stun",
- RadioSettingValueBoolean(
- _mem.dtmf_settings.mstun))
- dtmf_dec_settings.append(line)
+ j = 1
+ for dec in decode_code.decs:
+ val = dec.dec
+ if val == 255:
+ LOG.debug("Dec for Code " + str(i) + " Dec " + str(j) +
+ " is not yet configured. Setting to 0.")
+ val = 0
- line = RadioSetting("dtmf_settings.mkill",
- "Master Kill",
- RadioSettingValueBoolean(
- _mem.dtmf_settings.mkill))
- dtmf_dec_settings.append(line)
+ if val <= len( LIST_2TONE_DEC ):
+ line = RadioSetting(
+ "_2tone_dec_settings_" + str(i) + "_dec_" + str(j),
+ "Dec " + str(j), RadioSettingValueList
+ (LIST_2TONE_DEC,
+ LIST_2TONE_DEC[val]))
+ line.set_apply_callback(apply_list_value, dec.dec)
+ _2tone_dec_code.append(line)
+ else:
+ LOG.debug("Invalid value for 2tone dec! Disabling.")
- line = RadioSetting("dtmf_settings.mrevive",
- "Master Revive",
- RadioSettingValueBoolean(
- _mem.dtmf_settings.mrevive))
- dtmf_dec_settings.append(line)
+ val = dec.response
+ if val == 255:
+ LOG.debug("Response for Code " + str(i) + " Dec " + str(j)+
+ " is not yet configured. Setting to 0.")
+ val = 0
- val = RadioSettingValueString(0, 16,
- memory2string(
- _mem.dtmf_settings.viceid),
- False, CHARSET_DTMF_DIGITS)
- line = RadioSetting("dtmf_settings.viceid",
- "Vice Control ID ", val)
- line.set_apply_callback(apply_dmtf_frame,
- _mem.dtmf_settings.viceid)
- dtmf_dec_settings.append(line)
+ if val <= len( LIST_2TONE_RESPONSE ):
+ line = RadioSetting(
+ "_2tone_dec_settings_" + str(i) + "_resp_" + str(j),
+ "Response " + str(j), RadioSettingValueList
+ (LIST_2TONE_RESPONSE,
+ LIST_2TONE_RESPONSE[val]))
+ line.set_apply_callback(apply_list_value, dec.response)
+ _2tone_dec_code.append(line)
+ else:
+ LOG.debug("Invalid value for 2tone response! Disabling.")
- line = RadioSetting("dtmf_settings.vinspection",
- "Vice Inspection",
- RadioSettingValueBoolean(
- _mem.dtmf_settings.vinspection))
- dtmf_dec_settings.append(line)
+ val = dec.alert
+ if val == 255:
+ LOG.debug("Alert for Code " + str(i) + " Dec " + str(j) +
+ " is not yet configured. Setting to 0.")
+ val = 0
- line = RadioSetting("dtmf_settings.vmonitor",
- "Vice Monitor",
- RadioSettingValueBoolean(
- _mem.dtmf_settings.vmonitor))
- dtmf_dec_settings.append(line)
+ if val <= len( PTTIDCODE_LIST ):
+ line = RadioSetting(
+ "_2tone_dec_settings_" + str(i) + "_alert_" + str(j),
+ "Alert " + str(j), RadioSettingValueList
+ (PTTIDCODE_LIST,
+ PTTIDCODE_LIST[val]))
+ line.set_apply_callback(apply_list_value, dec.alert)
+ _2tone_dec_code.append(line)
+ else:
+ LOG.debug("Invalid value for 2tone alert! Disabling.")
+ j = j + 1
- line = RadioSetting("dtmf_settings.vstun",
- "Vice Stun",
- RadioSettingValueBoolean(
- _mem.dtmf_settings.vstun))
- dtmf_dec_settings.append(line)
+ freq = self._memobj._2tone.freqs[i-1]
+ for char in ['A', 'B', 'C', 'D']:
+ setting_name = "freq" + str(char)
- line = RadioSetting("dtmf_settings.vkill",
- "Vice Kill",
- RadioSettingValueBoolean(
- _mem.dtmf_settings.vkill))
- dtmf_dec_settings.append(line)
+ tmp = freq[setting_name]
+ if tmp == 65535:
+ tmp = 0
+ if tmp != 0:
+ expected = int(round(2304000.0/tmp))
+ from_mem = freq["derived_from_" + setting_name]
+ if expected != from_mem:
+ LOG.error("Expected " + str(expected) +
+ " but read " + str(from_mem ) +
+ ". Disabling 2Tone Decode Freqs!")
+ break
+ val = RadioSettingValueInteger(0, 65535, tmp)
+ frq = RadioSetting("2tone_dec_"+ str(i) + "_freq" + str(char),
+ ("Decode Frequency " +str(char)), val)
+ val.set_validate_callback(_2tone_validate)
+ frq.set_apply_callback(apply_2tone_freq_pair, freq)
+ _2tone_dec_code.append(frq)
- line = RadioSetting("dtmf_settings.vrevive",
- "Vice Revive",
- RadioSettingValueBoolean(
- _mem.dtmf_settings.vrevive))
- dtmf_dec_settings.append(line)
+ i = i + 1
- val = RadioSettingValueString(0, 16,
- memory2string(
- _mem.dtmf_settings.inspection),
- False, CHARSET_DTMF_DIGITS)
- line = RadioSetting("dtmf_settings.inspection",
- "Inspection", val)
- line.set_apply_callback(apply_dmtf_frame,
- _mem.dtmf_settings.inspection)
- dtmf_dec_settings.append(line)
+ return top
- val = RadioSettingValueString(0, 16,
- memory2string(
- _mem.dtmf_settings.alarmcode),
- False, CHARSET_DTMF_DIGITS)
- line = RadioSetting("dtmf_settings.alarmcode",
- "Alarm", val)
- line.set_apply_callback(apply_dmtf_frame,
- _mem.dtmf_settings.alarmcode)
- dtmf_dec_settings.append(line)
+ def set_settings(self, settings):
+ _settings = self._memobj.settings
+ for element in settings:
+ if not isinstance(element, RadioSetting):
+ if element.get_name() == "fm_preset":
+ self._set_fm_preset(element)
+ else:
+ self.set_settings(element)
+ continue
+ else:
+ try:
+ name = element.get_name()
+ if "." in name:
+ bits = name.split(".")
+ obj = self._memobj
+ for bit in bits[:-1]:
+ if "/" in bit:
+ bit, index = bit.split("/", 1)
+ index = int(index)
+ obj = getattr(obj, bit)[index]
+ else:
+ obj = getattr(obj, bit)
+ setting = bits[-1]
+ else:
+ obj = _settings
+ setting = element.get_name()
- val = RadioSettingValueString(0, 16,
- memory2string(
- _mem.dtmf_settings.kill),
- False, CHARSET_DTMF_DIGITS)
- line = RadioSetting("dtmf_settings.kill",
- "Kill", val)
- line.set_apply_callback(apply_dmtf_frame,
- _mem.dtmf_settings.kill)
- dtmf_dec_settings.append(line)
+ if element.has_apply_callback():
+ LOG.debug("Using apply callback")
+ element.run_apply_callback()
+ elif element.value.get_mutable():
+ LOG.debug("Setting %s = %s" % (setting, element.value))
+ setattr(obj, setting, element.value)
+ except Exception, e:
+ LOG.debug(element.get_name())
+ raise
- val = RadioSettingValueString(0, 16,
- memory2string(
- _mem.dtmf_settings.monitor),
- False, CHARSET_DTMF_DIGITS)
- line = RadioSetting("dtmf_settings.monitor",
- "Monitor", val)
- line.set_apply_callback(apply_dmtf_frame,
- _mem.dtmf_settings.monitor)
- dtmf_dec_settings.append(line)
+ @classmethod
+ def match_model(cls, filedata, filename):
+ match_size = False
+ match_model = False
- val = RadioSettingValueString(0, 16,
- memory2string(
- _mem.dtmf_settings.stun),
- False, CHARSET_DTMF_DIGITS)
- line = RadioSetting("dtmf_settings.stun",
- "Stun", val)
- line.set_apply_callback(apply_dmtf_frame,
- _mem.dtmf_settings.stun)
- dtmf_dec_settings.append(line)
+ # testing the file data size
+ if len(filedata) == MEM_SIZE:
+ match_size = True
- val = RadioSettingValueString(0, 16,
- memory2string(
- _mem.dtmf_settings.revive),
- False, CHARSET_DTMF_DIGITS)
- line = RadioSetting("dtmf_settings.revive",
- "Revive", val)
- line.set_apply_callback(apply_dmtf_frame,
- _mem.dtmf_settings.revive)
- dtmf_dec_settings.append(line)
+ # testing the firmware model fingerprint
+ match_model = model_match(cls, filedata)
+
+ if match_size and match_model:
+ return True
+ else:
+ return False
- def apply_dmtf_listvalue(setting, obj):
- LOG.debug("Setting value: "+ str(setting.value) + " from list")
- val = str(setting.value)
- index = LIST_DTMF_SPECIAL_DIGITS.index(val)
- val = LIST_DTMF_SPECIAL_VALUES[index]
- obj.set_value(val)
- idx = LIST_DTMF_SPECIAL_VALUES.index(_mem.dtmf_settings.groupcode)
- line = RadioSetting(
- "dtmf_settings.groupcode",
- "Group Code",
- RadioSettingValueList(LIST_DTMF_SPECIAL_DIGITS,
- LIST_DTMF_SPECIAL_DIGITS[idx]))
- line.set_apply_callback(apply_dmtf_listvalue,
- _mem.dtmf_settings.groupcode)
- dtmf_dec_settings.append(line)
+MEM_FORMAT = """
+#seekto 0x0000;
+struct {
+ lbcd rxfreq[4];
+ lbcd txfreq[4];
+ ul16 rxtone;
+ ul16 txtone;
+ u8 unknown0:4,
+ scode:4;
+ u8 unknown1:2,
+ spmute:1,
+ unknown2:3,
+ optsig:2;
+ u8 unknown3:3,
+ scramble:1,
+ unknown4:3,
+ power:1;
+ u8 unknown5:1,
+ wide:1,
+ unknown6:2,
+ bcl:1,
+ add:1,
+ pttid:2;
+} memory[200];
- idx = LIST_DTMF_SPECIAL_VALUES.index(_mem.dtmf_settings.spacecode)
- line = RadioSetting(
- "dtmf_settings.spacecode",
- "Space Code",
- RadioSettingValueList(LIST_DTMF_SPECIAL_DIGITS,
- LIST_DTMF_SPECIAL_DIGITS[idx]))
- line.set_apply_callback(apply_dmtf_listvalue,
- _mem.dtmf_settings.spacecode)
- dtmf_dec_settings.append(line)
+#seekto 0x0E00;
+struct {
+ u8 tdr;
+ u8 unknown1;
+ u8 sql;
+ u8 unknown2[2];
+ u8 tot;
+ u8 apo; // BTech radios use this as the Auto Power Off time
+ // other radios use this as pre-Time Out Alert
+ u8 unknown3;
+ u8 abr;
+ u8 beep;
+ u8 unknown4[4];
+ u8 dtmfst;
+ u8 unknown5[2];
+ u8 prisc;
+ u8 prich;
+ u8 screv;
+ u8 unknown6[2];
+ u8 pttid;
+ u8 pttlt;
+ u8 unknown7;
+ u8 emctp;
+ u8 emcch;
+ u8 ringt;
+ u8 unknown8;
+ u8 camdf;
+ u8 cbmdf;
+ u8 sync; // BTech radios use this as the display sync setting
+ // other radios use this as the auto keypad lock setting
+ u8 ponmsg;
+ u8 wtled;
+ u8 rxled;
+ u8 txled;
+ u8 unknown9[5];
+ u8 anil;
+ u8 reps;
+ u8 repm;
+ u8 tdrab;
+ u8 ste;
+ u8 rpste;
+ u8 rptdl;
+ u8 mgain;
+ u8 dtmfg;
+} settings;
- line = RadioSetting(
- "dtmf_settings.resettime",
- "Reset time",
- RadioSettingValueList(LIST_5TONE_RESET,
- LIST_5TONE_RESET[
- _mem.dtmf_settings.resettime]))
- dtmf_dec_settings.append(line)
+#seekto 0x0E80;
+struct {
+ u8 unknown1;
+ u8 vfomr;
+ u8 keylock;
+ u8 unknown2;
+ u8 unknown3:4,
+ vfomren:1,
+ unknown4:1,
+ reseten:1,
+ menuen:1;
+ u8 unknown5[11];
+ u8 dispab;
+ u8 mrcha;
+ u8 mrchb;
+ u8 menu;
+} settings2;
- line = RadioSetting(
- "dtmf_settings.delayproctime",
- "Delay processing time",
- RadioSettingValueList(LIST_DTMF_DELAY,
- LIST_DTMF_DELAY[
- _mem.dtmf_settings.delayproctime]))
- dtmf_dec_settings.append(line)
+#seekto 0x0EC0;
+struct {
+ char line1[6];
+ char line2[6];
+} poweron_msg;
+struct settings_vfo {
+ u8 freq[8];
+ u8 offset[6];
+ u8 unknown2[2];
+ ul16 rxtone;
+ ul16 txtone;
+ u8 scode;
+ u8 spmute;
+ u8 optsig;
+ u8 scramble;
+ u8 wide;
+ u8 power;
+ u8 shiftd;
+ u8 step;
+ u8 unknown3[4];
+};
- # 5 Tone Settings
- stds_5tone = RadioSettingGroup ("stds_5tone", "Standards")
- codes_5tone = RadioSettingGroup ("codes_5tone", "Codes")
+#seekto 0x0F00;
+struct {
+ struct settings_vfo a;
+ struct settings_vfo b;
+} vfo;
- group_5tone = RadioSettingGroup ("group_5tone", "5 Tone Settings")
- group_5tone.append(stds_5tone)
- group_5tone.append(codes_5tone)
+#seekto 0x1000;
+struct {
+ char name[6];
+ u8 unknown1[10];
+} names[200];
- top.append(group_5tone)
+#seekto 0x2400;
+struct {
+ u8 period; // one out of LIST_5TONE_STANDARD_PERIODS
+ u8 group_tone;
+ u8 repeat_tone;
+ u8 unused[13];
+} _5tone_std_settings[15];
- def apply_list_value(setting, obj):
- options = setting.value.get_options()
- obj.set_value ( options.index(str(setting.value)) )
+#seekto 0x2500;
+struct {
+ u8 frame1[5];
+ u8 frame2[5];
+ u8 frame3[5];
+ u8 standard; // one out of LIST_5TONE_STANDARDS
+} _5tone_codes[15];
- _5tone_standards = self._memobj._5tone_std_settings
- i = 0
- for standard in _5tone_standards:
- std_5tone = RadioSettingGroup ("std_5tone_" + str(i),
- LIST_5TONE_STANDARDS[i])
- stds_5tone.append(std_5tone)
-
- period = standard.period
- if period == 255:
- LOG.debug("Period for " + LIST_5TONE_STANDARDS[i] +
- " is not yet configured. Setting to 70ms.")
- period = 5
+#seekto 0x25F0;
+struct {
+ u8 _5tone_delay1; // * 10ms
+ u8 _5tone_delay2; // * 10ms
+ u8 _5tone_delay3; // * 10ms
+ u8 _5tone_first_digit_ext_length;
+ u8 unknown1;
+ u8 unknown2;
+ u8 unknown3;
+ u8 unknown4;
+ u8 decode_standard;
+ u8 unknown5:5,
+ _5tone_decode_call_frame3:1,
+ _5tone_decode_call_frame2:1,
+ _5tone_decode_call_frame1:1;
+ u8 unknown6:5,
+ _5tone_decode_disp_frame3:1,
+ _5tone_decode_disp_frame2:1,
+ _5tone_decode_disp_frame1:1;
+ u8 decode_reset_time; // * 100 + 100ms
+} _5tone_settings;
+
+#seekto 0x2900;
+struct {
+ u8 code[16]; // 0=x0A, A=0x0D, B=0x0E, C=0x0F, D=0x00, #=0x0C *=0x0B
+} dtmf_codes[15];
+
+#seekto 0x29F0;
+struct {
+ u8 dtmfspeed_on; //list with 50..2000ms in steps of 10
+ u8 dtmfspeed_off; //list with 50..2000ms in steps of 10
+ u8 unknown0[14];
+ u8 inspection[16];
+ u8 monitor[16];
+ u8 alarmcode[16];
+ u8 stun[16];
+ u8 kill[16];
+ u8 revive[16];
+ u8 unknown1[16];
+ u8 unknown2[16];
+ u8 unknown3[16];
+ u8 unknown4[16];
+ u8 unknown5[16];
+ u8 unknown6[16];
+ u8 unknown7[16];
+ u8 masterid[16];
+ u8 viceid[16];
+ u8 unused01:7,
+ mastervice:1;
+ u8 unused02:3,
+ mrevive:1,
+ mkill:1,
+ mstun:1,
+ mmonitor:1,
+ minspection:1;
+ u8 unused03:3,
+ vrevive:1,
+ vkill:1,
+ vstun:1,
+ vmonitor:1,
+ vinspection:1;
+ u8 unused04:6,
+ txdisable:1,
+ rxdisable:1;
+ u8 groupcode;
+ u8 spacecode;
+ u8 delayproctime; // * 100 + 100ms
+ u8 resettime; // * 100 + 100ms
+} dtmf_settings;
- if period <= len( LIST_5TONE_STANDARD_PERIODS ):
- line = RadioSetting(
- "_5tone_std_settings_" + str(i) + "_period",
- "Period (ms)", RadioSettingValueList
- (LIST_5TONE_STANDARD_PERIODS,
- LIST_5TONE_STANDARD_PERIODS[period]))
- line.set_apply_callback(apply_list_value, standard.period)
- std_5tone.append(line)
- else:
- LOG.debug("Invalid value for 5tone period! Disabling.")
+#seekto 0x2D00;
+struct {
+ struct {
+ ul16 freq1;
+ u8 unused01[6];
+ ul16 freq2;
+ u8 unused02[6];
+ } _2tone_encode[15];
+ u8 duration_1st_tone; // *10ms
+ u8 duration_2nd_tone; // *10ms
+ u8 duration_gap; // *10ms
+ u8 unused03[13];
+ struct {
+ struct {
+ u8 dec; // one out of LIST_2TONE_DEC
+ u8 response; // one out of LIST_2TONE_RESPONSE
+ u8 alert; // 1-16
+ } decs[4];
+ u8 unused04[4];
+ } _2tone_decode[15];
+ u8 unused05[16];
- group_tone = standard.group_tone
- if group_tone == 255:
- LOG.debug("Group-Tone for " + LIST_5TONE_STANDARDS[i] +
- " is not yet configured. Setting to A.")
- group_tone = 10
+ struct {
+ ul16 freqA;
+ ul16 freqB;
+ ul16 freqC;
+ ul16 freqD;
+ // unknown what those values mean, but they are
+ // derived from configured frequencies
+ ul16 derived_from_freqA; // 2304000/freqA
+ ul16 derived_from_freqB; // 2304000/freqB
+ ul16 derived_from_freqC; // 2304000/freqC
+ ul16 derived_from_freqD; // 2304000/freqD
+ }freqs[15];
+ u8 reset_time; // * 100 + 100ms - 100-8000ms
+} _2tone;
- if group_tone <= len( LIST_5TONE_DIGITS ):
- line = RadioSetting(
- "_5tone_std_settings_" + str(i) + "_grouptone",
- "Group Tone",
- RadioSettingValueList(LIST_5TONE_DIGITS,
- LIST_5TONE_DIGITS[
- group_tone]))
- line.set_apply_callback(apply_list_value,
- standard.group_tone)
- std_5tone.append(line)
- else:
- LOG.debug("Invalid value for 5tone digit! Disabling.")
+#seekto 0x3000;
+struct {
+ u8 freq[8];
+ char broadcast_station_name[6];
+ u8 unknown[2];
+} fm_radio_preset[16];
- repeat_tone = standard.repeat_tone
- if repeat_tone == 255:
- LOG.debug("Repeat-Tone for " + LIST_5TONE_STANDARDS[i] +
- " is not yet configured. Setting to E.")
- repeat_tone = 14
+#seekto 0x3C90;
+struct {
+ u8 vhf_low[3];
+ u8 vhf_high[3];
+ u8 uhf_low[3];
+ u8 uhf_high[3];
+} ranges;
- if repeat_tone <= len( LIST_5TONE_DIGITS ):
- line = RadioSetting(
- "_5tone_std_settings_" + str(i) + "_repttone",
- "Repeat Tone",
- RadioSettingValueList(LIST_5TONE_DIGITS,
- LIST_5TONE_DIGITS[
- repeat_tone]))
- line.set_apply_callback(apply_list_value,
- standard.repeat_tone)
- std_5tone.append(line)
- else:
- LOG.debug("Invalid value for 5tone digit! Disabling.")
- i = i + 1
+// the UV-2501+220 & KT8900R has different zones for storing ranges
- def my_apply_5tonestdlist_value(setting, obj):
- if LIST_5TONE_STANDARDS.index(str(setting.value)) == 15:
- obj.set_value(0xFF)
- else:
- obj.set_value( LIST_5TONE_STANDARDS.
- index(str(setting.value)) )
+#seekto 0x3CD0;
+struct {
+ u8 vhf_low[3];
+ u8 vhf_high[3];
+ u8 unknown1[4];
+ u8 unknown2[6];
+ u8 vhf2_low[3];
+ u8 vhf2_high[3];
+ u8 unknown3[4];
+ u8 unknown4[6];
+ u8 uhf_low[3];
+ u8 uhf_high[3];
+} ranges220;
- def apply_5tone_frame(setting, obj):
- LOG.debug("Setting 5 Tone: " + str(setting.value) )
- valstring = str(setting.value)
- if len(valstring) == 0:
- for i in range(0,5):
- obj[i] = 255
- else:
- validFrame = True
- for i in range(0,5):
- currentChar = valstring[i].upper()
- if currentChar in LIST_5TONE_DIGITS:
- obj[i] = LIST_5TONE_DIGITS.index(currentChar)
- else:
- validFrame = False
- LOG.debug("invalid char: " + str(currentChar))
- if not validFrame:
- LOG.debug("setting whole frame to FF" )
- for i in range(0,5):
- obj[i] = 255
+#seekto 0x3F70;
+struct {
+ char fp[6];
+} fingerprint;
- def validate_5tone_frame(value):
- if (len(str(value)) != 5) and (len(str(value)) != 0) :
- msg = ("5 Tone must have 5 digits or 0 digits")
- raise InvalidValueError(msg)
- for digit in str(value):
- if digit.upper() not in LIST_5TONE_DIGITS:
- msg = (str(digit) + " is not a valid digit for 5tones")
- raise InvalidValueError(msg)
- return value
+"""
- def frame2string(frame):
- frameString = ""
- for digit in frame:
- if digit != 255:
- frameString = frameString + LIST_5TONE_DIGITS[digit]
- return frameString
- _5tone_codes = self._memobj._5tone_codes
- i = 1
- for code in _5tone_codes:
- code_5tone = RadioSettingGroup ("code_5tone_" + str(i),
- "5 Tone code " + str(i))
- codes_5tone.append(code_5tone)
- if (code.standard == 255 ):
- currentVal = 15
- else:
- currentVal = code.standard
- line = RadioSetting("_5tone_code_" + str(i) + "_std",
- " Standard",
- RadioSettingValueList(LIST_5TONE_STANDARDS,
- LIST_5TONE_STANDARDS[
- currentVal]) )
- line.set_apply_callback(my_apply_5tonestdlist_value,
- code.standard)
- code_5tone.append(line)
+class BTech(BTechMobileCommon):
+ """BTECH's UV-5001 and alike radios"""
+ BANDS = 2
+ COLOR_LCD = False
+ NAME_LENGTH = 6
- val = RadioSettingValueString(0, 6,
- frame2string(code.frame1), False)
- line = RadioSetting("_5tone_code_" + str(i) + "_frame1",
- " Frame 1", val)
- val.set_validate_callback(validate_5tone_frame)
- line.set_apply_callback(apply_5tone_frame, code.frame1)
- code_5tone.append(line)
+ def set_options(self):
+ """This is to read the options from the image and set it in the
+ environment, for now just the limits of the freqs in the VHF/UHF
+ ranges"""
- val = RadioSettingValueString(0, 6,
- frame2string(code.frame2), False)
- line = RadioSetting("_5tone_code_" + str(i) + "_frame2",
- " Frame 2", val)
- val.set_validate_callback(validate_5tone_frame)
- line.set_apply_callback(apply_5tone_frame, code.frame2)
- code_5tone.append(line)
+ # setting the correct ranges for each radio type
+ if self.MODEL in ["UV-2501+220", "KT8900R"]:
+ # the model 2501+220 has a segment in 220
+ # and a different position in the memmap
+ # also the QYT KT8900R
+ ranges = self._memobj.ranges220
+ else:
+ ranges = self._memobj.ranges
- val = RadioSettingValueString(0, 6,
- frame2string(code.frame3), False)
- line = RadioSetting("_5tone_code_" + str(i) + "_frame3",
- " Frame 3", val)
- val.set_validate_callback(validate_5tone_frame)
- line.set_apply_callback(apply_5tone_frame, code.frame3)
- code_5tone.append(line)
- i = i + 1
+ # the normal dual bands
+ vhf = _decode_ranges(ranges.vhf_low, ranges.vhf_high)
+ uhf = _decode_ranges(ranges.uhf_low, ranges.uhf_high)
- _5_tone_decode1 = RadioSetting(
- "_5tone_settings._5tone_decode_call_frame1",
- "5 Tone decode call Frame 1",
- RadioSettingValueBoolean(
- _mem._5tone_settings._5tone_decode_call_frame1))
- group_5tone.append(_5_tone_decode1)
+ # DEBUG
+ LOG.info("Radio ranges: VHF %d to %d" % vhf)
+ LOG.info("Radio ranges: UHF %d to %d" % uhf)
- _5_tone_decode2 = RadioSetting(
- "_5tone_settings._5tone_decode_call_frame2",
- "5 Tone decode call Frame 2",
- RadioSettingValueBoolean(
- _mem._5tone_settings._5tone_decode_call_frame2))
- group_5tone.append(_5_tone_decode2)
+ # 220Mhz radios case
+ if self.MODEL in ["UV-2501+220", "KT8900R"]:
+ vhf2 = _decode_ranges(ranges.vhf2_low, ranges.vhf2_high)
+ LOG.info("Radio ranges: VHF(220) %d to %d" % vhf2)
+ self._220_range = vhf2
- _5_tone_decode3 = RadioSetting(
- "_5tone_settings._5tone_decode_call_frame3",
- "5 Tone decode call Frame 3",
- RadioSettingValueBoolean(
- _mem._5tone_settings._5tone_decode_call_frame3))
- group_5tone.append(_5_tone_decode3)
+ # set the class with the real data
+ self._vhf_range = vhf
+ self._uhf_range = uhf
- _5_tone_decode_disp1 = RadioSetting(
- "_5tone_settings._5tone_decode_disp_frame1",
- "5 Tone decode disp Frame 1",
- RadioSettingValueBoolean(
- _mem._5tone_settings._5tone_decode_disp_frame1))
- group_5tone.append(_5_tone_decode_disp1)
+ def process_mmap(self):
+ """Process the mem map into the mem object"""
- _5_tone_decode_disp2 = RadioSetting(
- "_5tone_settings._5tone_decode_disp_frame2",
- "5 Tone decode disp Frame 2",
- RadioSettingValueBoolean(
- _mem._5tone_settings._5tone_decode_disp_frame2))
- group_5tone.append(_5_tone_decode_disp2)
+ # Get it
+ self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+ # load specific parameters from the radio image
+ self.set_options()
- _5_tone_decode_disp3 = RadioSetting(
- "_5tone_settings._5tone_decode_disp_frame3",
- "5 Tone decode disp Frame 3",
- RadioSettingValueBoolean(
- _mem._5tone_settings._5tone_decode_disp_frame3))
- group_5tone.append(_5_tone_decode_disp3)
- decode_standard = _mem._5tone_settings.decode_standard
- if decode_standard == 255:
- decode_standard = 0
- if decode_standard <= len (LIST_5TONE_STANDARDS_without_none) :
- line = RadioSetting("_5tone_settings.decode_standard",
- "5 Tone-decode Standard",
- RadioSettingValueList(
- LIST_5TONE_STANDARDS_without_none,
- LIST_5TONE_STANDARDS_without_none[
- decode_standard]))
- group_5tone.append(line)
- else:
- LOG.debug("Invalid decode std...")
-
- _5tone_delay1 = _mem._5tone_settings._5tone_delay1
- if _5tone_delay1 == 255:
- _5tone_delay1 = 20
+# Declaring Aliases (Clones of the real radios)
+class JT2705M(chirp_common.Alias):
+ VENDOR = "Jetstream"
+ MODEL = "JT2705M"
- if _5tone_delay1 <= len( LIST_5TONE_DELAY ):
- list = RadioSettingValueList(LIST_5TONE_DELAY,
- LIST_5TONE_DELAY[
- _5tone_delay1])
- line = RadioSetting("_5tone_settings._5tone_delay1",
- "5 Tone Delay Frame 1", list)
- group_5tone.append(line)
- else:
- LOG.debug("Invalid value for 5tone delay (frame1) ! Disabling.")
- _5tone_delay2 = _mem._5tone_settings._5tone_delay2
- if _5tone_delay2 == 255:
- _5tone_delay2 = 20
- LOG.debug("5 Tone delay unconfigured! Resetting to 200ms.")
+class JT6188Mini(chirp_common.Alias):
+ VENDOR = "Juentai"
+ MODEL = "JT-6188 Mini"
- if _5tone_delay2 <= len( LIST_5TONE_DELAY ):
- list = RadioSettingValueList(LIST_5TONE_DELAY,
- LIST_5TONE_DELAY[
- _5tone_delay2])
- line = RadioSetting("_5tone_settings._5tone_delay2",
- "5 Tone Delay Frame 2", list)
- group_5tone.append(line)
- else:
- LOG.debug("Invalid value for 5tone delay (frame2)! Disabling.")
- _5tone_delay3 = _mem._5tone_settings._5tone_delay3
- if _5tone_delay3 == 255:
- _5tone_delay3 = 20
- LOG.debug("5 Tone delay unconfigured! Resetting to 200ms.")
+class JT6188Plus(chirp_common.Alias):
+ VENDOR = "Juentai"
+ MODEL = "JT-6188 Plus"
- if _5tone_delay3 <= len( LIST_5TONE_DELAY ):
- list = RadioSettingValueList(LIST_5TONE_DELAY,
- LIST_5TONE_DELAY[
- _5tone_delay3])
- line = RadioSetting("_5tone_settings._5tone_delay3",
- "5 Tone Delay Frame 3", list )
- group_5tone.append(line)
- else:
- LOG.debug("Invalid value for 5tone delay (frame3)! Disabling.")
- ext_length = _mem._5tone_settings._5tone_first_digit_ext_length
- if ext_length == 255:
- ext_length = 0
- LOG.debug("1st Tone ext lenght unconfigured! Resetting to 0")
+class SSGT890(chirp_common.Alias):
+ VENDOR = "Sainsonic"
+ MODEL = "GT-890"
- if ext_length <= len(
- LIST_5TONE_DELAY ):
- list = RadioSettingValueList(
- LIST_5TONE_DELAY,
- LIST_5TONE_DELAY[
- ext_length])
- line = RadioSetting(
- "_5tone_settings._5tone_first_digit_ext_length",
- "First digit extend length", list)
- group_5tone.append(line)
- else:
- LOG.debug("Invalid value for 5tone ext length! Disabling.")
- decode_reset_time = _mem._5tone_settings.decode_reset_time
- if decode_reset_time == 255:
- decode_reset_time = 59
- LOG.debug("Decode reset time unconfigured. resetting.")
- if decode_reset_time <= len(LIST_5TONE_RESET):
- list = RadioSettingValueList(
- LIST_5TONE_RESET,
- LIST_5TONE_RESET[
- decode_reset_time])
- line = RadioSetting("_5tone_settings.decode_reset_time",
- "Decode reset time", list)
- group_5tone.append(line)
- else:
- LOG.debug("Invalid value decode reset time! Disabling.")
+class ZastoneMP300(chirp_common.Alias):
+ VENDOR = "Zastone"
+ MODEL = "MP-300"
- # 2 Tone
- encode_2tone = RadioSettingGroup ("encode_2tone", "2 Tone Encode")
- decode_2tone = RadioSettingGroup ("decode_2tone", "2 Code Decode")
- top.append(encode_2tone)
- top.append(decode_2tone)
+# real radios
+ at directory.register
+class UV2501(BTech):
+ """Baofeng Tech UV2501"""
+ MODEL = "UV-2501"
+ _fileid = [UV2501G3_fp,
+ UV2501G2_fp,
+ UV2501pp2_fp,
+ UV2501pp_fp]
- duration_1st_tone = self._memobj._2tone.duration_1st_tone
- if duration_1st_tone == 255:
- LOG.debug("Duration of first 2 Tone digit is not yet " +
- "configured. Setting to 600ms")
- duration_1st_tone = 60
- if duration_1st_tone <= len( LIST_5TONE_DELAY ):
- line = RadioSetting("_2tone.duration_1st_tone",
- "Duration 1st Tone",
- RadioSettingValueList(LIST_5TONE_DELAY,
- LIST_5TONE_DELAY[
- duration_1st_tone]))
- encode_2tone.append(line)
+ at directory.register
+class UV2501_220(BTech):
+ """Baofeng Tech UV2501+220"""
+ MODEL = "UV-2501+220"
+ BANDS = 3
+ _magic = MSTRING_220
+ _id2 = UV2501_220pp_id
+ _fileid = [UV2501_220G3_fp,
+ UV2501_220G2_fp,
+ UV2501_220_fp,
+ UV2501_220pp_fp]
- duration_2nd_tone = self._memobj._2tone.duration_2nd_tone
- if duration_2nd_tone == 255:
- LOG.debug("Duration of second 2 Tone digit is not yet " +
- "configured. Setting to 600ms")
- duration_2nd_tone = 60
- if duration_2nd_tone <= len( LIST_5TONE_DELAY ):
- line = RadioSetting("_2tone.duration_2nd_tone",
- "Duration 2nd Tone",
- RadioSettingValueList(LIST_5TONE_DELAY,
- LIST_5TONE_DELAY[
- duration_2nd_tone]))
- encode_2tone.append(line)
+ at directory.register
+class UV5001(BTech):
+ """Baofeng Tech UV5001"""
+ MODEL = "UV-5001"
+ _fileid = [UV5001G3_fp,
+ UV5001G22_fp,
+ UV5001G2_fp,
+ UV5001alpha_fp,
+ UV5001pp_fp]
+ _power_levels = [chirp_common.PowerLevel("High", watts=50),
+ chirp_common.PowerLevel("Low", watts=10)]
- duration_gap = self._memobj._2tone.duration_gap
- if duration_gap == 255:
- LOG.debug("Duration of gap is not yet " +
- "configured. Setting to 300ms")
- duration_gap = 30
- if duration_gap <= len( LIST_5TONE_DELAY ):
- line = RadioSetting("_2tone.duration_gap", "Duration of gap",
- RadioSettingValueList(LIST_5TONE_DELAY,
- LIST_5TONE_DELAY[
- duration_gap]))
- encode_2tone.append(line)
+ at directory.register
+class MINI8900(BTech):
+ """WACCOM MINI-8900"""
+ VENDOR = "WACCOM"
+ MODEL = "MINI-8900"
+ _magic = MSTRING_MINI8900
+ _fileid = [MINI8900_fp, ]
+ # Clones
+ ALIASES = [JT6188Plus, ]
- def _2tone_validate(value):
- if value == 0:
- return 65535
- if value == 65535:
- return value
- if not (300 <= value and value <= 3000):
- msg = ("2 Tone Frequency: Must be between 300 and 3000 Hz")
- raise InvalidValueError(msg)
- return value
- def apply_2tone_freq(setting, obj):
- val = int(setting.value)
- if (val == 0) or (val == 65535):
- obj.set_value(65535)
- else:
- obj.set_value(val)
+ at directory.register
+class KTUV980(BTech):
+ """QYT KT-UV980"""
+ VENDOR = "QYT"
+ MODEL = "KT-UV980"
+ _vhf_range = (136000000, 175000000)
+ _uhf_range = (400000000, 481000000)
+ _magic = MSTRING_MINI8900
+ _fileid = [KTUV980_fp, ]
+ # Clones
+ ALIASES = [JT2705M, ]
- i = 1
- for code in self._memobj._2tone._2tone_encode:
- code_2tone = RadioSettingGroup ("code_2tone_" + str(i),
- "Encode Code " + str(i))
- encode_2tone.append(code_2tone)
+# Please note that there is a version of this radios that is a clone of the
+# Waccom Mini8900, maybe an early version?
+ at directory.register
+class KT9800(BTech):
+ """QYT KT8900"""
+ VENDOR = "QYT"
+ MODEL = "KT8900"
+ _vhf_range = (136000000, 175000000)
+ _uhf_range = (400000000, 481000000)
+ _magic = MSTRING_KT8900
+ _fileid = [KT8900_fp,
+ KT8900_fp1,
+ KT8900_fp2,
+ KT8900_fp3,
+ KT8900_fp4,
+ KT8900_fp5]
+ _id2 = KT8900_id
+ # Clones
+ ALIASES = [JT6188Mini, SSGT890, ZastoneMP300]
- tmp = code.freq1
- if tmp == 65535:
- tmp = 0
- val1 = RadioSettingValueInteger(0, 65535, tmp)
- freq1 = RadioSetting("2tone_code_"+ str(i) + "_freq1",
- "Frequency 1", val1)
- val1.set_validate_callback(_2tone_validate)
- freq1.set_apply_callback(apply_2tone_freq, code.freq1)
- code_2tone.append(freq1)
- tmp = code.freq2
- if tmp == 65535:
- tmp = 0
- val2 = RadioSettingValueInteger(0, 65535, tmp)
- freq2 = RadioSetting("2tone_code_"+ str(i) + "_freq2",
- "Frequency 2", val2)
- val2.set_validate_callback(_2tone_validate)
- freq2.set_apply_callback(apply_2tone_freq, code.freq2)
- code_2tone.append(freq2)
+ at directory.register
+class KT9800R(BTech):
+ """QYT KT8900R"""
+ VENDOR = "QYT"
+ MODEL = "KT8900R"
+ BANDS = 3
+ _vhf_range = (136000000, 175000000)
+ _220_range = (240000000, 271000000)
+ _uhf_range = (400000000, 481000000)
+ _magic = MSTRING_KT8900R
+ _fileid = [KT8900R_fp,
+ KT8900R_fp1,
+ KT8900R_fp2,
+ KT8900R_fp3,
+ KT8900R_fp4]
+ _id2 = KT8900R_id
- i = i + 1
- decode_reset_time = _mem._2tone.reset_time
- if decode_reset_time == 255:
- decode_reset_time = 59
- LOG.debug("Decode reset time unconfigured. resetting.")
- if decode_reset_time <= len(LIST_5TONE_RESET):
- list = RadioSettingValueList(
- LIST_5TONE_RESET,
- LIST_5TONE_RESET[
- decode_reset_time])
- line = RadioSetting("_2tone.reset_time",
- "Decode reset time", list)
- decode_2tone.append(line)
- else:
- LOG.debug("Invalid value decode reset time! Disabling.")
+ at directory.register
+class LT588UV(BTech):
+ """LUITON LT-588UV"""
+ VENDOR = "LUITON"
+ MODEL = "LT-588UV"
+ _vhf_range = (136000000, 175000000)
+ _uhf_range = (400000000, 481000000)
+ _magic = MSTRING_KT8900
+ _fileid = [LT588UV_fp,
+ LT588UV_fp1]
+ _power_levels = [chirp_common.PowerLevel("High", watts=60),
+ chirp_common.PowerLevel("Low", watts=10)]
- def apply_2tone_freq_pair(setting, obj):
- val = int(setting.value)
- derived_val = 65535
- frqname = str(setting._name[-5:])
- derivedname = "derived_from_" + frqname
- if (val == 0):
- val = 65535
- derived_val = 65535
- else:
- derived_val = int(round(2304000.0/val))
+COLOR_MEM_FORMAT = """
+#seekto 0x0000;
+struct {
+ lbcd rxfreq[4];
+ lbcd txfreq[4];
+ ul16 rxtone;
+ ul16 txtone;
+ u8 unknown0:4,
+ scode:4;
+ u8 unknown1:2,
+ spmute:1,
+ unknown2:3,
+ optsig:2;
+ u8 unknown3:3,
+ scramble:1,
+ unknown4:3,
+ power:1;
+ u8 unknown5:1,
+ wide:1,
+ unknown6:2,
+ bcl:1,
+ add:1,
+ pttid:2;
+} memory[200];
- obj[frqname].set_value( val )
- obj[derivedname].set_value( derived_val )
+#seekto 0x0E00;
+struct {
+ u8 tmr;
+ u8 unknown1;
+ u8 sql;
+ u8 unknown2[2];
+ u8 tot;
+ u8 apo;
+ u8 unknown3;
+ u8 abr;
+ u8 beep;
+ u8 unknown4[4];
+ u8 dtmfst;
+ u8 unknown5[2];
+ u8 screv;
+ u8 unknown6[2];
+ u8 pttid;
+ u8 pttlt;
+ u8 unknown7;
+ u8 emctp;
+ u8 emcch;
+ u8 sigbp;
+ u8 unknown8;
+ u8 camdf;
+ u8 cbmdf;
+ u8 ccmdf;
+ u8 cdmdf;
+ u8 langua;
+ u8 sync; // BTech radios use this as the display sync
+ // setting, other radios use this as the auto
+ // keypad lock setting
+ u8 mainfc;
+ u8 mainbc;
+ u8 menufc;
+ u8 menubc;
+ u8 stafc;
+ u8 stabc;
+ u8 sigfc;
+ u8 sigbc;
+ u8 rxfc;
+ u8 txfc;
+ u8 txdisp;
+ u8 unknown9[5];
+ u8 anil;
+ u8 reps;
+ u8 repm;
+ u8 tmrmr;
+ u8 ste;
+ u8 rpste;
+ u8 rptdl;
+ u8 dtmfg;
+ u8 mgain;
+ u8 skiptx;
+ u8 scmode;
+} settings;
- LOG.debug("Apply " + frqname + ": " + str(val) + " | "
- + derivedname + ": " + str(derived_val))
+#seekto 0x0E80;
+struct {
+ u8 unknown1;
+ u8 vfomr;
+ u8 keylock;
+ u8 unknown2;
+ u8 unknown3:4,
+ vfomren:1,
+ unknown4:1,
+ reseten:1,
+ menuen:1;
+ u8 unknown5[11];
+ u8 dispab;
+ u8 unknown6[2];
+ u8 menu;
+ u8 unknown7[7];
+ u8 vfomra;
+ u8 vfomrb;
+ u8 vfomrc;
+ u8 vfomrd;
+ u8 mrcha;
+ u8 mrchb;
+ u8 mrchc;
+ u8 mrchd;
+} settings2;
- i = 1
- for decode_code in self._memobj._2tone._2tone_decode:
- _2tone_dec_code = RadioSettingGroup ("code_2tone_" + str(i),
- "Decode Code " + str(i))
- decode_2tone.append(_2tone_dec_code)
+struct settings_vfo {
+ u8 freq[8];
+ u8 offset[6];
+ u8 unknown2[2];
+ ul16 rxtone;
+ ul16 txtone;
+ u8 scode;
+ u8 spmute;
+ u8 optsig;
+ u8 scramble;
+ u8 wide;
+ u8 power;
+ u8 shiftd;
+ u8 step;
+ u8 unknown3[4];
+};
- j = 1
- for dec in decode_code.decs:
- val = dec.dec
- if val == 255:
- LOG.debug("Dec for Code " + str(i) + " Dec " + str(j) +
- " is not yet configured. Setting to 0.")
- val = 0
+#seekto 0x0F00;
+struct {
+ struct settings_vfo a;
+ struct settings_vfo b;
+ struct settings_vfo c;
+ struct settings_vfo d;
+} vfo;
- if val <= len( LIST_2TONE_DEC ):
- line = RadioSetting(
- "_2tone_dec_settings_" + str(i) + "_dec_" + str(j),
- "Dec " + str(j), RadioSettingValueList
- (LIST_2TONE_DEC,
- LIST_2TONE_DEC[val]))
- line.set_apply_callback(apply_list_value, dec.dec)
- _2tone_dec_code.append(line)
- else:
- LOG.debug("Invalid value for 2tone dec! Disabling.")
+#seekto 0x0F80;
+struct {
+ char line1[8];
+ char line2[8];
+ char line3[8];
+ char line4[8];
+ char line5[8];
+ char line6[8];
+ char line7[8];
+ char line8[8];
+} poweron_msg;
- val = dec.response
- if val == 255:
- LOG.debug("Response for Code " + str(i) + " Dec " + str(j)+
- " is not yet configured. Setting to 0.")
- val = 0
+#seekto 0x1000;
+struct {
+ char name[8];
+ u8 unknown1[8];
+} names[200];
- if val <= len( LIST_2TONE_RESPONSE ):
- line = RadioSetting(
- "_2tone_dec_settings_" + str(i) + "_resp_" + str(j),
- "Response " + str(j), RadioSettingValueList
- (LIST_2TONE_RESPONSE,
- LIST_2TONE_RESPONSE[val]))
- line.set_apply_callback(apply_list_value, dec.response)
- _2tone_dec_code.append(line)
- else:
- LOG.debug("Invalid value for 2tone response! Disabling.")
+#seekto 0x2400;
+struct {
+ u8 period; // one out of LIST_5TONE_STANDARD_PERIODS
+ u8 group_tone;
+ u8 repeat_tone;
+ u8 unused[13];
+} _5tone_std_settings[15];
- val = dec.alert
- if val == 255:
- LOG.debug("Alert for Code " + str(i) + " Dec " + str(j) +
- " is not yet configured. Setting to 0.")
- val = 0
+#seekto 0x2500;
+struct {
+ u8 frame1[5];
+ u8 frame2[5];
+ u8 frame3[5];
+ u8 standard; // one out of LIST_5TONE_STANDARDS
+} _5tone_codes[15];
- if val <= len( PTTIDCODE_LIST ):
- line = RadioSetting(
- "_2tone_dec_settings_" + str(i) + "_alert_" + str(j),
- "Alert " + str(j), RadioSettingValueList
- (PTTIDCODE_LIST,
- PTTIDCODE_LIST[val]))
- line.set_apply_callback(apply_list_value, dec.alert)
- _2tone_dec_code.append(line)
- else:
- LOG.debug("Invalid value for 2tone alert! Disabling.")
- j = j + 1
+#seekto 0x25F0;
+struct {
+ u8 _5tone_delay1; // * 10ms
+ u8 _5tone_delay2; // * 10ms
+ u8 _5tone_delay3; // * 10ms
+ u8 _5tone_first_digit_ext_length;
+ u8 unknown1;
+ u8 unknown2;
+ u8 unknown3;
+ u8 unknown4;
+ u8 decode_standard;
+ u8 unknown5:5,
+ _5tone_decode_call_frame3:1,
+ _5tone_decode_call_frame2:1,
+ _5tone_decode_call_frame1:1;
+ u8 unknown6:5,
+ _5tone_decode_disp_frame3:1,
+ _5tone_decode_disp_frame2:1,
+ _5tone_decode_disp_frame1:1;
+ u8 decode_reset_time; // * 100 + 100ms
+} _5tone_settings;
- freq = self._memobj._2tone.freqs[i-1]
- for char in ['A', 'B', 'C', 'D']:
- setting_name = "freq" + str(char)
+#seekto 0x2900;
+struct {
+ u8 code[16]; // 0=x0A, A=0x0D, B=0x0E, C=0x0F, D=0x00, #=0x0C *=0x0B
+} dtmf_codes[15];
- tmp = freq[setting_name]
- if tmp == 65535:
- tmp = 0
- if tmp != 0:
- expected = int(round(2304000.0/tmp))
- from_mem = freq["derived_from_" + setting_name]
- if expected != from_mem:
- LOG.error("Expected " + str(expected) +
- " but read " + str(from_mem ) +
- ". Disabling 2Tone Decode Freqs!")
- break
- val = RadioSettingValueInteger(0, 65535, tmp)
- frq = RadioSetting("2tone_dec_"+ str(i) + "_freq" + str(char),
- ("Decode Frequency " +str(char)), val)
- val.set_validate_callback(_2tone_validate)
- frq.set_apply_callback(apply_2tone_freq_pair, freq)
- _2tone_dec_code.append(frq)
+#seekto 0x29F0;
+struct {
+ u8 dtmfspeed_on; //list with 50..2000ms in steps of 10
+ u8 dtmfspeed_off; //list with 50..2000ms in steps of 10
+ u8 unknown0[14];
+ u8 inspection[16];
+ u8 monitor[16];
+ u8 alarmcode[16];
+ u8 stun[16];
+ u8 kill[16];
+ u8 revive[16];
+ u8 unknown1[16];
+ u8 unknown2[16];
+ u8 unknown3[16];
+ u8 unknown4[16];
+ u8 unknown5[16];
+ u8 unknown6[16];
+ u8 unknown7[16];
+ u8 masterid[16];
+ u8 viceid[16];
+ u8 unused01:7,
+ mastervice:1;
+ u8 unused02:3,
+ mrevive:1,
+ mkill:1,
+ mstun:1,
+ mmonitor:1,
+ minspection:1;
+ u8 unused03:3,
+ vrevive:1,
+ vkill:1,
+ vstun:1,
+ vmonitor:1,
+ vinspection:1;
+ u8 unused04:6,
+ txdisable:1,
+ rxdisable:1;
+ u8 groupcode;
+ u8 spacecode;
+ u8 delayproctime; // * 100 + 100ms
+ u8 resettime; // * 100 + 100ms
+} dtmf_settings;
- i = i + 1
+#seekto 0x2D00;
+struct {
+ struct {
+ ul16 freq1;
+ u8 unused01[6];
+ ul16 freq2;
+ u8 unused02[6];
+ } _2tone_encode[15];
+ u8 duration_1st_tone; // *10ms
+ u8 duration_2nd_tone; // *10ms
+ u8 duration_gap; // *10ms
+ u8 unused03[13];
+ struct {
+ struct {
+ u8 dec; // one out of LIST_2TONE_DEC
+ u8 response; // one out of LIST_2TONE_RESPONSE
+ u8 alert; // 1-16
+ } decs[4];
+ u8 unused04[4];
+ } _2tone_decode[15];
+ u8 unused05[16];
- return top
+ struct {
+ ul16 freqA;
+ ul16 freqB;
+ ul16 freqC;
+ ul16 freqD;
+ // unknown what those values mean, but they are
+ // derived from configured frequencies
+ ul16 derived_from_freqA; // 2304000/freqA
+ ul16 derived_from_freqB; // 2304000/freqB
+ ul16 derived_from_freqC; // 2304000/freqC
+ ul16 derived_from_freqD; // 2304000/freqD
+ }freqs[15];
+ u8 reset_time; // * 100 + 100ms - 100-8000ms
+} _2tone;
- def set_settings(self, settings):
- _settings = self._memobj.settings
- for element in settings:
- if not isinstance(element, RadioSetting):
- if element.get_name() == "fm_preset":
- self._set_fm_preset(element)
- else:
- self.set_settings(element)
- continue
- else:
- try:
- name = element.get_name()
- if "." in name:
- bits = name.split(".")
- obj = self._memobj
- for bit in bits[:-1]:
- if "/" in bit:
- bit, index = bit.split("/", 1)
- index = int(index)
- obj = getattr(obj, bit)[index]
- else:
- obj = getattr(obj, bit)
- setting = bits[-1]
- else:
- obj = _settings
- setting = element.get_name()
+#seekto 0x3D80;
+struct {
+ u8 vhf_low[3];
+ u8 vhf_high[3];
+ u8 unknown1[4];
+ u8 unknown2[6];
+ u8 vhf2_low[3];
+ u8 vhf2_high[3];
+ u8 unknown3[4];
+ u8 unknown4[6];
+ u8 uhf_low[3];
+ u8 uhf_high[3];
+ u8 unknown5[4];
+ u8 unknown6[6];
+ u8 uhf2_low[3];
+ u8 uhf2_high[3];
+} ranges;
- if element.has_apply_callback():
- LOG.debug("Using apply callback")
- element.run_apply_callback()
- elif element.value.get_mutable():
- LOG.debug("Setting %s = %s" % (setting, element.value))
- setattr(obj, setting, element.value)
- except Exception, e:
- LOG.debug(element.get_name())
- raise
+#seekto 0x3F70;
+struct {
+ char fp[6];
+} fingerprint;
- @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)
+class BTechColor(BTechMobileCommon):
+ """BTECH's Color LCD Mobile and alike radios"""
+ COLOR_LCD = True
+ NAME_LENGTH = 8
- if match_size and match_model:
- return True
- else:
- return False
+ def process_mmap(self):
+ """Process the mem map into the mem object"""
+ # Get it
+ self._memobj = bitwise.parse(COLOR_MEM_FORMAT, self._mmap)
-# Declaring Aliases (Clones of the real radios)
-class JT2705M(chirp_common.Alias):
- VENDOR = "Jetstream"
- MODEL = "JT2705M"
+ # load specific parameters from the radio image
+ self.set_options()
+ def set_options(self):
+ """This is to read the options from the image and set it in the
+ environment, for now just the limits of the freqs in the VHF/UHF
+ ranges"""
-class JT6188Mini(chirp_common.Alias):
- VENDOR = "Juentai"
- MODEL = "JT-6188 Mini"
+ # setting the correct ranges for each radio type
+ ranges = self._memobj.ranges
+ # the normal dual bands
+ vhf = _decode_ranges(ranges.vhf_low, ranges.vhf_high)
+ uhf = _decode_ranges(ranges.uhf_low, ranges.uhf_high)
-class JT6188Plus(chirp_common.Alias):
- VENDOR = "Juentai"
- MODEL = "JT-6188 Plus"
+ # DEBUG
+ LOG.info("Radio ranges: VHF %d to %d" % vhf)
+ LOG.info("Radio ranges: UHF %d to %d" % uhf)
+ # the additional bands
+ if self.MODEL in ["UV-25X4", "KT7900D"]:
+ # 200Mhz band
+ vhf2 = _decode_ranges(ranges.vhf2_low, ranges.vhf2_high)
+ LOG.info("Radio ranges: VHF(220) %d to %d" % vhf2)
+ self._220_range = vhf2
-class SSGT890(chirp_common.Alias):
- VENDOR = "Sainsonic"
- MODEL = "GT-890"
+ # 350Mhz band
+ uhf2 = _decode_ranges(ranges.uhf2_low, ranges.uhf2_high)
+ LOG.info("Radio ranges: UHF(350) %d to %d" % uhf2)
+ self._350_range = uhf2
+ # set the class with the real data
+ self._vhf_range = vhf
+ self._uhf_range = uhf
+
-class ZastoneMP300(chirp_common.Alias):
- VENDOR = "Zastone"
- MODEL = "MP-300"
+# Declaring Aliases (Clones of the real radios)
+class SKT8900D(chirp_common.Alias):
+ VENDOR = "Surecom"
+ MODEL = "S-KT8900D"
# real radios
@directory.register
-class UV2501(BTech):
- """Baofeng Tech UV2501"""
- MODEL = "UV-2501"
- _fileid = [UV2501G3_fp,
- UV2501G2_fp,
- UV2501pp2_fp,
- UV2501pp_fp]
-
-
- at directory.register
-class UV2501_220(BTech):
- """Baofeng Tech UV2501+220"""
- MODEL = "UV-2501+220"
- _magic = MSTRING_220
- _id2 = UV2501_220pp_id
- _fileid = [UV2501_220G3_fp,
- UV2501_220G2_fp,
- UV2501_220_fp,
- UV2501_220pp_fp]
+class UV25X2(BTechColor):
+ """Baofeng Tech UV25X2"""
+ MODEL = "UV-25X2"
+ BANDS = 2
+ _vhf_range = (130000000, 180000000)
+ _uhf_range = (400000000, 521000000)
+ _magic = MSTRING_UV25X2
+ _fileid = [UV25X2_fp, ]
@directory.register
-class UV5001(BTech):
- """Baofeng Tech UV5001"""
- MODEL = "UV-5001"
- _fileid = [UV5001G3_fp,
- UV5001G22_fp,
- UV5001G2_fp,
- UV5001alpha_fp,
- UV5001pp_fp]
+class UV25X4(BTechColor):
+ """Baofeng Tech UV25X4"""
+ MODEL = "UV-25X4"
+ BANDS = 4
+ _vhf_range = (130000000, 180000000)
+ _220_range = (200000000, 271000000)
+ _uhf_range = (400000000, 521000000)
+ _350_range = (350000000, 391000000)
+ _magic = MSTRING_UV25X4
+ _fileid = [UV25X4_fp, ]
@directory.register
-class MINI8900(BTech):
- """WACCOM MINI-8900"""
- VENDOR = "WACCOM"
- MODEL = "MINI-8900"
- _magic = MSTRING_MINI8900
- _fileid = [MINI8900_fp, ]
- # Clones
- ALIASES = [JT6188Plus, ]
-
+class UV50X2(BTechColor):
+ """Baofeng Tech UV50X2"""
+ MODEL = "UV-50X2"
+ BANDS = 2
+ _vhf_range = (130000000, 180000000)
+ _uhf_range = (400000000, 521000000)
+ _magic = MSTRING_UV25X2
+ _fileid = [UV50X2_fp, ]
+ _power_levels = [chirp_common.PowerLevel("High", watts=50),
+ chirp_common.PowerLevel("Low", watts=10)]
- at directory.register
-class KTUV980(BTech):
- """QYT KT-UV980"""
- VENDOR = "QYT"
- MODEL = "KT-UV980"
- _vhf_range = (136000000, 175000000)
- _uhf_range = (400000000, 481000000)
- _magic = MSTRING_MINI8900
- _fileid = [KTUV980_fp, ]
- # Clones
- ALIASES = [JT2705M, ]
-# Please note that there is a version of this radios that is a clone of the
-# Waccom Mini8900, maybe an early version?
@directory.register
-class KT9800(BTech):
- """QYT KT8900"""
+class KT7900D(BTechColor):
+ """QYT KT7900D"""
VENDOR = "QYT"
- MODEL = "KT8900"
+ MODEL = "KT7900D"
+ BANDS = 4
_vhf_range = (136000000, 175000000)
+ _220_range = (200000000, 271000000)
_uhf_range = (400000000, 481000000)
- _magic = MSTRING_KT8900
- _fileid = [KT8900_fp,
- KT8900_fp1,
- KT8900_fp2,
- KT8900_fp3,
- KT8900_fp4,
- KT8900_fp5]
- _id2 = KT8900_id
+ _350_range = (350000000, 371000000)
+ _magic = MSTRING_KT8900D
+ _fileid = [KT7900D_fp, ]
# Clones
- ALIASES = [JT6188Mini, SSGT890, ZastoneMP300]
+ ALIASES = [SKT8900D, ]
@directory.register
-class KT9800R(BTech):
- """QYT KT8900R"""
+class KT8900D(BTechColor):
+ """QYT KT8900D"""
VENDOR = "QYT"
- MODEL = "KT8900R"
- _vhf_range = (136000000, 175000000)
- _220_range = (240000000, 271000000)
- _uhf_range = (400000000, 481000000)
- _magic = MSTRING_KT8900R
- _fileid = [KT8900R_fp,
- KT8900R_fp1,
- KT8900R_fp2,
- KT8900R_fp3,
- KT8900R_fp4]
- _id2 = KT8900R_id
-
-
- at directory.register
-class LT588UV(BTech):
- """LUITON LT-588UV"""
- VENDOR = "LUITON"
- MODEL = "LT-588UV"
+ MODEL = "KT8900D"
+ BANDS = 2
_vhf_range = (136000000, 175000000)
_uhf_range = (400000000, 481000000)
- _magic = MSTRING_KT8900
- _fileid = [LT588UV_fp,
- LT588UV_fp1]
+ _magic = MSTRING_KT8900D
+ _fileid = [KT8900D_fp, ]
diff --git a/chirp/drivers/ft1d.py b/chirp/drivers/ft1d.py
index 8616d3c..1c3550a 100644
--- a/chirp/drivers/ft1d.py
+++ b/chirp/drivers/ft1d.py
@@ -20,14 +20,15 @@ import logging
from chirp.drivers import yaesu_clone
from chirp import chirp_common, directory, bitwise
-from chirp.settings import RadioSettingGroup, RadioSetting, RadioSettings
-from chirp.settings import RadioSettingValueInteger, RadioSettingValueString
-from chirp.settings import RadioSettingValueList, RadioSettingValueBoolean
+from chirp.settings import RadioSettingGroup, RadioSetting, RadioSettings, \
+ RadioSettingValueInteger, RadioSettingValueString, \
+ RadioSettingValueList, RadioSettingValueBoolean, \
+ InvalidValueError
from textwrap import dedent
LOG = logging.getLogger(__name__)
-MEM_FORMAT = """
+MEM_SETTINGS_FORMAT = """
#seekto 0x049a;
struct {
u8 vfo_a;
@@ -141,17 +142,21 @@ struct {
u8 unknown[2];
u8 name[16];
} bank_info[24];
+"""
+MEM_FORMAT = """
#seekto 0x2D4A;
struct {
- u8 unknown1;
+ u8 unknown0:2,
+ mode_alt:1, // mode for FTM-3200D
+ unknown1:5;
u8 mode:2,
duplex:2,
tune_step:4;
bbcd freq[3];
u8 power:2,
- unknown2:4,
- tone_mode:2;
+ unknown2:2,
+ tone_mode:4;
u8 charsetbits[2];
char label[16];
bbcd offset[3];
@@ -160,7 +165,7 @@ struct {
u8 unknown6:1,
dcs:7;
u8 unknown7[3];
-} memory[900];
+} memory[%d];
#seekto 0x280A;
struct {
@@ -170,8 +175,10 @@ struct {
skip:1,
used:1,
valid:1;
-} flag[900];
+} flag[%d];
+"""
+MEM_APRS_FORMAT = """
#seekto 0xbeca;
struct {
u8 rx_baud;
@@ -334,7 +341,33 @@ struct {
char path_and_body[66];
u8 unknown[70];
} aprs_message_pkt[60];
+"""
+MEM_BACKTRACK_FORMAT = """
+#seekto 0xdf06;
+struct {
+ u8 status; // 01 full 08 empty
+ u8 reserved0; // 00
+ bbcd year; // 17
+ bbcd mon; // 06
+ bbcd day; // 01
+ u8 reserved1; // 06
+ bbcd hour; // 21
+ bbcd min; // xx
+ u8 reserved2; // 00
+ u8 reserved3; // 00
+ char NShemi[1];
+ char lat[3];
+ char lat_min[2];
+ char lat_dec_sec[4];
+ char WEhemi[1];
+ char lon[3];
+ char lon_min[2];
+ char lon_dec_sec[4];
+} backtrack[3];
+
+"""
+MEM_CHECKSUM_FORMAT = """
#seekto 0x1FDC9;
u8 checksum;
"""
@@ -500,11 +533,7 @@ class FT1BankModel(chirp_common.BankModel):
return banks
-def _wipe_memory(mem):
- mem.set_raw("\x00" * (mem.size() / 8))
- mem.unknown1 = 0x05
-
-
+# Note: other radios like FTM3200Radio subclass this radio
@directory.register
class FT1Radio(yaesu_clone.YaesuCloneModeRadio):
"""Yaesu FT1DR"""
@@ -517,7 +546,9 @@ class FT1Radio(yaesu_clone.YaesuCloneModeRadio):
_memsize = 130507
_block_lengths = [10, 130497]
_block_size = 32
- _mem_params = (0xFECA, # APRS beacon metadata address.
+ _mem_params = (900, # size of memories array
+ 900, # size of flags array
+ 0xFECA, # APRS beacon metadata address.
60, # Number of beacons stored.
0x1064A, # APRS beacon content address.
134, # Length of beacon data stored.
@@ -590,6 +621,9 @@ class FT1Radio(yaesu_clone.YaesuCloneModeRadio):
_DTMF_SPEED = ("50ms", "100ms")
_DTMF_DELAY = ("50ms", "250ms", "450ms", "750ms", "1000ms")
_MY_SYMBOL = ("/[ Person", "/b Bike", "/> Car", "User selected")
+ _BACKTRACK_STATUS = ("Valid", "Invalid")
+ _NS_HEMI = ("N", "S")
+ _WE_HEMI = ("W", "E")
@classmethod
def get_prompts(cls):
@@ -610,7 +644,9 @@ class FT1Radio(yaesu_clone.YaesuCloneModeRadio):
return rp
def process_mmap(self):
- self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap)
+ mem_format = MEM_SETTINGS_FORMAT + MEM_FORMAT + MEM_APRS_FORMAT + \
+ MEM_BACKTRACK_FORMAT + MEM_CHECKSUM_FORMAT
+ self._memobj = bitwise.parse(mem_format % self._mem_params, self._mmap)
def get_features(self):
rf = chirp_common.RadioFeatures()
@@ -632,7 +668,8 @@ class FT1Radio(yaesu_clone.YaesuCloneModeRadio):
return rf
def get_raw_memory(self, number):
- return repr(self._memobj.memory[number])
+ return "\n".join([repr(self._memobj.memory[number - 1]),
+ repr(self._memobj.flag[number - 1])])
def _checksums(self):
return [yaesu_clone.YaesuChecksum(0x064A, 0x06C8),
@@ -666,21 +703,53 @@ class FT1Radio(yaesu_clone.YaesuCloneModeRadio):
mem.freq = chirp_common.fix_rounded_step(int(_mem.freq) * 1000)
mem.offset = int(_mem.offset) * 1000
mem.rtone = mem.ctone = chirp_common.TONES[_mem.tone]
- mem.tmode = TMODES[_mem.tone_mode]
+ self._get_tmode(mem, _mem)
mem.duplex = DUPLEX[_mem.duplex]
if mem.duplex == "split":
mem.offset = chirp_common.fix_rounded_step(mem.offset)
- mem.mode = MODES[_mem.mode]
+ mem.mode = self._decode_mode(_mem)
mem.dtcs = chirp_common.DTCS_CODES[_mem.dcs]
mem.tuning_step = STEPS[_mem.tune_step]
- mem.power = POWER_LEVELS[3 - _mem.power]
+ mem.power = self._decode_power_level(_mem)
mem.skip = flag.pskip and "P" or flag.skip and "S" or ""
- charset = ''.join(CHARSET).ljust(256, '.')
- mem.name = str(_mem.label).rstrip("\xFF").translate(charset)
+ mem.name = self._decode_label(_mem)
return mem
+ def _decode_label(self, mem):
+ charset = ''.join(CHARSET).ljust(256, '.')
+ return str(mem.label).rstrip("\xFF").translate(charset)
+
+ def _encode_label(self, mem):
+ label = "".join([chr(CHARSET.index(x)) for x in mem.name.rstrip()])
+ return self._add_ff_pad(label, 16)
+
+ def _encode_charsetbits(self, mem):
+ # We only speak english here in chirpville
+ return [0x00, 0x00]
+
+ def _decode_power_level(self, mem):
+ return POWER_LEVELS[3 - mem.power]
+
+ def _encode_power_level(self, mem):
+ return 3 - POWER_LEVELS.index(mem.power)
+
+ def _decode_mode(self, mem):
+ return MODES[mem.mode]
+
+ def _encode_mode(self, mem):
+ return MODES.index(mem.mode)
+
+ def _get_tmode(self, mem, _mem):
+ mem.tmode = TMODES[_mem.tone_mode]
+
+ def _set_tmode(self, _mem, mem):
+ _mem.tone_mode = TMODES.index(mem.tmode)
+
+ def _set_mode(self, _mem, mem):
+ _mem.mode = self._encode_mode(mem)
+
def _debank(self, mem):
bm = self.get_bank_model()
for bank in bm.get_memory_mappings(mem):
@@ -693,7 +762,7 @@ class FT1Radio(yaesu_clone.YaesuCloneModeRadio):
self._debank(mem)
if not mem.empty and not flag.valid:
- _wipe_memory(_mem)
+ self._wipe_memory(_mem)
if mem.empty and flag.valid and not flag.used:
flag.valid = False
@@ -714,25 +783,28 @@ class FT1Radio(yaesu_clone.YaesuCloneModeRadio):
_mem.freq = int(mem.freq / 1000)
_mem.offset = int(mem.offset / 1000)
_mem.tone = chirp_common.TONES.index(mem.rtone)
- _mem.tone_mode = TMODES.index(mem.tmode)
+ self._set_tmode(_mem, mem)
_mem.duplex = DUPLEX.index(mem.duplex)
- _mem.mode = MODES.index(mem.mode)
+ self._set_mode(_mem, mem)
_mem.dcs = chirp_common.DTCS_CODES.index(mem.dtcs)
_mem.tune_step = STEPS.index(mem.tuning_step)
if mem.power:
- _mem.power = 3 - POWER_LEVELS.index(mem.power)
+ _mem.power = self._encode_power_level(mem)
else:
_mem.power = 0
- label = "".join([chr(CHARSET.index(x)) for x in mem.name.rstrip()])
- _mem.label = self._add_ff_pad(label, 16)
- # We only speak english here in chirpville
- _mem.charsetbits[0] = 0x00
- _mem.charsetbits[1] = 0x00
+ _mem.label = self._encode_label(mem)
+ charsetbits = self._encode_charsetbits(mem)
+ _mem.charsetbits[0], _mem.charsetbits[1] = charsetbits
flag.skip = mem.skip == "S"
flag.pskip = mem.skip == "P"
+ @classmethod
+ def _wipe_memory(cls, mem):
+ mem.set_raw("\x00" * (mem.size() / 8))
+ mem.unknown1 = 0x05
+
def get_bank_model(self):
return FT1BankModel(self)
@@ -1412,6 +1484,183 @@ class FT1Radio(yaesu_clone.YaesuCloneModeRadio):
return menu
+ def backtrack_ll_validate(self, number, min, max):
+ if str(number).lstrip('0').strip().isdigit() and \
+ int(str(number).lstrip('0')) <= max and \
+ int(str(number).lstrip('0')) >= min:
+ return True
+
+ return False
+
+ def backtrack_zero_pad(self, number, l):
+ number = str(number).strip()
+ while len(number) < l:
+ number = '0' + number
+
+ return str(number)
+
+ def _get_backtrack_settings(self):
+
+ menu = RadioSettingGroup("backtrack", "Backtrack")
+
+ for i in range(3):
+ prefix = ''
+ if i == 0:
+ prefix = "Star "
+ if i == 1:
+ prefix = "L1 "
+ if i == 2:
+ prefix = "L2 "
+
+ bt_idx = "backtrack[%d]" % i
+
+ bt = self._memobj.backtrack[i]
+
+ val = RadioSettingValueList(
+ self._BACKTRACK_STATUS,
+ self._BACKTRACK_STATUS[0 if bt.status == 1 else 1])
+ rs = RadioSetting(
+ "%s.status" % bt_idx,
+ prefix + "status", val)
+ rs.set_apply_callback(self.apply_backtrack_status, bt)
+ menu.append(rs)
+
+ if bt.status == 1 and int(bt.year) < 100:
+ val = RadioSettingValueInteger(0, 99, bt.year)
+ else:
+ val = RadioSettingValueInteger(0, 99, 0)
+ rs = RadioSetting(
+ "%s.year" % bt_idx,
+ prefix + "year", val)
+ menu.append(rs)
+
+ if bt.status == 1 and int(bt.mon) <= 12:
+ val = RadioSettingValueInteger(0, 12, bt.mon)
+ else:
+ val = RadioSettingValueInteger(0, 12, 0)
+ rs = RadioSetting(
+ "%s.mon" % bt_idx,
+ prefix + "month", val)
+ menu.append(rs)
+
+ if bt.status == 1:
+ val = RadioSettingValueInteger(0, 31, bt.day)
+ else:
+ val = RadioSettingValueInteger(0, 31, 0)
+ rs = RadioSetting(
+ "%s.day" % bt_idx,
+ prefix + "day", val)
+ menu.append(rs)
+
+ if bt.status == 1:
+ val = RadioSettingValueInteger(0, 23, bt.hour)
+ else:
+ val = RadioSettingValueInteger(0, 23, 0)
+ rs = RadioSetting(
+ "%s.hour" % bt_idx,
+ prefix + "hour", val)
+ menu.append(rs)
+
+ if bt.status == 1:
+ val = RadioSettingValueInteger(0, 59, bt.min)
+ else:
+ val = RadioSettingValueInteger(0, 59, 0)
+ rs = RadioSetting(
+ "%s.min" % bt_idx,
+ prefix + "min", val)
+ menu.append(rs)
+
+ if bt.status == 1 and \
+ (str(bt.NShemi) == 'N' or str(bt.NShemi) == 'S'):
+ val = RadioSettingValueString(0, 1, str(bt.NShemi))
+ else:
+ val = RadioSettingValueString(0, 1, ' ')
+ rs = RadioSetting(
+ "%s.NShemi" % bt_idx,
+ prefix + "NS hemisphere", val)
+ rs.set_apply_callback(self.apply_NShemi, bt)
+ menu.append(rs)
+
+ if bt.status == 1 and self.backtrack_ll_validate(bt.lat, 0, 90):
+ val = RadioSettingValueString(
+ 0, 3, self.backtrack_zero_pad(bt.lat, 3))
+ else:
+ val = RadioSettingValueString(0, 3, ' ')
+ rs = RadioSetting("%s.lat" % bt_idx, prefix + "Latitude", val)
+ rs.set_apply_callback(self.apply_bt_lat, bt)
+ menu.append(rs)
+
+ if bt.status == 1 and \
+ self.backtrack_ll_validate(bt.lat_min, 0, 59):
+ val = RadioSettingValueString(
+ 0, 2, self.backtrack_zero_pad(bt.lat_min, 2))
+ else:
+ val = RadioSettingValueString(0, 2, ' ')
+ rs = RadioSetting(
+ "%s.lat_min" % bt_idx,
+ prefix + "Latitude Minutes", val)
+ rs.set_apply_callback(self.apply_bt_lat_min, bt)
+ menu.append(rs)
+
+ if bt.status == 1 and \
+ self.backtrack_ll_validate(bt.lat_dec_sec, 0, 9999):
+ val = RadioSettingValueString(
+ 0, 4, self.backtrack_zero_pad(bt.lat_dec_sec, 4))
+ else:
+ val = RadioSettingValueString(0, 4, ' ')
+ rs = RadioSetting(
+ "%s.lat_dec_sec" % bt_idx,
+ prefix + "Latitude Decimal Seconds", val)
+ rs.set_apply_callback(self.apply_bt_lat_dec_sec, bt)
+ menu.append(rs)
+
+ if bt.status == 1 and \
+ (str(bt.WEhemi) == 'W' or str(bt.WEhemi) == 'E'):
+ val = RadioSettingValueString(
+ 0, 1, str(bt.WEhemi))
+ else:
+ val = RadioSettingValueString(0, 1, ' ')
+ rs = RadioSetting(
+ "%s.WEhemi" % bt_idx,
+ prefix + "WE hemisphere", val)
+ rs.set_apply_callback(self.apply_WEhemi, bt)
+ menu.append(rs)
+
+ if bt.status == 1 and self.backtrack_ll_validate(bt.lon, 0, 180):
+ val = RadioSettingValueString(
+ 0, 3, self.backtrack_zero_pad(bt.lon, 3))
+ else:
+ val = RadioSettingValueString(0, 3, ' ')
+ rs = RadioSetting("%s.lon" % bt_idx, prefix + "Longitude", val)
+ rs.set_apply_callback(self.apply_bt_lon, bt)
+ menu.append(rs)
+
+ if bt.status == 1 and \
+ self.backtrack_ll_validate(bt.lon_min, 0, 59):
+ val = RadioSettingValueString(
+ 0, 2, self.backtrack_zero_pad(bt.lon_min, 2))
+ else:
+ val = RadioSettingValueString(0, 2, ' ')
+ rs = RadioSetting(
+ "%s.lon_min" % bt_idx,
+ prefix + "Longitude Minutes", val)
+ rs.set_apply_callback(self.apply_bt_lon_min, bt)
+ menu.append(rs)
+
+ if bt.status == 1 and \
+ self.backtrack_ll_validate(bt.lon_dec_sec, 0, 9999):
+ val = RadioSettingValueString(
+ 0, 4, self.backtrack_zero_pad(bt.lon_dec_sec, 4))
+ else:
+ val = RadioSettingValueString(0, 4, ' ')
+ rs = RadioSetting(
+ "%s.lon_dec_sec" % bt_idx,
+ prefix + "Longitude Decimal Seconds", val)
+ rs.set_apply_callback(self.apply_bt_lon_dec_sec, bt)
+ menu.append(rs)
+
+ return menu
+
def _get_scan_settings(self):
menu = RadioSettingGroup("scan_settings", "Scan")
scan_settings = self._memobj.scan_settings
@@ -1492,7 +1741,8 @@ class FT1Radio(yaesu_clone.YaesuCloneModeRadio):
self._get_aprs_beacons(),
self._get_dtmf_settings(),
self._get_misc_settings(),
- self._get_scan_settings())
+ self._get_scan_settings(),
+ self._get_backtrack_settings())
return top
def get_settings(self):
@@ -1647,3 +1897,69 @@ class FT1Radio(yaesu_clone.YaesuCloneModeRadio):
for x in range(len(val), 16):
val.append(0xFF)
cls._memobj.dtmf[i].memory = val
+
+ def apply_backtrack_status(cls, setting, obj):
+ status = setting.value.get_value()
+
+ if status == 'Valid':
+ val = 1
+ else:
+ val = 8
+ setattr(obj, "status", val)
+
+ def apply_NShemi(cls, setting, obj):
+ hemi = setting.value.get_value().upper()
+
+ if hemi != 'N' and hemi != 'S':
+ hemi = ' '
+ setattr(obj, "NShemi", hemi)
+
+ def apply_WEhemi(cls, setting, obj):
+ hemi = setting.value.get_value().upper()
+
+ if hemi != 'W' and hemi != 'E':
+ hemi = ' '
+ setattr(obj, "WEhemi", hemi)
+
+ def apply_WEhemi(cls, setting, obj):
+ hemi = setting.value.get_value().upper()
+
+ if hemi != 'W' and hemi != 'E':
+ hemi = ' '
+ setattr(obj, "WEhemi", hemi)
+
+ def apply_bt_lat(cls, setting, obj):
+ val = setting.value.get_value()
+ val = cls.backtrack_zero_pad(val, 3)
+
+ setattr(obj, "lat", val)
+
+ def apply_bt_lat_min(cls, setting, obj):
+ val = setting.value.get_value()
+ val = cls.backtrack_zero_pad(val, 2)
+
+ setattr(obj, "lat_min", val)
+
+ def apply_bt_lat_dec_sec(cls, setting, obj):
+ val = setting.value.get_value()
+ val = cls.backtrack_zero_pad(val, 4)
+
+ setattr(obj, "lat_dec_sec", val)
+
+ def apply_bt_lon(cls, setting, obj):
+ val = setting.value.get_value()
+ val = cls.backtrack_zero_pad(val, 3)
+
+ setattr(obj, "lon", val)
+
+ def apply_bt_lon_min(cls, setting, obj):
+ val = setting.value.get_value()
+ val = cls.backtrack_zero_pad(val, 2)
+
+ setattr(obj, "lon_min", val)
+
+ def apply_bt_lon_dec_sec(cls, setting, obj):
+ val = setting.value.get_value()
+ val = cls.backtrack_zero_pad(val, 4)
+
+ setattr(obj, "lon_dec_sec", val)
diff --git a/chirp/drivers/ft2d.py b/chirp/drivers/ft2d.py
new file mode 100644
index 0000000..50ff482
--- /dev/null
+++ b/chirp/drivers/ft2d.py
@@ -0,0 +1,128 @@
+# Copyright 2010 Dan Smith <dsmith at danplanet.com>
+# Portions Copyright 2017 Wade Simmons <wade at wades.im>
+# Copyright 2017 Declan Rieb <darieb at comcast.net>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+from textwrap import dedent
+
+from chirp.drivers import yaesu_clone, ft1d
+from chirp import chirp_common, directory, bitwise
+from chirp.settings import RadioSettings
+
+# Differences from Yaesu FT1D
+# 999 memories, but 901-999 are only for skipping VFO frequencies
+# Text in memory and memory bank structures is ASCII encoded
+# Expanded modes
+# Slightly different clone-mode instructions
+
+LOG = logging.getLogger(__name__)
+
+TMODES = ["", "Tone", "TSQL", "DTCS", "RTone", "JRfrq", "PRSQL", "Pager"]
+
+class FT2Bank(chirp_common.NamedBank): # Like FT1D except for name in ASCII
+ def get_name(self):
+ _bank = self._model._radio._memobj.bank_info[self.index]
+ name = ""
+ for i in _bank.name:
+ if i == 0xff:
+ break
+ name += chr(i & 0xFF)
+ return name.rstrip()
+
+ def set_name(self, name):
+ _bank = self._model._radio._memobj.bank_info[self.index]
+ _bank.name = [ord(x) for x in name.ljust(16, chr(0xFF))[:16]]
+
+class FT2BankModel(ft1d.FT1BankModel): #just need this one to launch FT2Bank
+ """A FT1D bank model"""
+ def __init__(self, radio, name='Banks'):
+ super(FT2BankModel, self).__init__(radio, name)
+
+ _banks = self._radio._memobj.bank_info
+ self._bank_mappings = []
+ for index, _bank in enumerate(_banks):
+ bank = FT2Bank(self, "%i" % index, "BANK-%i" % index)
+ bank.index = index
+ self._bank_mappings.append(bank)
+
+ at directory.register
+class FT2D(ft1d.FT1Radio):
+ """Yaesu FT-2D"""
+ BAUD_RATE = 38400
+ VENDOR = "Yaesu"
+ MODEL = "FT2D" # Yaesu doesn't use a hyphen in its documents
+ VARIANT = "R"
+
+ _model = "AH60M" # Get this from chirp .img file after saving once
+ _has_vibrate = True
+ _mem_params = (999, # size of memories array
+ 999, # size of flags array
+ 0xFECA, # APRS beacon metadata address.
+ 60, # Number of beacons stored.
+ 0x1064A, # APRS beacon content address.
+ 134, # Length of beacon data stored.
+ 60) # Number of beacons stored.
+
+ @classmethod
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.pre_download = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to DATA terminal.
+ 3. Press and hold [DISP] key while turning on radio
+ ("CLONE" will appear on the display).
+ 4. <b>After clicking OK here in chirp</b>,
+ press the [Send] screen button."""))
+ rp.pre_upload = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to DATA terminal.
+ 3. Press and hold in [DISP] key while turning on radio
+ ("CLONE" will appear on radio LCD).
+ 4. Press [RECEIVE] screen button
+ ("-WAIT-" will appear on radio LCD).
+ 5. Finally, press OK button below."""))
+ return rp
+
+ def get_features(self): # AFAICT only TMODES & memory bounds are different
+ rf = super(FT2D, self).get_features()
+ rf.valid_tmodes = list(TMODES)
+ rf.memory_bounds = (1, 999)
+ return rf
+
+ def get_bank_model(self): # here only to launch the bank model
+ return FT2BankModel(self)
+
+ def get_memory(self, number):
+ mem = super(FT2D, self).get_memory(number)
+ flag = self._memobj.flag[number - 1]
+ if number >= 901 and number <= 999: # for FT2D; enforces skip
+ mem.skip = "S"
+ flag.skip = True
+ return mem
+
+ def _decode_label(self, mem):
+ return str(mem.label).rstrip("\xFF").decode('ascii', 'replace')
+
+ def _encode_label(self, mem):
+ label = mem.name.rstrip().encode('ascii', 'ignore')
+ return self._add_ff_pad(label, 16)
+
+ def set_memory(self, mem):
+ flag = self._memobj.flag[mem.number - 1]
+ if mem.number >= 901 and mem.number <= 999: # for FT2D; enforces skip
+ flag.skip = True
+ mem.skip = "S"
+ super(FT2D, self).set_memory(mem)
\ No newline at end of file
diff --git a/chirp/drivers/ft857.py b/chirp/drivers/ft857.py
index dee850e..5a8fe32 100644
--- a/chirp/drivers/ft857.py
+++ b/chirp/drivers/ft857.py
@@ -189,8 +189,8 @@ class FT857Radio(ft817.FT817Radio):
pkt1200:7;
u8 unknown15:1,
pkt9600:7;
- il16 dig_shift;
- il16 dig_disp;
+ i16 dig_shift;
+ i16 dig_disp;
i8 r_lsb_car;
i8 r_usb_car;
i8 t_lsb_car;
diff --git a/chirp/drivers/ftm3200d.py b/chirp/drivers/ftm3200d.py
new file mode 100644
index 0000000..1b27012
--- /dev/null
+++ b/chirp/drivers/ftm3200d.py
@@ -0,0 +1,201 @@
+# Copyright 2010 Dan Smith <dsmith at danplanet.com>
+# Copyright 2017 Wade Simmons <wade at wades.im>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+from textwrap import dedent
+
+from chirp.drivers import yaesu_clone, ft1d
+from chirp import chirp_common, directory, bitwise
+from chirp.settings import RadioSettings
+
+LOG = logging.getLogger(__name__)
+
+POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=5),
+ chirp_common.PowerLevel("Mid", watts=30),
+ chirp_common.PowerLevel("Hi", watts=65)]
+
+TMODES = ["", "Tone", "TSQL", "DTCS", "TSQL-R", None, None, "Pager", "Cross"]
+CROSS_MODES = [None, "DTCS->", "Tone->DTCS", "DTCS->Tone"]
+
+MODES = ["FM", "NFM"]
+STEPS = [0, 5, 6.25, 10, 12.5, 15, 20, 25, 50, 100] # 0 = auto
+RFSQUELCH = ["OFF", "S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8"]
+
+# Charset is subset of ASCII + some unknown chars \x80-\x86
+VALID_CHARS = ["%i" % int(x) for x in range(0, 10)] + \
+ list(":>=<?@") + \
+ [chr(x) for x in range(ord("A"), ord("Z") + 1)] + \
+ list("[\\]_") + \
+ [chr(x) for x in range(ord("a"), ord("z") + 1)] + \
+ list("%*+,-/=$ ")
+
+MEM_FORMAT = """
+#seekto 0xceca;
+struct {
+ u8 unknown5;
+ u8 unknown3;
+ u8 unknown4:6,
+ dsqtype:2;
+ u8 dsqcode;
+ u8 unknown1[2];
+ char mycall[10];
+ u8 unknown2[368];
+} settings;
+
+#seekto 0xfec9;
+u8 checksum;
+"""
+
+
+ at directory.register
+class FTM3200Radio(ft1d.FT1Radio):
+ """Yaesu FTM-3200D"""
+ BAUD_RATE = 38400
+ VENDOR = "Yaesu"
+ MODEL = "FTM-3200D"
+ VARIANT = "R"
+
+ _model = "AH52N"
+ _memsize = 65227
+ _block_lengths = [10, 65217]
+ _has_vibrate = False
+ _has_af_dual = False
+
+ _mem_params = (199, # size of memories array
+ 199) # size of flags array
+
+ @classmethod
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.pre_download = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to DATA terminal.
+ 3. Press and hold in the [MHz(SETUP)] key while turning the radio
+ on ("CLONE" will appear on the display).
+ 4. <b>After clicking OK</b>, press the [REV(DW)] key
+ to send image."""))
+ rp.pre_upload = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to DATA terminal.
+ 3. Press and hold in the [MHz(SETUP)] key while turning the radio
+ on ("CLONE" will appear on the display).
+ 4. Press the [MHz(SETUP)] key
+ ("-WAIT-" will appear on the LCD)."""))
+ return rp
+
+ def process_mmap(self):
+ mem_format = ft1d.MEM_FORMAT + MEM_FORMAT
+ self._memobj = bitwise.parse(mem_format % self._mem_params, self._mmap)
+
+ def get_features(self):
+ rf = chirp_common.RadioFeatures()
+ rf.has_dtcs_polarity = False
+ rf.valid_modes = list(MODES)
+ rf.valid_tmodes = [x for x in TMODES if x is not None]
+ rf.valid_cross_modes = [x for x in CROSS_MODES if x is not None]
+ rf.valid_duplexes = list(ft1d.DUPLEX)
+ rf.valid_tuning_steps = list(STEPS)
+ rf.valid_bands = [(136000000, 174000000)]
+ # rf.valid_skips = SKIPS
+ rf.valid_power_levels = POWER_LEVELS
+ rf.valid_characters = "".join(VALID_CHARS)
+ rf.valid_name_length = 8
+ rf.memory_bounds = (1, 199)
+ rf.can_odd_split = True
+ rf.has_ctone = False
+ rf.has_bank = False
+ rf.has_bank_names = False
+ # disable until implemented
+ rf.has_settings = False
+ return rf
+
+ def _decode_label(self, mem):
+ # TODO preserve the unknown \x80-x86 chars?
+ return str(mem.label).rstrip("\xFF").decode('ascii', 'replace')
+
+ def _encode_label(self, mem):
+ label = mem.name.rstrip().encode('ascii', 'ignore')
+ return self._add_ff_pad(label, 16)
+
+ def _encode_charsetbits(self, mem):
+ # TODO this is a setting to decide if the memory should be displayed
+ # as a name or frequency. Should we expose this setting to the user
+ # instead of autoselecting it (and losing their preference)?
+ if mem.name.rstrip() == '':
+ return [0x00, 0x00]
+ return [0x00, 0x80]
+
+ def _decode_power_level(self, mem):
+ return POWER_LEVELS[mem.power - 1]
+
+ def _encode_power_level(self, mem):
+ return POWER_LEVELS.index(mem.power) + 1
+
+ def _decode_mode(self, mem):
+ return MODES[mem.mode_alt]
+
+ def _encode_mode(self, mem):
+ return MODES.index(mem.mode)
+
+ def _get_tmode(self, mem, _mem):
+ if _mem.tone_mode > 8:
+ tmode = "Cross"
+ mem.cross_mode = CROSS_MODES[_mem.tone_mode - 8]
+ else:
+ tmode = TMODES[_mem.tone_mode]
+
+ if tmode == "Pager":
+ # TODO chirp_common does not allow 'Pager'
+ # Expose as a different setting?
+ mem.tmode = ""
+ else:
+ mem.tmode = tmode
+
+ def _set_tmode(self, _mem, mem):
+ if mem.tmode == "Cross":
+ _mem.tone_mode = 8 + CROSS_MODES.index(mem.cross_mode)
+ else:
+ _mem.tone_mode = TMODES.index(mem.tmode)
+
+ def _set_mode(self, _mem, mem):
+ _mem.mode_alt = self._encode_mode(mem)
+
+ def get_bank_model(self):
+ return None
+
+ def _debank(self, mem):
+ return
+
+ def _checksums(self):
+ return [yaesu_clone.YaesuChecksum(0x064A, 0x06C8),
+ yaesu_clone.YaesuChecksum(0x06CA, 0x0748),
+ yaesu_clone.YaesuChecksum(0x074A, 0x07C8),
+ yaesu_clone.YaesuChecksum(0x07CA, 0x0848),
+ yaesu_clone.YaesuChecksum(0x0000, 0xFEC9)]
+
+ def _get_settings(self):
+ # TODO
+ top = RadioSettings()
+ return top
+
+ @classmethod
+ def _wipe_memory(cls, mem):
+ mem.set_raw("\x00" * (mem.size() / 8))
+
+ def sync_out(self):
+ # Need to give enough time for the radio to ACK after writes
+ self.pipe.timeout = 1
+ return super(FTM3200Radio, self).sync_out()
diff --git a/chirp/drivers/generic_xml.py b/chirp/drivers/generic_xml.py
index 06c5da9..adb993d 100644
--- a/chirp/drivers/generic_xml.py
+++ b/chirp/drivers/generic_xml.py
@@ -24,10 +24,7 @@ LOG = logging.getLogger(__name__)
def validate_doc(doc):
"""Validate the document"""
- basepath = platform.get_platform().executable_path()
- path = os.path.abspath(os.path.join(basepath, "chirp.xsd"))
- if not os.path.exists(path):
- path = "/usr/share/chirp/chirp.xsd"
+ path = platform.get_platform().find_resource("chirp.xsd")
try:
ctx = libxml2.schemaNewParserCtxt(path)
diff --git a/chirp/drivers/ic2300.py b/chirp/drivers/ic2300.py
new file mode 100644
index 0000000..d39cc44
--- /dev/null
+++ b/chirp/drivers/ic2300.py
@@ -0,0 +1,385 @@
+# Copyright 2017 Windsor Schmidt <windsor.schmidt 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
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from chirp import chirp_common, directory, bitwise
+from chirp.drivers import icf
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+ RadioSettingValueList, RadioSettingValueBoolean, RadioSettings
+
+# The Icom IC-2300H is a 65W, 144MHz mobile transceiver based on the IC-2200H.
+# Unlike the IC-2200H, this model does not accept Icom's UT-118 D-STAR board.
+#
+# A simple USB interface based on a typical FT232RL breakout board was used
+# during development of this module. A schematic diagram is as follows:
+#
+#
+# 3.5mm plug from IC-2300H
+# sleeve / ring / tip
+# --.______________
+# | | | \
+# |______|___|___/ FT232RL breakout
+# --' | | .------------------.
+# | +--------| RXD |
+# | | D1 | |
+# | +--|>|---| TXD | USB/PC
+# | | R1 | |-------->
+# | +--[_]---| VCC (5V) |
+# | | |
+# +------------| GND |
+# `------------------'
+#
+# D1: 1N4148 shottky diode
+# R1: 10K ohm resistor
+
+MEM_FORMAT = """
+#seekto 0x0000; // channel memories
+struct {
+ ul16 frequency;
+ ul16 offset;
+ char name[6];
+ u8 repeater_tone;
+ u8 ctcss_tone;
+ u8 dtcs_code;
+ u8 tuning_step:4,
+ tone_mode:4;
+ u8 unknown1:3,
+ mode_narrow:1,
+ unknown2:4;
+ u8 dtcs_polarity:2,
+ duplex:2,
+ unknown3:1,
+ reverse_duplex:1,
+ unknown4:1,
+ display_style:1;
+} memory[200];
+#seekto 0x1340; // channel memory flags
+struct {
+ u8 unknown5:2,
+ empty:1,
+ skip:1,
+ bank:4;
+} flags[200];
+#seekto 0x1660; // power-on and regular set menu items
+struct {
+ u8 key_beep;
+ u8 tx_timeout;
+ u8 auto_repeater;
+ u8 auto_power_off;
+ u8 repeater_lockout;
+ u8 squelch_delay;
+ u8 squelch_type;
+ u8 dtmf_speed;
+ u8 display_type;
+ u8 unknown6;
+ u8 tone_burst;
+ u8 voltage_display;
+ u8 unknown7;
+ u8 display_brightness;
+ u8 display_color;
+ u8 auto_dimmer;
+ u8 display_contrast;
+ u8 scan_pause_timer;
+ u8 mic_gain;
+ u8 scan_resume_timer;
+ u8 weather_alert;
+ u8 bank_link_enable;
+ u8 bank_link[10];
+} settings;
+"""
+
+TUNING_STEPS = [5.0, 6.25, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0]
+TONE_MODES = ["", "Tone", "TSQL", "DTCS"]
+DUPLEX = ["", "-", "+"]
+DTCSP = ["NN", "NR", "RN", "RR"]
+DTCS_POLARITY = ["NN", "NR", "RN", "RR"]
+
+POWER_LEVELS = [chirp_common.PowerLevel("High", watts=65),
+ chirp_common.PowerLevel("Mid", watts=25),
+ chirp_common.PowerLevel("MidLow", watts=10),
+ chirp_common.PowerLevel("Low", watts=5)]
+
+
+def _wipe_memory(mem, char):
+ mem.set_raw(char * (mem.size() / 8))
+
+
+ at directory.register
+class IC2300Radio(icf.IcomCloneModeRadio):
+ """Icom IC-2300"""
+ VENDOR = "Icom"
+ MODEL = "IC-2300H"
+
+ _model = "\x32\x51\x00\x01"
+ _memsize = 6304
+ _endframe = "Icom Inc.C5\xfd"
+ _can_hispeed = True
+ _ranges = [(0x0000, 0x18a0, 32)] # upload entire memory for now
+
+ def get_features(self):
+ rf = chirp_common.RadioFeatures()
+ rf.memory_bounds = (0, 199)
+ rf.valid_modes = ["FM", "NFM"]
+ rf.valid_tmodes = list(TONE_MODES)
+ rf.valid_duplexes = list(DUPLEX)
+ rf.valid_tuning_steps = list(TUNING_STEPS)
+ rf.valid_bands = [(136000000, 174000000)] # USA tx range: 144-148MHz
+ rf.valid_skips = ["", "S"]
+ rf.valid_power_levels = POWER_LEVELS
+ rf.has_settings = True
+ return rf
+
+ def process_mmap(self):
+ self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+ def _get_bank(self, loc):
+ _flag = self._memobj.flags[loc]
+ if _flag.bank == 0x0a:
+ return None
+ else:
+ return _flag.bank
+
+ def _set_bank(self, loc, bank):
+ _flag = self._memobj.flags[loc]
+ if bank is None:
+ _flag.bank = 0x0a
+ else:
+ _flag.bank = bank
+
+ def get_memory(self, number):
+ _mem = self._memobj.memory[number]
+ _flag = self._memobj.flags[number]
+ mem = chirp_common.Memory()
+ mem.number = number
+ if _flag.empty:
+ mem.empty = True
+ return mem
+ mult = int(TUNING_STEPS[_mem.tuning_step] * 1000)
+ mem.freq = (_mem.frequency * mult)
+ mem.offset = (_mem.offset * mult)
+ mem.name = str(_mem.name).rstrip()
+ mem.rtone = chirp_common.TONES[_mem.repeater_tone]
+ mem.ctone = chirp_common.TONES[_mem.ctcss_tone]
+ mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs_code]
+ mem.tuning_step = TUNING_STEPS[_mem.tuning_step]
+ mem.tmode = TONE_MODES[_mem.tone_mode]
+ mem.mode = "NFM" if _mem.mode_narrow else "FM"
+ mem.dtcs_polarity = DTCS_POLARITY[_mem.dtcs_polarity]
+ mem.duplex = DUPLEX[_mem.duplex]
+ mem.skip = "S" if _flag.skip else ""
+
+ # Reverse duplex
+ mem.extra = RadioSettingGroup("extra", "Extra")
+ rev = RadioSetting("reverse_duplex", "Reverse duplex",
+ RadioSettingValueBoolean(bool(_mem.reverse_duplex)))
+ rev.set_doc("Reverse duplex")
+ mem.extra.append(rev)
+
+ # Memory display style
+ opt = ["Frequency", "Label"]
+ dsp = RadioSetting("display_style", "Display style",
+ RadioSettingValueList(opt, opt[_mem.display_style]))
+ dsp.set_doc("Memory display style")
+ mem.extra.append(dsp)
+
+ return mem
+
+ def get_raw_memory(self, number):
+ return repr(self._memobj.memory[number])
+
+ def set_memory(self, mem):
+ number = mem.number
+ _mem = self._memobj.memory[number]
+ _flag = self._memobj.flags[number]
+ was_empty = int(_flag.empty)
+ _flag.empty = mem.empty
+ if mem.empty:
+ _wipe_memory(_mem, "\xff")
+ return
+ if was_empty:
+ _wipe_memory(_mem, "\x00")
+ mult = mem.tuning_step * 1000
+ _mem.frequency = (mem.freq / mult)
+ _mem.offset = mem.offset / mult
+ _mem.name = mem.name.ljust(6)
+ _mem.repeater_tone = chirp_common.TONES.index(mem.rtone)
+ _mem.ctcss_tone = chirp_common.TONES.index(mem.ctone)
+ _mem.dtcs_code = chirp_common.DTCS_CODES.index(mem.dtcs)
+ _mem.tuning_step = TUNING_STEPS.index(mem.tuning_step)
+ _mem.tone_mode = TONE_MODES.index(mem.tmode)
+ _mem.mode_narrow = mem.mode.startswith("N")
+ _mem.dtcs_polarity = DTCSP.index(mem.dtcs_polarity)
+ _mem.duplex = DUPLEX.index(mem.duplex)
+ _flag.skip = mem.skip != ""
+
+ for setting in mem.extra:
+ setattr(_mem, setting.get_name(), setting.value)
+
+ def get_settings(self):
+ _settings = self._memobj.settings
+ basic = RadioSettingGroup("basic", "Basic Settings")
+ front_panel = RadioSettingGroup("front_panel", "Front Panel Settings")
+ top = RadioSettings(basic, front_panel)
+
+ # Transmit timeout
+ opt = ['Disabled', '1 minute'] + \
+ [s + ' minutes' for s in map(str, range(2, 31))]
+ rs = RadioSetting("tx_timeout", "Transmit timeout (min)",
+ RadioSettingValueList(opt, opt[
+ _settings.tx_timeout
+ ]))
+ basic.append(rs)
+
+ # Auto Repeater (USA model only)
+ opt = ["Disabled", "Duplex Only", "Duplex and tone"]
+ rs = RadioSetting("auto_repeater", "Auto repeater",
+ RadioSettingValueList(opt, opt[
+ _settings.auto_repeater
+ ]))
+ basic.append(rs)
+
+ # Auto Power Off
+ opt = ["Disabled", "30 minutes", "60 minutes", "120 minutes"]
+ rs = RadioSetting("auto_power_off", "Auto power off",
+ RadioSettingValueList(opt, opt[
+ _settings.auto_power_off
+ ]))
+ basic.append(rs)
+
+ # Squelch Delay
+ opt = ["Short", "Long"]
+ rs = RadioSetting("squelch_delay", "Squelch delay",
+ RadioSettingValueList(opt, opt[
+ _settings.squelch_delay
+ ]))
+ basic.append(rs)
+
+ # Squelch Type
+ opt = ["Noise squelch", "S-meter squelch", "Squelch attenuator"]
+ rs = RadioSetting("squelch_type", "Squelch type",
+ RadioSettingValueList(opt, opt[
+ _settings.squelch_type
+ ]))
+ basic.append(rs)
+
+ # Repeater Lockout
+ opt = ["Disabled", "Repeater lockout", "Busy lockout"]
+ rs = RadioSetting("repeater_lockout", "Repeater lockout",
+ RadioSettingValueList(opt, opt[
+ _settings.repeater_lockout
+ ]))
+ basic.append(rs)
+
+ # DTMF Speed
+ opt = ["100ms interval, 5.0 cps",
+ "200ms interval, 2.5 cps",
+ "300ms interval, 1.6 cps",
+ "500ms interval, 1.0 cps"]
+ rs = RadioSetting("dtmf_speed", "DTMF speed",
+ RadioSettingValueList(opt, opt[
+ _settings.dtmf_speed
+ ]))
+ basic.append(rs)
+
+ # Scan pause timer
+ opt = [s + ' seconds' for s in map(str, range(2, 22, 2))] + ['Hold']
+ rs = RadioSetting("scan_pause_timer", "Scan pause timer",
+ RadioSettingValueList(
+ opt, opt[_settings.scan_pause_timer]))
+ basic.append(rs)
+
+ # Scan Resume Timer
+ opt = ['Immediate'] + \
+ [s + ' seconds' for s in map(str, range(1, 6))] + ['Hold']
+ rs = RadioSetting("scan_resume_timer", "Scan resume timer",
+ RadioSettingValueList(
+ opt, opt[_settings.scan_resume_timer]))
+ basic.append(rs)
+
+ # Weather Alert (USA model only)
+ rs = RadioSetting("weather_alert", "Weather alert",
+ RadioSettingValueBoolean(_settings.weather_alert))
+ basic.append(rs)
+
+ # Tone Burst
+ rs = RadioSetting("tone_burst", "Tone burst",
+ RadioSettingValueBoolean(_settings.tone_burst))
+ basic.append(rs)
+
+ # Memory Display Type
+ opt = ["Frequency", "Channel", "Name"]
+ rs = RadioSetting("display_type", "Memory display",
+ RadioSettingValueList(opt,
+ opt[_settings.display_type]))
+ front_panel.append(rs)
+
+ # Display backlight brightness;
+ opt = ["1 (dimmest)", "2", "3", "4 (brightest)"]
+ rs = RadioSetting("display_brightness", "Backlight brightness",
+ RadioSettingValueList(
+ opt,
+ opt[_settings.display_brightness]))
+ front_panel.append(rs)
+
+ # Display backlight color
+ opt = ["Amber", "Yellow", "Green"]
+ rs = RadioSetting("display_color", "Backlight color",
+ RadioSettingValueList(opt,
+ opt[_settings.display_color]))
+ front_panel.append(rs)
+
+ # Display contrast
+ opt = ["1 (lightest)", "2", "3", "4 (darkest)"]
+ rs = RadioSetting("display_contrast", "Display contrast",
+ RadioSettingValueList(
+ opt,
+ opt[_settings.display_contrast]))
+ front_panel.append(rs)
+
+ # Auto dimmer
+ opt = ["Disabled", "Backlight off", "1 (dimmest)", "2", "3"]
+ rs = RadioSetting("auto_dimmer", "Auto dimmer",
+ RadioSettingValueList(opt,
+ opt[_settings.auto_dimmer]))
+ front_panel.append(rs)
+
+ # Microphone gain
+ opt = ["Low", "High"]
+ rs = RadioSetting("mic_gain", "Microphone gain",
+ RadioSettingValueList(opt,
+ opt[_settings.mic_gain]))
+ front_panel.append(rs)
+
+ # Key press beep
+ rs = RadioSetting("key_beep", "Key press beep",
+ RadioSettingValueBoolean(_settings.key_beep))
+ front_panel.append(rs)
+
+ # Voltage Display;
+ rs = RadioSetting("voltage_display", "Voltage display",
+ RadioSettingValueBoolean(_settings.voltage_display))
+ front_panel.append(rs)
+
+ # TODO: Add Bank Links settings to GUI
+
+ return top
+
+ def set_settings(self, settings):
+ _settings = self._memobj.settings
+ for element in settings:
+ if not isinstance(element, RadioSetting):
+ self.set_settings(element)
+ continue
+ setting = element.get_name()
+ setattr(_settings, setting, element.value)
diff --git a/chirp/drivers/icf.py b/chirp/drivers/icf.py
index 1feab74..e544571 100644
--- a/chirp/drivers/icf.py
+++ b/chirp/drivers/icf.py
@@ -198,6 +198,8 @@ def send_clone_frame(pipe, cmd, data, raw=False, checksum=False):
pass
pipe.write(frame)
+ pipe.flush()
+ pipe.read(len(frame)) # discard echoback
return frame
@@ -261,6 +263,7 @@ def start_hispeed_clone(radio, cmd):
LOG.debug("Starting HiSpeed Clone:\n%s" % util.hexprint(buf))
radio.pipe.write(buf)
radio.pipe.flush()
+ radio.pipe.read(len(buf)) # discard echoback
def _clone_from_radio(radio):
diff --git a/chirp/drivers/icp7.py b/chirp/drivers/icp7.py
new file mode 100644
index 0000000..04db27e
--- /dev/null
+++ b/chirp/drivers/icp7.py
@@ -0,0 +1,243 @@
+# Copyright 2017 SASANO Takayoshi (JG1UAA) <uaa at uaa.org.uk>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from chirp.drivers import icf
+from chirp import chirp_common, directory, bitwise
+
+# memory nuber:
+# 000 - 999 regular memory channels (supported, others not)
+# 1000 - 1049 scan edges
+# 1050 - 1249 auto write channels
+# 1250 call channel (C0)
+# 1251 call channel (C1)
+
+
+MEM_FORMAT = """
+struct {
+ ul32 freq;
+ ul32 offset;
+ ul16 train_sql:2,
+ tmode:3,
+ duplex:2,
+ train_tone:9;
+ ul16 tuning_step:4,
+ rtone:6,
+ ctone:6;
+ ul16 unknown0:6,
+ mode:3,
+ dtcs:7;
+ u8 unknown1:6,
+ dtcs_polarity:2;
+ char name[6];
+} memory[1251];
+
+#seekto 0x6b1e;
+struct {
+ u8 bank;
+ u8 index;
+} banks[1050];
+
+#seekto 0x689e;
+u8 used[132];
+
+#seekto 0x6922;
+u8 skips[132];
+
+#seekto 0x69a6;
+u8 pskips[132];
+
+#seekto 0x7352;
+struct {
+ char name[6];
+} bank_names[18];
+
+"""
+
+MODES = ["FM", "WFM", "AM", "Auto"]
+TMODES = ["", "Tone", "TSQL", "", "DTCS"]
+DUPLEX = ["", "-", "+"]
+DTCS_POLARITY = ["NN", "NR", "RN", "RR"]
+TUNING_STEPS = [5.0, 6.25, 8.33, 9.0, 10.0, 12.5, 15.0, 20.0,
+ 25.0, 30.0, 50.0, 100.0, 200.0, 0.0] # 0.0 as "Auto"
+
+
+class ICP7Bank(icf.IcomBank):
+ """ICP7 bank"""
+ def get_name(self):
+ _bank = self._model._radio._memobj.bank_names[self.index]
+ return str(_bank.name).rstrip()
+
+ def set_name(self, name):
+ _bank = self._model._radio._memobj.bank_names[self.index]
+ _bank.name = name.ljust(6)[:6]
+
+
+ at directory.register
+class ICP7Radio(icf.IcomCloneModeRadio):
+ """Icom IC-P7"""
+ VENDOR = "Icom"
+ MODEL = "IC-P7"
+
+ _model = "\x28\x69\x00\x01"
+ _memsize = 0x7500
+ _endframe = "Icom Inc\x2e\x41\x38"
+
+ _ranges = [(0x0000, 0x7500, 32)]
+
+ _num_banks = 18
+ _bank_class = ICP7Bank
+ _can_hispeed = True
+
+ def _get_bank(self, loc):
+ _bank = self._memobj.banks[loc]
+ if _bank.bank != 0xff:
+ return _bank.bank
+ else:
+ return None
+
+ def _set_bank(self, loc, bank):
+ _bank = self._memobj.banks[loc]
+ if bank is None:
+ _bank.bank = 0xff
+ else:
+ _bank.bank = bank
+
+ def _get_bank_index(self, loc):
+ _bank = self._memobj.banks[loc]
+ return _bank.index
+
+ def _set_bank_index(self, loc, index):
+ _bank = self._memobj.banks[loc]
+ _bank.index = index
+
+ def get_features(self):
+ rf = chirp_common.RadioFeatures()
+ rf.memory_bounds = (0, 999)
+ rf.valid_tmodes = TMODES
+ rf.valid_duplexes = DUPLEX
+ rf.valid_modes = MODES
+ rf.valid_bands = [(495000, 999990000)]
+ rf.valid_skips = ["", "S", "P"]
+ rf.valid_tuning_steps = TUNING_STEPS
+ rf.valid_name_length = 6
+ rf.has_settings = True
+ rf.has_ctone = True
+ rf.has_bank = True
+ rf.has_bank_index = True
+ rf.has_bank_names = True
+ return rf
+
+ def process_mmap(self):
+ self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+ def get_raw_memory(self, number):
+ return repr(self._memobj.memory[number])
+
+ def get_memory(self, number):
+ bit = 1 << (number % 8)
+ byte = int(number / 8)
+
+ _mem = self._memobj.memory[number]
+ _usd = self._memobj.used[byte]
+ _skp = self._memobj.skips[byte]
+ _psk = self._memobj.pskips[byte]
+
+ mem = chirp_common.Memory()
+ mem.number = number
+
+ if _usd & bit:
+ mem.empty = True
+ return mem
+
+ mem.freq = _mem.freq / 3
+ mem.offset = _mem.offset / 3
+ mem.tmode = TMODES[_mem.tmode]
+ mem.duplex = DUPLEX[_mem.duplex]
+ mem.tuning_step = TUNING_STEPS[_mem.tuning_step]
+ mem.rtone = chirp_common.TONES[_mem.rtone]
+ mem.ctone = chirp_common.TONES[_mem.ctone]
+ mem.mode = MODES[_mem.mode]
+ mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs]
+ mem.dtcs_polarity = DTCS_POLARITY[_mem.dtcs_polarity]
+ mem.name = str(_mem.name).rstrip()
+
+ if _skp & bit:
+ mem.skip = "P" if _psk & bit else "S"
+ else:
+ mem.skip = ""
+
+ return mem
+
+ def set_memory(self, mem):
+ bit = 1 << (mem.number % 8)
+ byte = int(mem.number / 8)
+
+ _mem = self._memobj.memory[mem.number]
+ _usd = self._memobj.used[byte]
+ _skp = self._memobj.skips[byte]
+ _psk = self._memobj.pskips[byte]
+
+ if mem.empty:
+ _usd |= bit
+
+ # We use default value instead of zero-fill
+ # to avoid unexpected behavior.
+ _mem.freq = 15000
+ _mem.offset = 479985000
+ _mem.train_sql = ~0
+ _mem.tmode = ~0
+ _mem.duplex = ~0
+ _mem.train_tone = ~0
+ _mem.tuning_step = ~0
+ _mem.rtone = ~0
+ _mem.ctone = ~0
+ _mem.unknown0 = 0
+ _mem.mode = ~0
+ _mem.dtcs = ~0
+ _mem.unknown1 = ~0
+ _mem.dtcs_polarity = ~0
+ _mem.name = " "
+
+ _skp |= bit
+ _psk |= bit
+
+ else:
+ _usd &= ~bit
+
+ _mem.freq = mem.freq * 3
+ _mem.offset = mem.offset * 3
+ _mem.train_sql = 0 # Train SQL mode (0:off 1:Tone 2:MSK)
+ _mem.tmode = TMODES.index(mem.tmode)
+ _mem.duplex = DUPLEX.index(mem.duplex)
+ _mem.train_tone = 228 # Train SQL Tone (x10Hz)
+ _mem.tuning_step = TUNING_STEPS.index(mem.tuning_step)
+ _mem.rtone = chirp_common.TONES.index(mem.rtone)
+ _mem.ctone = chirp_common.TONES.index(mem.ctone)
+ _mem.unknown0 = 0 # unknown (always zero)
+ _mem.mode = MODES.index(mem.mode)
+ _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs)
+ _mem.unknown1 = ~0 # unknown (always one)
+ _mem.dtcs_polarity = DTCS_POLARITY.index(mem.dtcs_polarity)
+ _mem.name = mem.name.ljust(6)[:6]
+
+ if mem.skip == "S":
+ _skp |= bit
+ _psk &= ~bit
+ elif mem.skip == "P":
+ _skp |= bit
+ _psk |= bit
+ else:
+ _skp &= ~bit
+ _psk &= ~bit
diff --git a/chirp/drivers/icx8x.py b/chirp/drivers/icx8x.py
index 6a5c8b0..4564f89 100644
--- a/chirp/drivers/icx8x.py
+++ b/chirp/drivers/icx8x.py
@@ -75,8 +75,7 @@ class ICx8xRadio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
rf.valid_modes = ["FM", "NFM", "DV"]
rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
rf.valid_duplexes = ["", "-", "+"]
- rf.valid_tuning_steps = [x for x in chirp_common.TUNING_STEPS
- if x != 6.25]
+ rf.valid_tuning_steps = [5., 10., 12.5, 15., 20., 25., 30., 50.]
if self._isuhf:
rf.valid_bands = [(420000000, 470000000)]
else:
diff --git a/chirp/drivers/id880.py b/chirp/drivers/id880.py
index 713258d..c3b8ebb 100644
--- a/chirp/drivers/id880.py
+++ b/chirp/drivers/id880.py
@@ -18,7 +18,9 @@ from chirp import chirp_common, directory, bitwise
MEM_FORMAT = """
struct {
- u24 freq;
+ u24 rxmult:3,
+ txmult:3,
+ freq:18;
u16 offset;
u16 rtone:6,
ctone:6,
@@ -84,6 +86,7 @@ DTCSP = ["NN", "NR", "RN", "RR"]
MODES = ["FM", "NFM", "?2", "AM", "NAM", "DV"]
STEPS = [5.0, 6.25, 8.33, 9.0, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0,
100.0, 125.0, 200.0]
+FREQ_MULTIPLIER = [5000, 6250, 6250, 8333, 9000]
def decode_call(sevenbytes):
@@ -136,28 +139,15 @@ def encode_call(call):
return "".join([chr(x) for x in buf[:7]])
-def _get_freq(_mem):
- val = int(_mem.freq)
+def _decode_freq(freq, mult):
+ return int(freq) * FREQ_MULTIPLIER[mult]
- if val & 0x00200000:
- mult = 6250
- else:
- mult = 5000
- val &= 0x0003FFFF
-
- return (val * mult)
-
-
-def _set_freq(_mem, freq):
- if chirp_common.is_fractional_step(freq):
- mult = 6250
- flag = 0x00200000
- else:
- mult = 5000
- flag = 0x00000000
-
- _mem.freq = (freq / mult) | flag
+def _encode_freq(freq):
+ for i, step in reversed(list(enumerate(FREQ_MULTIPLIER))):
+ if freq % step == 0:
+ return freq / step, i
+ raise ValueError("%d cannot be factored by multiplier table." % freq)
def _wipe_memory(mem, char):
@@ -279,8 +269,8 @@ class ID880Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
mem.empty = True
return mem
- mem.freq = _get_freq(_mem)
- mem.offset = (_mem.offset * 5) * 1000
+ mem.freq = _decode_freq(_mem.freq, _mem.rxmult)
+ mem.offset = _decode_freq(_mem.offset, _mem.txmult)
mem.rtone = chirp_common.TONES[_mem.rtone]
mem.ctone = chirp_common.TONES[_mem.ctone]
mem.tmode = TMODES[_mem.tmode]
@@ -317,8 +307,8 @@ class ID880Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
if was_empty:
_wipe_memory(_mem, "\x00")
- _set_freq(_mem, mem.freq)
- _mem.offset = int((mem.offset / 1000) / 5)
+ _mem.freq, _mem.rxmult = _encode_freq(mem.freq)
+ _mem.offset, _mem.txmult = _encode_freq(mem.offset)
_mem.rtone = chirp_common.TONES.index(mem.rtone)
_mem.ctone = chirp_common.TONES.index(mem.ctone)
_mem.tmode = TMODES.index(mem.tmode)
diff --git a/chirp/drivers/kguv8d.py b/chirp/drivers/kguv8d.py
index bf92856..bd7aa84 100644
--- a/chirp/drivers/kguv8d.py
+++ b/chirp/drivers/kguv8d.py
@@ -589,34 +589,33 @@ class KGUV8DRadio(chirp_common.CloneModeRadio,
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 = tx_mode = None
+ rxtone = txtone = 0xFFFF
+
+ if mem.tmode == "Tone":
+ tx_mode = "Tone"
rx_mode = None
- else:
- tx_mode = rx_mode = mem.tmode
-
- if tx_mode == "DTCS":
- _mem.txtone = mem.tmode != "DTCS" and \
- _set_dcs(mem.dtcs, mem.dtcs_polarity[0]) or \
- _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[0])
- _mem.txtone += 0x4000
- elif tx_mode:
- _mem.txtone = tx_mode == "Tone" and \
- int(mem.rtone * 10) or int(mem.ctone * 10)
- _mem.txtone += 0x8000
- else:
- _mem.txtone = 0
-
- if rx_mode == "DTCS":
- _mem.rxtone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
- _mem.rxtone += 0x4000
- elif rx_mode:
- _mem.rxtone = int(mem.ctone * 10)
- _mem.rxtone += 0x8000
- else:
- _mem.rxtone = 0
+ txtone = int(mem.rtone * 10)
+ elif mem.tmode == "TSQL":
+ rx_mode = tx_mode = "Tone"
+ rxtone = txtone = int(mem.ctone * 10)
+ elif mem.tmode == "DTCS":
+ tx_mode = rx_mode = "DTCS"
+ txtone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
+ rxtone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1])
+ elif mem.tmode == "Cross":
+ tx_mode, rx_mode = mem.cross_mode.split("->")
+ if tx_mode == "DTCS":
+ txtone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
+ elif tx_mode == "Tone":
+ txtone = int(mem.rtone * 10)
+ if rx_mode == "DTCS":
+ rxtone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
+ elif rx_mode == "Tone":
+ rxtone = int(mem.ctone * 10)
+
+ _mem.rxtone = rxtone
+ _mem.txtone = txtone
LOG.debug("Set TX %s (%i) RX %s (%i)" %
(tx_mode, _mem.txtone, rx_mode, _mem.rxtone))
diff --git a/chirp/drivers/kyd.py b/chirp/drivers/kyd.py
index c14af0b..7a44c34 100644
--- a/chirp/drivers/kyd.py
+++ b/chirp/drivers/kyd.py
@@ -358,30 +358,33 @@ class NC630aRadio(chirp_common.CloneModeRadio):
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
+ rx_mode = tx_mode = None
+ rx_tone = 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
+ if mem.tmode == "Tone":
+ tx_mode = "Tone"
+ rx_mode = None
+ tx_tone = int(mem.rtone * 10)
+ elif mem.tmode == "TSQL":
+ rx_mode = tx_mode = "Tone"
+ rx_tone = tx_tone = int(mem.ctone * 10)
+ elif mem.tmode == "DTCS":
+ tx_mode = rx_mode = "DTCS"
+ tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
+ rx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1])
+ elif mem.tmode == "Cross":
+ tx_mode, rx_mode = mem.cross_mode.split("->")
+ if tx_mode == "DTCS":
+ tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
+ elif tx_mode == "Tone":
+ tx_tone = int(mem.rtone * 10)
+ if rx_mode == "DTCS":
+ rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
+ elif rx_mode == "Tone":
+ rx_tone = int(mem.ctone * 10)
+
+ _mem.rx_tone = rx_tone
+ _mem.tx_tone = tx_tone
LOG.debug("Set TX %s (%i) RX %s (%i)" %
(tx_mode, _mem.tx_tone, rx_mode, _mem.rx_tone))
diff --git a/chirp/drivers/kyd_IP620.py b/chirp/drivers/kyd_IP620.py
index 4bff0d6..5200a40 100644
--- a/chirp/drivers/kyd_IP620.py
+++ b/chirp/drivers/kyd_IP620.py
@@ -436,33 +436,36 @@ class IP620Radio(chirp_common.CloneModeRadio,
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
+ rx_mode = tx_mode = None
+ rx_tone = 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))
+ if mem.tmode == "Tone":
+ tx_mode = "Tone"
+ rx_mode = None
+ tx_tone = int(mem.rtone * 10)
+ elif mem.tmode == "TSQL":
+ rx_mode = tx_mode = "Tone"
+ rx_tone = tx_tone = int(mem.ctone * 10)
+ elif mem.tmode == "DTCS":
+ tx_mode = rx_mode = "DTCS"
+ tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
+ rx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1])
+ elif mem.tmode == "Cross":
+ tx_mode, rx_mode = mem.cross_mode.split("->")
+ if tx_mode == "DTCS":
+ tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
+ elif tx_mode == "Tone":
+ tx_tone = int(mem.rtone * 10)
+ if rx_mode == "DTCS":
+ rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
+ elif rx_mode == "Tone":
+ rx_tone = int(mem.ctone * 10)
+
+ _mem.rx_tone = rx_tone
+ _mem.tx_tone = tx_tone
+
+ 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]
diff --git a/chirp/drivers/lt725uv.py b/chirp/drivers/lt725uv.py
index 2f3f757..2940cbb 100644
--- a/chirp/drivers/lt725uv.py
+++ b/chirp/drivers/lt725uv.py
@@ -322,7 +322,7 @@ class LT725UV(chirp_common.CloneModeRadio,
def get_prompts(cls):
rp = chirp_common.RadioPrompts()
rp.experimental = \
- ('The UV-50X3 driver is a beta version.\n'
+ ('The LT725UV driver is a beta version.\n'
'\n'
'Please save an unedited copy of your first successful\n'
'download to a CHIRP Radio Images(*.img) file.'
diff --git a/chirp/drivers/kyd.py b/chirp/drivers/radtel_t18.py
similarity index 50%
copy from chirp/drivers/kyd.py
copy to chirp/drivers/radtel_t18.py
index c14af0b..b5eea5c 100644
--- a/chirp/drivers/kyd.py
+++ b/chirp/drivers/radtel_t18.py
@@ -1,5 +1,4 @@
-# Copyright 2014 Jim Unroe <rock.unroe at gmail.com>
-# Copyright 2014 Dan Smith <dsmith at danplanet.com>
+# Copyright 2017 Jim Unroe <rock.unroe 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
@@ -17,6 +16,7 @@
import time
import os
import struct
+import unittest
import logging
from chirp import chirp_common, directory, memmap
@@ -30,67 +30,67 @@ LOG = logging.getLogger(__name__)
MEM_FORMAT = """
#seekto 0x0010;
struct {
- lbcd rxfreq[4];
- lbcd txfreq[4];
- ul16 rx_tone;
- ul16 tx_tone;
- u8 unknown1:3,
- bcl:2, // Busy Lock
- unknown2:3;
- u8 unknown3:2,
- highpower:1, // Power Level
- wide:1, // Bandwidth
- unknown4:4;
- u8 unknown5[2];
+ 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 0x012F;
+#seekto 0x03C0;
struct {
- u8 voice; // Voice Annunciation
- u8 tot; // Time-out Timer
- u8 totalert; // Time-out Timer Pre-alert
- u8 unknown1[2];
- u8 squelch; // Squelch Level
- u8 save; // Battery Saver
- u8 beep; // Beep
- u8 unknown2[3];
- u8 vox; // VOX Gain
- u8 voxdelay; // VOX Delay
+ 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;
-
-#seekto 0x017E;
-u8 skipflags[2]; // SCAN_ADD
"""
CMD_ACK = "\x06"
+BLOCK_SIZE = 0x08
-NC630A_POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1.00),
- chirp_common.PowerLevel("High", watts=5.00)]
-
-NC630A_DTCS = sorted(chirp_common.DTCS_CODES + [645])
-
-BCL_LIST = ["Off", "Carrier", "QT/DQT"]
-TIMEOUTTIMER_LIST = [""] + ["%s seconds" % x for x in range(15, 615, 15)]
-TOTALERT_LIST = ["", "Off"] + ["%s seconds" % x for x in range(1, 11)]
VOICE_LIST = ["Off", "Chinese", "English"]
-VOX_LIST = ["OFF"] + ["%s" % x for x in range(1, 17)]
-VOXDELAY_LIST = ["0.3", "0.5", "1.0", "1.5", "2.0", "3.0"]
+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 = {
- "bcl": BCL_LIST,
- "tot": TIMEOUTTIMER_LIST,
- "totalert": TOTALERT_LIST,
"voice": VOICE_LIST,
- "vox": VOX_LIST,
- "voxdelay": VOXDELAY_LIST,
- }
+ "timeouttimer": TIMEOUTTIMER_LIST,
+ "scanmode": SCANMODE_LIST,
+ "voxlevel": VOXLEVEL_LIST,
+ "voxdelay": VOXDELAY_LIST
+}
-def _nc630a_enter_programming_mode(radio):
+def _t18_enter_programming_mode(radio):
serial = radio.pipe
try:
- serial.write("PROGRAM")
+ serial.write("\x02")
+ time.sleep(0.1)
+ serial.write("1ROGRAM")
ack = serial.read(1)
except:
raise errors.RadioError("Error communicating with radio")
@@ -106,7 +106,7 @@ def _nc630a_enter_programming_mode(radio):
except:
raise errors.RadioError("Error communicating with radio")
- if not ident.startswith(radio._fileid):
+ if not ident.startswith("SMP558"):
LOG.debug(util.hexprint(ident))
raise errors.RadioError("Radio returned unknown identification string")
@@ -119,17 +119,44 @@ def _nc630a_enter_programming_mode(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 _nc630a_read_block(radio, block_addr, block_size):
+def _t18_exit_programming_mode(radio):
serial = radio.pipe
+ try:
+ serial.write("b")
+ except:
+ raise errors.RadioError("Radio refused to exit programming mode")
- cmd = struct.pack(">cHb", 'R', block_addr, block_size)
+
+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)
+ response = serial.read(4 + BLOCK_SIZE)
if response[:4] != expectedresponse:
raise Exception("Error reading block %04x." % (block_addr))
@@ -146,11 +173,11 @@ def _nc630a_read_block(radio, block_addr, block_size):
return block_data
-def _nc630a_write_block(radio, block_addr, block_size):
+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 + block_size]
+ 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))
@@ -166,7 +193,7 @@ def _nc630a_write_block(radio, block_addr, block_size):
def do_download(radio):
LOG.debug("download")
- _nc630a_enter_programming_mode(radio)
+ _t18_enter_programming_mode(radio)
data = ""
@@ -176,16 +203,18 @@ def do_download(radio):
status.cur = 0
status.max = radio._memsize
- for addr in range(0, radio._memsize, radio._block_size):
- status.cur = addr + radio._block_size
+ for addr in range(0, radio._memsize, BLOCK_SIZE):
+ status.cur = addr + BLOCK_SIZE
radio.status_fn(status)
- block = _nc630a_read_block(radio, addr, radio._block_size)
+ 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)
@@ -193,57 +222,66 @@ def do_upload(radio):
status = chirp_common.Status()
status.msg = "Uploading to radio"
- _nc630a_enter_programming_mode(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, radio._block_size):
- status.cur = addr + radio._block_size
+ for addr in range(start_addr, end_addr, BLOCK_SIZE):
+ status.cur = addr + BLOCK_SIZE
radio.status_fn(status)
- _nc630a_write_block(radio, addr, radio._block_size)
+ _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"""
-class MT700Alias(chirp_common.Alias):
- VENDOR = "Plant-Tours"
- MODEL = "MT-700"
+ if len(data) == cls._memsize:
+ rid = data[0x03D0:0x03D8]
+ return "P558" in rid
+ else:
+ return False
@directory.register
-class NC630aRadio(chirp_common.CloneModeRadio):
- """KYD NC-630A"""
- VENDOR = "KYD"
- MODEL = "NC-630A"
- ALIASES = [MT700Alias]
+class T18Radio(chirp_common.CloneModeRadio):
+ """radtel T18"""
+ VENDOR = "Radtel"
+ MODEL = "T18"
BAUD_RATE = 9600
_ranges = [
- (0x0000, 0x0330),
- ]
- _memsize = 0x03C8
- _block_size = 0x08
- _fileid = "P32073"
+ (0x0000, 0x03F0),
+ ]
+ _memsize = 0x03F0
def get_features(self):
rf = chirp_common.RadioFeatures()
rf.has_settings = True
- rf.has_bank = False
+ 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.has_rx_dtcs = True
+ rf.valid_cross_modes = [
+ "Tone->Tone",
+ "DTCS->",
+ "->DTCS",
+ "Tone->DTCS",
+ "DTCS->Tone",
+ "->Tone",
+ "DTCS->DTCS"]
rf.has_tuning_step = False
- rf.can_odd_split = True
+ rf.has_bank = False
rf.has_name = False
- rf.valid_skips = ["", "S"]
- rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
- rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
- "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
- rf.valid_power_levels = NC630A_POWER_LEVELS
- rf.valid_duplexes = ["", "-", "+", "split", "off"]
- rf.valid_modes = ["NFM", "FM"] # 12.5 KHz, 25 kHz.
rf.memory_bounds = (1, 16)
- rf.valid_bands = [(400000000, 520000000)]
+ rf.valid_bands = [(400000000, 470000000)]
return rf
@@ -260,56 +298,32 @@ class NC630aRadio(chirp_common.CloneModeRadio):
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"
+ 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:
- 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"
+ 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:
- 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))
+ raise Exception("Internal error: invalid mode `%s'" % mode)
def get_memory(self, number):
- bitpos = (1 << ((number - 1) % 8))
- bytepos = ((number - 1) / 8)
- LOG.debug("bitpos %s" % bitpos)
- LOG.debug("bytepos %s" % bytepos)
-
_mem = self._memobj.memory[number - 1]
- _skp = self._memobj.skipflags[bytepos]
mem = chirp_common.Memory()
@@ -326,81 +340,45 @@ class NC630aRadio(chirp_common.CloneModeRadio):
mem.empty = True
return mem
- if int(_mem.rxfreq) == int(_mem.txfreq):
+ 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 = _mem.wide and "FM" or "NFM"
-
- self._get_tone(_mem, mem)
+ mem.mode = not _mem.narrow and "FM" or "NFM"
- mem.power = NC630A_POWER_LEVELS[_mem.highpower]
+ mem.skip = _mem.skip and "S" or ""
- mem.skip = "" if (_skp & bitpos) else "S"
- LOG.debug("mem.skip %s" % mem.skip)
+ 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",
- RadioSettingValueList(
- BCL_LIST, BCL_LIST[_mem.bcl]))
+ 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_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):
- bitpos = (1 << ((mem.number - 1) % 8))
- bytepos = ((mem.number - 1) / 8)
- LOG.debug("bitpos %s" % bitpos)
- LOG.debug("bytepos %s" % bytepos)
-
+ # Get a low-level memory object mapped to the image
_mem = self._memobj.memory[mem.number - 1]
- _skp = self._memobj.skipflags[bytepos]
if mem.empty:
- _mem.set_raw("\xFF" * 16)
+ _mem.set_raw("\xFF" * (_mem.size() / 8))
return
- _mem.set_raw("\x00" * 14 + "\xFF" * 2)
-
_mem.rxfreq = mem.freq / 10
if mem.duplex == "off":
@@ -415,64 +393,64 @@ class NC630aRadio(chirp_common.CloneModeRadio):
else:
_mem.txfreq = mem.freq / 10
- _mem.wide = mem.mode == "FM"
+ txtone, rxtone = chirp_common.split_tone_encode(mem)
+ self._encode_tone(_mem.txtone, *txtone)
+ self._encode_tone(_mem.rxtone, *rxtone)
- self._set_tone(mem, _mem)
-
- _mem.highpower = mem.power == NC630A_POWER_LEVELS[1]
-
- if mem.skip != "S":
- _skp |= bitpos
- else:
- _skp &= ~bitpos
- LOG.debug("_skp %s" % _skp)
+ _mem.narrow = 'N' in mem.mode
+ _mem.skip = mem.skip == "S"
for setting in mem.extra:
- setattr(_mem, setting.get_name(), setting.value)
+ # 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("tot", "Time-out timer",
- RadioSettingValueList(
- TIMEOUTTIMER_LIST,
- TIMEOUTTIMER_LIST[_settings.tot]))
+ rs = RadioSetting("squelchlevel", "Squelch level",
+ RadioSettingValueInteger(
+ 0, 9, _settings.squelchlevel))
basic.append(rs)
- rs = RadioSetting("totalert", "TOT Pre-alert",
+ rs = RadioSetting("timeouttimer", "Timeout timer",
RadioSettingValueList(
- TOTALERT_LIST,
- TOTALERT_LIST[_settings.totalert]))
+ TIMEOUTTIMER_LIST,
+ TIMEOUTTIMER_LIST[
+ _settings.timeouttimer]))
basic.append(rs)
- rs = RadioSetting("vox", "VOX Gain",
+ rs = RadioSetting("scanmode", "Scan mode",
RadioSettingValueList(
- VOX_LIST, VOX_LIST[_settings.vox]))
+ SCANMODE_LIST,
+ SCANMODE_LIST[_settings.scanmode]))
basic.append(rs)
- rs = RadioSetting("voice", "Voice Annumciation",
+ rs = RadioSetting("voiceprompt", "Voice prompt",
RadioSettingValueList(
- VOICE_LIST, VOICE_LIST[_settings.voice]))
+ VOICE_LIST,
+ VOICE_LIST[_settings.voiceprompt]))
basic.append(rs)
- rs = RadioSetting("squelch", "Squelch Level",
- RadioSettingValueInteger(0, 9, _settings.squelch))
+ rs = RadioSetting("voxlevel", "Vox level",
+ RadioSettingValueList(
+ VOXLEVEL_LIST,
+ VOXLEVEL_LIST[_settings.voxlevel]))
basic.append(rs)
- rs = RadioSetting("voxdelay", "VOX Delay",
+ rs = RadioSetting("voxdelay", "VOX delay",
RadioSettingValueList(
VOXDELAY_LIST,
VOXDELAY_LIST[_settings.voxdelay]))
basic.append(rs)
- rs = RadioSetting("beep", "Beep",
- RadioSettingValueBoolean(_settings.beep))
+ rs = RadioSetting("batterysaver", "Battery saver",
+ RadioSettingValueBoolean(_settings.batterysaver))
basic.append(rs)
- rs = RadioSetting("save", "Battery Saver",
- RadioSettingValueBoolean(_settings.save))
+ rs = RadioSetting("beep", "Beep",
+ RadioSettingValueBoolean(_settings.beep))
basic.append(rs)
return top
@@ -494,23 +472,28 @@ class NC630aRadio(chirp_common.CloneModeRadio):
obj = self._memobj.settings
setting = element.get_name()
- LOG.debug("Setting %s = %s" % (setting, element.value))
- setattr(obj, setting, element.value)
+ 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 = match_model = False
+ match_size = False
+ match_model = False
# testing the file data size
- if len(filedata) in [0x338, 0x3C8]:
+ if len(filedata) == cls._memsize:
match_size = True
- # testing model fingerprint
- if filedata[0x01B8:0x01BE] == cls._fileid:
- match_model = True
+ # testing the model fingerprint
+ match_model = model_match(cls, filedata)
if match_size and match_model:
return True
diff --git a/chirp/drivers/retevis_rt21.py b/chirp/drivers/retevis_rt21.py
index 29f83c7..326fb03 100644
--- a/chirp/drivers/retevis_rt21.py
+++ b/chirp/drivers/retevis_rt21.py
@@ -386,30 +386,33 @@ class RT21Radio(chirp_common.CloneModeRadio):
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
+ rx_mode = tx_mode = None
+ rx_tone = 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
+ if mem.tmode == "Tone":
+ tx_mode = "Tone"
+ rx_mode = None
+ tx_tone = int(mem.rtone * 10)
+ elif mem.tmode == "TSQL":
+ rx_mode = tx_mode = "Tone"
+ rx_tone = tx_tone = int(mem.ctone * 10)
+ elif mem.tmode == "DTCS":
+ tx_mode = rx_mode = "DTCS"
+ tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
+ rx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1])
+ elif mem.tmode == "Cross":
+ tx_mode, rx_mode = mem.cross_mode.split("->")
+ if tx_mode == "DTCS":
+ tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
+ elif tx_mode == "Tone":
+ tx_tone = int(mem.rtone * 10)
+ if rx_mode == "DTCS":
+ rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
+ elif rx_mode == "Tone":
+ rx_tone = int(mem.ctone * 10)
+
+ _mem.rx_tone = rx_tone
+ _mem.tx_tone = tx_tone
LOG.debug("Set TX %s (%i) RX %s (%i)" %
(tx_mode, _mem.tx_tone, rx_mode, _mem.rx_tone))
diff --git a/chirp/drivers/retevis_rt22.py b/chirp/drivers/retevis_rt22.py
index 9c0e2aa..0aa1370 100644
--- a/chirp/drivers/retevis_rt22.py
+++ b/chirp/drivers/retevis_rt22.py
@@ -436,30 +436,33 @@ class RT22Radio(chirp_common.CloneModeRadio):
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
+ rx_mode = tx_mode = None
+ rx_tone = 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
+ if mem.tmode == "Tone":
+ tx_mode = "Tone"
+ rx_mode = None
+ tx_tone = int(mem.rtone * 10)
+ elif mem.tmode == "TSQL":
+ rx_mode = tx_mode = "Tone"
+ rx_tone = tx_tone = int(mem.ctone * 10)
+ elif mem.tmode == "DTCS":
+ tx_mode = rx_mode = "DTCS"
+ tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
+ rx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1])
+ elif mem.tmode == "Cross":
+ tx_mode, rx_mode = mem.cross_mode.split("->")
+ if tx_mode == "DTCS":
+ tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
+ elif tx_mode == "Tone":
+ tx_tone = int(mem.rtone * 10)
+ if rx_mode == "DTCS":
+ rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
+ elif rx_mode == "Tone":
+ rx_tone = int(mem.ctone * 10)
+
+ _mem.rx_tone = rx_tone
+ _mem.tx_tone = tx_tone
LOG.debug("Set TX %s (%i) RX %s (%i)" %
(tx_mode, _mem.tx_tone, rx_mode, _mem.rx_tone))
@@ -630,3 +633,8 @@ class LT316(RT22Radio):
"""Luiton LT-316"""
VENDOR = "LUITON"
MODEL = "LT-316"
+
+ at directory.register
+class TDM8(RT22Radio):
+ VENDOR = "TID"
+ MODEL = "TD-M8"
diff --git a/chirp/drivers/retevis_rt23.py b/chirp/drivers/retevis_rt23.py
new file mode 100644
index 0000000..0ccb088
--- /dev/null
+++ b/chirp/drivers/retevis_rt23.py
@@ -0,0 +1,867 @@
+# Copyright 2017 Jim Unroe <rock.unroe 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
+# 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 re
+import logging
+
+from chirp import chirp_common, directory, memmap
+from chirp import bitwise, errors, util
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+ RadioSettingValueInteger, RadioSettingValueList, \
+ RadioSettingValueBoolean, RadioSettingValueString, \
+ RadioSettings
+
+LOG = logging.getLogger(__name__)
+
+MEM_FORMAT = """
+struct memory {
+ lbcd rxfreq[4];
+ lbcd txfreq[4];
+ lbcd rxtone[2];
+ lbcd txtone[2];
+ u8 unknown1;
+ u8 pttid:2, // PTT-ID
+ unknown2:1,
+ signaling:1, // Signaling(ANI)
+ unknown3:1,
+ bcl:1, // Busy Channel Lockout
+ unknown4:2;
+ u8 unknown5:3,
+ highpower:1, // Power Level
+ isnarrow:1, // Bandwidth
+ scan:1, // Scan Add
+ unknown6:2;
+ u8 unknown7;
+};
+
+#seekto 0x0010;
+struct memory channels[128];
+
+#seekto 0x0810;
+struct memory vfo_a;
+struct memory vfo_b;
+
+#seekto 0x0830;
+struct {
+ u8 unknown_0830_1:4,
+ color:2, // Background Color
+ dst:1, // DTMF Side Tone
+ txsel:1; // Priority TX Channel Select
+ u8 scans:2, // Scan Mode
+ unknown_0831:1,
+ autolk:1, // Auto Key Lock
+ save:1, // Battery Save
+ beep:1, // Key Beep
+ voice:2; // Voice Prompt
+ u8 vfomr_fm:1, // FM Radio Display Mode
+ led:2, // Background Light
+ unknown_0832_2:1,
+ dw:1, // FM Radio Dual Watch
+ name:1, // Display Names
+ vfomr_a:2; // Display Mode A
+ u8 opnset:2, // Power On Message
+ unknown_0833_1:3,
+ dwait:1, // Dual Standby
+ vfomr_b:2; // Display Mode B
+ u8 mrcha; // mr a ch num
+ u8 mrchb; // mr b ch num
+ u8 fmch; // fm radio ch num
+ u8 unknown_0837_1:1,
+ ste:1, // Squelch Tail Eliminate
+ roger:1, // Roger Beep
+ unknown_0837_2:1,
+ vox:4; // VOX
+ u8 step:4, // Step
+ unknown_0838_1:4;
+ u8 squelch; // Squelch
+ u8 tot; // Time Out Timer
+ u8 rptmod:1, // Repeater Mode
+ volmod:2, // Volume Mode
+ rptptt:1, // Repeater PTT Switch
+ rptspk:1, // Repeater Speaker
+ relay:3; // Cross Band Repeater Enable
+ u8 unknown_083C:4, // 0x083C
+ rptrl:4; // Repeater TX Delay
+ u8 pf1:4, // Function Key 1
+ pf2:4; // Function Key 2
+ u8 vot; // VOX Delay Time
+} settings;
+
+#seekto 0x0848;
+struct {
+ char line1[7];
+} poweron_msg;
+
+struct limit {
+ bbcd lower[2];
+ bbcd upper[2];
+};
+
+#seekto 0x0850;
+struct {
+ struct limit vhf;
+ struct limit uhf;
+} limits;
+
+#seekto 0x08D0;
+struct {
+ char name[7];
+ u8 unknown2[1];
+} names[128];
+
+#seekto 0x0D20;
+u8 usedflags[16];
+u8 scanflags[16];
+
+#seekto 0x0FA0;
+struct {
+ u8 unknown_0FA0_1:4,
+ dispab:1, // select a/b
+ unknown_0FA0_2:3;
+} settings2;
+"""
+
+CMD_ACK = "\x06"
+BLOCK_SIZE = 0x10
+
+RT23_POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1.00),
+ chirp_common.PowerLevel("High", watts=2.50)]
+
+
+RT23_DTCS = sorted(chirp_common.DTCS_CODES +
+ [17, 50, 55, 135, 217, 254, 305, 645, 765])
+
+RT23_CHARSET = chirp_common.CHARSET_UPPER_NUMERIC + \
+ ":;<=>?@ !\"#$%&'()*+,-./"
+
+LIST_COLOR = ["Blue", "Orange", "Purple"]
+LIST_LED = ["Off", "On", "Auto"]
+LIST_OPNSET = ["Full", "Voltage", "Message"]
+LIST_PFKEY = [
+ "Radio",
+ "Sub-channel Sent",
+ "Scan",
+ "Alarm",
+ "DTMF",
+ "Squelch Off Momentarily",
+ "Battery Power Indicator",
+ "Tone 1750",
+ "Tone 2100",
+ "Tone 1000",
+ "Tone 1450"]
+LIST_PTTID = ["Off", "BOT", "EOT", "Both"]
+LIST_RPTMOD = ["Single", "Double"]
+LIST_RPTRL = ["0.5S", "1.0S", "1.5S", "2.0S", "2.5S", "3.0S", "3.5S", "4.0S",
+ "4.5S"]
+LIST_SCANS = ["Time Operated", "Carrier Operated", "Search"]
+LIST_SIGNALING = ["No", "DTMF"]
+LIST_TOT = ["OFF"] + ["%s seconds" % x for x in range(30, 300, 30)]
+LIST_TXSEL = ["Edit", "Busy"]
+LIST_STEP = ["2.50K", "5.00K", "6.25K", "10.00K", "12,50K", "20.00K", "25.00K",
+ "50.00K"]
+LIST_VFOMR = ["VFO", "MR(Frequency)", "MR(Channel #/Name)"]
+LIST_VFOMRFM = ["VFO", "Channel"]
+LIST_VOICE = ["Off", "Chinese", "English"]
+LIST_VOLMOD = ["Off", "Sub", "Main"]
+LIST_VOT = ["0.5S", "1.0S", "1.5S", "2.0S", "3.0S"]
+LIST_VOX = ["OFF"] + ["%s" % x for x in range(1, 6)]
+
+
+def _rt23_enter_programming_mode(radio):
+ serial = radio.pipe
+
+ magic = "PROIUAM"
+ exito = False
+ for i in range(0, 5):
+ for j in range(0, len(magic)):
+ time.sleep(0.005)
+ serial.write(magic[j])
+ ack = serial.read(1)
+
+ try:
+ if ack == CMD_ACK:
+ exito = True
+ break
+ except:
+ LOG.debug("Attempt #%s, failed, trying again" % i)
+ pass
+
+ # check if we had EXITO
+ if exito is False:
+ msg = "The radio did not accept program mode after five tries.\n"
+ msg += "Check you interface cable and power cycle your radio."
+ raise errors.RadioError(msg)
+
+ try:
+ serial.write("\x02")
+ ident = serial.read(8)
+ except:
+ raise errors.RadioError("Error communicating with radio")
+
+ if not ident.startswith("P31183"):
+ 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")
+
+
+def _rt23_exit_programming_mode(radio):
+ serial = radio.pipe
+ try:
+ serial.write("E")
+ except:
+ raise errors.RadioError("Radio refused to exit programming mode")
+
+
+def _rt23_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 + 1)
+ if response[:4] != expectedresponse:
+ raise Exception("Error reading block %04x." % (block_addr))
+
+ chunk = response[4:]
+
+ cs = 0
+ for byte in chunk[:-1]:
+ cs += ord(byte)
+ if ord(chunk[-1]) != (cs & 0xFF):
+ raise Exception("Block failed checksum!")
+
+ block_data = chunk[:-1]
+ except:
+ raise errors.RadioError("Failed to read block at %04x" % block_addr)
+
+ return block_data
+
+
+def _rt23_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 + BLOCK_SIZE]
+ cs = 0
+ for byte in data:
+ cs += ord(byte)
+ data += chr(cs & 0xFF)
+
+ 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")
+ _rt23_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 = _rt23_read_block(radio, addr, BLOCK_SIZE)
+ if addr == 0 and block.startswith("\xFF" * 6):
+ block = "P31183" + "\xFF" * 10
+ data += block
+
+ LOG.debug("Address: %04x" % addr)
+ LOG.debug(util.hexprint(block))
+
+ _rt23_exit_programming_mode(radio)
+
+ return memmap.MemoryMap(data)
+
+
+def do_upload(radio):
+ status = chirp_common.Status()
+ status.msg = "Uploading to radio"
+
+ _rt23_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)
+ _rt23_write_block(radio, addr, BLOCK_SIZE)
+
+
+def model_match(cls, data):
+ """Match the opened/downloaded image to the correct version"""
+
+ if len(data) == 0x1000:
+ rid = data[0x0000:0x0006]
+ return rid == "P31183"
+ else:
+ return False
+
+
+def _split(rf, f1, f2):
+ """Returns False if the two freqs are in the same band (no split)
+ or True otherwise"""
+
+ # determine if the two freqs are in the same band
+ for low, high in rf.valid_bands:
+ if f1 >= low and f1 <= high and \
+ f2 >= low and f2 <= high:
+ # if the two freqs are on the same Band this is not a split
+ return False
+
+ # if you get here is because the freq pairs are split
+ return True
+
+
+ at directory.register
+class RT23Radio(chirp_common.CloneModeRadio):
+ """RETEVIS RT23"""
+ VENDOR = "Retevis"
+ MODEL = "RT23"
+ BAUD_RATE = 9600
+
+ _ranges = [
+ (0x0000, 0x0EC0),
+ ]
+ _memsize = 0x1000
+
+ def get_features(self):
+ rf = chirp_common.RadioFeatures()
+ rf.has_settings = True
+ rf.has_bank = False
+ rf.has_ctone = True
+ rf.has_cross = True
+ rf.has_rx_dtcs = True
+ rf.has_tuning_step = False
+ rf.can_odd_split = True
+ rf.valid_name_length = 7
+ rf.valid_characters = RT23_CHARSET
+ rf.has_name = True
+ rf.valid_skips = ["", "S"]
+ rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
+ rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
+ "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
+ rf.valid_power_levels = RT23_POWER_LEVELS
+ rf.valid_duplexes = ["", "-", "+", "split", "off"]
+ rf.valid_modes = ["FM", "NFM"] # 25 KHz, 12.5 KHz.
+ rf.memory_bounds = (1, 128)
+ rf.valid_bands = [
+ (136000000, 174000000),
+ (400000000, 480000000)]
+
+ return rf
+
+ def process_mmap(self):
+ self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+ def sync_in(self):
+ """Download from radio"""
+ try:
+ data = do_download(self)
+ except errors.RadioError:
+ # Pass through any real errors we raise
+ raise
+ except:
+ # If anything unexpected happens, make sure we raise
+ # a RadioError and log the problem
+ LOG.exception('Unexpected error during download')
+ raise errors.RadioError('Unexpected error communicating '
+ 'with the radio')
+ self._mmap = data
+ self.process_mmap()
+
+ def sync_out(self):
+ """Upload to radio"""
+ try:
+ do_upload(self)
+ except:
+ # If anything unexpected happens, make sure we raise
+ # a RadioError and log the problem
+ LOG.exception('Unexpected error during upload')
+ raise errors.RadioError('Unexpected error communicating '
+ 'with the radio')
+
+ def get_raw_memory(self, number):
+ return repr(self._memobj.memory[number - 1])
+
+ 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_memory(self, number):
+ mem = chirp_common.Memory()
+ _mem = self._memobj.channels[number-1]
+ _nam = self._memobj.names[number - 1]
+ mem.number = number
+ bitpos = (1 << ((number - 1) % 8))
+ bytepos = ((number - 1) / 8)
+ _scn = self._memobj.scanflags[bytepos]
+ _usd = self._memobj.usedflags[bytepos]
+ isused = bitpos & int(_usd)
+ isscan = bitpos & int(_scn)
+
+ if not isused:
+ mem.empty = True
+ return mem
+
+ 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.empty = True
+ return mem
+
+ if _mem.get_raw() == ("\xFF" * 16):
+ LOG.debug("Initializing empty memory")
+ _mem.set_raw("\x00" * 16)
+
+ # 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 freq set
+ offset = (int(_mem.txfreq) * 10) - mem.freq
+ if offset != 0:
+ if _split(self.get_features(), mem.freq, int(_mem.txfreq) * 10):
+ mem.duplex = "split"
+ mem.offset = int(_mem.txfreq) * 10
+ elif offset < 0:
+ mem.offset = abs(offset)
+ mem.duplex = "-"
+ elif offset > 0:
+ mem.offset = offset
+ mem.duplex = "+"
+ else:
+ mem.offset = 0
+
+ for char in _nam.name:
+ if str(char) == "\xFF":
+ char = " "
+ mem.name += str(char)
+ mem.name = mem.name.rstrip()
+
+ mem.mode = _mem.isnarrow and "NFM" or "FM"
+
+ rxtone = txtone = None
+ txtone = self.decode_tone(_mem.txtone)
+ rxtone = self.decode_tone(_mem.rxtone)
+ chirp_common.split_tone_decode(mem, txtone, rxtone)
+
+ mem.power = RT23_POWER_LEVELS[_mem.highpower]
+
+ if not isscan:
+ mem.skip = "S"
+
+ mem.extra = RadioSettingGroup("Extra", "extra")
+
+ rs = RadioSetting("bcl", "BCL",
+ RadioSettingValueBoolean(_mem.bcl))
+ mem.extra.append(rs)
+
+ rs = RadioSetting("pttid", "PTT ID",
+ RadioSettingValueList(
+ LIST_PTTID, LIST_PTTID[_mem.pttid]))
+ mem.extra.append(rs)
+
+ rs = RadioSetting("signaling", "Optional Signaling",
+ RadioSettingValueList(LIST_SIGNALING,
+ LIST_SIGNALING[_mem.signaling]))
+ mem.extra.append(rs)
+
+ return mem
+
+ def set_memory(self, mem):
+ LOG.debug("Setting %i(%s)" % (mem.number, mem.extd_number))
+ _mem = self._memobj.channels[mem.number - 1]
+ _nam = self._memobj.names[mem.number - 1]
+ bitpos = (1 << ((mem.number - 1) % 8))
+ bytepos = ((mem.number - 1) / 8)
+ _scn = self._memobj.scanflags[bytepos]
+ _usd = self._memobj.usedflags[bytepos]
+
+ if mem.empty:
+ _mem.set_raw("\xFF" * 16)
+ _nam.name = ("\xFF" * 7)
+ _usd &= ~bitpos
+ _scn &= ~bitpos
+ return
+ else:
+ _usd |= bitpos
+
+ if _mem.get_raw() == ("\xFF" * 16):
+ LOG.debug("Initializing empty memory")
+ _mem.set_raw("\x00" * 16)
+ _scn |= bitpos
+
+ _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
+
+ _namelength = self.get_features().valid_name_length
+ for i in range(_namelength):
+ try:
+ _nam.name[i] = mem.name[i]
+ except IndexError:
+ _nam.name[i] = "\xFF"
+
+ _mem.scan = mem.skip != "S"
+ if mem.skip == "S":
+ _scn &= ~bitpos
+ else:
+ _scn |= bitpos
+ _mem.isnarrow = mem.mode == "NFM"
+
+ ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
+ chirp_common.split_tone_encode(mem)
+ self.encode_tone(_mem.txtone, txmode, txtone, txpol)
+ self.encode_tone(_mem.rxtone, rxmode, rxtone, rxpol)
+
+ _mem.highpower = mem.power == RT23_POWER_LEVELS[1]
+
+ for setting in mem.extra:
+ setattr(_mem, setting.get_name(), setting.value)
+
+ def get_settings(self):
+ _settings = self._memobj.settings
+ _mem = self._memobj
+ basic = RadioSettingGroup("basic", "Basic Settings")
+ advanced = RadioSettingGroup("advanced", "Advanced Settings")
+ other = RadioSettingGroup("other", "Other Settings")
+ workmode = RadioSettingGroup("workmode", "Workmode Settings")
+ fmradio = RadioSettingGroup("fmradio", "FM Radio Settings")
+ top = RadioSettings(basic, advanced, other, workmode, fmradio)
+
+ save = RadioSetting("save", "Battery Saver",
+ RadioSettingValueBoolean(_settings.save))
+ basic.append(save)
+
+ vox = RadioSetting("vox", "VOX Gain",
+ RadioSettingValueList(
+ LIST_VOX, LIST_VOX[_settings.vox]))
+ basic.append(vox)
+
+ squelch = RadioSetting("squelch", "Squelch Level",
+ RadioSettingValueInteger(
+ 0, 9, _settings.squelch))
+ basic.append(squelch)
+
+ relay = RadioSetting("relay", "Repeater",
+ RadioSettingValueBoolean(_settings.relay))
+ basic.append(relay)
+
+ tot = RadioSetting("tot", "Time-out timer", RadioSettingValueList(
+ LIST_TOT, LIST_TOT[_settings.tot]))
+ basic.append(tot)
+
+ beep = RadioSetting("beep", "Key Beep",
+ RadioSettingValueBoolean(_settings.beep))
+ basic.append(beep)
+
+ color = RadioSetting("color", "Background Color", RadioSettingValueList(
+ LIST_COLOR, LIST_COLOR[_settings.color - 1]))
+ basic.append(color)
+
+ vot = RadioSetting("vot", "VOX Delay Time", RadioSettingValueList(
+ LIST_VOT, LIST_VOT[_settings.vot]))
+ basic.append(vot)
+
+ dwait = RadioSetting("dwait", "Dual Standby",
+ RadioSettingValueBoolean(_settings.dwait))
+ basic.append(dwait)
+
+ led = RadioSetting("led", "Background Light", RadioSettingValueList(
+ LIST_LED, LIST_LED[_settings.led]))
+ basic.append(led)
+
+ voice = RadioSetting("voice", "Voice Prompt", RadioSettingValueList(
+ LIST_VOICE, LIST_VOICE[_settings.voice]))
+ basic.append(voice)
+
+ roger = RadioSetting("roger", "Roger Beep",
+ RadioSettingValueBoolean(_settings.roger))
+ basic.append(roger)
+
+ autolk = RadioSetting("autolk", "Auto Key Lock",
+ RadioSettingValueBoolean(_settings.autolk))
+ basic.append(autolk)
+
+ opnset = RadioSetting("opnset", "Open Mode Set",
+ RadioSettingValueList(
+ LIST_OPNSET, LIST_OPNSET[_settings.opnset]))
+ basic.append(opnset)
+
+ def _filter(name):
+ filtered = ""
+ for char in str(name):
+ if char in chirp_common.CHARSET_ASCII:
+ filtered += char
+ else:
+ filtered += " "
+ return filtered
+
+ _msg = self._memobj.poweron_msg
+ ponmsg = RadioSetting("poweron_msg.line1", "Power-On Message",
+ RadioSettingValueString(
+ 0, 7, _filter(_msg.line1)))
+ basic.append(ponmsg)
+
+
+ scans = RadioSetting("scans", "Scan Mode", RadioSettingValueList(
+ LIST_SCANS, LIST_SCANS[_settings.scans]))
+ basic.append(scans)
+
+ dw = RadioSetting("dw", "FM Radio Dual Watch",
+ RadioSettingValueBoolean(_settings.dw))
+ basic.append(dw)
+
+ name = RadioSetting("name", "Display Names",
+ RadioSettingValueBoolean(_settings.name))
+ basic.append(name)
+
+ rptrl = RadioSetting("rptrl", "Repeater TX Delay",
+ RadioSettingValueList(LIST_RPTRL, LIST_RPTRL[
+ _settings.rptrl]))
+ basic.append(rptrl)
+
+ rptspk = RadioSetting("rptspk", "Repeater Speaker",
+ RadioSettingValueBoolean(_settings.rptspk))
+ basic.append(rptspk)
+
+ rptptt = RadioSetting("rptptt", "Repeater PTT Switch",
+ RadioSettingValueBoolean(_settings.rptptt))
+ basic.append(rptptt)
+
+ rptmod = RadioSetting("rptmod", "Repeater Mode",
+ RadioSettingValueList(
+ LIST_RPTMOD, LIST_RPTMOD[_settings.rptmod]))
+ basic.append(rptmod)
+
+ volmod = RadioSetting("volmod", "Volume Mode",
+ RadioSettingValueList(
+ LIST_VOLMOD, LIST_VOLMOD[_settings.volmod]))
+ basic.append(volmod)
+
+ dst = RadioSetting("dst", "DTMF Side Tone",
+ RadioSettingValueBoolean(_settings.dst))
+ basic.append(dst)
+
+ txsel = RadioSetting("txsel", "Priority TX Channel",
+ RadioSettingValueList(
+ LIST_TXSEL, LIST_TXSEL[_settings.txsel]))
+ basic.append(txsel)
+
+ ste = RadioSetting("ste", "Squelch Tail Eliminate",
+ RadioSettingValueBoolean(_settings.ste))
+ basic.append(ste)
+
+ #advanced
+ if _settings.pf1 > 0x0A:
+ val = 0x00
+ else:
+ val = _settings.pf1
+ pf1 = RadioSetting("pf1", "PF1 Key",
+ RadioSettingValueList(
+ LIST_PFKEY, LIST_PFKEY[val]))
+ advanced.append(pf1)
+
+ if _settings.pf2 > 0x0A:
+ val = 0x00
+ else:
+ val = _settings.pf2
+ pf2 = RadioSetting("pf2", "PF2 Key",
+ RadioSettingValueList(
+ LIST_PFKEY, LIST_PFKEY[val]))
+ advanced.append(pf2)
+
+ # other
+ _limit = str(int(_mem.limits.vhf.lower) / 10)
+ val = RadioSettingValueString(0, 3, _limit)
+ val.set_mutable(False)
+ rs = RadioSetting("limits.vhf.lower", "VHF low", val)
+ other.append(rs)
+
+ _limit = str(int(_mem.limits.vhf.upper) / 10)
+ val = RadioSettingValueString(0, 3, _limit)
+ val.set_mutable(False)
+ rs = RadioSetting("limits.vhf.upper", "VHF high", val)
+ other.append(rs)
+
+ _limit = str(int(_mem.limits.uhf.lower) / 10)
+ val = RadioSettingValueString(0, 3, _limit)
+ val.set_mutable(False)
+ rs = RadioSetting("limits.uhf.lower", "UHF low", val)
+ other.append(rs)
+
+ _limit = str(int(_mem.limits.uhf.upper) / 10)
+ val = RadioSettingValueString(0, 3, _limit)
+ val.set_mutable(False)
+ rs = RadioSetting("limits.uhf.upper", "UHF high", val)
+ other.append(rs)
+
+ #work mode
+ vfomr_a = RadioSetting("vfomr_a", "Display Mode A",
+ RadioSettingValueList(
+ LIST_VFOMR, LIST_VFOMR[_settings.vfomr_a]))
+ workmode.append(vfomr_a)
+
+ vfomr_b = RadioSetting("vfomr_b", "Display Mode B",
+ RadioSettingValueList(
+ LIST_VFOMR, LIST_VFOMR[_settings.vfomr_b]))
+ workmode.append(vfomr_b)
+
+ mrcha = RadioSetting("mrcha", "Channel # A",
+ RadioSettingValueInteger(
+ 1, 128, _settings.mrcha))
+ workmode.append(mrcha)
+
+ mrchb = RadioSetting("mrchb", "Channel # B",
+ RadioSettingValueInteger(
+ 1, 128, _settings.mrchb))
+ workmode.append(mrchb)
+
+ #fm radio
+ vfomr_fm = RadioSetting("vfomr_fm", "FM Radio Display Mode",
+ RadioSettingValueList(
+ LIST_VFOMRFM, LIST_VFOMRFM[
+ _settings.vfomr_fm]))
+ fmradio.append(vfomr_fm)
+
+ fmch = RadioSetting("fmch", "FM Radio Channel #",
+ RadioSettingValueInteger(
+ 1, 25, _settings.fmch))
+ fmradio.append(fmch)
+
+ 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()
+ elif setting == "color":
+ setattr(obj, setting, int(element.value) + 1)
+ elif element.value.get_mutable():
+ 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) in [0x1000, ]:
+ match_size = True
+
+ # testing the model fingerprint
+ match_model = model_match(cls, filedata)
+
+ if match_size and match_model:
+ return True
+ else:
+ return False
diff --git a/chirp/drivers/rh5r_v2.py b/chirp/drivers/rh5r_v2.py
new file mode 100644
index 0000000..01ffc96
--- /dev/null
+++ b/chirp/drivers/rh5r_v2.py
@@ -0,0 +1,290 @@
+# Copyright 2017 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 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""Rugged RH5R V2 radio management module"""
+
+import struct
+import logging
+
+from chirp import chirp_common, bitwise, errors, directory, memmap, util
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+ RadioSettingValueInteger, RadioSettingValueList, \
+ RadioSettingValueBoolean, RadioSettingValueString, \
+ RadioSettings
+
+
+LOG = logging.getLogger(__name__)
+
+
+def _identify(radio):
+ try:
+ radio.pipe.write("PGM2015")
+ ack = radio.pipe.read(2)
+ if ack != "\x06\x30":
+ raise errors.RadioError("Radio did not ACK first command: %r" %
+ ack)
+ except:
+ LOG.exception('')
+ raise errors.RadioError("Unable to communicate with the radio")
+
+
+def _download(radio):
+ _identify(radio)
+ data = []
+ for i in range(0, 0x2000, 0x40):
+ msg = struct.pack('>cHb', 'R', i, 0x40)
+ radio.pipe.write(msg)
+ block = radio.pipe.read(0x40 + 4)
+ if len(block) != (0x40 + 4):
+ raise errors.RadioError("Radio sent a short block (%02x/%02x)" % (
+ len(block), 0x44))
+ data += block[4:]
+
+ if radio.status_fn:
+ status = chirp_common.Status()
+ status.cur = i
+ status.max = 0x2000
+ status.msg = "Cloning from radio"
+ radio.status_fn(status)
+
+ radio.pipe.write("E")
+ data += 'PGM2015'
+
+ return memmap.MemoryMap(data)
+
+
+def _upload(radio):
+ _identify(radio)
+ for i in range(0, 0x2000, 0x40):
+ msg = struct.pack('>cHb', 'W', i, 0x40)
+ msg += radio._mmap[i:(i + 0x40)]
+ radio.pipe.write(msg)
+ ack = radio.pipe.read(1)
+ if ack != '\x06':
+ raise errors.RadioError('Radio did not ACK block %i (0x%04x)' % (
+ i, i))
+
+ if radio.status_fn:
+ status = chirp_common.Status()
+ status.cur = i
+ status.max = 0x2000
+ status.msg = "Cloning from radio"
+ radio.status_fn(status)
+
+ radio.pipe.write("E")
+
+
+MEM_FORMAT = """
+struct memory {
+ bbcd rx_freq[4];
+ bbcd tx_freq[4];
+ lbcd rx_tone[2];
+ lbcd tx_tone[2];
+
+ u8 unknown10:5,
+ highpower:1,
+ unknown11:2;
+ u8 unknown20:4,
+ narrow:1,
+ unknown21:3;
+ u8 unknown31:1,
+ scanadd:1,
+ unknown32:6;
+ u8 unknown4;
+};
+
+struct name {
+ char name[7];
+};
+
+#seekto 0x0010;
+struct memory channels[128];
+
+#seekto 0x08C0;
+struct name names[128];
+
+#seekto 0x2020;
+struct memory vfo1;
+struct memory vfo2;
+"""
+
+
+POWER_LEVELS = [chirp_common.PowerLevel('Low', watts=1),
+ chirp_common.PowerLevel('High', watts=5)]
+
+
+class TYTTHUVF8_V2(chirp_common.CloneModeRadio):
+ VENDOR = "TYT"
+ MODEL = "TH-UVF8F"
+ BAUD_RATE = 9600
+ _FILEID = 'OEMOEM \XFF'
+
+ def get_features(self):
+ rf = chirp_common.RadioFeatures()
+ rf.memory_bounds = (1, 128)
+ rf.has_bank = False
+ rf.has_ctone = True
+ rf.has_tuning_step = False
+ rf.has_cross = True
+ rf.has_rx_dtcs = True
+ rf.has_settings = False
+ rf.can_odd_split = False
+ rf.valid_duplexes = ['', '-', '+']
+ rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
+ rf.valid_characters = chirp_common.CHARSET_UPPER_NUMERIC + "-"
+ rf.valid_bands = [(136000000, 174000000),
+ (400000000, 480000000)]
+ rf.valid_skips = ["", "S"]
+ rf.valid_power_levels = POWER_LEVELS
+ rf.valid_modes = ["FM", "NFM"]
+ rf.valid_name_length = 7
+ rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
+ "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
+ return rf
+
+ def sync_in(self):
+ self._mmap = _download(self)
+ self.process_mmap()
+
+ def sync_out(self):
+ _upload(self)
+
+ @classmethod
+ def match_model(cls, filedata, filename):
+ return (filedata.endswith("PGM2015") and
+ filedata[0x840:0x848] == cls._FILEID)
+
+ def process_mmap(self):
+ print MEM_FORMAT
+ self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+ def get_raw_memory(self, number):
+ return (repr(self._memobj.channels[number - 1]) +
+ repr(self._memobj.names[number - 1]))
+
+ def _get_memobjs(self, number):
+ return (self._memobj.channels[number - 1],
+ self._memobj.names[number - 1])
+
+ def _decode_tone(self, toneval):
+ pol = "N"
+ rawval = (toneval[1].get_bits(0xFF) << 8) | toneval[0].get_bits(0xFF)
+
+ if toneval[0].get_bits(0xFF) == 0xFF:
+ mode = ""
+ val = 0
+ elif toneval[1].get_bits(0xC0) == 0xC0:
+ mode = "DTCS"
+ val = int("%x" % (rawval & 0x3FFF))
+ pol = "R"
+ elif toneval[1].get_bits(0x80):
+ mode = "DTCS"
+ val = int("%x" % (rawval & 0x3FFF))
+ else:
+ mode = "Tone"
+ val = int(toneval) / 10.0
+
+ return mode, val, pol
+
+ def _encode_tone(self, _toneval, mode, val, pol):
+ toneval = 0
+ if mode == "Tone":
+ toneval = int("%i" % (val * 10), 16)
+ elif mode == "DTCS":
+ toneval = int("%i" % val, 16)
+ toneval |= 0x8000
+ if pol == "R":
+ toneval |= 0x4000
+ else:
+ toneval = 0xFFFF
+
+ _toneval[0].set_raw(toneval & 0xFF)
+ _toneval[1].set_raw((toneval >> 8) & 0xFF)
+
+ def get_memory(self, number):
+ _mem, _name = self._get_memobjs(number)
+
+ mem = chirp_common.Memory()
+
+ if isinstance(number, str):
+ mem.number = SPECIALS[number]
+ mem.extd_number = number
+ else:
+ mem.number = number
+
+ if _mem.get_raw().startswith("\xFF\xFF\xFF\xFF"):
+ mem.empty = True
+ return mem
+
+ mem.freq = int(_mem.rx_freq) * 10
+ offset = (int(_mem.tx_freq) - int(_mem.rx_freq)) * 10
+ if not offset:
+ mem.offset = 0
+ mem.duplex = ''
+ elif offset < 0:
+ mem.offset = abs(offset)
+ mem.duplex = '-'
+ else:
+ mem.offset = offset
+ mem.duplex = '+'
+
+ txmode, txval, txpol = self._decode_tone(_mem.tx_tone)
+ rxmode, rxval, rxpol = self._decode_tone(_mem.rx_tone)
+
+ chirp_common.split_tone_decode(mem,
+ (txmode, txval, txpol),
+ (rxmode, rxval, rxpol))
+
+ mem.mode = 'NFM' if _mem.narrow else 'FM'
+ mem.skip = '' if _mem.scanadd else 'S'
+ mem.power = POWER_LEVELS[int(_mem.highpower)]
+ mem.name = str(_name.name).rstrip('\xFF ')
+
+ return mem
+
+ def set_memory(self, mem):
+ _mem, _name = self._get_memobjs(mem.number)
+ if mem.empty:
+ _mem.set_raw('\xFF' * 16)
+ _name.set_raw('\xFF' * 7)
+ return
+ _mem.set_raw('\x00' * 16)
+
+ _mem.rx_freq = mem.freq / 10
+ if mem.duplex == '-':
+ mult = -1
+ elif not mem.duplex:
+ mult = 0
+ else:
+ mult = 1
+ _mem.tx_freq = (mem.freq + (mem.offset * mult)) / 10
+
+ (txmode, txval, txpol), (rxmode, rxval, rxpol) = \
+ chirp_common.split_tone_encode(mem)
+
+ self._encode_tone(_mem.tx_tone, txmode, txval, txpol)
+ self._encode_tone(_mem.rx_tone, rxmode, rxval, rxpol)
+
+ _mem.narrow = mem.mode == 'NFM'
+ _mem.scanadd = mem.skip != 'S'
+ _mem.highpower = POWER_LEVELS.index(mem.power) if mem.power else 1
+ _name.name = mem.name.rstrip(' ').ljust(7, '\xFF')
+
+
+ at directory.register
+class RH5RV2(TYTTHUVF8_V2):
+ VENDOR = "Rugged"
+ MODEL = "RH5R-V2"
+ _FILEID = 'RUGGED \xFF'
diff --git a/chirp/drivers/tdxone_tdq8a.py b/chirp/drivers/tdxone_tdq8a.py
new file mode 100644
index 0000000..122c056
--- /dev/null
+++ b/chirp/drivers/tdxone_tdq8a.py
@@ -0,0 +1,1154 @@
+# Copyright 2016:
+# * Jim Unroe KC9HI, <rock.unroe 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
+# 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 struct
+import logging
+import re
+
+from chirp import chirp_common, directory, memmap
+from chirp import bitwise, errors, util
+from chirp.settings import RadioSettingGroup, RadioSetting, \
+ RadioSettingValueBoolean, RadioSettingValueList, \
+ RadioSettingValueString, RadioSettingValueInteger, \
+ RadioSettingValueFloat, RadioSettings, \
+ InvalidValueError
+from textwrap import dedent
+
+LOG = logging.getLogger(__name__)
+
+MEM_FORMAT = """
+#seekto 0x0010;
+struct {
+ lbcd rxfreq[4];
+ lbcd txfreq[4];
+ ul16 rxtone;
+ ul16 txtone;
+ u8 unknown1:2,
+ dtmf:1, // DTMF
+ unknown2:1,
+ bcl:1, // Busy Channel Lockout
+ unknown3:3;
+ u8 unknown4:1,
+ scan:1, // Scan Add
+ highpower:1, // TX Power Level
+ wide:1, // BandWidth
+ unknown5:4;
+ u8 unknown6[2];
+} memory[128];
+
+#seekto 0x0E17;
+struct {
+ u8 displayab:1, // Selected Display
+ unknown1:6,
+ unknown2:1;
+} settings1;
+
+#seekto 0x0E22;
+struct {
+ u8 squelcha; // menu 02a Squelch Level 0xe22
+ u8 unknown1;
+ u8 tdrab; // TDR A/B 0xe24
+ u8 roger; // menu 20 Roger Beep 0xe25
+ u8 timeout; // menu 16 TOT 0xe26
+ u8 vox; // menu 05 VOX 0xe27
+ u8 unknown2;
+ u8 mdfb; // menu 27b Memory Display Format B 0xe37
+ u8 dw; // menu 37 DW 0xe2a
+ u8 tdr; // menu 29 Dual Watch 0xe2b
+ u8 voice; // menu 03 Voice Prompts 0xe2c
+ u8 beep; // menu 01 Key Beep 0xe2d
+ u8 ani; // menu 30 ANI 0xe2e
+ u8 unknown3[4];
+ u8 pttdly; // menu 31 PTT-ID Delay 0xe33
+ u8 unknown4;
+ u8 dtmfst; // menu 33 DTMF Side Tone 0xe35
+ u8 toa; // menu 15 TOT Pre-Alert 0xe36
+ u8 mdfa; // menu 27a Memory Display Format A 0xe37
+ u8 screv; // menu 09 Scan Resume Method 0xe38
+ u8 pttid; // menu 32 PTT-ID Enable 0xe39
+ u8 ponmsg; // menu 36 Power-on Message 0xe3a
+ u8 pf1; // menu 28 Programmable Function Key 0xe3b
+ u8 unknown5;
+ u8 wtled; // menu 17 Standby LED Color 0xe3d
+ u8 rxled; // menu 18 RX LED Color 0xe3e
+ u8 txled; // menu 19 TX LED Color 0xe3f
+ u8 unknown6;
+ u8 autolk; // menu 06 Auto Key Lock 0xe41
+ u8 squelchb; // menu 02b Squelch Level 0xe42
+ u8 control; // Control Code 0xe43
+ u8 unknown7;
+ u8 ach; // Selected A channel Number 0xe45
+ u8 unknown8[4];
+ u8 password[6]; // Control Password 0xe4a-0xe4f
+ u8 unknown9[7];
+ u8 code[3]; // PTT ID Code 0xe57-0xe59
+ u8 vfomr; // Frequency/Channel Modevel 0xe5a
+ u8 keylk; // Key Lock 0xe5b
+ u8 unknown10[2];
+ u8 prioritych; // Priority Channel 0xe5e
+ u8 bch; // Selected B channel Number 0xe5f
+} settings;
+
+struct vfo {
+ u8 unknown0[8];
+ u8 freq[8];
+ u8 offset[6];
+ ul16 rxtone;
+ ul16 txtone;
+ u8 unused0:7,
+ band:1;
+ u8 unknown3;
+ u8 unknown4:2,
+ sftd:2,
+ scode:4;
+ u8 unknown5;
+ u8 unknown6:1,
+ step:3,
+ unknown7:4;
+ u8 txpower:1,
+ widenarr:1,
+ unknown8:6;
+};
+
+#seekto 0x0F10;
+struct {
+ struct vfo a;
+ struct vfo b;
+} vfo;
+
+#seekto 0x1010;
+struct {
+ u8 name[6];
+ u8 unknown[10];
+} names[128];
+
+"""
+
+##### MAGICS #########################################################
+
+# TDXone TD-Q8A magic string
+MSTRING_TDQ8A = "\x02PYNCRAM"
+
+#STIMEOUT = 2
+
+LIST_DTMF = ["QT", "QT+DTMF"]
+LIST_VOICE = ["Off", "Chinese", "English"]
+LIST_OFF1TO9 = ["Off"] + list("123456789")
+LIST_OFF1TO10 = LIST_OFF1TO9 + ["10"]
+LIST_RESUME = ["Time Operated(TO)", "Carrier Operated(CO)", "Search(SE)"]
+LIST_COLOR = ["Off", "Blue", "Orange", "Purple"]
+LIST_MODE = ["Channel", "Frequency", "Name"]
+LIST_PF1 = ["Off", "Scan", "Lamp", "FM Radio", "Alarm"]
+LIST_OFF1TO30 = ["OFF"] + ["%s" % x for x in range(1, 31)]
+LIST_DTMFST = ["Off", "DTMF Sidetone", "ANI Sidetone", "DTMF+ANI Sidetone"]
+LIST_PONMSG = ["Full", "Welcome", "Battery Voltage"]
+LIST_TIMEOUT = ["Off"] + ["%s sec" % x for x in range(15, 615, 15)]
+LIST_PTTID = ["BOT", "EOT", "Both"]
+LIST_ROGER = ["Off"] + LIST_PTTID
+LIST_PRIORITY = ["Off"] + ["%s" % x for x in range(1, 129)]
+LIST_WORKMODE = ["Frequency", "Channel"]
+LIST_AB = ["A", "B"]
+
+LIST_ALMOD = ["Site", "Tone", "Code"]
+LIST_BANDWIDTH = ["Wide", "Narrow"]
+LIST_DELAYPROCTIME = ["%s ms" % x for x in range(100, 4100, 100)]
+LIST_DTMFSPEED = ["%s ms" % x for x in range(50, 2010, 10)]
+LIST_OFFAB = ["Off"] + LIST_AB
+LIST_RESETTIME = ["%s ms" % x for x in range(100, 16100, 100)]
+LIST_SCODE = ["%s" % x for x in range(1, 16)]
+LIST_RPSTE = ["Off"] + ["%s" % x for x in range(1, 11)]
+LIST_SAVE = ["Off", "1:1", "1:2", "1:3", "1:4"]
+LIST_SHIFTD = ["Off", "+", "-"]
+LIST_STEDELAY = ["Off"] + ["%s ms" % x for x in range(100, 1100, 100)]
+#LIST_STEP = [str(x) for x in STEPS]
+LIST_TXPOWER = ["High", "Low"]
+LIST_DTMF_SPECIAL_DIGITS = [ "*", "#", "A", "B", "C", "D"]
+LIST_DTMF_SPECIAL_VALUES = [ 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00]
+
+CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ?+-*"
+STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0]
+POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5),
+ chirp_common.PowerLevel("Low", watts=1)]
+VALID_BANDS = [(136000000, 174000000),
+ (400000000, 520000000)]
+
+
+#def _clean_buffer(radio):
+# radio.pipe.timeout = 0.005
+# junk = radio.pipe.read(256)
+# radio.pipe.timeout = STIMEOUT
+# if junk:
+# LOG.debug("Got %i bytes of junk before starting" % len(junk))
+
+
+def _rawrecv(radio, amount):
+ """Raw read from the radio device"""
+ data = ""
+ try:
+ data = radio.pipe.read(amount)
+ except:
+ msg = "Generic error reading data from radio; check your cable."
+ raise errors.RadioError(msg)
+
+ if len(data) != amount:
+ msg = "Error reading data from radio: not the amount of data we want."
+ raise errors.RadioError(msg)
+
+ return data
+
+
+def _rawsend(radio, data):
+ """Raw send to the radio device"""
+ try:
+ radio.pipe.write(data)
+ except:
+ raise errors.RadioError("Error sending data to radio")
+
+
+def _make_frame(cmd, addr, length, data=""):
+ """Pack the info in the headder format"""
+ frame = struct.pack(">BHB", ord(cmd), addr, length)
+ # add the data if set
+ if len(data) != 0:
+ frame += data
+ # return the data
+ return frame
+
+
+def _recv(radio, addr, length):
+ """Get data from the radio """
+ # read 4 bytes of header
+ hdr = _rawrecv(radio, 4)
+
+ # read data
+ data = _rawrecv(radio, length)
+
+ # DEBUG
+ LOG.info("Response:")
+ LOG.debug(util.hexprint(hdr + data))
+
+ c, a, l = struct.unpack(">BHB", hdr)
+ if a != addr or l != length or c != ord("W"):
+ LOG.error("Invalid answer for block 0x%04x:" % addr)
+ LOG.debug("CMD: %s ADDR: %04x SIZE: %02x" % (c, a, l))
+ raise errors.RadioError("Unknown response from the radio")
+
+ return data
+
+
+def _do_ident(radio, magic):
+ """Put the radio in PROGRAM mode"""
+ # set the serial discipline
+ radio.pipe.baudrate = 9600
+ ####radio.pipe.timeout = STIMEOUT
+
+ ## flush input buffer
+ #_clean_buffer(radio)
+
+ # send request to enter program mode
+ _rawsend(radio, magic)
+
+ ack = _rawrecv(radio, 1)
+ if ack != "\x06":
+ if ack:
+ LOG.debug(repr(ack))
+ raise errors.RadioError("Radio did not respond")
+
+ _rawsend(radio, "\x02")
+
+ # Ok, get the response
+ ident = _rawrecv(radio, radio._magic_response_length)
+
+ # check if response is OK
+ if not ident.startswith("P3107"):
+ # bad response
+ msg = "Unexpected response, got this:"
+ msg += util.hexprint(ident)
+ LOG.debug(msg)
+ raise errors.RadioError("Unexpected response from radio.")
+
+ # DEBUG
+ LOG.info("Valid response, got this:")
+ LOG.debug(util.hexprint(ident))
+
+ _rawsend(radio, "\x06")
+ ack = _rawrecv(radio, 1)
+ if ack != "\x06":
+ if ack:
+ LOG.debug(repr(ack))
+ raise errors.RadioError("Radio refused clone")
+
+ return ident
+
+
+def _ident_radio(radio):
+ for magic in radio._magic:
+ error = None
+ try:
+ data = _do_ident(radio, magic)
+ return data
+ except errors.RadioError, e:
+ print e
+ error = e
+ time.sleep(2)
+ if error:
+ raise error
+ raise errors.RadioError("Radio did not respond")
+
+
+def _download(radio):
+ """Get the memory map"""
+ # put radio in program mode
+ ident = _ident_radio(radio)
+
+ # UI progress
+ status = chirp_common.Status()
+ status.cur = 0
+ status.max = radio._mem_size / radio._recv_block_size
+ status.msg = "Cloning from radio..."
+ radio.status_fn(status)
+
+ data = ""
+ for addr in range(0, radio._mem_size, radio._recv_block_size):
+ frame = _make_frame("R", addr, radio._recv_block_size)
+ # DEBUG
+ LOG.info("Request sent:")
+ LOG.debug(util.hexprint(frame))
+
+ # sending the read request
+ _rawsend(radio, frame)
+
+ # now we read
+ d = _recv(radio, addr, radio._recv_block_size)
+
+ time.sleep(0.05)
+
+ _rawsend(radio, "\x06")
+
+ ack = _rawrecv(radio, 1)
+ if ack != "\x06":
+ raise errors.RadioError(
+ "Radio refused to send block 0x%04x" % addr)
+
+ ####time.sleep(0.05)
+
+ # aggregate the data
+ data += d
+
+ # UI Update
+ status.cur = addr / radio._recv_block_size
+ status.msg = "Cloning from radio..."
+ radio.status_fn(status)
+
+ data += radio.MODEL.ljust(8)
+
+ return data
+
+
+def _upload(radio):
+ """Upload procedure"""
+ # put radio in program mode
+ _ident_radio(radio)
+
+
+
+ addr = 0x0f80
+ frame = _make_frame("R", addr, radio._recv_block_size)
+ # DEBUG
+ LOG.info("Request sent:")
+ LOG.debug(util.hexprint(frame))
+
+ # sending the read request
+ _rawsend(radio, frame)
+
+ # now we read
+ d = _recv(radio, addr, radio._recv_block_size)
+
+ time.sleep(0.05)
+
+ _rawsend(radio, "\x06")
+
+ ack = _rawrecv(radio, 1)
+ if ack != "\x06":
+ raise errors.RadioError(
+ "Radio refused to send block 0x%04x" % addr)
+
+
+
+ _ranges = radio._ranges
+
+ # UI progress
+ status = chirp_common.Status()
+ status.cur = 0
+ status.max = radio._mem_size / radio._send_block_size
+ status.msg = "Cloning to radio..."
+ radio.status_fn(status)
+
+ # the fun start here
+ for start, end in _ranges:
+ for addr in range(start, end, radio._send_block_size):
+ # sending the data
+ data = radio.get_mmap()[addr:addr + radio._send_block_size]
+
+ frame = _make_frame("W", addr, radio._send_block_size, data)
+
+ _rawsend(radio, frame)
+ #time.sleep(0.05)
+
+ # receiving the response
+ ack = _rawrecv(radio, 1)
+ if ack != "\x06":
+ msg = "Bad ack writing block 0x%04x" % addr
+ raise errors.RadioError(msg)
+
+ # UI Update
+ status.cur = addr / radio._send_block_size
+ status.msg = "Cloning to radio..."
+ radio.status_fn(status)
+
+
+def model_match(cls, data):
+ """Match the opened/downloaded image to the correct version"""
+
+ if len(data) == 0x2008:
+ rid = data[0x2000:0x2008]
+ print rid
+ return rid.startswith(cls.MODEL)
+ else:
+ return False
+
+
+def _split(rf, f1, f2):
+ """Returns False if the two freqs are in the same band (no split)
+ or True otherwise"""
+
+ # determine if the two freqs are in the same band
+ for low, high in rf.valid_bands:
+ if f1 >= low and f1 <= high and \
+ f2 >= low and f2 <= high:
+ # if the two freqs are on the same Band this is not a split
+ return False
+
+ # if you get here is because the freq pairs are split
+ return True
+
+
+ at directory.register
+class TDXoneTDQ8A(chirp_common.CloneModeRadio,
+ chirp_common.ExperimentalRadio):
+ """TDXone TD-Q8A Radio"""
+ VENDOR = "TDXone"
+ MODEL = "TD-Q8A"
+
+ ####_fileid = [TDQ8A_fp1, ]
+
+ _magic = [MSTRING_TDQ8A, MSTRING_TDQ8A,]
+ _magic_response_length = 8
+ _fw_ver_start = 0x1EF0
+ _recv_block_size = 0x40
+ _mem_size = 0x2000
+
+ #_ranges = [(0x0000, 0x2000)]
+ # same as radio
+ #_ranges = [(0x0010, 0x0810),
+ # (0x0F10, 0x0F30),
+ # (0x1010, 0x1810),
+ # (0x0E20, 0x0E60),
+ # (0x1F10, 0x1F30)]
+ # in increasing order
+ _ranges = [(0x0010, 0x0810),
+ (0x0E20, 0x0E60),
+ (0x0F10, 0x0F30),
+ (0x1010, 0x1810),
+ (0x1F10, 0x1F30)]
+ _send_block_size = 0x10
+
+ #DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645])
+ #DTCS_CODES = sorted(chirp_common.ALL_DTCS_CODES)
+ #POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1.00),
+ # chirp_common.PowerLevel("High", watts=5.00)]
+ #VALID_BANDS = [(136000000, 174000000),
+ # (400000000, 520000000)]
+
+
+ @classmethod
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.experimental = \
+ ('The TDXone TD-Q8A driver is a beta version.\n'
+ '\n'
+ 'Please save an unedited copy of your first successful\n'
+ 'download to a CHIRP Radio Images(*.img) file.'
+ )
+ rp.pre_download = _(dedent("""\
+ Follow these instructions to download your info:
+
+ 1 - Turn off your radio
+ 2 - Connect your interface cable
+ 3 - Turn on your radio
+ 4 - Do the download of your radio data
+ """))
+ rp.pre_upload = _(dedent("""\
+ Follow this instructions to upload your info:
+
+ 1 - Turn off your radio
+ 2 - Connect your interface cable
+ 3 - Turn on your radio
+ 4 - Do the upload of your radio data
+ """))
+ return rp
+
+
+ def get_features(self):
+ """Get the radio's features"""
+
+ rf = chirp_common.RadioFeatures()
+ rf.has_settings = True
+ rf.has_bank = False
+ rf.has_tuning_step = False
+ rf.can_odd_split = True
+ rf.has_name = True
+ rf.has_offset = True
+ rf.has_mode = True
+ rf.has_dtcs = False #True
+ rf.has_rx_dtcs = False #True
+ rf.has_dtcs_polarity = False #True
+ rf.has_ctone = True
+ rf.has_cross = True
+ rf.valid_modes = ["FM", "NFM"]
+ #rf.valid_characters = self.VALID_CHARS
+ rf.valid_characters = CHARSET
+ rf.valid_name_length = 6
+ rf.valid_duplexes = ["", "-", "+", "split", "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_tmodes = ['', 'Tone', 'TSQL', 'Cross']
+ rf.valid_cross_modes = [
+ "Tone->Tone",
+ "->Tone"]
+ rf.valid_skips = ["", "S"]
+ #rf.valid_dtcs_codes = self.DTCS_CODES
+ rf.memory_bounds = (1, 128)
+ rf.valid_power_levels = POWER_LEVELS
+ rf.valid_bands = VALID_BANDS
+
+ return rf
+
+
+ def process_mmap(self):
+ """Process the mem map into the mem object"""
+ self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+
+ def sync_in(self):
+ """Download from radio"""
+ try:
+ data = _download(self)
+ except errors.RadioError:
+ # Pass through any real errors we raise
+ raise
+ except:
+ # If anything unexpected happens, make sure we raise
+ # a RadioError and log the problem
+ LOG.exception('Unexpected error during download')
+ raise errors.RadioError('Unexpected error communicating '
+ 'with the radio')
+ self._mmap = memmap.MemoryMap(data)
+ self.process_mmap()
+
+
+ def sync_out(self):
+ """Upload to radio"""
+ try:
+ _upload(self)
+ except:
+ # If anything unexpected happens, make sure we raise
+ # a RadioError and log the problem
+ LOG.exception('Unexpected error during upload')
+ raise errors.RadioError('Unexpected error communicating '
+ 'with the radio')
+
+
+ def _is_txinh(self, _mem):
+ raw_tx = ""
+ for i in range(0, 4):
+ raw_tx += _mem.txfreq[i].get_raw()
+ return raw_tx == "\xFF\xFF\xFF\xFF"
+
+
+ def _get_mem(self, number):
+ return self._memobj.memory[number - 1]
+
+ def _get_nam(self, number):
+ return self._memobj.names[number - 1]
+
+ def get_memory(self, number):
+ _mem = self._get_mem(number)
+ _nam = self._get_nam(number)
+
+ mem = chirp_common.Memory()
+ mem.number = number
+
+ if _mem.get_raw()[0] == "\xff":
+ mem.empty = True
+ return mem
+
+ mem.freq = int(_mem.rxfreq) * 10
+
+ if self._is_txinh(_mem):
+ # TX freq not set
+ mem.duplex = "off"
+ mem.offset = 0
+ else:
+ # TX freq set
+ offset = (int(_mem.txfreq) * 10) - mem.freq
+ if offset != 0:
+ if _split(self.get_features(), mem.freq, int(_mem.txfreq) * 10):
+ mem.duplex = "split"
+ mem.offset = int(_mem.txfreq) * 10
+ elif offset < 0:
+ mem.offset = abs(offset)
+ mem.duplex = "-"
+ elif offset > 0:
+ mem.offset = offset
+ mem.duplex = "+"
+ else:
+ mem.offset = 0
+
+ if _nam.name:
+ for char in _nam.name:
+ try:
+ mem.name += CHARSET[char]
+ except IndexError:
+ break
+ mem.name = mem.name.rstrip()
+
+ #dtcs_pol = ["N", "N"]
+
+ if _mem.txtone in [0, 0xFFFF]:
+ txmode = ""
+ elif _mem.txtone >= 0x0258:
+ txmode = "Tone"
+ mem.rtone = int(_mem.txtone) / 10.0
+ else:
+ LOG.warn("Bug: txtone is %04x" % _mem.txtone)
+
+ #elif _mem.txtone <= 0x0258:
+ # txmode = "DTCS"
+ # if _mem.txtone > 0x69:
+ # index = _mem.txtone - 0x6A
+ # dtcs_pol[0] = "R"
+ # else:
+ # index = _mem.txtone - 1
+ # mem.dtcs = self.DTCS_CODES[index]
+ #else:
+ # LOG.warn("Bug: txtone is %04x" % _mem.txtone)
+
+ if _mem.rxtone in [0, 0xFFFF]:
+ rxmode = ""
+ elif _mem.rxtone >= 0x0258:
+ rxmode = "Tone"
+ mem.ctone = int(_mem.rxtone) / 10.0
+ else:
+ LOG.warn("Bug: rxtone is %04x" % _mem.rxtone)
+
+ #elif _mem.rxtone <= 0x0258:
+ # rxmode = "DTCS"
+ # if _mem.rxtone >= 0x6A:
+ # index = _mem.rxtone - 0x6A
+ # dtcs_pol[1] = "R"
+ # else:
+ # index = _mem.rxtone - 1
+ # mem.rx_dtcs = self.DTCS_CODES[index]
+ #else:
+ # LOG.warn("Bug: rxtone is %04x" % _mem.rxtone)
+
+ if txmode == "Tone" and not rxmode:
+ mem.tmode = "Tone"
+ elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
+ mem.tmode = "TSQL"
+ elif rxmode or txmode:
+ mem.tmode = "Cross"
+ mem.cross_mode = "%s->%s" % (txmode, rxmode)
+
+ #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)
+
+ #mem.dtcs_polarity = "".join(dtcs_pol)
+
+ if not _mem.scan:
+ mem.skip = "S"
+
+ mem.power = POWER_LEVELS[1 - _mem.highpower]
+
+ mem.mode = _mem.wide and "FM" or "NFM"
+
+ mem.extra = RadioSettingGroup("Extra", "extra")
+
+ rs = RadioSetting("dtmf", "DTMF",
+ RadioSettingValueList(LIST_DTMF,
+ LIST_DTMF[_mem.dtmf]))
+ mem.extra.append(rs)
+
+ rs = RadioSetting("bcl", "BCL",
+ RadioSettingValueBoolean(_mem.bcl))
+ mem.extra.append(rs)
+
+ return mem
+
+
+ def _set_mem(self, number):
+ return self._memobj.memory[number - 1]
+
+ def _set_nam(self, number):
+ return self._memobj.names[number - 1]
+
+ def set_memory(self, mem):
+ _mem = self._get_mem(mem.number)
+ _nam = self._get_nam(mem.number)
+
+ if mem.empty:
+ _mem.set_raw("\xff" * 12 + "\xbf" +"\xff" * 3)
+ _nam.set_raw("\xff" * 16)
+ return
+
+ #_mem.set_raw("\x00" * 16)
+ _mem.set_raw("\xff" * 12 + "\x9f" +"\xff" * 3)
+
+ _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
+
+ if _nam.name:
+ for i in range(0, 6):
+ try:
+ _nam.name[i] = CHARSET.index(mem.name[i])
+ except IndexError:
+ _nam.name[i] = 0xFF
+
+ rxmode = txmode = ""
+ if mem.tmode == "Tone":
+ _mem.txtone = int(mem.rtone * 10)
+ _mem.rxtone = 0
+ elif mem.tmode == "TSQL":
+ _mem.txtone = int(mem.ctone * 10)
+ _mem.rxtone = int(mem.ctone * 10)
+ #elif mem.tmode == "DTCS":
+ # rxmode = txmode = "DTCS"
+ # _mem.txtone = self.DTCS_CODES.index(mem.dtcs) + 1
+ # _mem.rxtone = self.DTCS_CODES.index(mem.dtcs) + 1
+ elif mem.tmode == "Cross":
+ txmode, rxmode = mem.cross_mode.split("->", 1)
+ if txmode == "Tone":
+ _mem.txtone = int(mem.rtone * 10)
+ #elif txmode == "DTCS":
+ # _mem.txtone = self.DTCS_CODES.index(mem.dtcs) + 1
+ else:
+ _mem.txtone = 0
+ if rxmode == "Tone":
+ _mem.rxtone = int(mem.ctone * 10)
+ #elif rxmode == "DTCS":
+ # _mem.rxtone = self.DTCS_CODES.index(mem.rx_dtcs) + 1
+ else:
+ _mem.rxtone = 0
+ else:
+ _mem.rxtone = 0
+ _mem.txtone = 0
+
+ #if txmode == "DTCS" and mem.dtcs_polarity[0] == "R":
+ # _mem.txtone += 0x69
+ #if rxmode == "DTCS" and mem.dtcs_polarity[1] == "R":
+ # _mem.rxtone += 0x69
+
+ _mem.scan = mem.skip != "S"
+ _mem.wide = mem.mode == "FM"
+
+ _mem.highpower = mem.power == POWER_LEVELS[0]
+
+ for setting in mem.extra:
+ setattr(_mem, setting.get_name(), setting.value)
+
+
+ def get_settings(self):
+ # """Translate the bit in the mem_struct into settings in the UI"""
+ _mem = self._memobj
+ basic = RadioSettingGroup("basic", "Basic Settings")
+ advanced = RadioSettingGroup("advanced", "Advanced Settings")
+ #other = RadioSettingGroup("other", "Other Settings")
+ #work = RadioSettingGroup("work", "Work Mode Settings")
+ #fm_preset = RadioSettingGroup("fm_preset", "FM Preset")
+ #dtmfe = RadioSettingGroup("dtmfe", "DTMF Encode Settings")
+ #dtmfd = RadioSettingGroup("dtmfd", "DTMF Decode Settings")
+ #service = RadioSettingGroup("service", "Service Settings")
+ #top = RadioSettings(basic, advanced, other, work, fm_preset, dtmfe,
+ # dtmfd, service)
+ top = RadioSettings(basic, advanced, )
+
+ # Basic settings
+ rs = RadioSetting("settings.beep", "Beep",
+ RadioSettingValueBoolean(_mem.settings.beep))
+ basic.append(rs)
+
+ if _mem.settings.squelcha > 0x09:
+ val = 0x00
+ else:
+ val = _mem.settings.squelcha
+ rs = RadioSetting("squelcha", "Squelch Level A",
+ RadioSettingValueInteger(0, 9, _mem.settings.squelcha))
+ basic.append(rs)
+
+
+ if _mem.settings.squelchb > 0x09:
+ val = 0x00
+ else:
+ val = _mem.settings.squelchb
+ rs = RadioSetting("squelchb", "Squelch Level B",
+ RadioSettingValueInteger(0, 9, _mem.settings.squelchb))
+ basic.append(rs)
+
+
+ if _mem.settings.voice > 0x02:
+ val = 0x01
+ else:
+ val = _mem.settings.voice
+ rs = RadioSetting("settings.voice", "Voice Prompt",
+ RadioSettingValueList(
+ LIST_VOICE, LIST_VOICE[val]))
+ basic.append(rs)
+
+ if _mem.settings.vox > 0x0A:
+ val = 0x00
+ else:
+ val = _mem.settings.vox
+ rs = RadioSetting("settings.vox", "VOX",
+ RadioSettingValueList(
+ LIST_OFF1TO10, LIST_OFF1TO10[val]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.autolk", "Automatic Key Lock",
+ RadioSettingValueBoolean(_mem.settings.autolk))
+ basic.append(rs)
+
+ if _mem.settings.screv > 0x02:
+ val = 0x01
+ else:
+ val = _mem.settings.screv
+ rs = RadioSetting("settings.screv", "Scan Resume",
+ RadioSettingValueList(
+ LIST_RESUME, LIST_RESUME[val]))
+ basic.append(rs)
+
+ if _mem.settings.toa > 0x0A:
+ val = 0x00
+ else:
+ val = _mem.settings.toa
+ rs = RadioSetting("settings.toa", "Time-out Pre-Alert",
+ RadioSettingValueList(
+ LIST_OFF1TO10, LIST_OFF1TO10[val]))
+ basic.append(rs)
+
+ if _mem.settings.timeout > 0x28:
+ val = 0x03
+ else:
+ val = _mem.settings.timeout
+ rs = RadioSetting("settings.timeout", "Timeout Timer",
+ RadioSettingValueList(
+ LIST_TIMEOUT, LIST_TIMEOUT[val]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.wtled", "Standby LED Color",
+ RadioSettingValueList(
+ LIST_COLOR, LIST_COLOR[_mem.settings.wtled]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.rxled", "RX LED Color",
+ RadioSettingValueList(
+ LIST_COLOR, LIST_COLOR[_mem.settings.rxled]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.txled", "TX LED Color",
+ RadioSettingValueList(
+ LIST_COLOR, LIST_COLOR[_mem.settings.txled]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.roger", "Roger Beep",
+ RadioSettingValueList(LIST_ROGER, LIST_ROGER[
+ _mem.settings.roger]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.mdfa", "Display Mode (A)",
+ RadioSettingValueList(LIST_MODE, LIST_MODE[
+ _mem.settings.mdfa]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.mdfb", "Display Mode (B)",
+ RadioSettingValueList(LIST_MODE, LIST_MODE[
+ _mem.settings.mdfb]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.pf1", "PF1 Key Assignment",
+ RadioSettingValueList(LIST_PF1, LIST_PF1[
+ _mem.settings.pf1]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.tdr", "Dual Watch(TDR)",
+ RadioSettingValueBoolean(_mem.settings.tdr))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.ani", "ANI",
+ RadioSettingValueBoolean(_mem.settings.ani))
+ basic.append(rs)
+
+ if _mem.settings.pttdly > 0x0A:
+ val = 0x00
+ else:
+ val = _mem.settings.pttdly
+ rs = RadioSetting("settings.pttdly", "PTT ID Delay",
+ RadioSettingValueList(
+ LIST_OFF1TO30, LIST_OFF1TO30[val]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.pttid", "When to send PTT ID",
+ RadioSettingValueList(LIST_PTTID,
+ LIST_PTTID[_mem.settings.pttid]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.dtmfst", "DTMF Sidetone",
+ RadioSettingValueList(LIST_DTMFST, LIST_DTMFST[
+ _mem.settings.dtmfst]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.ponmsg", "Power-On Message",
+ RadioSettingValueList(LIST_PONMSG, LIST_PONMSG[
+ _mem.settings.ponmsg]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.dw", "DW",
+ RadioSettingValueBoolean(_mem.settings.dw))
+ basic.append(rs)
+
+ # Advanced settings
+ rs = RadioSetting("settings.prioritych", "Priority Channel",
+ RadioSettingValueList(LIST_PRIORITY, LIST_PRIORITY[
+ _mem.settings.prioritych]))
+ advanced.append(rs)
+
+ rs = RadioSetting("settings.vfomr", "Work Mode",
+ RadioSettingValueList(LIST_WORKMODE, LIST_WORKMODE[
+ _mem.settings.vfomr]))
+ advanced.append(rs)
+
+ dtmfchars = "0123456789"
+ _codeobj = _mem.settings.code
+ _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
+ val = RadioSettingValueString(0, 3, _code, False)
+ val.set_charset(dtmfchars)
+ rs = RadioSetting("settings.code", "PTT-ID Code", val)
+
+ def apply_code(setting, obj):
+ code = []
+ for j in range(0, 3):
+ try:
+ code.append(dtmfchars.index(str(setting.value)[j]))
+ except IndexError:
+ code.append(0xFF)
+ obj.code = code
+ rs.set_apply_callback(apply_code, _mem.settings)
+ advanced.append(rs)
+
+ _codeobj = _mem.settings.password
+ _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
+ val = RadioSettingValueString(0, 6, _code, False)
+ val.set_charset(dtmfchars)
+ rs = RadioSetting("settings.password", "Control Password", val)
+
+ def apply_code(setting, obj):
+ code = []
+ for j in range(0, 6):
+ try:
+ code.append(dtmfchars.index(str(setting.value)[j]))
+ except IndexError:
+ code.append(0xFF)
+ obj.password = code
+ rs.set_apply_callback(apply_code, _mem.settings)
+ advanced.append(rs)
+
+ if _mem.settings.tdrab > 0x01:
+ val = 0x00
+ else:
+ val = _mem.settings.tdrab
+ rs = RadioSetting("settings.tdrab", "Dual Watch TX Priority",
+ RadioSettingValueList(
+ LIST_AB, LIST_AB[val]))
+ advanced.append(rs)
+
+ rs = RadioSetting("settings.keylk", "Key Lock",
+ RadioSettingValueBoolean(_mem.settings.keylk))
+ advanced.append(rs)
+
+ rs = RadioSetting("settings.control", "Control Code",
+ RadioSettingValueBoolean(_mem.settings.control))
+ advanced.append(rs)
+
+ return top
+
+
+
+ """
+ # Other settings
+ def _filter(name):
+ filtered = ""
+ for char in str(name):
+ if char in chirp_common.CHARSET_ASCII:
+ filtered += char
+ else:
+ filtered += " "
+ return filtered
+
+ _msg = _mem.sixpoweron_msg
+ val = RadioSettingValueString(0, 7, _filter(_msg.line1))
+ val.set_mutable(False)
+ rs = RadioSetting("sixpoweron_msg.line1", "6+Power-On Message 1", val)
+ other.append(rs)
+ val = RadioSettingValueString(0, 7, _filter(_msg.line2))
+ val.set_mutable(False)
+ rs = RadioSetting("sixpoweron_msg.line2", "6+Power-On Message 2", val)
+ other.append(rs)
+
+ _msg = _mem.poweron_msg
+ rs = RadioSetting("poweron_msg.line1", "Power-On Message 1",
+ RadioSettingValueString(
+ 0, 7, _filter(_msg.line1)))
+ other.append(rs)
+ rs = RadioSetting("poweron_msg.line2", "Power-On Message 2",
+ RadioSettingValueString(
+ 0, 7, _filter(_msg.line2)))
+ other.append(rs)
+
+ # DTMF encode settings
+
+ if _mem.ani.dtmfon > 0xC3:
+ val = 0x03
+ else:
+ val = _mem.ani.dtmfon
+ rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)",
+ RadioSettingValueList(LIST_DTMFSPEED,
+ LIST_DTMFSPEED[val]))
+ dtmfe.append(rs)
+
+ if _mem.ani.dtmfoff > 0xC3:
+ val = 0x03
+ else:
+ val = _mem.ani.dtmfoff
+ rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
+ RadioSettingValueList(LIST_DTMFSPEED,
+ LIST_DTMFSPEED[val]))
+ dtmfe.append(rs)
+
+ """
+
+
+ def set_settings(self, settings):
+ _settings = self._memobj.settings
+ _mem = self._memobj
+ for element in settings:
+ if not isinstance(element, RadioSetting):
+ if element.get_name() == "fm_preset":
+ self._set_fm_preset(element)
+ else:
+ self.set_settings(element)
+ continue
+ else:
+ try:
+ name = element.get_name()
+ if "." in name:
+ bits = name.split(".")
+ obj = self._memobj
+ for bit in bits[:-1]:
+ if "/" in bit:
+ bit, index = bit.split("/", 1)
+ index = int(index)
+ obj = getattr(obj, bit)[index]
+ else:
+ obj = getattr(obj, bit)
+ setting = bits[-1]
+ else:
+ obj = _settings
+ setting = element.get_name()
+
+ if element.has_apply_callback():
+ LOG.debug("Using apply callback")
+ element.run_apply_callback()
+ elif element.value.get_mutable():
+ LOG.debug("Setting %s = %s" % (setting, element.value))
+ setattr(obj, setting, element.value)
+ except Exception, e:
+ LOG.debug(element.get_name())
+ raise
+
+ def _set_fm_preset(self, settings):
+ for element in settings:
+ try:
+ val = element.value
+ if self._memobj.fm_presets <= 108.0 * 10 - 650:
+ value = int(val.get_value() * 10 - 650)
+ else:
+ value = int(val.get_value() * 10)
+ LOG.debug("Setting fm_presets = %s" % (value))
+ self._memobj.fm_presets = 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) == 0x2008:
+ match_size = True
+
+ # testing the model fingerprint
+ match_model = model_match(cls, filedata)
+
+ #if match_size and match_model:
+ if match_size and match_model:
+ return True
+ else:
+ return False
diff --git a/chirp/drivers/th9000.py b/chirp/drivers/th9000.py
index 58e138e..ad2d7eb 100644
--- a/chirp/drivers/th9000.py
+++ b/chirp/drivers/th9000.py
@@ -434,7 +434,8 @@ def _finish(radio):
endframe = "\x45\x4E\x44"
_echo_write(radio, endframe)
result = radio.pipe.read(1)
- if result != "\x06":
+ # TYT radios acknowledge the "endframe" command, Luiton radios do not.
+ if result != "" and result != "\x06":
LOG.error( "Got:\n%s" % util.hexprint(result))
raise errors.RadioError("Radio did not finish cleanly")
@@ -813,6 +814,17 @@ class Th9000Radio(chirp_common.CloneModeRadio,
return False
+# Declaring Aliases (Clones of the real radios)
+class LT580VHF(chirp_common.Alias):
+ VENDOR = "LUITON"
+ MODEL = "LT-580_VHF"
+
+
+class LT580UHF(chirp_common.Alias):
+ VENDOR = "LUITON"
+ MODEL = "LT-580_UHF"
+
+
@directory.register
class Th9000220Radio(Th9000Radio):
"""TYT TH-9000 220"""
@@ -828,6 +840,7 @@ class Th9000144Radio(Th9000220Radio):
MODEL = "TH9000_144"
BAUD_RATE = 9600
valid_freq = [(136000000, 174000000)]
+ ALIASES = [LT580VHF, ]
@directory.register
class Th9000440Radio(Th9000220Radio):
@@ -836,3 +849,4 @@ class Th9000440Radio(Th9000220Radio):
MODEL = "TH9000_440"
BAUD_RATE = 9600
valid_freq = [(400000000, 490000000)]
+ ALIASES = [LT580UHF, ]
diff --git a/chirp/drivers/th_uv3r.py b/chirp/drivers/th_uv3r.py
index 10273a2..8e40f3a 100644
--- a/chirp/drivers/th_uv3r.py
+++ b/chirp/drivers/th_uv3r.py
@@ -179,7 +179,7 @@ class TYTUV3RRadio(chirp_common.CloneModeRadio):
rx_mode = tx_mode = "Tone"
rx_tone = tx_tone = _tone(mem.ctone)
elif mem.tmode == "DTCS":
- rx_tone = tx_tone = "DTCS"
+ rx_mode = tx_mode = "DTCS"
tx_tone = _dcs(mem.dtcs, mem.dtcs_polarity[0])
rx_tone = _dcs(mem.dtcs, mem.dtcs_polarity[1])
elif mem.tmode == "Cross":
diff --git a/chirp/drivers/thd72.py b/chirp/drivers/thd72.py
index 2cf978f..71614a3 100644
--- a/chirp/drivers/thd72.py
+++ b/chirp/drivers/thd72.py
@@ -69,6 +69,12 @@ struct {
u8 passwd[6];
} frontmatter;
+#seekto 0x02c0;
+struct {
+ ul32 start_freq;
+ ul32 end_freq;
+} prog_vfo[6];
+
#seekto 0x0300;
struct {
char power_on_msg[8];
@@ -89,8 +95,8 @@ struct {
#seekto 0x0c00;
struct {
- u8 disabled:7,
- unknown0:1;
+ u8 disabled:4,
+ prog_vfo:4;
u8 skip;
} flag[1032];
@@ -183,8 +189,24 @@ DUPLEX_REV = {
EXCH_R = "R\x00\x00\x00\x00"
EXCH_W = "W\x00\x00\x00\x00"
-# Uploads result in "MCP Error" and garbage data in memory
-# Clone driver disabled in favor of error-checking live driver.
+DEFAULT_PROG_VFO = (
+ (136000000, 174000000),
+ (410000000, 470000000),
+ (118000000, 136000000),
+ (136000000, 174000000),
+ (320000000, 400000000),
+ (400000000, 524000000),
+)
+# index of PROG_VFO used for setting memory.unknown1 and memory.unknown2
+# see http://chirp.danplanet.com/issues/1611#note-9
+UNKNOWN_LOOKUP = (0, 7, 4, 0, 4, 7)
+
+
+def get_prog_vfo(frequency):
+ for i, (start, end) in enumerate(DEFAULT_PROG_VFO):
+ if start <= frequency < end:
+ return i
+ raise ValueError("Frequency is out of range.")
@directory.register
@@ -283,7 +305,7 @@ class THD72Radio(chirp_common.CloneModeRadio):
def get_raw_memory(self, number):
return repr(self._memobj.memory[number]) + \
- repr(self._memobj.flag[(number)])
+ repr(self._memobj.flag[number])
def get_memory(self, number):
if isinstance(number, str):
@@ -305,7 +327,7 @@ class THD72Radio(chirp_common.CloneModeRadio):
if number > 999:
mem.extd_number = THD72_SPECIAL_REV[number]
- if flag.disabled == 0x7f:
+ if flag.disabled == 0xf:
mem.empty = True
return mem
@@ -348,9 +370,9 @@ class THD72Radio(chirp_common.CloneModeRadio):
self.add_dirty_block(self._memobj.flag[mem.number])
# only delete non-WX channels
- was_empty = flag.disabled == 0x7f
+ was_empty = flag.disabled == 0xf
if mem.empty:
- flag.disabled = 0x7f
+ flag.disabled = 0xf
return
flag.disabled = 0
@@ -373,6 +395,10 @@ class THD72Radio(chirp_common.CloneModeRadio):
_mem.offset = mem.offset
_mem.mode = MODES_REV[mem.mode]
+ prog_vfo = get_prog_vfo(mem.freq)
+ flag.prog_vfo = prog_vfo
+ _mem.unknown1 = _mem.unknown2 = UNKNOWN_LOOKUP[prog_vfo]
+
if mem.number < 999:
flag.skip = chirp_common.SKIP_VALUES.index(mem.skip)
@@ -509,9 +535,8 @@ class THD72Radio(chirp_common.CloneModeRadio):
raise errors.RadioError("No response to ID command")
def initialize(self, mmap):
- mmap[0] = \
- "\x80\xc8\xb3\x08\x00\x01\x00\x08" + \
- "\x08\x00\xc0\x27\x09\x00\x00\xff"
+ mmap.set_raw("\x00\xc8\xb3\x08\x00\x01\x00\x08"
+ "\x08\x00\xc0\x27\x09\x00\x00\x00")
def _get_settings(self):
top = RadioSettings(self._get_display_settings(),
diff --git a/chirp/drivers/tk270.py b/chirp/drivers/tk270.py
index 1b76027..766b8d2 100644
--- a/chirp/drivers/tk270.py
+++ b/chirp/drivers/tk270.py
@@ -292,21 +292,16 @@ def do_upload(radio):
handshake(radio, "Rx error in block %03i" % addr)
-def get_rid(data):
+def get_radio_id(data):
"""Extract the radio identification from the firmware"""
- rid = data[0x03d0:0x03d8]
- # we have to invert rid
- nrid = ""
- for i in range(1, len(rid) + 1):
- nrid += rid[-i]
- rid = nrid
-
- return rid
+ # Reverse the radio id string. MemoryMap does not support the step/stride
+ # slice argument, so it is first sliced to a str then reversed.
+ return data[0x03d0:0x03d8][::-1]
def model_match(cls, data):
"""Match the opened/downloaded image to the correct version"""
- rid = get_rid(data)
+ rid = get_radio_id(data)
# DEBUG
#print("Full ident string is %s" % util.hexprint(rid))
@@ -331,13 +326,16 @@ class Kenwood_P60_Radio(chirp_common.CloneModeRadio, chirp_common.ExperimentalRa
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'
+ ('This driver is experimental; not all features have been '
+ 'implemented, but it has those features most used by hams.\n'
'\n'
- 'This is a first release, so it will contains bugs, use it'
- 'on your own risk, keep a mem backup with the original '
- 'software at hand\n'
+ 'This radios are able to work slightly outside the OEM '
+ 'frequency limits. After testing, the limit in Chirp has '
+ 'been set 4% outside the OEM limit. This allows you to use '
+ 'some models on the ham bands.\n'
'\n'
+ 'Nevertheless, each radio has its own hardware limits and '
+ 'your mileage may vary.\n'
)
rp.pre_download = _(dedent("""\
Follow this instructions to read your radio:
@@ -408,19 +406,23 @@ class Kenwood_P60_Radio(chirp_common.CloneModeRadio, chirp_common.ExperimentalRa
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)
+ rid = get_radio_id(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]
+
+ # Frequency ranges: some model/variants are able to work the near
+ # ham bands, even if they are outside the OEM ranges.
+ # By experimentation we found that a +/- 4% at the edges is in most
+ # cases safe and will cover the near ham band in full
+ self._range = [low * 1000000 * 0.96, high * 1000000 * 1.04]
# 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"
+ # In the OEM string we show the real OEM ranges
+ self._VARIANT += self._kind + ", %d - %d MHz" % (low, high)
# DEBUG
#print self._VARIANT
@@ -470,9 +472,6 @@ class Kenwood_P60_Radio(chirp_common.CloneModeRadio, chirp_common.ExperimentalRa
res = self._memobj.active[byte] & (pow(2, bit))
res = not bool(res)
- # DEBUG
- #print("GET Chan %s, Byte %s, Bit %s, Active %s" % (chan, byte, bit, int(res)))
-
return res
def set_active(self, chan, value=True):
@@ -942,4 +941,3 @@ class TK378_Radio(Kenwood_P60_Radio):
"P0378\x04\x00\x00": (32, 370, 470, "SP1"),
"P0378\x02\x00\x00": (32, 350, 427, "SP2"),
}
-
diff --git a/chirp/drivers/tk760.py b/chirp/drivers/tk760.py
index 760a3e3..c9a2ff1 100644
--- a/chirp/drivers/tk760.py
+++ b/chirp/drivers/tk760.py
@@ -333,10 +333,10 @@ def model_match(cls, data):
return False
-class Kenwood_M60_Radio(chirp_common.CloneModeRadio):
+class Kenwood_M60_Radio(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
"""Kenwood Mobile Family 60 Radios"""
VENDOR = "Kenwood"
- _range = [136000000, 500000000] # don't mind, it will be overited
+ _range = [136000000, 500000000] # don't mind, it will be overwritten
_upper = 32
VARIANT = ""
MODEL = ""
@@ -345,18 +345,16 @@ class Kenwood_M60_Radio(chirp_common.CloneModeRadio):
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'
+ ('This driver is experimental; not all features have been '
+ 'implemented, but it has those features most used by hams.\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'
+ 'This radios are able to work slightly outside the OEM '
+ 'frequency limits. After testing, the limit in Chirp has '
+ 'been set 4% outside the OEM limit. This allows you to use '
+ 'some models on the ham bands.\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.'
+ 'Nevertheless, each radio has its own hardware limits and '
+ 'your mileage may vary.\n'
)
rp.pre_download = _(dedent("""\
Follow this instructions to read your radio:
@@ -432,14 +430,18 @@ class Kenwood_M60_Radio(chirp_common.CloneModeRadio):
# 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]
+
+ # Frequency ranges: some model/variants are able to work the near
+ # ham bands, even if they are outside the OEM ranges.
+ # By experimentation we found that a +/- 4% at the edges is in most
+ # cases safe and will cover the near ham band in full
+ self._range = [low * 1000000 * 0.96, high * 1000000 * 1.04]
# 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"
+ # In the OEM string we show the real OEM ranges
+ self._VARIANT += self._kind + ", %d - %d MHz" % (low, high)
except KeyError:
LOG.debug("Wrong Kenwood radio, ID or unknown variant")
@@ -814,7 +816,7 @@ class TK760_Radio(Kenwood_M60_Radio):
TYPE = "M0760"
VARIANTS = {
"M0760\x01\x00\x00": (32, 136, 156, "K2"),
- "M0760\x00\x00\x00": (32, 144, 174, "K") # 148-147 Original
+ "M0760\x00\x00\x00": (32, 148, 174, "K")
}
@@ -825,7 +827,7 @@ class TK762_Radio(Kenwood_M60_Radio):
TYPE = "M0762"
VARIANTS = {
"M0762\x01\x00\x00": (2, 136, 156, "K2"),
- "M0762\x00\x00\x00": (2, 144, 174, "K") # 148-147 Original
+ "M0762\x00\x00\x00": (2, 148, 174, "K")
}
@@ -836,7 +838,7 @@ class TK768_Radio(Kenwood_M60_Radio):
TYPE = "M0768"
VARIANTS = {
"M0768\x21\x00\x00": (32, 136, 156, "K2"),
- "M0768\x20\x00\x00": (32, 144, 174, "K") # 148-147 Original
+ "M0768\x20\x00\x00": (32, 148, 174, "K")
}
@@ -846,7 +848,7 @@ class TK860_Radio(Kenwood_M60_Radio):
MODEL = "TK-860"
TYPE = "M0860"
VARIANTS = {
- "M0860\x05\x00\x00": (32, 406, 440, "F4"), # 406-430 Original
+ "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")
@@ -859,7 +861,7 @@ class TK862_Radio(Kenwood_M60_Radio):
MODEL = "TK-862"
TYPE = "M0862"
VARIANTS = {
- "M0862\x05\x00\x00": (2, 406, 440, "F4"), # 406-430 Original
+ "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")
@@ -872,7 +874,7 @@ class TK868_Radio(Kenwood_M60_Radio):
MODEL = "TK-868"
TYPE = "M0868"
VARIANTS = {
- "M0868\x25\x00\x00": (32, 406, 440, "F4"), # 406-430 Original
+ "M0868\x25\x00\x00": (32, 406, 430, "F4"),
"M0868\x24\x00\x00": (32, 488, 512, "F3"),
"M0868\x23\x00\x00": (32, 470, 496, "F2"),
"M0868\x22\x00\x00": (32, 450, 476, "F1")
diff --git a/chirp/drivers/tk760g.py b/chirp/drivers/tk760g.py
index 6563e06..0f90d5e 100644
--- a/chirp/drivers/tk760g.py
+++ b/chirp/drivers/tk760g.py
@@ -696,7 +696,7 @@ class memBank(chirp_common.Bank):
index = 0
-class Kenwood_Serie_60G(chirp_common.CloneModeRadio):
+class Kenwood_Serie_60G(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
"""Kenwood Serie 60G Radios base class"""
VENDOR = "Kenwood"
BAUD_RATE = 9600
@@ -715,28 +715,16 @@ class Kenwood_Serie_60G(chirp_common.CloneModeRadio):
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.'
- ''
- 'The band limits on some of this radios are set here far from'
- 'the vendor limits to cover most of the ham bands. It is a'
- 'known fact that this radios can work safely outside the OEM'
- 'limits, but each radio is different, you may find in trouble'
- 'with some particular radios, but this is also possible with'
- 'the OEM software; as usual YMMV.'
- ''
- 'A detail: this driver is slow reading from the radio in'
- 'Windows, due to the way windows serial works.'
+ ('This driver is experimental; not all features have been '
+ 'implemented, but it has those features most used by hams.\n'
+ '\n'
+ 'This radios are able to work slightly outside the OEM '
+ 'frequency limits. After testing, the limit in Chirp has '
+ 'been set 4% outside the OEM limit. This allows you to use '
+ 'some models on the ham bands.\n'
+ '\n'
+ 'Nevertheless, each radio has its own hardware limits and '
+ 'your mileage may vary.\n'
)
rp.pre_download = _(dedent("""\
Follow this instructions to download your info:
@@ -797,7 +785,7 @@ class Kenwood_Serie_60G(chirp_common.CloneModeRadio):
"""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"""
+ and a last one in x7000 with flag data"""
rchs = 0
data = dict()
@@ -830,7 +818,7 @@ class Kenwood_Serie_60G(chirp_common.CloneModeRadio):
k = 1
raise errors.InvalidValueError(
"Invalid bank value '%k', bad data in the image? \
- Triying to fix this, review your bank data!" % k)
+ Trying to fix this, review your bank data!" % k)
c = 1
for i in v:
fdata += chr(k) + chr(c) + chr(k - 1) + chr(i)
@@ -877,7 +865,12 @@ class Kenwood_Serie_60G(chirp_common.CloneModeRadio):
# 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]
+
+ # Frequency ranges: some model/variants are able to work the near
+ # ham bands, even if they are outside the OEM ranges.
+ # By experimentation we found that 4% at the edges is in most
+ # cases safe and will cover the near ham bands in full
+ self._range = [low * 1000000 * 0.96, high * 1000000 * 1.04]
# setting the bank data in the features, 8 & 16 CH dont have banks
if self._upper < 32:
@@ -887,8 +880,8 @@ class Kenwood_Serie_60G(chirp_common.CloneModeRadio):
# 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"
+ # In the OEM string we show the real OEM ranges
+ self._VARIANT += self._kind + ", %d - %d MHz" % (low, high)
except KeyError:
LOG.debug("Wrong Kenwood radio, ID or unknown variant")
@@ -1261,13 +1254,8 @@ class Kenwood_Serie_60G(chirp_common.CloneModeRadio):
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]
+ valid_chars = ",-/:[]" + chirp_common.CHARSET_ALPHANUMERIC
+ mstr = "".join([c for c in self._VARIANT if c in valid_chars])
val = RadioSettingValueString(0, 35, mstr)
val.set_mutable(False)
@@ -1539,24 +1527,13 @@ class Kenwood_Serie_60G(chirp_common.CloneModeRadio):
#
# WARNING !!!! Radios With Password in the data section ###############
#
-# when a radio has a data password (aka to program it) the last byte (#8)
+# 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
-#
-# WARNING !!!! the band limits on some of this radios are set here far from the
-# vendor limits to cover most of the ham bands. It's a known fact that this
-# radios can work safely outside the OEM limits, but each radio is different,
-# you may find in trouble with some particular radios, but this is also
-# possible with the OEM software, as usual YMMV.
-#
-# Thanks to Stephen (GN5SWP) with his work characterizing some of the members of
-# this family about the safe band limits.
-#
-# In the radios with mod band limits you will find a comment next to it with
-# the OEM limits just for the record.
+# Translation: Chirps will read and write password protected radios
+# with no problem.
@directory.register
@@ -1567,8 +1544,8 @@ class TK868G_Radios(Kenwood_Serie_60G):
VARIANTS = {
"M8680\x18\xff": (8, 400, 490, "M"),
"M8680;\xff": (128, 350, 390, "C1"),
- "M86808\xff": (128, 400, 450, "C2"), # 400-430Mhz original
- "M86806\xff": (128, 430, 490, "C3"), # 450-490Mhz original
+ "M86808\xff": (128, 400, 430, "C2"),
+ "M86806\xff": (128, 450, 490, "C3"),
}
@@ -1580,8 +1557,8 @@ class TK862G_Radios(Kenwood_Serie_60G):
VARIANTS = {
"M8620\x06\xff": (8, 450, 490, "K"),
"M8620\x07\xff": (8, 485, 512, "K2"),
- "M8620&\xff": (8, 430, 470, "E"), # 440-470Mhz original
- "M8620V\xff": (8, 430, 470, "(N)E"), # 440-470Mhz original
+ "M8620&\xff": (8, 440, 470, "E"),
+ "M8620V\xff": (8, 440, 470, "(N)E"),
}
@@ -1591,11 +1568,11 @@ class TK860G_Radios(Kenwood_Serie_60G):
MODEL = "TK-860G"
TYPE = "M8600"
VARIANTS = {
- "M8600\x08\xff": (128, 400, 450, "K"), # 400-430Mhz original
- "M8600\x06\xff": (128, 430, 490, "K1"), # 450-490Mhz original
+ "M8600\x08\xff": (128, 400, 430, "K"),
+ "M8600\x06\xff": (128, 450, 490, "K1"),
"M8600\x07\xff": (128, 485, 512, "K2"),
- "M8600\x18\xff": (128, 400, 450, "M"), # 400-430Mhz original
- "M8600\x16\xff": (128, 430, 490, "M1"), # 450-490Mhz original
+ "M8600\x18\xff": (128, 400, 430, "M"),
+ "M8600\x16\xff": (128, 450, 490, "M1"),
"M8600\x17\xff": (128, 485, 520, "M2"),
}
@@ -1608,9 +1585,9 @@ class TK768G_Radios(Kenwood_Serie_60G):
# Note that 8 CH don't have banks
VARIANTS = {
"M7680\x15\xff": (8, 136, 162, "M2"),
- "M7680\x14\xff": (8, 144, 174, "M"), # 148-174 original
+ "M7680\x14\xff": (8, 148, 174, "M"),
"M76805\xff": (128, 136, 162, "C2"),
- "M76804\xff": (128, 144, 174, "C"), # 148-174 original
+ "M76804\xff": (128, 148, 174, "C"),
}
@@ -1622,9 +1599,9 @@ class TK762G_Radios(Kenwood_Serie_60G):
# Note that 8 CH don't have banks
VARIANTS = {
"M7620\x05\xff": (8, 136, 162, "K2"),
- "M7620\x04\xff": (8, 144, 172, "K"), # 148-172 original
- "M7620$\xff": (8, 144, 172, "E"), # 148-172 original
- "M7620T\xff": (8, 144, 172, "NE"), # 148-172 original
+ "M7620\x04\xff": (8, 148, 172, "K"),
+ "M7620$\xff": (8, 148, 172, "E"),
+ "M7620T\xff": (8, 148, 172, "NE"),
}
@@ -1635,9 +1612,9 @@ class TK760G_Radios(Kenwood_Serie_60G):
TYPE = "M7600"
VARIANTS = {
"M7600\x05\xff": (128, 136, 162, "K2"),
- "M7600\x04\xff": (128, 138, 174, "K"), # 148-174 original
- "M7600\x14\xff": (128, 138, 174, "M"), # 148-174 original
- "M7600T\xff": (128, 138, 174, "NE") # 148-174 original
+ "M7600\x04\xff": (128, 148, 174, "K"),
+ "M7600\x14\xff": (128, 148, 174, "M"),
+ "M7600T\xff": (128, 148, 174, "NE")
}
@@ -1657,10 +1634,10 @@ class TK378G_Radios(Kenwood_Serie_60G):
MODEL = "TK-378G"
TYPE = "P3780"
VARIANTS = {
- "P3780\x16\xff": (16, 440, 470, "M"), # 450-470 Mhz original
- "P3780\x17\xff": (16, 400, 430, "M1"), # 400-420 Mhz original
+ "P3780\x16\xff": (16, 450, 470, "M"),
+ "P3780\x17\xff": (16, 400, 420, "M1"),
"P3780\x36\xff": (128, 490, 512, "C"),
- "P3780\x39\xff": (128, 403, 440, "C1") # 403-430 Mhz original
+ "P3780\x39\xff": (128, 403, 430, "C1")
}
@@ -1670,10 +1647,10 @@ class TK372G_Radios(Kenwood_Serie_60G):
MODEL = "TK-372G"
TYPE = "P3720"
VARIANTS = {
- "P3720\x06\xff": (32, 440, 470, "K"), # 450-470 Mhz original
+ "P3720\x06\xff": (32, 450, 470, "K"),
"P3720\x07\xff": (32, 470, 490, "K1"),
"P3720\x08\xff": (32, 490, 512, "K2"),
- "P3720\x09\xff": (32, 403, 440, "K3") # 403-430 Mhz original
+ "P3720\x09\xff": (32, 403, 430, "K3")
}
@@ -1683,16 +1660,16 @@ class TK370G_Radios(Kenwood_Serie_60G):
MODEL = "TK-370G"
TYPE = "P3700"
VARIANTS = {
- "P3700\x06\xff": (128, 440, 470, "K"), # 450-470 Mhz original
+ "P3700\x06\xff": (128, 450, 470, "K"),
"P3700\x07\xff": (128, 470, 490, "K1"),
"P3700\x08\xff": (128, 490, 512, "K2"),
- "P3700\x09\xff": (128, 403, 440, "K3"), # 403-430 Mhz original
- "P3700\x16\xff": (128, 440, 470, "M"), # 450-470 Mhz original
+ "P3700\x09\xff": (128, 403, 430, "K3"),
+ "P3700\x16\xff": (128, 450, 470, "M"),
"P3700\x17\xff": (128, 470, 490, "M1"),
"P3700\x18\xff": (128, 490, 520, "M2"),
- "P3700\x19\xff": (128, 403, 440, "M3"), # 403-430 Mhz original
- "P3700&\xff": (128, 430, 470, "E"), # 440-470 Mhz original
- "P3700V\xff": (128, 430, 470, "NE") # 440-470 Mhz original
+ "P3700\x19\xff": (128, 403, 430, "M3"),
+ "P3700&\xff": (128, 440, 470, "E"),
+ "P3700V\xff": (128, 440, 470, "NE")
}
@@ -1702,17 +1679,17 @@ class TK360G_Radios(Kenwood_Serie_60G):
MODEL = "TK-360G"
TYPE = "P3600"
VARIANTS = {
- "P3600\x06\xff": (8, 440, 470, "K"), # 450-470 Mhz original
+ "P3600\x06\xff": (8, 450, 470, "K"),
"P3600\x07\xff": (8, 470, 490, "K1"),
"P3600\x08\xff": (8, 490, 512, "K2"),
- "P3600\x09\xff": (8, 403, 440, "K3"), # 403-430 Mhz original
- "P3600&\xff": (8, 430, 470, "E"), # 440-470 Mhz original
- "P3600)\xff": (8, 406, 440, "E1"), # 403-430 Mhz original
- "P3600\x16\xff": (8, 440, 470, "M"), # 450-470 Mhz original
+ "P3600\x09\xff": (8, 403, 430, "K3"),
+ "P3600&\xff": (8, 440, 470, "E"),
+ "P3600)\xff": (8, 406, 430, "E1"),
+ "P3600\x16\xff": (8, 450, 470, "M"),
"P3600\x17\xff": (8, 470, 490, "M1"),
- "P3600\x19\xff": (8, 403, 440, "M2"), # 403-430 Mhz original
- "P3600V\xff": (8, 430, 470, "NE"), # 440-470 Mhz original
- "P3600Y\xff": (8, 403, 440, "NE1") # 403-430 Mhz original
+ "P3600\x19\xff": (8, 403, 430, "M2"),
+ "P3600V\xff": (8, 440, 470, "NE"),
+ "P3600Y\xff": (8, 403, 430, "NE1")
}
@@ -1724,9 +1701,9 @@ class TK278G_Radios(Kenwood_Serie_60G):
# Note that 16 CH don't have banks
VARIANTS = {
"P27805\xff": (128, 136, 150, "C1"),
- "P27804\xff": (128, 144, 174, "C"), # 150-174 original
+ "P27804\xff": (128, 150, 174, "C"),
"P2780\x15\xff": (16, 136, 150, "M1"),
- "P2780\x14\xff": (16, 144, 174, "M") # 150-174 original
+ "P2780\x14\xff": (16, 150, 174, "M")
}
@@ -1737,7 +1714,7 @@ class TK272G_Radios(Kenwood_Serie_60G):
TYPE = "P2720"
VARIANTS = {
"P2720\x05\xfb": (32, 136, 150, "K1"),
- "P2720\x04\xfb": (32, 144, 174, "K") # 150-174 original
+ "P2720\x04\xfb": (32, 150, 174, "K")
}
@@ -1747,11 +1724,11 @@ class TK270G_Radios(Kenwood_Serie_60G):
MODEL = "TK-270G"
TYPE = "P2700"
VARIANTS = {
- "P2700T\xff": (128, 144, 174, "NE/NT"), # 146-174 original
- "P2700$\xff": (128, 144, 174, "E"), # 146-174 original
- "P2700\x14\xff": (128, 144, 174, "M"), # 150-174 original
+ "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, 144, 174, "K") # 150-174 original
+ "P2700\x04\xff": (128, 150, 174, "K")
}
@@ -1763,9 +1740,9 @@ class TK260G_Radios(Kenwood_Serie_60G):
TYPE = "P2600"
VARIANTS = {
"P2600U\xff": (8, 136, 150, "N1"),
- "P2600T\xff": (8, 144, 174, "N"), # 146-174 original
- "P2600$\xff": (8, 144, 174, "E"), # 144-174 original
- "P2600\x14\xff": (8, 144, 174, "M"), # 150-174 original
+ "P2600T\xff": (8, 146, 174, "N"),
+ "P2600$\xff": (8, 150, 174, "E"),
+ "P2600\x14\xff": (8, 150, 174, "M"),
"P2600\x05\xff": (8, 136, 150, "K1"),
- "P2600\x04\xff": (8, 144, 174, "K") # 150-174 original
+ "P2600\x04\xff": (8, 150, 174, "K")
}
diff --git a/chirp/drivers/tk8102.py b/chirp/drivers/tk8102.py
index b4491c4..cd4a84c 100644
--- a/chirp/drivers/tk8102.py
+++ b/chirp/drivers/tk8102.py
@@ -302,30 +302,33 @@ class KenwoodTKx102Radio(chirp_common.CloneModeRadio):
val += 0xA000
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
+ rx_mode = tx_mode = None
+ rx_tone = 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
+ if mem.tmode == "Tone":
+ tx_mode = "Tone"
+ rx_mode = None
+ tx_tone = int(mem.rtone * 10)
+ elif mem.tmode == "TSQL":
+ rx_mode = tx_mode = "Tone"
+ rx_tone = tx_tone = int(mem.ctone * 10)
+ elif mem.tmode == "DTCS":
+ tx_mode = rx_mode = "DTCS"
+ tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
+ rx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1])
+ elif mem.tmode == "Cross":
+ tx_mode, rx_mode = mem.cross_mode.split("->")
+ if tx_mode == "DTCS":
+ tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
+ elif tx_mode == "Tone":
+ tx_tone = int(mem.rtone * 10)
+ if rx_mode == "DTCS":
+ rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
+ elif rx_mode == "Tone":
+ rx_tone = int(mem.ctone * 10)
+
+ _mem.rx_tone = rx_tone
+ _mem.tx_tone = tx_tone
LOG.debug("Set TX %s (%i) RX %s (%i)" %
(tx_mode, _mem.tx_tone, rx_mode, _mem.rx_tone))
diff --git a/chirp/drivers/uv5r.py b/chirp/drivers/uv5r.py
index 83ce148..0cb46bb 100644
--- a/chirp/drivers/uv5r.py
+++ b/chirp/drivers/uv5r.py
@@ -282,7 +282,7 @@ vhf_220_radio = "\x02"
BASETYPE_UV5R = ["BFS", "BFB", "N5R-2", "N5R2", "N5RV", "BTS", "D5R2"]
BASETYPE_F11 = ["USA"]
-BASETYPE_UV82 = ["US2S", "B82S", "BF82", "N82-2", "N822"]
+BASETYPE_UV82 = ["US2S2", "B82S", "BF82", "N82-2", "N822"]
BASETYPE_BJ55 = ["BJ55"] # needed for for the Baojie UV-55 in bjuv55.py
BASETYPE_UV6 = ["BF1", "UV6"]
BASETYPE_KT980HP = ["BFP3V3 B"]
@@ -395,7 +395,7 @@ def _firmware_version_from_image(radio):
return version
-def _do_ident(radio, magic):
+def _do_ident(radio, magic, secondack=True):
serial = radio.pipe
serial.timeout = 1
@@ -448,10 +448,11 @@ def _do_ident(radio, magic):
LOG.debug(msg)
raise errors.RadioError("Unexpected response from radio.")
- serial.write("\x06")
- ack = serial.read(1)
- if ack != "\x06":
- raise errors.RadioError("Radio refused clone")
+ if secondack:
+ serial.write("\x06")
+ ack = serial.read(1)
+ if ack != "\x06":
+ raise errors.RadioError("Radio refused clone")
return ident
@@ -501,6 +502,11 @@ def _get_radio_firmware_version(radio):
return version
+IDENT_BLACKLIST = {
+ "\x50\x0D\x0C\x20\x16\x03\x28": "Radio identifies as BTECH UV-5X3",
+}
+
+
def _ident_radio(radio):
for magic in radio._idents:
error = None
@@ -511,6 +517,22 @@ def _ident_radio(radio):
LOG.error(e)
error = e
time.sleep(2)
+
+ for magic, reason in IDENT_BLACKLIST.items():
+ try:
+ _do_ident(radio, magic, secondack=False)
+ except errors.RadioError as e:
+ # No match, try the next one
+ continue
+
+ # If we got here, it means we identified the radio as
+ # something other than one of our valid idents. Warn
+ # the user so they can do the right thing.
+ LOG.warning(('Identified radio as a blacklisted model '
+ '(details: %s)') % reason)
+ raise errors.RadioError(('%s. Please choose the proper vendor/'
+ 'model and try again.') % reason)
+
if error:
raise error
raise errors.RadioError("Radio did not respond")
@@ -522,8 +544,29 @@ def _do_download(radio):
radio_version = _get_radio_firmware_version(radio)
LOG.info("Radio Version is %s" % repr(radio_version))
- if not any(type in radio_version for type in radio._basetype):
+ if "HN5RV" in radio_version:
+ # A radio with HN5RV firmware has been detected. It could be a
+ # UV-5R style radio with HIGH/LOW power levels or it could be a
+ # BF-F8HP style radio with HIGH/MID/LOW power levels.
+ # We are going to count on the user to make the right choice and
+ # then append that model type to the end of the image so it can
+ # be properly detected when loaded.
+ append_model = True
+ elif "\xFF" * 14 in radio_version:
+ # A radio UV-5R style radio that reports no firmware version has
+ # been detected.
+ # We are going to count on the user to make the right choice and
+ # then append that model type to the end of the image so it can
+ # be properly detected when loaded.
+ append_model = True
+ elif not any(type in radio_version for type in radio._basetype):
+ # This radio can't be properly detected by parsing its firmware
+ # version.
raise errors.RadioError("Incorrect 'Model' selected.")
+ else:
+ # This radio can be properly detected by parsing its firmware version.
+ # There is no need to append its model type to the end of the image.
+ append_model = False
# Main block
LOG.debug("downloading main block...")
@@ -536,6 +579,10 @@ def _do_download(radio):
# Auxiliary block starts at 0x1ECO (?)
for i in range(0x1EC0, 0x2000, 0x40):
data += _read_block(radio, i, 0x40, False)
+
+ if append_model:
+ data += radio.MODEL.ljust(8)
+
LOG.debug("done.")
return memmap.MemoryMap(data)
@@ -564,34 +611,68 @@ def _do_upload(radio):
LOG.info("Image Version is %s" % repr(image_version))
LOG.info("Radio Version is %s" % repr(radio_version))
- if image_version != radio_version:
+ # default ranges
+ _ranges_main_default = [
+ (0x0008, 0x0CF8),
+ (0x0D08, 0x0DF8),
+ (0x0E08, 0x1808)
+ ]
+ _ranges_aux_default = [
+ (0x1EC0, 0x1EF0),
+ ]
+
+ # extra aux ranges
+ _ranges_aux_extra = [
+ (0x1F60, 0x1F70),
+ (0x1F80, 0x1F90),
+ (0x1FC0, 0x1FD0)
+ ]
+
+ if image_version == radio_version:
+ image_matched_radio = True
+ if image_version.startswith("HN5RV"):
+ ranges_main = _ranges_main_default
+ ranges_aux = _ranges_aux_default + _ranges_aux_extra
+ elif image_version == 0xFF * 14:
+ ranges_main = _ranges_main_default
+ ranges_aux = _ranges_aux_default + _ranges_aux_extra
+ else:
+ ranges_main = radio._ranges_main
+ ranges_aux = radio._ranges_aux
+ elif any(type in radio_version for type in radio._basetype):
+ image_matched_radio = False
+ ranges_main = _ranges_main_default
+ ranges_aux = _ranges_aux_default
+ else:
msg = ("The upload was stopped because the firmware "
"version of the image (%s) does not match that "
"of the radio (%s).")
raise errors.RadioError(msg % (image_version, radio_version))
# Main block
- for i in range(0x08, 0x1808, 0x10):
- _send_block(radio, i - 0x08, radio.get_mmap()[i:i + 0x10])
- _do_status(radio, i)
- _do_status(radio, radio.get_memsize())
+ for start_addr, end_addr in ranges_main:
+ for i in range(start_addr, end_addr, 0x10):
+ _send_block(radio, i - 0x08, radio.get_mmap()[i:i + 0x10])
+ _do_status(radio, i)
+ _do_status(radio, radio.get_memsize())
if len(radio.get_mmap().get_packed()) == 0x1808:
LOG.info("Old image, not writing aux block")
return # Old image, no aux block
- if image_version != radio_version:
+ # Auxiliary block at radio address 0x1EC0, our offset 0x1808
+ for start_addr, end_addr in ranges_aux:
+ for i in range(start_addr, end_addr, 0x10):
+ addr = 0x1808 + (i - 0x1EC0)
+ _send_block(radio, i, radio.get_mmap()[addr:addr + 0x10])
+
+ if not image_matched_radio:
msg = ("Upload finished, but the 'Other Settings' "
"could not be sent because the firmware "
"version of the image (%s) does not match "
"that of the radio (%s).")
raise errors.RadioError(msg % (image_version, radio_version))
- # Auxiliary block at radio address 0x1EC0, our offset 0x1808
- for i in range(0x1EC0, 0x2000, 0x10):
- addr = 0x1808 + (i - 0x1EC0)
- _send_block(radio, i, radio.get_mmap()[addr:addr + 0x10])
-
UV5R_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=4.00),
chirp_common.PowerLevel("Low", watts=1.00)]
@@ -605,6 +686,20 @@ UV5R_CHARSET = chirp_common.CHARSET_UPPER_NUMERIC + \
"!@#$%^&*()+-=[]:\";'<>?,./"
+def model_match(cls, data):
+ """Match the opened/downloaded image to the correct version"""
+
+ if len(data) == 0x1950:
+ rid = data[0x1948:0x1950]
+ return rid.startswith(cls.MODEL)
+ elif len(data) == 0x1948:
+ rid = data[cls._fw_ver_file_start:cls._fw_ver_file_stop]
+ if any(type in rid for type in cls._basetype):
+ return True
+ else:
+ return False
+
+
class BaofengUV5R(chirp_common.CloneModeRadio,
chirp_common.ExperimentalRadio):
@@ -627,6 +722,13 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
_fw_ver_file_start = 0x1838
_fw_ver_file_stop = 0x1846
+ _ranges_main = [
+ (0x0008, 0x1808),
+ ]
+ _ranges_aux = [
+ (0x1EC0, 0x2000),
+ ]
+
@classmethod
def get_prompts(cls):
rp = chirp_common.RadioPrompts()
@@ -687,13 +789,10 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
def match_model(cls, filedata, filename):
match_size = False
match_model = False
- if len(filedata) in [0x1808, 0x1948]:
+ if len(filedata) in [0x1808, 0x1948, 0x1950]:
match_size = True
- fwdata = _firmware_version_from_data(filedata,
- cls._fw_ver_file_start,
- cls._fw_ver_file_stop)
- if any(type in fwdata for type in cls._basetype):
- match_model = True
+ match_model = model_match(cls, filedata)
+
if match_size and match_model:
return True
else:
@@ -1167,8 +1266,9 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
if self.MODEL == "UV-82HP":
# this is a UV-82HP only feature
- rs = RadioSetting("vfomrlock", "VFO/MR Switching",
- RadioSettingValueBoolean(_settings.vfomrlock))
+ rs = RadioSetting(
+ "vfomrlock", "VFO/MR Switching (BTech UV-82HP only)",
+ RadioSettingValueBoolean(_settings.vfomrlock))
advanced.append(rs)
if self.MODEL == "UV-82":
@@ -1179,15 +1279,15 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
if self.MODEL == "UV-82HP":
# this is an UV-82HP only feature
- rs = RadioSetting("singleptt", "Single PTT",
+ rs = RadioSetting("singleptt", "Single PTT (BTech UV-82HP only)",
RadioSettingValueBoolean(_settings.singleptt))
advanced.append(rs)
if self.MODEL == "UV-82HP":
# this is an UV-82HP only feature
- rs = RadioSetting("tdrch", "Tone Burst Frequency",
- RadioSettingValueList(
- RTONE_LIST, RTONE_LIST[_settings.tdrch]))
+ rs = RadioSetting(
+ "tdrch", "Tone Burst Frequency (BTech UV-82HP only)",
+ RadioSettingValueList(RTONE_LIST, RTONE_LIST[_settings.tdrch]))
advanced.append(rs)
if len(self._mmap.get_packed()) == 0x1808:
@@ -1648,9 +1748,14 @@ class RT5_TPAlias(chirp_common.Alias):
MODEL = "RT5(tri-power)"
+class RH5RAlias(chirp_common.Alias):
+ VENDOR = "Rugged"
+ MODEL = "RH5R"
+
+
@directory.register
class BaofengUV5RGeneric(BaofengUV5R):
- ALIASES = [UV5XAlias, RT5RAlias, RT5RVAlias, RT5Alias]
+ ALIASES = [UV5XAlias, RT5RAlias, RT5RVAlias, RT5Alias, RH5RAlias]
@directory.register
@@ -1734,7 +1839,7 @@ class IntekKT980Radio(BaofengUV5R):
class BaofengBFF8HPRadio(BaofengUV5R):
VENDOR = "Baofeng"
MODEL = "BF-F8HP"
- ALIASES = [RT5_TPAlias, ]
+ ALIASES = [RT5_TPAlias]
_basetype = BASETYPE_F8HP
_idents = [UV5R_MODEL_291,
UV5R_MODEL_A58
diff --git a/chirp/drivers/uvb5.py b/chirp/drivers/uvb5.py
index f05aa73..3300005 100644
--- a/chirp/drivers/uvb5.py
+++ b/chirp/drivers/uvb5.py
@@ -238,10 +238,19 @@ def do_upload(radio):
radio.pipe.write(frame)
ack = radio.pipe.read(1)
if ack != "\x06":
- raise errors.RadioError("Radio NAK'd block at address 0x%04x" % i)
+ # UV-B5/UV-B6 radios with 27 menus do not support service settings
+ # and will stop ACKing when the upload reaches 0x0F10
+ if i == 0x0F10:
+ # must be a radio with 27 menus detected - stop upload
+ break
+ else:
+ LOG.debug("Radio NAK'd block at address 0x%04x" % i)
+ raise errors.RadioError(
+ "Radio NAK'd block at address 0x%04x" % i)
+ LOG.debug("Radio ACK'd block at address 0x%04x" % i)
do_status(radio, "to", i)
-DUPLEX = ["", "-", "+", 'off', "split"]
+DUPLEX = ["", "-", "+"]
UVB5_STEPS = [5.00, 6.25, 10.0, 12.5, 20.0, 25.0]
CHARSET = "0123456789- ABCDEFGHIJKLMNOPQRSTUVWXYZ/_+*"
SPECIALS = {
@@ -296,7 +305,7 @@ class BaofengUVB5(chirp_common.CloneModeRadio,
rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
"->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
- rf.valid_duplexes = DUPLEX
+ rf.valid_duplexes = DUPLEX + ["split"]
rf.can_odd_split = True
rf.valid_skips = ["", "S"]
rf.valid_characters = CHARSET
@@ -404,10 +413,6 @@ class BaofengUVB5(chirp_common.CloneModeRadio,
mem.skip = "" if _mem.scanadd else "S"
mem.power = POWER_LEVELS[_mem.highpower]
- if mem.freq == mem.offset and mem.duplex == "-":
- mem.duplex = "off"
- mem.offset = 0
-
if _nam:
for char in _nam:
try:
@@ -450,10 +455,7 @@ class BaofengUVB5(chirp_common.CloneModeRadio,
_mem.freq = mem.freq / 10
- if mem.duplex == "off":
- _mem.duplex = DUPLEX.index("-")
- _mem.offset = _mem.freq
- elif mem.duplex == "split":
+ if mem.duplex == "split":
diff = mem.offset - mem.freq
_mem.duplex = DUPLEX.index("-") if diff < 0 else DUPLEX.index("+")
_mem.offset = abs(diff) / 10
diff --git a/chirp/drivers/vx3.py b/chirp/drivers/vx3.py
index b187b79..31cd991 100644
--- a/chirp/drivers/vx3.py
+++ b/chirp/drivers/vx3.py
@@ -141,11 +141,18 @@ struct {
u8 banks_unk1;
#seekto 0x0356;
-u32 banks_unk2;
+struct {
+ u32 unmask;
+} banks_unmask1;
#seekto 0x0409;
u8 banks_unk3;
+#seekto 0x0416;
+struct {
+ u32 unmask;
+} banks_unmask2;
+
#seekto 0x04CA;
struct {
u8 memory[16];
@@ -300,7 +307,11 @@ class VX3BankModel(chirp_common.BankModel):
channels_in_bank.add(memory.number)
self._update_bank_with_channel_numbers(bank, channels_in_bank)
_bank_used = self._radio._memobj.bank_used[bank.index]
- _bank_used.in_use = 0x0000
+ _bank_used.in_use = ((len(channels_in_bank) - 1) * 2)
+ _banks_unmask1 = self._radio._memobj.banks_unmask1
+ _banks_unmask2 = self._radio._memobj.banks_unmask2
+ _banks_unmask1.unmask = 0x0017FFFF
+ _banks_unmask2.unmask = 0x0017FFFF
def remove_memory_from_mapping(self, memory, bank):
channels_in_bank = self._get_channel_numbers_in_bank(bank)
@@ -311,8 +322,10 @@ class VX3BankModel(chirp_common.BankModel):
(memory.number, bank))
self._update_bank_with_channel_numbers(bank, channels_in_bank)
- if not channels_in_bank:
- _bank_used = self._radio._memobj.bank_used[bank.index]
+ _bank_used = self._radio._memobj.bank_used[bank.index]
+ if channels_in_bank:
+ _bank_used.in_use = ((len(channels_in_bank) - 1) * 2)
+ else:
_bank_used.in_use = 0xFFFF
def get_mapping_memories(self, bank):
diff --git a/chirp/drivers/vx5.py b/chirp/drivers/vx5.py
index aa6d4bd..fea8659 100644
--- a/chirp/drivers/vx5.py
+++ b/chirp/drivers/vx5.py
@@ -16,6 +16,7 @@
from chirp.drivers import yaesu_clone
from chirp import chirp_common, directory, errors, bitwise
+from textwrap import dedent
MEM_FORMAT = """
#seekto 0x002A;
@@ -70,12 +71,7 @@ u8 current_bank;
TMODES = ["", "Tone", "TSQL", "DTCS"]
DUPLEX = ["", "-", "+", "split"]
MODES = ["FM", "AM", "WFM"]
-STEPS = list(chirp_common.TUNING_STEPS)
-STEPS.remove(6.25)
-STEPS.remove(30.0)
-STEPS.append(100.0)
-STEPS.append(9.0)
-
+STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0]
POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=5.00),
chirp_common.PowerLevel("L3", watts=2.50),
chirp_common.PowerLevel("L2", watts=1.00),
@@ -167,7 +163,7 @@ class VX5Radio(yaesu_clone.YaesuCloneModeRadio):
_block_size = 8
def _checksums(self):
- return [yaesu_clone.YaesuChecksum(0x0000, 0x1FB9)]
+ return [yaesu_clone.YaesuChecksum(0x0000, 0x1FB9, 0x1FBA)]
def get_features(self):
rf = chirp_common.RadioFeatures()
@@ -177,6 +173,7 @@ class VX5Radio(yaesu_clone.YaesuCloneModeRadio):
rf.has_dtcs_polarity = False
rf.valid_modes = MODES + ["NFM"]
rf.valid_tmodes = TMODES
+ rf.valid_tuning_steps = STEPS
rf.valid_duplexes = DUPLEX
rf.memory_bounds = (1, 220)
rf.valid_bands = [(500000, 16000000),
@@ -277,6 +274,26 @@ class VX5Radio(yaesu_clone.YaesuCloneModeRadio):
_flg.pskip = mem.skip == "P"
@classmethod
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.pre_download = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to MIC/EAR jack.
+ 3. Press and hold in the [F/W] key while turning the radio on
+ ("CLONE" will appear on the display).
+ 4. <b>After clicking OK</b>, press the [VFO(DW)SC] key to receive
+ the image from the radio."""))
+ rp.pre_upload = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to MIC/EAR jack.
+ 3. Press and hold in the [F/W] key while turning the radio on
+ ("CLONE" will appear on the display).
+ 4. Press the [MR(SKP)SC] key ("CLONE WAIT" will appear
+ on the LCD).
+ 5. Click OK to send image to radio."""))
+ return rp
+
+ @classmethod
def match_model(cls, filedata, filename):
return len(filedata) == cls._memsize
diff --git a/chirp/drivers/vx7.py b/chirp/drivers/vx7.py
index 1d7b11a..ee5d383 100644
--- a/chirp/drivers/vx7.py
+++ b/chirp/drivers/vx7.py
@@ -81,11 +81,7 @@ DUPLEX = ["", "-", "+", "split"]
MODES = ["FM", "AM", "WFM", "Auto"]
TMODES = ["", "Tone", "TSQL", "DTCS", "Cross"]
CROSS_MODES = ["DTCS->", "Tone->DTCS", "DTCS->Tone"]
-STEPS = list(chirp_common.TUNING_STEPS)
-STEPS.remove(6.25)
-STEPS.remove(30.0)
-STEPS.append(100.0)
-STEPS.append(9.0)
+STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0, 9.0]
CHARSET = ["%i" % int(x) for x in range(0, 10)] + \
[" "] + \
diff --git a/chirp/drivers/vx8.py b/chirp/drivers/vx8.py
index 6e8dd5f..4fc9836 100644
--- a/chirp/drivers/vx8.py
+++ b/chirp/drivers/vx8.py
@@ -243,9 +243,15 @@ struct {
} entry[8];
} digi_path_7;
u8 unknown22[2];
- struct {
- char padded_string[16];
- } message_macro[7];
+} aprs;
+
+#seekto 0x%04X;
+struct {
+ char padded_string[16];
+} aprs_msg_macro[%d];
+
+#seekto 0x%04X;
+struct {
u8 unknown23:5,
selected_msg_group:3;
u8 unknown24;
@@ -285,7 +291,7 @@ struct {
vibrate_grp:6;
u8 unknown34:2,
vibrate_bln:6;
-} aprs;
+} aprs2;
#seekto 0x%04X;
struct {
@@ -496,14 +502,16 @@ class VX8Radio(yaesu_clone.YaesuCloneModeRadio):
"""Yaesu VX-8"""
BAUD_RATE = 38400
VENDOR = "Yaesu"
- MODEL = "VX-8"
- VARIANT = "R"
+ MODEL = "VX-8R"
_model = "AH029"
_memsize = 65227
_block_lengths = [10, 65217]
_block_size = 32
- _mem_params = (0xC24A, # APRS beacon metadata address.
+ _mem_params = (0xC128, # APRS message macros
+ 5, # Number of message macros
+ 0xC178, # APRS2
+ 0xC24A, # APRS beacon metadata address.
40, # Number of beacons stored.
0xC60A, # APRS beacon content address.
194, # Length of beacon data stored.
@@ -511,6 +519,62 @@ class VX8Radio(yaesu_clone.YaesuCloneModeRadio):
_has_vibrate = False
_has_af_dual = True
+ _SG_RE = re.compile(r"(?P<sign>[-+NESW]?)(?P<d>[\d]+)[\s\.,]*"
+ "(?P<m>[\d]*)[\s\']*(?P<s>[\d]*)")
+
+ _RX_BAUD = ("off", "1200 baud", "9600 baud")
+ _TX_DELAY = ("100ms", "200ms", "300ms",
+ "400ms", "500ms", "750ms", "1000ms")
+ _WIND_UNITS = ("m/s", "mph")
+ _RAIN_UNITS = ("mm", "inch")
+ _TEMP_UNITS = ("C", "F")
+ _ALT_UNITS = ("m", "ft")
+ _DIST_UNITS = ("km", "mile")
+ _POS_UNITS = ("dd.mmmm'", "dd mm'ss\"")
+ _SPEED_UNITS = ("km/h", "knot", "mph")
+ _TIME_SOURCE = ("manual", "GPS")
+ _TZ = ("-13:00", "-13:30", "-12:00", "-12:30", "-11:00", "-11:30",
+ "-10:00", "-10:30", "-09:00", "-09:30", "-08:00", "-08:30",
+ "-07:00", "-07:30", "-06:00", "-06:30", "-05:00", "-05:30",
+ "-04:00", "-04:30", "-03:00", "-03:30", "-02:00", "-02:30",
+ "-01:00", "-01:30", "-00:00", "-00:30", "+01:00", "+01:30",
+ "+02:00", "+02:30", "+03:00", "+03:30", "+04:00", "+04:30",
+ "+05:00", "+05:30", "+06:00", "+06:30", "+07:00", "+07:30",
+ "+08:00", "+08:30", "+09:00", "+09:30", "+10:00", "+10:30",
+ "+11:00", "+11:30")
+ _BEACON_TYPE = ("Off", "Interval")
+ _BEACON_INT = ("15s", "30s", "1m", "2m", "3m", "5m", "10m", "15m",
+ "30m")
+ _DIGI_PATHS = ("OFF", "WIDE1-1", "WIDE1-1, WIDE2-1", "Digi Path 4",
+ "Digi Path 5", "Digi Path 6", "Digi Path 7", "Digi Path 8")
+ _MSG_GROUP_NAMES = ("Message Group 1", "Message Group 2",
+ "Message Group 3", "Message Group 4",
+ "Message Group 5", "Message Group 6",
+ "Message Group 7", "Message Group 8")
+ _POSITIONS = ("GPS", "Manual Latitude/Longitude",
+ "Manual Latitude/Longitude", "P1", "P2", "P3", "P4",
+ "P5", "P6", "P7", "P8", "P9", "P10")
+ _FLASH = ("OFF", "ON")
+ _BEEP_SELECT = ("Off", "Key+Scan", "Key")
+ _SQUELCH = ["%d" % x for x in range(0, 16)]
+ _VOLUME = ["%d" % x for x in range(0, 33)]
+ _OPENING_MESSAGE = ("Off", "DC", "Message", "Normal")
+ _SCAN_RESUME = ["%.1fs" % (0.5 * x) for x in range(4, 21)] + \
+ ["Busy", "Hold"]
+ _SCAN_RESTART = ["%.1fs" % (0.1 * x) for x in range(1, 10)] + \
+ ["%.1fs" % (0.5 * x) for x in range(2, 21)]
+ _LAMP_KEY = ["Key %d sec" % x for x in range(2, 11)] + \
+ ["Continuous", "OFF"]
+ _LCD_CONTRAST = ["Level %d" % x for x in range(1, 33)]
+ _LCD_DIMMER = ["Level %d" % x for x in range(1, 5)]
+ _TOT_TIME = ["Off"] + ["%.1f min" % (0.5 * x) for x in range(1, 21)]
+ _OFF_ON = ("Off", "On")
+ _VOL_MODE = ("Normal", "Auto Back")
+ _DTMF_MODE = ("Manual", "Auto")
+ _DTMF_SPEED = ("50ms", "100ms")
+ _DTMF_DELAY = ("50ms", "250ms", "450ms", "750ms", "1000ms")
+ _MY_SYMBOL = ("/[ Person", "/b Bike", "/> Car", "User selected")
+
@classmethod
def get_prompts(cls):
rp = chirp_common.RadioPrompts()
@@ -547,6 +611,7 @@ class VX8Radio(yaesu_clone.YaesuCloneModeRadio):
rf.can_odd_split = True
rf.has_ctone = False
rf.has_bank_names = True
+ rf.has_settings = True
return rf
def get_raw_memory(self, number):
@@ -654,89 +719,6 @@ class VX8Radio(yaesu_clone.YaesuCloneModeRadio):
def get_bank_model(self):
return VX8BankModel(self)
-
- at directory.register
-class VX8DRadio(VX8Radio):
- """Yaesu VX-8DR"""
- _model = "AH29D"
- _mem_params = (0xC24A, # APRS beacon metadata address.
- 50, # Number of beacons stored.
- 0xC6FA, # APRS beacon content address.
- 146, # Length of beacon data stored.
- 50) # Number of beacons stored.
- VARIANT = "DR"
-
- _SG_RE = re.compile(r"(?P<sign>[-+NESW]?)(?P<d>[\d]+)[\s\.,]*"
- "(?P<m>[\d]*)[\s\']*(?P<s>[\d]*)")
-
- _RX_BAUD = ("off", "1200 baud", "9600 baud")
- _TX_DELAY = ("100ms", "150ms", "200ms", "250ms", "300ms",
- "400ms", "500ms", "750ms", "1000ms")
- _WIND_UNITS = ("m/s", "mph")
- _RAIN_UNITS = ("mm", "inch")
- _TEMP_UNITS = ("C", "F")
- _ALT_UNITS = ("m", "ft")
- _DIST_UNITS = ("km", "mile")
- _POS_UNITS = ("dd.mmmm'", "dd mm'ss\"")
- _SPEED_UNITS = ("km/h", "knot", "mph")
- _TIME_SOURCE = ("manual", "GPS")
- _TZ = ("-13:00", "-13:30", "-12:00", "-12:30", "-11:00", "-11:30",
- "-10:00", "-10:30", "-09:00", "-09:30", "-08:00", "-08:30",
- "-07:00", "-07:30", "-06:00", "-06:30", "-05:00", "-05:30",
- "-04:00", "-04:30", "-03:00", "-03:30", "-02:00", "-02:30",
- "-01:00", "-01:30", "-00:00", "-00:30", "+01:00", "+01:30",
- "+02:00", "+02:30", "+03:00", "+03:30", "+04:00", "+04:30",
- "+05:00", "+05:30", "+06:00", "+06:30", "+07:00", "+07:30",
- "+08:00", "+08:30", "+09:00", "+09:30", "+10:00", "+10:30",
- "+11:00", "+11:30")
- _BEACON_TYPE = ("Off", "Interval", "SmartBeaconing")
- _SMARTBEACON_PROFILE = ("Off", "Type 1", "Type 2", "Type 3")
- _BEACON_INT = ("30s", "1m", "2m", "3m", "5m", "10m", "15m",
- "20m", "30m", "60m")
- _DIGI_PATHS = ("OFF", "WIDE1-1", "WIDE1-1, WIDE2-1", "Digi Path 4",
- "Digi Path 5", "Digi Path 6", "Digi Path 7", "Digi Path 8")
- _MSG_GROUP_NAMES = ("Message Group 1", "Message Group 2",
- "Message Group 3", "Message Group 4",
- "Message Group 5", "Message Group 6",
- "Message Group 7", "Message Group 8")
- _POSITIONS = ("GPS", "Manual Latitude/Longitude",
- "Manual Latitude/Longitude", "P1", "P2", "P3", "P4",
- "P5", "P6", "P7", "P8", "P9")
- _FLASH = ("OFF", "2 seconds", "4 seconds", "6 seconds", "8 seconds",
- "10 seconds", "20 seconds", "30 seconds", "60 seconds",
- "CONTINUOUS", "every 2 seconds", "every 3 seconds",
- "every 4 seconds", "every 5 seconds", "every 6 seconds",
- "every 7 seconds", "every 8 seconds", "every 9 seconds",
- "every 10 seconds", "every 20 seconds", "every 30 seconds",
- "every 40 seconds", "every 50 seconds", "every minute",
- "every 2 minutes", "every 3 minutes", "every 4 minutes",
- "every 5 minutes", "every 6 minutes", "every 7 minutes",
- "every 8 minutes", "every 9 minutes", "every 10 minutes")
- _BEEP_SELECT = ("Off", "Key+Scan", "Key")
- _SQUELCH = ["%d" % x for x in range(0, 16)]
- _VOLUME = ["%d" % x for x in range(0, 33)]
- _OPENING_MESSAGE = ("Off", "DC", "Message", "Normal")
- _SCAN_RESUME = ["%.1fs" % (0.5 * x) for x in range(4, 21)] + \
- ["Busy", "Hold"]
- _SCAN_RESTART = ["%.1fs" % (0.1 * x) for x in range(1, 10)] + \
- ["%.1fs" % (0.5 * x) for x in range(2, 21)]
- _LAMP_KEY = ["Key %d sec" % x for x in range(2, 11)] + \
- ["Continuous", "OFF"]
- _LCD_CONTRAST = ["Level %d" % x for x in range(1, 16)]
- _LCD_DIMMER = ["Level %d" % x for x in range(1, 5)]
- _TOT_TIME = ["Off"] + ["%.1f min" % (0.5 * x) for x in range(1, 21)]
- _OFF_ON = ("Off", "On")
- _VOL_MODE = ("Normal", "Auto Back")
- _DTMF_MODE = ("Manual", "Auto")
- _DTMF_SPEED = ("50ms", "100ms")
- _DTMF_DELAY = ("50ms", "250ms", "450ms", "750ms", "1000ms")
- _MY_SYMBOL = ("/[ Person", "/b Bike", "/> Car", "User selected")
-
- def get_features(self):
- rf = VX8Radio.get_features(self)
- rf.has_settings = True
- return rf
-
@classmethod
def _digi_path_to_str(cls, path):
path_cmp = []
@@ -816,6 +798,7 @@ class VX8DRadio(VX8Radio):
def _get_aprs_general_settings(self):
menu = RadioSettingGroup("aprs_general", "APRS General")
aprs = self._memobj.aprs
+ aprs2 = self._memobj.aprs2
val = RadioSettingValueString(
0, 6, str(aprs.my_callsign.callsign).rstrip("\xFF"))
@@ -830,8 +813,8 @@ class VX8DRadio(VX8Radio):
menu.append(rs)
val = RadioSettingValueList(self._MY_SYMBOL,
- self._MY_SYMBOL[aprs.selected_my_symbol])
- rs = RadioSetting("aprs.selected_my_symbol", "My Symbol", val)
+ self._MY_SYMBOL[aprs2.selected_my_symbol])
+ rs = RadioSetting("aprs2.selected_my_symbol", "My Symbol", val)
menu.append(rs)
symbols = list(chirp_common.APRS_SYMBOLS)
@@ -958,6 +941,7 @@ class VX8DRadio(VX8Radio):
def _get_aprs_rx_settings(self):
menu = RadioSettingGroup("aprs_rx", "APRS Receive")
aprs = self._memobj.aprs
+ aprs2 = self._memobj.aprs2
val = RadioSettingValueList(self._RX_BAUD, self._RX_BAUD[aprs.rx_baud])
rs = RadioSetting("aprs.rx_baud", "Modem RX", val)
@@ -981,55 +965,55 @@ class VX8DRadio(VX8Radio):
menu.append(rs)
val = RadioSettingValueList(self._FLASH,
- self._FLASH[aprs.flash_msg])
- rs = RadioSetting("aprs.flash_msg", "Flash on personal message", val)
+ self._FLASH[aprs2.flash_msg])
+ rs = RadioSetting("aprs2.flash_msg", "Flash on personal message", val)
menu.append(rs)
if self._has_vibrate:
val = RadioSettingValueList(self._FLASH,
- self._FLASH[aprs.vibrate_msg])
- rs = RadioSetting("aprs.vibrate_msg",
+ self._FLASH[aprs2.vibrate_msg])
+ rs = RadioSetting("aprs2.vibrate_msg",
"Vibrate on personal message", val)
menu.append(rs)
val = RadioSettingValueList(self._FLASH[:10],
- self._FLASH[aprs.flash_bln])
- rs = RadioSetting("aprs.flash_bln", "Flash on bulletin message", val)
+ self._FLASH[aprs2.flash_bln])
+ rs = RadioSetting("aprs2.flash_bln", "Flash on bulletin message", val)
menu.append(rs)
if self._has_vibrate:
val = RadioSettingValueList(self._FLASH[:10],
- self._FLASH[aprs.vibrate_bln])
- rs = RadioSetting("aprs.vibrate_bln",
+ self._FLASH[aprs2.vibrate_bln])
+ rs = RadioSetting("aprs2.vibrate_bln",
"Vibrate on bulletin message", val)
menu.append(rs)
val = RadioSettingValueList(self._FLASH[:10],
- self._FLASH[aprs.flash_grp])
- rs = RadioSetting("aprs.flash_grp", "Flash on group message", val)
+ self._FLASH[aprs2.flash_grp])
+ rs = RadioSetting("aprs2.flash_grp", "Flash on group message", val)
menu.append(rs)
if self._has_vibrate:
val = RadioSettingValueList(self._FLASH[:10],
- self._FLASH[aprs.vibrate_grp])
- rs = RadioSetting("aprs.vibrate_grp",
+ self._FLASH[aprs2.vibrate_grp])
+ rs = RadioSetting("aprs2.vibrate_grp",
"Vibrate on group message", val)
menu.append(rs)
- filter_val = [m.padded_string for m in aprs.msg_group]
+ filter_val = [m.padded_string for m in aprs2.msg_group]
filter_val = self._strip_ff_pads(filter_val)
for index, filter_text in enumerate(filter_val):
val = RadioSettingValueString(0, 9, filter_text)
- rs = RadioSetting("aprs.msg_group_%d" % index,
+ rs = RadioSetting("aprs2.msg_group_%d" % index,
"Message Group %d" % (index + 1), val)
menu.append(rs)
rs.set_apply_callback(self.apply_ff_padded_string,
- aprs.msg_group[index])
+ aprs2.msg_group[index])
# TODO: Use filter_val as the list entries and update it on edit.
val = RadioSettingValueList(
self._MSG_GROUP_NAMES,
- self._MSG_GROUP_NAMES[aprs.selected_msg_group])
- rs = RadioSetting("aprs.selected_msg_group", "Selected Message Group",
+ self._MSG_GROUP_NAMES[aprs2.selected_msg_group])
+ rs = RadioSetting("aprs2.selected_msg_group", "Selected Message Group",
val)
menu.append(rs)
@@ -1068,6 +1052,7 @@ class VX8DRadio(VX8Radio):
def _get_aprs_tx_settings(self):
menu = RadioSettingGroup("aprs_tx", "APRS Transmit")
aprs = self._memobj.aprs
+ aprs2 = self._memobj.aprs2
beacon_type = (aprs.tx_smartbeacon << 1) | aprs.tx_interval_beacon
val = RadioSettingValueList(
@@ -1103,40 +1088,17 @@ class VX8DRadio(VX8Radio):
"Beacon Status Text", val)
menu.append(rs)
- message_macro = [m.padded_string for m in aprs.message_macro]
+ message_macro = [m.padded_string for m in self._memobj.aprs_msg_macro]
message_macro = self._strip_ff_pads(message_macro)
for index, msg_text in enumerate(message_macro):
val = RadioSettingValueString(0, 16, msg_text)
- rs = RadioSetting("aprs.message_macro_%d" % index,
+ rs = RadioSetting("aprs_msg_macro_%d" % index,
"Message Macro %d" % (index + 1), val)
rs.set_apply_callback(self.apply_ff_padded_string,
- aprs.message_macro[index])
+ self._memobj.aprs_msg_macro[index])
menu.append(rs)
path_str = list(self._DIGI_PATHS)
- path_str[3] = self._digi_path_to_str(aprs.digi_path_3_6[0])
- val = RadioSettingValueString(0, 22, path_str[3])
- rs = RadioSetting("aprs.digi_path_3", "Digi Path 4 (2 entries)", val)
- rs.set_apply_callback(self.apply_digi_path, aprs.digi_path_3_6[0])
- menu.append(rs)
-
- path_str[4] = self._digi_path_to_str(aprs.digi_path_3_6[1])
- val = RadioSettingValueString(0, 22, path_str[4])
- rs = RadioSetting("aprs.digi_path_4", "Digi Path 5 (2 entries)", val)
- rs.set_apply_callback(self.apply_digi_path, aprs.digi_path_3_6[1])
- menu.append(rs)
-
- path_str[5] = self._digi_path_to_str(aprs.digi_path_3_6[2])
- val = RadioSettingValueString(0, 22, path_str[5])
- rs = RadioSetting("aprs.digi_path_5", "Digi Path 6 (2 entries)", val)
- rs.set_apply_callback(self.apply_digi_path, aprs.digi_path_3_6[2])
- menu.append(rs)
-
- path_str[6] = self._digi_path_to_str(aprs.digi_path_3_6[3])
- val = RadioSettingValueString(0, 22, path_str[6])
- rs = RadioSetting("aprs.digi_path_6", "Digi Path 7 (2 entries)", val)
- rs.set_apply_callback(self.apply_digi_path, aprs.digi_path_3_6[3])
- menu.append(rs)
path_str[7] = self._digi_path_to_str(aprs.digi_path_7)
val = RadioSettingValueString(0, 88, path_str[7])
@@ -1151,71 +1113,14 @@ class VX8DRadio(VX8Radio):
# path_str[5] = path_str[5] or self._DIGI_PATHS[5]
# path_str[6] = path_str[6] or self._DIGI_PATHS[6]
# path_str[7] = path_str[7] or self._DIGI_PATHS[7]
- path_str[3] = self._DIGI_PATHS[3]
- path_str[4] = self._DIGI_PATHS[4]
- path_str[5] = self._DIGI_PATHS[5]
- path_str[6] = self._DIGI_PATHS[6]
+
path_str[7] = self._DIGI_PATHS[7]
val = RadioSettingValueList(path_str,
- path_str[aprs.selected_digi_path])
- rs = RadioSetting("aprs.selected_digi_path", "Selected Digi Path", val)
- menu.append(rs)
-
- return menu
-
- def _get_aprs_smartbeacon(self):
- menu = RadioSettingGroup("aprs_smartbeacon", "APRS SmartBeacon")
- aprs = self._memobj.aprs
-
- val = RadioSettingValueList(
- self._SMARTBEACON_PROFILE,
- self._SMARTBEACON_PROFILE[aprs.active_smartbeaconing])
- rs = RadioSetting("aprs.active_smartbeaconing", "SmartBeacon profile",
- val)
+ path_str[aprs2.selected_digi_path])
+ rs = RadioSetting("aprs2.selected_digi_path",
+ "Selected Digi Path", val)
menu.append(rs)
- for profile in range(3):
- pfx = "type%d" % (profile + 1)
- path = "aprs.smartbeaconing_profile[%d]" % profile
- prof = aprs.smartbeaconing_profile[profile]
-
- low_val = RadioSettingValueInteger(2, 30, prof.low_speed_mph)
- high_val = RadioSettingValueInteger(3, 70, prof.high_speed_mph)
- low_val.get_max = lambda: min(30, int(high_val.get_value()) - 1)
-
- rs = RadioSetting("%s.low_speed_mph" % path,
- "%s Low Speed (mph)" % pfx, low_val)
- menu.append(rs)
-
- rs = RadioSetting("%s.high_speed_mph" % path,
- "%s High Speed (mph)" % pfx, high_val)
- menu.append(rs)
-
- val = RadioSettingValueInteger(1, 100, prof.slow_rate_min)
- rs = RadioSetting("%s.slow_rate_min" % path,
- "%s Slow rate (minutes)" % pfx, val)
- menu.append(rs)
-
- val = RadioSettingValueInteger(10, 180, prof.fast_rate_sec)
- rs = RadioSetting("%s.fast_rate_sec" % path,
- "%s Fast rate (seconds)" % pfx, val)
- menu.append(rs)
-
- val = RadioSettingValueInteger(5, 90, prof.turn_angle)
- rs = RadioSetting("%s.turn_angle" % path,
- "%s Turn angle (degrees)" % pfx, val)
- menu.append(rs)
-
- val = RadioSettingValueInteger(1, 255, prof.turn_slop)
- rs = RadioSetting("%s.turn_slop" % path,
- "%s Turn slop" % pfx, val)
- menu.append(rs)
-
- val = RadioSettingValueInteger(5, 180, prof.turn_time_sec)
- rs = RadioSetting("%s.turn_time_sec" % path,
- "%s Turn time (seconds)" % pfx, val)
- menu.append(rs)
-
return menu
def _get_dtmf_settings(self):
@@ -1389,7 +1294,6 @@ class VX8DRadio(VX8Radio):
top = RadioSettings(self._get_aprs_general_settings(),
self._get_aprs_rx_settings(),
self._get_aprs_tx_settings(),
- self._get_aprs_smartbeacon(),
self._get_dtmf_settings(),
self._get_misc_settings(),
self._get_scan_settings())
@@ -1550,9 +1454,207 @@ class VX8DRadio(VX8Radio):
@directory.register
+class VX8DRadio(VX8Radio):
+ """Yaesu VX-8DR"""
+ MODEL = "VX-8DR"
+ _model = "AH29D"
+ _mem_params = (0xC128, # APRS message macros
+ 7, # Number of message macros
+ 0xC198, # APRS2
+ 0xC24A, # APRS beacon metadata address.
+ 50, # Number of beacons stored.
+ 0xC6FA, # APRS beacon content address.
+ 146, # Length of beacon data stored.
+ 50) # Number of beacons stored.
+
+ _BEACON_TYPE = ("Off", "Interval", "SmartBeaconing")
+ _SMARTBEACON_PROFILE = ("Off", "Type 1", "Type 2", "Type 3")
+ _POSITIONS = ("GPS", "Manual Latitude/Longitude",
+ "Manual Latitude/Longitude", "P1", "P2", "P3", "P4",
+ "P5", "P6", "P7", "P8", "P9")
+ _FLASH = ("OFF", "2 seconds", "4 seconds", "6 seconds", "8 seconds",
+ "10 seconds", "20 seconds", "30 seconds", "60 seconds",
+ "CONTINUOUS", "every 2 seconds", "every 3 seconds",
+ "every 4 seconds", "every 5 seconds", "every 6 seconds",
+ "every 7 seconds", "every 8 seconds", "every 9 seconds",
+ "every 10 seconds", "every 20 seconds", "every 30 seconds",
+ "every 40 seconds", "every 50 seconds", "every minute",
+ "every 2 minutes", "every 3 minutes", "every 4 minutes",
+ "every 5 minutes", "every 6 minutes", "every 7 minutes",
+ "every 8 minutes", "every 9 minutes", "every 10 minutes")
+ _LCD_CONTRAST = ["Level %d" % x for x in range(1, 16)]
+ _MY_SYMBOL = ("/[ Person", "/b Bike", "/> Car", "User selected")
+
+ def _get_aprs_tx_settings(self):
+ menu = RadioSettingGroup("aprs_tx", "APRS Transmit")
+ aprs = self._memobj.aprs
+ aprs2 = self._memobj.aprs2
+
+ beacon_type = (aprs.tx_smartbeacon << 1) | aprs.tx_interval_beacon
+ val = RadioSettingValueList(
+ self._BEACON_TYPE, self._BEACON_TYPE[beacon_type])
+ rs = RadioSetting("aprs.transmit", "TX Beacons", val)
+ rs.set_apply_callback(self.apply_beacon_type, aprs)
+ menu.append(rs)
+
+ val = RadioSettingValueList(
+ self._TX_DELAY, self._TX_DELAY[aprs.tx_delay])
+ rs = RadioSetting("aprs.tx_delay", "TX Delay", val)
+ menu.append(rs)
+
+ val = RadioSettingValueList(
+ self._BEACON_INT, self._BEACON_INT[aprs.beacon_interval])
+ rs = RadioSetting("aprs.beacon_interval", "Beacon Interval", val)
+ menu.append(rs)
+
+ desc = []
+ status = [m.padded_string for m in self._memobj.aprs_beacon_status_txt]
+ status = self._strip_ff_pads(status)
+ for index, msg_text in enumerate(status):
+ val = RadioSettingValueString(0, 60, msg_text)
+ desc.append("Beacon Status Text %d" % (index + 1))
+ rs = RadioSetting("aprs_beacon_status_txt_%d" % index, desc[-1],
+ val)
+ rs.set_apply_callback(self.apply_ff_padded_string,
+ self._memobj.aprs_beacon_status_txt[index])
+ menu.append(rs)
+ val = RadioSettingValueList(desc,
+ desc[aprs.selected_beacon_status_txt])
+ rs = RadioSetting("aprs.selected_beacon_status_txt",
+ "Beacon Status Text", val)
+ menu.append(rs)
+
+ message_macro = [m.padded_string for m in self._memobj.aprs_msg_macro]
+ message_macro = self._strip_ff_pads(message_macro)
+ for index, msg_text in enumerate(message_macro):
+ val = RadioSettingValueString(0, 16, msg_text)
+ rs = RadioSetting("aprs_msg_macro_%d" % index,
+ "Message Macro %d" % (index + 1), val)
+ rs.set_apply_callback(self.apply_ff_padded_string,
+ self._memobj.aprs_msg_macro[index])
+ menu.append(rs)
+
+ path_str = list(self._DIGI_PATHS)
+ path_str[3] = self._digi_path_to_str(aprs2.digi_path_3_6[0])
+ val = RadioSettingValueString(0, 22, path_str[3])
+ rs = RadioSetting("aprs2.digi_path_3", "Digi Path 4 (2 entries)", val)
+ rs.set_apply_callback(self.apply_digi_path, aprs2.digi_path_3_6[0])
+ menu.append(rs)
+
+ path_str[4] = self._digi_path_to_str(aprs2.digi_path_3_6[1])
+ val = RadioSettingValueString(0, 22, path_str[4])
+ rs = RadioSetting("aprs2.digi_path_4", "Digi Path 5 (2 entries)", val)
+ rs.set_apply_callback(self.apply_digi_path, aprs2.digi_path_3_6[1])
+ menu.append(rs)
+
+ path_str[5] = self._digi_path_to_str(aprs2.digi_path_3_6[2])
+ val = RadioSettingValueString(0, 22, path_str[5])
+ rs = RadioSetting("aprs2.digi_path_5", "Digi Path 6 (2 entries)", val)
+ rs.set_apply_callback(self.apply_digi_path, aprs2.digi_path_3_6[2])
+ menu.append(rs)
+
+ path_str[6] = self._digi_path_to_str(aprs2.digi_path_3_6[3])
+ val = RadioSettingValueString(0, 22, path_str[6])
+ rs = RadioSetting("aprs2.digi_path_6", "Digi Path 7 (2 entries)", val)
+ rs.set_apply_callback(self.apply_digi_path, aprs2.digi_path_3_6[3])
+ menu.append(rs)
+
+ path_str[7] = self._digi_path_to_str(aprs.digi_path_7)
+ val = RadioSettingValueString(0, 88, path_str[7])
+ rs = RadioSetting("aprs.digi_path_7", "Digi Path 8 (8 entries)", val)
+ rs.set_apply_callback(self.apply_digi_path, aprs.digi_path_7)
+ menu.append(rs)
+
+ # Show friendly messages for empty slots rather than blanks.
+ # TODO: Rebuild this when digi_path_[34567] change.
+ # path_str[3] = path_str[3] or self._DIGI_PATHS[3]
+ # path_str[4] = path_str[4] or self._DIGI_PATHS[4]
+ # path_str[5] = path_str[5] or self._DIGI_PATHS[5]
+ # path_str[6] = path_str[6] or self._DIGI_PATHS[6]
+ # path_str[7] = path_str[7] or self._DIGI_PATHS[7]
+ path_str[3] = self._DIGI_PATHS[3]
+ path_str[4] = self._DIGI_PATHS[4]
+ path_str[5] = self._DIGI_PATHS[5]
+ path_str[6] = self._DIGI_PATHS[6]
+ path_str[7] = self._DIGI_PATHS[7]
+ val = RadioSettingValueList(path_str,
+ path_str[aprs2.selected_digi_path])
+ rs = RadioSetting("aprs2.selected_digi_path",
+ "Selected Digi Path", val)
+ menu.append(rs)
+
+ return menu
+
+ def _get_aprs_smartbeacon(self):
+ menu = RadioSettingGroup("aprs_smartbeacon", "APRS SmartBeacon")
+ aprs2 = self._memobj.aprs2
+
+ val = RadioSettingValueList(
+ self._SMARTBEACON_PROFILE,
+ self._SMARTBEACON_PROFILE[aprs2.active_smartbeaconing])
+ rs = RadioSetting("aprs2.active_smartbeaconing",
+ "SmartBeacon profile", val)
+ menu.append(rs)
+
+ for profile in range(3):
+ pfx = "type%d" % (profile + 1)
+ path = "aprs2.smartbeaconing_profile[%d]" % profile
+ prof = aprs2.smartbeaconing_profile[profile]
+
+ low_val = RadioSettingValueInteger(2, 30, prof.low_speed_mph)
+ high_val = RadioSettingValueInteger(3, 70, prof.high_speed_mph)
+ low_val.get_max = lambda: min(30, int(high_val.get_value()) - 1)
+
+ rs = RadioSetting("%s.low_speed_mph" % path,
+ "%s Low Speed (mph)" % pfx, low_val)
+ menu.append(rs)
+
+ rs = RadioSetting("%s.high_speed_mph" % path,
+ "%s High Speed (mph)" % pfx, high_val)
+ menu.append(rs)
+
+ val = RadioSettingValueInteger(1, 100, prof.slow_rate_min)
+ rs = RadioSetting("%s.slow_rate_min" % path,
+ "%s Slow rate (minutes)" % pfx, val)
+ menu.append(rs)
+
+ val = RadioSettingValueInteger(10, 180, prof.fast_rate_sec)
+ rs = RadioSetting("%s.fast_rate_sec" % path,
+ "%s Fast rate (seconds)" % pfx, val)
+ menu.append(rs)
+
+ val = RadioSettingValueInteger(5, 90, prof.turn_angle)
+ rs = RadioSetting("%s.turn_angle" % path,
+ "%s Turn angle (degrees)" % pfx, val)
+ menu.append(rs)
+
+ val = RadioSettingValueInteger(1, 255, prof.turn_slop)
+ rs = RadioSetting("%s.turn_slop" % path,
+ "%s Turn slop" % pfx, val)
+ menu.append(rs)
+
+ val = RadioSettingValueInteger(5, 180, prof.turn_time_sec)
+ rs = RadioSetting("%s.turn_time_sec" % path,
+ "%s Turn time (seconds)" % pfx, val)
+ menu.append(rs)
+
+ return menu
+
+ def _get_settings(self):
+ top = RadioSettings(self._get_aprs_general_settings(),
+ self._get_aprs_rx_settings(),
+ self._get_aprs_tx_settings(),
+ self._get_aprs_smartbeacon(),
+ self._get_dtmf_settings(),
+ self._get_misc_settings(),
+ self._get_scan_settings())
+ return top
+
+
+ at directory.register
class VX8GERadio(VX8DRadio):
"""Yaesu VX-8GE"""
+ MODEL = "VX-8GE"
_model = "AH041"
- VARIANT = "GE"
_has_vibrate = True
_has_af_dual = False
diff --git a/chirp/drivers/wouxun.py b/chirp/drivers/wouxun.py
index 58536de..fc482fe 100644
--- a/chirp/drivers/wouxun.py
+++ b/chirp/drivers/wouxun.py
@@ -810,30 +810,33 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
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
+ rx_mode = tx_mode = None
+ rx_tone = 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
+ if mem.tmode == "Tone":
+ tx_mode = "Tone"
+ rx_mode = None
+ tx_tone = int(mem.rtone * 10)
+ elif mem.tmode == "TSQL":
+ rx_mode = tx_mode = "Tone"
+ rx_tone = tx_tone = int(mem.ctone * 10)
+ elif mem.tmode == "DTCS":
+ tx_mode = rx_mode = "DTCS"
+ tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
+ rx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1])
+ elif mem.tmode == "Cross":
+ tx_mode, rx_mode = mem.cross_mode.split("->")
+ if tx_mode == "DTCS":
+ tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
+ elif tx_mode == "Tone":
+ tx_tone = int(mem.rtone * 10)
+ if rx_mode == "DTCS":
+ rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
+ elif rx_mode == "Tone":
+ rx_tone = int(mem.ctone * 10)
+
+ _mem.rx_tone = rx_tone
+ _mem.tx_tone = tx_tone
LOG.debug("Set TX %s (%i) RX %s (%i)" %
(tx_mode, _mem.tx_tone, rx_mode, _mem.rx_tone))
diff --git a/chirp/platform.py b/chirp/platform.py
index 8250af1..3c5bc2e 100644
--- a/chirp/platform.py
+++ b/chirp/platform.py
@@ -243,6 +243,23 @@ class Platform:
return os.path.dirname(os.path.abspath(os.path.join(_find_me(),
"..")))
+ def find_resource(self, filename):
+ """Searches for files installed to a share/ prefix."""
+ execpath = self.executable_path()
+ share_candidates = [
+ os.path.join(execpath, "share"),
+ os.path.join(sys.prefix, "share"),
+ "/usr/local/share",
+ "/usr/share",
+ ]
+ pkgshare_candidates = [os.path.join(i, "chirp") for i in share_candidates]
+ search_paths = [execpath] + pkgshare_candidates + share_candidates
+ for path in search_paths:
+ candidate = os.path.join(path, filename)
+ if os.path.exists(candidate):
+ return candidate
+ return ""
+
def _unix_editor():
macos_textedit = "/Applications/TextEdit.app/Contents/MacOS/TextEdit"
diff --git a/chirp/ui/mainapp.py b/chirp/ui/mainapp.py
index a6835c9..1ba9d51 100644
--- a/chirp/ui/mainapp.py
+++ b/chirp/ui/mainapp.py
@@ -545,10 +545,7 @@ of file.
count = eset.do_import(config)
def copy_shipped_stock_configs(self, stock_dir):
- execpath = platform.get_platform().executable_path()
- basepath = os.path.abspath(os.path.join(execpath, "stock_configs"))
- if not os.path.exists(basepath):
- basepath = "/usr/share/chirp/stock_configs"
+ basepath = platform.get_platform().find_resource("stock_configs")
files = glob(os.path.join(basepath, "*.csv"))
for fn in files:
@@ -1907,11 +1904,9 @@ of file.
a.connect_accelerator()
def _set_icon(self):
- execpath = platform.get_platform().executable_path()
- path = os.path.abspath(os.path.join(execpath, "share", "chirp.png"))
- if not os.path.exists(path):
- path = "/usr/share/pixmaps/chirp.png"
-
+ this_platform = platform.get_platform()
+ path = (this_platform.find_resource("chirp.png") or
+ this_platform.find_resource(os.path.join("pixmaps", "chirp.png")))
if os.path.exists(path):
self.set_icon_from_file(path)
else:
@@ -1945,13 +1940,33 @@ of file.
d.destroy()
def _init_macos(self, menu_bar):
+ macapp = None
+
+ # for KK7DS runtime <= R10
try:
import gtk_osxapplication
macapp = gtk_osxapplication.OSXApplication()
- except ImportError, e:
+ except ImportError:
+ pass
+
+ # for gtk-mac-integration >= 2.0.7
+ try:
+ import gtkosx_application
+ macapp = gtkosx_application.Application()
+ except ImportError:
+ pass
+
+ if macapp is None:
LOG.error("No MacOS support: %s" % e)
return
+ this_platform = platform.get_platform()
+ icon = (this_platform.find_resource("chirp.png") or
+ this_platform.find_resource(os.path.join("pixmaps", "chirp.png")))
+ if os.path.exists(icon):
+ icon_pixmap = gtk.gdk.pixbuf_new_from_file(icon)
+ macapp.set_dock_icon_pixbuf(icon_pixmap)
+
menu_bar.hide()
macapp.set_menu_bar(menu_bar)
diff --git a/chirp/ui/memedit.py b/chirp/ui/memedit.py
index 8a2b51e..3c98c4c 100644
--- a/chirp/ui/memedit.py
+++ b/chirp/ui/memedit.py
@@ -496,7 +496,7 @@ class MemoryEditor(common.Editor):
to_remove = []
for path in paths:
iter = self.store.get_iter(path)
- cur_pos, = self.store.get(iter, self.col("Loc"))
+ cur_pos, = self.store.get(iter, self.col(_("Loc")))
to_remove.append(cur_pos)
self.store.set(iter, self.col("_filled"), False)
job = common.RadioJob(None, "erase_memory", cur_pos)
@@ -935,9 +935,13 @@ class MemoryEditor(common.Editor):
def cell_editing_started(self, rend, event, path):
self._in_editing = True
+ self._edit_path = self.view.get_cursor()
def cell_editing_stopped(self, *args):
self._in_editing = False
+ print 'Would activate %s' % str(self._edit_path)
+ self.view.grab_focus()
+ self.view.set_cursor(*self._edit_path)
def make_editor(self):
types = tuple([x[1] for x in self.cols])
@@ -968,7 +972,7 @@ class MemoryEditor(common.Editor):
LOG.error(e)
col_order = default_col_order
- non_editable = ["Loc"]
+ non_editable = [_("Loc")]
unsupported_cols = self.get_unsupported_columns()
visible_cols = self.get_columns_visible()
diff --git a/chirp/ui/radiobrowser.py b/chirp/ui/radiobrowser.py
index 971a4d6..83cd968 100644
--- a/chirp/ui/radiobrowser.py
+++ b/chirp/ui/radiobrowser.py
@@ -202,7 +202,7 @@ class CharArrayEditor(BitwiseEditor):
def _build_ui(self):
ent = FixedEntry(len(self._element))
- ent.set_text(str(self._element))
+ ent.set_text(str(self._element).rstrip("\x00"))
ent.connect('changed', self._changed)
ent.show()
self.pack_start(ent, 1, 1, 1)
diff --git a/chirp/ui/settingsedit.py b/chirp/ui/settingsedit.py
index 289c796..34707e4 100644
--- a/chirp/ui/settingsedit.py
+++ b/chirp/ui/settingsedit.py
@@ -188,7 +188,8 @@ class SettingsEditor(common.Editor):
widget = gtk.Entry()
widget.set_width_chars(32)
widget.set_text(str(value).rstrip())
- widget.connect("changed", self._save_setting, value)
+ widget.connect("focus-out-event", lambda w, e, v:
+ self._save_setting(w, v), value)
else:
LOG.error("Unsupported widget type: %s" % value.__class__)
diff --git a/chirpw b/chirpw
index 9199332..6ea9ba0 100755
--- a/chirpw
+++ b/chirpw
@@ -34,10 +34,7 @@ import logging
LOG = logging.getLogger("chirpw")
-execpath = platform.get_platform().executable_path()
-localepath = os.path.abspath(os.path.join(execpath, "locale"))
-if not os.path.exists(localepath):
- localepath = "/usr/share/chirp/locale"
+localepath = platform.get_platform().find_resource("locale")
conf = config.get()
manual_language = conf.get("language", "state")
--
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