Developers
Threads by month
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- 3 participants
- 2964 discussions
# HG changeset patch
# User Kosta A. <ve7kcy(a)gmail.com>
# Date 1625341895 25200
# Sat Jul 03 12:51:35 2021 -0700
# Branch open-recent
# Node ID f4b5cc47b12ab123cd0605e0a0004eb0050b7afa
# Parent bea11de983007eac96d69b3a0aeccfdcaab1c2a7
Resolving a couple pep errors.
diff --git a/chirp/ui/mainapp.py b/chirp/ui/mainapp.py
--- a/chirp/ui/mainapp.py
+++ b/chirp/ui/mainapp.py
@@ -557,7 +557,8 @@
if (index < len(recent_files)):
fname = recent_files[index]
widget_label = os.path.basename(fname).replace("_", "__")
- widget_tip = _("Open recent file") + (" {name}").format(name=fname)
+ widget_tip = _("Open recent file") + \
+ (" {name}").format(name=fname)
widget_path = path + "/" + action_name
action = gtk.Action(action_name, widget_label, widget_tip, "")
@@ -588,7 +589,7 @@
def clear_recent_files(self):
self._set_recent_list([])
- self.update_recent_files();
+ self.update_recent_files()
def import_stock_config(self, action, config):
eset = self.get_current_editorset()
@@ -1982,7 +1983,8 @@
('open', gtk.STOCK_OPEN, None, None, None, self.mh),
('openstock', None, _("Open Stock Config"), None, None, self.mh),
('recent', None, _("Open _Recent"), None, None, self.mh),
- ('clearrecent', None, _("Clear Recently Opened"), None, None, self.mh),
+ ('clearrecent', None, _("Clear Recently Opened"),
+ None, None, self.mh),
('save', gtk.STOCK_SAVE, None, None, None, self.mh),
('saveas', gtk.STOCK_SAVE_AS, None, None, None, self.mh),
('loadmod', None, _("Load Module"), None, None, self.mh),
2
1
---------- Forwarded message ---------
From: Jim Unroe <kc9hi(a)comcast.net>
Date: Sat, Jul 3, 2021 at 3:40 PM
Subject: [PATCH] [RB26] Fix for varying ACK
To: <Rock.Unroe(a)gmail.com>
# HG changeset patch
# User Jim Unroe <rock.unroe(a)gmail.com>
# Date 1625340946 14400
# Sat Jul 03 15:35:46 2021 -0400
# Node ID b845893bebcdeba1754abe44c423c823ffafce1c
# Parent 0eab8146b294ef686ca4a49c17ea38abde54c7ab
[RB26] Fix for varying ACK
The RB26 sometimes ACKs the "magic" string with a "\x00\x06" and sometimes
it
ACKs with only a "\x06". CHIRP currently expects to see the "\x00\x06" as
the
response so the process fails if the "\x00" is missing.
This patch "chews up" the "\x00 if present and then only the individual
"\x06" is expected as the ACK.
The Retevis RT76 seems to have the same issue and is fixed as a result of
this
patch.
Fixes #9035
diff -r 0eab8146b294 -r b845893bebcd chirp/drivers/retevis_rt21.py
--- a/chirp/drivers/retevis_rt21.py Sat Jul 03 14:13:35 2021 -0400
+++ b/chirp/drivers/retevis_rt21.py Sat Jul 03 15:35:46 2021 -0400
@@ -297,9 +297,9 @@
exito = False
for i in range(0, 5):
serial.write(radio._magic)
- if radio.MODEL == "RB26" or radio.MODEL == "RT76":
- serial.read(1)
ack = serial.read(1)
+ if ack == "\x00":
+ ack = serial.read(1)
try:
if ack == CMD_ACK:
1
0
[chirp_devel] Fwd: [PATCH] [RT98] Add support for Retevis RT98 Single Band Mini Mobile Radios
by Jim Unroe 03 Jul '21
by Jim Unroe 03 Jul '21
03 Jul '21
Second try...
---------- Forwarded message ---------
From: Jim Unroe <kc9hi(a)comcast.net>
Date: Sat, Jul 3, 2021 at 2:25 PM
Subject: [PATCH] [RT98] Add support for Retevis RT98 Single Band Mini
Mobile Radios
To: <Rock.Unroe(a)gmail.com>
# HG changeset patch
# User Jim Unroe <rock.unroe(a)gmail.com>
# Date 1625336015 14400
# Sat Jul 03 14:13:35 2021 -0400
# Node ID 0eab8146b294ef686ca4a49c17ea38abde54c7ab
# Parent f586574bc8786fd6bef1e5d54d08d381c81edd47
[RT98] Add support for Retevis RT98 Single Band Mini Mobile Radios
This patch adds support for the various Retevis RT98 mobile radio
models/modes.
VHF FreeNet
VHF COM
VHF COMII
UHF PMR
UHF COM
UHF COMII
#9181
diff -r f586574bc878 -r 0eab8146b294 chirp/drivers/retevis_rt98.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/chirp/drivers/retevis_rt98.py Sat Jul 03 14:13:35 2021 -0400
@@ -0,0 +1,1364 @@
+# Copyright 2021 Jim Unroe <rock.unroe(a)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 os
+import struct
+import time
+import logging
+
+from chirp import bitwise
+from chirp import chirp_common
+from chirp import directory
+from chirp import errors
+from chirp import memmap
+from chirp import util
+from chirp.settings import RadioSettingGroup, RadioSetting, RadioSettings,
\
+ RadioSettingValueList, RadioSettingValueString,
RadioSettingValueBoolean, \
+ RadioSettingValueInteger, RadioSettingValueString, \
+ RadioSettingValueFloat, InvalidValueError
+
+LOG = logging.getLogger(__name__)
+
+#
+# Chirp Driver for Retevis RT98 models: RT98V (136-174 Mhz)
+# RT98U (400-490 Mhz)
+#
+#
+#
+# Global Parameters
+#
+TONES = [62.5] + list(chirp_common.TONES)
+TMODES = ['', 'Tone', 'DTCS']
+DUPLEXES = ['', '+', '-']
+
+TXPOWER_LOW = 0x00
+TXPOWER_MED = 0x01
+TXPOWER_HIGH = 0x02
+
+DUPLEX_NOSPLIT = 0x00
+DUPLEX_POSSPLIT = 0x01
+DUPLEX_NEGSPLIT = 0x02
+
+CHANNEL_WIDTH_12d5kHz = 0x00
+CHANNEL_WIDTH_20kHz = 0x01
+CHANNEL_WIDTH_25kHz = 0x02
+
+TUNING_STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 20.0, 25.0, 30.0, 50.0]
+
+POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=5),
+ chirp_common.PowerLevel("Mid", watts=10),
+ chirp_common.PowerLevel("High", watts=15)]
+
+PMR_POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=0.5), ]
+
+FREENET_POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1), ]
+
+PMR_FREQS = [446.00625, 446.01875, 446.03125, 446.04375,
+ 446.05625, 446.06875, 446.08125, 446.09375,
+ 446.10625, 446.11875, 446.13125, 446.14375,
+ 446.15625, 446.16875, 446.18125, 446.19375]
+
+FREENET_FREQS = [149.02500, 149.03750, 149.05000,
+ 149.08750, 149.10000, 149.11250]
+
+CROSS_MODES = ["Tone->Tone", "DTCS->", "->DTCS", "Tone->DTCS",
"DTCS->Tone",
+ "->Tone", "DTCS->DTCS"]
+
+LIST_STEP = [str(x) for x in TUNING_STEPS]
+LIST_TIMEOUT = ["Off"] + ["%s min" % x for x in range(1, 31)]
+LIST_APO = ["Off", "30 min", "1 hr", "2 hrs"]
+LIST_SQUELCH = ["Off"] + ["Level %s" % x for x in range(1, 10)]
+LIST_DISPLAY_MODE = ["Channel", "Frequency", "Name"]
+LIST_AOP = ["Manual", "Auto"]
+LIST_STE_TYPE = ["Off", "Silent", "120 Degree", "180 Degree", "240 Degree"]
+LIST_STE_FREQ = ["Off", "55.2 Hz", "259.2 Hz"]
+
+LIST_PRIORITY_CH = ["Off", "Priority Channel 1", "Priority Channel 2",
+ "Priority Channel 1 + Priority Channel 2"]
+
+LIST_REVERT_CH = ["Selected", "Selected + TalkBack", "Priority Channel 1",
+ "Priority Channel 2", "Last Called", "Last Used",
+ "Priority Channel 1 + TalkBack",
+ "Priority Channel 2 + TalkBack"]
+
+LIST_TIME50 = ["0.1", "0.2", "0.3", "0.4", "0.5",
+ "0.6", "0.7", "0.8", "0.9", "1.0",
+ "1.1", "1.2", "1.3", "1.4", "1.5",
+ "1.6", "1.7", "1.8", "1.9", "2.0",
+ "2.1", "3.2", "2.3", "2.4", "2.5",
+ "2.6", "2.7", "2.8", "2.9", "3.0",
+ "3.1", "3.2", "3.3", "3.4", "3.5",
+ "3.6", "3.7", "3.8", "3.9", "4.0",
+ "4.1", "4.2", "4.3", "4.4", "4.5",
+ "4.6", "4.7", "4.8", "4.9", "5.0"]
+LIST_TIME46 = LIST_TIME50[4:]
+
+LIST_RT98V_MODES = ["FreeNet", "COM", "COMII"]
+LIST_RT98U_MODES = ["PMR", "COM", "COMII"]
+
+LIST_RT98V_FREQS = ["Rx(149 - 149.2 MHz) Tx(149 - 149.2 MHz)",
+ "Rx(136 - 174 MHz) Tx(136 - 174 MHz)",
+ "Rx(147 - 174 MHz) Tx(147 - 174 MHz)"]
+
+LIST_RT98U_FREQS = ["Rx(446 - 446.2 MHz) Tx(446 - 446.2 MHz)",
+ "Rx(400 - 470 MHz) Tx(400 - 470 MHz)",
+ "Rx(450 - 470 MHz) Tx(450 - 470 MHz)"]
+
+SETTING_LISTS = {
+ "tuning_step": LIST_STEP,
+ "timeout_timer": LIST_TIMEOUT,
+ "auto_power_off": LIST_APO,
+ "squelch": LIST_SQUELCH,
+ "display_mode": LIST_DISPLAY_MODE,
+ "auto_power_on": LIST_AOP,
+ "ste_type": LIST_STE_TYPE,
+ "ste_frequency": LIST_STE_FREQ,
+ "priority_ch": LIST_PRIORITY_CH,
+ "revert_ch": LIST_REVERT_CH,
+ "settings2.dropout_delay_time": LIST_TIME50,
+ "settings2.dwell_time": LIST_TIME50,
+ "settings2.look_back_time_a": LIST_TIME46,
+ "settings2.look_back_time_b": LIST_TIME46
+}
+
+# RT98 memory map
+# section: 1 Channel Bank
+# description of channel bank (199 channels , range 1-199)
+# Each 32 Byte (0x20 hex) record:
+# bytes:bit type description
+#
---------------------------------------------------------------------------
+# 4 bbcd freq[4] receive frequency in packed binary coded
+# decimal
+# 4 bbcd offset[4] transceive offset in packed binary coded
+# decimal (note: +/- direction set by
+# 'duplex' field)
+# 1 u8 unknown0
+# 1 u8
+# :1 reverse:1 reverse flag, 0=off, 1=on (reverses
+# transmit and receive freqencies)
+# :1 txoff:1 transmitt off flag, 0=transmit, 1=do not
+# transmit
+# :2 power:2 transmit power setting, value range 0-2,
+# 0=low, 1=middle, 2=high
+# :2 duplex:2 duplex settings, 0=simplex, 1=plus (+)
+# offset, 2=minus(-) offset (see offset
field)
+# :2 channel_width:2 channel spacing, 0=12.5kHz, 1=20kHz,
2=25kHz
+# 1 u8
+# :2 unknown1:2
+# :1 talkaround:1 talkaround flag, 0=off, 1=on
+# (bypasses repeater)
+# :1 squelch_mode:1 squelch mode flag, 0=carrier,
1=ctcss/dcs
+# :1 rxdcsextra:1 use with rxcode for index of rx DCS to
use
+# :1 rxinv:1 inverse DCS rx polarity flag, 0=N, 1=I
+# :1 txdcsextra:1 use with txcode for index of tx DCS to
use
+# :1 txinv:1 inverse DCS tx polarity flag, 0=N, 1=I
+# 1 u8
+# :4 unknown2:4
+# :2 rxtmode:2 rx tone mode, value range 0-2, 0=none,
+# 1=CTCSS, 2=DCS (ctcss tone in field
rxtone)
+# :2 txtmode:2 tx tone mode, value range 0-2, 0=none,
+# 1=CTCSS, 3=DCS (ctcss tone in field
txtone)
+# 1 u8
+# :2 unknown3:2
+# :6 txtone:6 tx ctcss tone, menu index
+# 1 u8
+# :2 unknown4:2
+# :6 rxtone:6 rx ctcss tone, menu index
+# 1 u8 txcode ?, not used for ctcss
+# 1 u8 rxcode ?, not used for ctcss
+# 1 u8
+# :6 unknown5:6
+# :1 busychannellockout:1 busy channel lockout flag, 0=off,
1=enabled
+# :1 unknown6:1
+# 6 char name[6] 6 byte char string for channel name
+# 9 u8 unknown7[9]
+#
+MEM_FORMAT = """
+#seekto 0x0000;
+struct {
+ bbcd freq[4];
+ bbcd offset[4];
+ u8 unknown0;
+ u8 reverse:1,
+ tx_off:1,
+ txpower:2,
+ duplex:2,
+ channel_width:2;
+ u8 unknown1:2,
+ talkaround:1,
+ squelch_mode:1,
+ rxdcsextra:1,
+ rxinv:1,
+ txdcsextra:1,
+ txinv:1;
+ u8 unknown2:4,
+ rxtmode:2,
+ txtmode:2;
+ u8 unknown3:2,
+ txtone:6;
+ u8 unknown4:2,
+ rxtone:6;
+ u8 txcode;
+ u8 rxcode;
+ u8 unknown5:6,
+ busychannellockout:1,
+ unknown6:1;
+ char name[6];
+ u8 unknown7[9];
+} memory[199];
+"""
+
+# RT98 memory map
+# section: 2 and 3 Channel Set/Skip Flags
+#
+# Channel Set (starts 0x3240) : Channel Set bit is value 0 if a memory
+# location in the channel bank is active.
+# Channel Skip (starts 0x3260): Channel Skip bit is value 0 if a memory
+# location in the channel bank is active.
+#
+# Both flag maps are a total 24 bytes in length, aligned on 32 byte
records.
+# bit = 0 channel not set/skip, 1 is channel set/no skip
+#
+# to index a channel:
+# cbyte = channel / 8 ;
+# cbit = channel % 8 ;
+# setflag = csetflag[cbyte].c[cbit] ;
+# skipflag = cskipflag[cbyte].c[cbit] ;
+#
+# channel range is 1-199, range is 32 bytes (last 7 unknown)
+#
+MEM_FORMAT = MEM_FORMAT + """
+#seekto 0x3240;
+struct {
+ bit c[8];
+} csetflag[32];
+
+#seekto 0x3260;
+struct {
+ bit c[8];
+} cskipflag[32];
+
+"""
+
+# RT98 memory map
+# section: 4 Startup Label
+#
+# bytes:bit type description
+#
---------------------------------------------------------------------------
+# 6 char start_label[6] label displayed at startup (usually
+# your call sign)
+#
+MEM_FORMAT = MEM_FORMAT + """
+#seekto 0x3300;
+struct {
+ char startname[6];
+} slabel;
+"""
+
+# RT98 memory map
+# section: 5, 6 and 7 Radio Options
+# used to set a number of radio options
+#
+# description of function setup options, starting at 0x3310 (settings3)
+#
+# bytes:bit type description
+#
---------------------------------------------------------------------------
+# 1 u8
+# :6 unknown:6
+# :2 bandlimit_3310:2 frequency ranges, range 0-2,
+# 0=freenet(vhf) or pmr(uhf), 1=com,
2=comii
+# rt98v - 00 FreeNet Rx(149 - 149.2 MHz) Tx(149 - 149.2
MHz)
+# 01 COM Rx(136 - 174 MHz) Tx(136 - 174 MHz)
+# 02 COMII Rx(147 - 174 MHz) Tx(147 - 174 MHz)
+# rt98u - 00 PMR Rx(446 - 446.2 MHz) Tx(446 - 446.2
MHz)
+# 01 COM Rx(400 - 470 MHz) Tx(400 - 470 MHz)
+# 02 COMII Rx(450 - 470 MHz) Tx(450 - 470 MHz)
+# 1 u8 ch_number; channel number, range 1-199
+#
+# description of function setup options, starting at 0x3340 (settings)
+#
+# bytes:bit type description
+#
---------------------------------------------------------------------------
+# 1 u8
+# :4 unknown_3340:4
+# :4 tuning_step:4 tuning step, menu index value from 0-8
+# 2.5, 5, 6.25, 10, 12.5, 20, 25, 30, 50
+# 1 u8
+# :7 unknown_3341:7
+# :1 beep:1 beep mode, range 0-1, 0=off, 1=on
+# 1 u8
+# :3 unknown_3342:3
+# :5 timeout_timer:5 timeout timer, range off (no timeout),
+# 1-30 minutes
+# 1 u8
+# :6 unknown_3343:6
+# :2 auto_power_off:2 auto power off, range 0-3, off, 30min,
+# 1hr, 2hr
+# 1 u8
+# :4 unknown_3344:4
+# :4 squelch:4 squelch level, range off, 1-9
+# 1 u8
+# :3 unknown_3345:3
+# :5 volume:5 volume level, range 1-30 (no zero)
+# 1 u8 unknown_3346
+# 1 u8 unknown_3347
+# 1 u8 0x3348 [12]
+# :6 unknown_3348:6
+# :2 display_mode display mode, range 0-2, 0=channel,
+# 1=frequency, 2=name
+# 1 u8
+# :7 unknown_3349:7
+# :1 auto_power_on:1 auto power on, range 0-1, 0=manual,
+# 1=auto
+# 1 u8
+# :3 unknown_334A:3
+# :5 mic_gain:5 mic gain, range 1-30 (no zero)
+# 1 u8
+# :5 unknown_334C:5
+# :3 ste_type:3 ste type, range 0-4, 0=off, 1=silent,
+# 2=120degree, 3=180degree, 4=240degree
+# 1 u8
+# :7 unknown_334D:7
+# :1 ste_frequency:1 ste frequency, range 0-2, 0=off,
+# 1=55.2Hz, 2=259.2Hz
+# 1 u8
+# :2 unknown_0x334E:2
+# :1 forbid_setting:1 forbid setting(optional function),
+# range 0-1, 0=disabled, 1=enabled
+# :1 forbid_initialize:1 forbid initialize operate, range 0-1,
+# 0=enabled, 1=disabled (inverted)
+# :1 save_chan_param:1 save channel parameters, range 0-1,
+# 0=disabled, 1=enabled
+# :1 forbid_chan_menu:1 forbid channel menu, range 0-1,
+# 0=disabled, 1=enabled
+# :1 sql_key_function:1 sql key function, range 0-1,
+# 0=squelch off momentary, 1=squelch off
+# :1 unknown:1
+#
+# description of function setup options, starting at 0x3380 (settings2)
+#
+# bytes:bit type description
+#
---------------------------------------------------------------------------
+# 1 u8
+# :7 unknown_3380:7
+# :1 scan_mode:1 scan mode, range 0-1, 0=off, 1=on
+# 1 u8
+# :6 unknown_3381:6
+# :2 priority_ch:2 priority channel, range 0-3, 0=off,
+# 1=priority channel 1,
+# 2=priority channel 2,
+# 3=priority channel 1 + priority
channel 2
+# 1 u8 priority_ch1 priority channel 1 number, range 1-199
+# 1 u8 priority_ch2 priority channel 2 number, range 1-199
+# 1 u8
+# :4 unknown_3384:4
+# :4 revert_ch:4 revert channel, range 0-3, 0=selected,
+# 1=selected + talkback, 2=last called,
+# 3=last used
+# 1 u8 look_back_time_a look back time a, range 0-45
+# 1 u8 look_back_time_b look back time b, range 0-45
+# 1 u8 dropout_delay_time dropout delay time, range 0-49
+# 1 u8 dwell_time dwell time, range 0-49
+#
+MEM_FORMAT = MEM_FORMAT + """
+#seekto 0x3310;
+struct {
+ u8 bandlimit;
+ u8 ch_number;
+} settings3;
+"""
+
+MEM_FORMAT = MEM_FORMAT + """
+#seekto 0x3340;
+struct {
+ u8 unknown_3340:4,
+ tuning_step:4;
+ u8 unknown_3341:7,
+ beep:1;
+ u8 unknown_3342:3,
+ timeout_timer:5;
+ u8 unknown_3343:6,
+ auto_power_off:2;
+ u8 unknown_3344:4,
+ squelch:4;
+ u8 unknown_3345:3,
+ volume:5;
+ u8 unknown_3346;
+ u8 unknown_3347;
+ u8 unknown_3348:6,
+ display_mode:2;
+ u8 unknown_3349:7,
+ auto_power_on:1;
+ u8 unknown_334A:3,
+ mic_gain:5;
+ u8 unknown_334B;
+ u8 unknown_334C:5,
+ ste_type:3;
+ u8 unknown_334D:6,
+ ste_frequency:2;
+ u8 unknown_334E:1,
+ forbid_setting:1,
+ unknown1:1,
+ forbid_initialize:1,
+ save_chan_param:1,
+ forbid_chan_menu:1,
+ sql_key_function:1,
+ unknown2:1;
+} settings;
+"""
+
+MEM_FORMAT = MEM_FORMAT + """
+#seekto 0x3380;
+struct {
+ u8 unknown_3380:7,
+ scan_mode:1;
+ u8 unknown_3381:6,
+ priority_ch:2;
+ u8 priority_ch1;
+ u8 priority_ch2;
+ u8 unknown_3384:4,
+ revert_ch:4;
+ u8 look_back_time_a;
+ u8 look_back_time_b;
+ u8 dropout_delay_time;
+ u8 dwell_time;
+} settings2;
+"""
+
+# RT98 memory map
+# section: 8 Embedded Messages
+#
+# bytes:bit type description
+#
---------------------------------------------------------------------------
+# 6 char radio_type[5] radio type, vhf=rt98v, uhf=rt98u
+# 2 u8 unknown1[2]
+# 4 char mcu_version[4] mcu version, [x.xx]
+# 2 u8 unknown2[2]
+# 1 u8 mode rt98u mode: 0=pmr, 1=com, 2=comii
+# rt98v mode: 0=freenet, 1=com, 2=comii
+# 1 u8 unknown3
+# 10 u8 unused1[10]
+# 4 u8 unknown4[4]
+# 3 u8 unused2[3]
+# 16 u8 unknown5[16]
+# 10 char date_mfg[16] date manufactured, [yyyy-mm-dd]
+#
+MEM_FORMAT = MEM_FORMAT + """
+#seekto 0x3D00;
+struct {
+char radio_type[5];
+u8 unknown1[2];
+char mcu_version[4];
+u8 unknown2[2];
+u8 mode;
+u8 unknown3;
+u8 unused1[10];
+u8 unknown4[4];
+u8 unused2[3];
+u8 unknown5[16];
+char date_mfg[10];
+} embedded_msg;
+"""
+
+
+# Format for the version messages returned by the radio
+VER_FORMAT = '''
+u8 hdr;
+char model[5];
+u8 unknown[2];
+u8 bandlimit;
+char version[6];
+u8 ack;
+'''
+
+
+# Radio supports upper case and symbols
+CHARSET_ASCII_PLUS = chirp_common.CHARSET_UPPER_NUMERIC + '- '
+
+# Band limits as defined by the band byte in ver_response, defined in Hz,
for
+# VHF and UHF, used for RX and TX.
+RT98V_BAND_LIMITS = {0x00: [(149000000, 149200000)],
+ 0x01: [(136000000, 174000000)],
+ 0x02: [(147000000, 174000000)]}
+
+RT98U_BAND_LIMITS = {0x00: [(446000000, 446200000)],
+ 0x01: [(400000000, 470000000)],
+ 0x02: [(450000000, 470000000)]}
+
+
+# Get band limits from a band limit value
+def get_band_limits_Hz(radio_type, limit_value):
+ if radio_type == "RT98U":
+ if limit_value not in RT98U_BAND_LIMITS:
+ limit_value = 0x01
+ LOG.warning('Unknown band limit value 0x%02x, default to 0x01')
+ bandlimitfrequencies = RT98U_BAND_LIMITS[limit_value]
+ elif radio_type == "RT98V":
+ if limit_value not in RT98V_BAND_LIMITS:
+ limit_value = 0x01
+ LOG.warning('Unknown band limit value 0x%02x, default to 0x01')
+ bandlimitfrequencies = RT98V_BAND_LIMITS[limit_value]
+ return bandlimitfrequencies
+
+
+def _echo_write(radio, data):
+ try:
+ radio.pipe.write(data)
+ radio.pipe.read(len(data))
+ except Exception, e:
+ LOG.error("Error writing to radio: %s" % e)
+ raise errors.RadioError("Unable to write to radio")
+
+
+def _checksum(data):
+ cs = 0
+ for byte in data:
+ cs += ord(byte)
+ return cs % 256
+
+
+def _read(radio, length):
+ try:
+ data = radio.pipe.read(length)
+ except Exception, e:
+ _finish(radio)
+ LOG.error("Error reading from radio: %s" % e)
+ raise errors.RadioError("Unable to read from radio")
+
+ if len(data) != length:
+ _finish(radio)
+ LOG.error("Short read from radio (%i, expected %i)" %
+ (len(data), length))
+ LOG.debug(util.hexprint(data))
+ raise errors.RadioError("Short read from radio")
+ return data
+
+
+# strip trailing 0x00 to convert a string returned by bitwise.parse into a
+# python string
+def cstring_to_py_string(cstring):
+ return "".join(c for c in cstring if c != '\x00')
+
+
+# Check the radio version reported to see if it's one we support,
+# returns bool version supported, and the band index
+def check_ver(ver_response, allowed_types):
+ ''' Check the returned radio version is one we approve of '''
+
+ LOG.debug('ver_response = ')
+ LOG.debug(util.hexprint(ver_response))
+
+ resp = bitwise.parse(VER_FORMAT, ver_response)
+ verok = False
+
+ if resp.hdr == 0x49 and resp.ack == 0x06:
+ model, version =
[cstring_to_py_string(bitwise.get_string(s)).strip()
+ for s in (resp.model, resp.version)]
+ LOG.debug('radio model: \'%s\' version: \'%s\'' %
+ (model, version))
+ LOG.debug('allowed_types = %s' % allowed_types)
+
+ if model in allowed_types:
+ LOG.debug('model in allowed_types')
+
+ if version in allowed_types[model]:
+ LOG.debug('version in allowed_types[model]')
+ verok = True
+ else:
+ _finish(radio)
+ raise errors.RadioError('Failed to parse version response')
+
+ return verok, str(resp.model), int(resp.bandlimit)
+
+
+def _ident(radio):
+ radio.pipe.timeout = 1
+ _echo_write(radio, "PROGRAM")
+ response = radio.pipe.read(3)
+ if response != "QX\06":
+ _finish(radio)
+ LOG.debug("Response was :\n%s" % util.hexprint(response))
+ raise errors.RadioError("Radio did not respond. Check connection.")
+ _echo_write(radio, "\x02")
+ ver_response = radio.pipe.read(16)
+ LOG.debug(util.hexprint(ver_response))
+
+ verok, model, bandlimit = check_ver(ver_response,
+ radio.ALLOWED_RADIO_TYPES)
+ if not verok:
+ _finish(radio)
+ raise errors.RadioError(
+ 'Radio version not in allowed list for %s-%s: %s' %
+ (radio.VENDOR, radio.MODEL, util.hexprint(ver_response)))
+
+ return model, bandlimit
+
+
+def _send(radio, cmd, addr, length, data=None):
+ frame = struct.pack(">cHb", cmd, addr, length)
+ if data:
+ frame += data
+ frame += chr(_checksum(frame[1:]))
+ frame += "\x06"
+ _echo_write(radio, frame)
+ LOG.debug("Sent:\n%s" % util.hexprint(frame))
+ if data:
+ result = radio.pipe.read(1)
+ if result != "\x06":
+ _finish(radio)
+ LOG.debug("Ack was: %s" % repr(result))
+ raise errors.RadioError("Radio did not accept block at %04x"
+ % addr)
+ return
+ result = _read(radio, length + 6)
+ LOG.debug("Got:\n%s" % util.hexprint(result))
+ header = result[0:4]
+ data = result[4:-2]
+ ack = result[-1]
+ if ack != "\x06":
+ _finish(radio)
+ LOG.debug("Ack was: %s" % repr(ack))
+ raise errors.RadioError("Radio NAK'd block at %04x" % addr)
+ _cmd, _addr, _length = struct.unpack(">cHb", header)
+ if _addr != addr or _length != _length:
+ _finish(radio)
+ LOG.debug("Expected/Received:")
+ LOG.debug(" Length: %02x/%02x" % (length, _length))
+ LOG.debug(" Addr: %04x/%04x" % (addr, _addr))
+ raise errors.RadioError("Radio send unexpected block")
+ cs = _checksum(result[1:-2])
+ if cs != ord(result[-2]):
+ _finish(radio)
+ LOG.debug("Calculated: %02x" % cs)
+ LOG.debug("Actual: %02x" % ord(result[-2]))
+ raise errors.RadioError("Block at 0x%04x failed checksum" % addr)
+ return data
+
+
+def _finish(radio):
+ endframe = "\x45\x4E\x44"
+ _echo_write(radio, endframe)
+ result = radio.pipe.read(1)
+ if result != "\x06":
+ LOG.error("Got:\n%s" % util.hexprint(result))
+ raise errors.RadioError("Radio did not finish cleanly")
+
+
+def do_download(radio):
+
+ _ident(radio)
+
+ _memobj = None
+ data = ""
+
+ for addr in range(0, radio._memsize, 0x10):
+ block = _send(radio, 'R', addr, 0x10)
+ data += block
+ status = chirp_common.Status()
+ status.cur = len(data)
+ status.max = radio._memsize
+ status.msg = "Downloading from radio"
+ radio.status_fn(status)
+
+ _finish(radio)
+
+ return memmap.MemoryMap(data)
+
+
+def do_upload(radio):
+ model, bandlimit = _ident(radio)
+ _embedded = radio._memobj.embedded_msg
+
+ if model != str(_embedded.radio_type):
+ LOG.warning('radio and image model types differ')
+ LOG.warning('model type (radio): %s' % str(model))
+ LOG.warning('model type (image): %s' % str(_embedded.radio_type))
+
+ _finish(radio)
+
+ msg = ("The upload was stopped because the radio type "
+ "of the image (%s) does not match that "
+ "of the radio (%s).")
+ raise errors.RadioError(msg % (str(_embedded.radio_type),
str(model)))
+
+ if bandlimit != int(_embedded.mode):
+ if str(_embedded.radio_type) == "RT98U":
+ image_band_limits = LIST_RT98U_FREQS[int(_embedded.mode)]
+ if str(_embedded.radio_type) == "RT98V":
+ image_band_limits = LIST_RT98V_FREQS[int(_embedded.mode)]
+ if model == "RT98U":
+ radio_band_limits = LIST_RT98U_FREQS[int(bandlimit)]
+ if model == "RT98V":
+ radio_band_limits = LIST_RT98V_FREQS[int(bandlimit)]
+
+ LOG.warning('radio and image band limits differ')
+ LOG.warning('image band limits: %s' % image_band_limits)
+ LOG.warning('radio band limits: %s' % radio_band_limits)
+
+ _finish(radio)
+
+ msg = ("The upload was stopped because the band limits "
+ "of the image (%s) does not match that "
+ "of the radio (%s).")
+ raise errors.RadioError(msg % (image_band_limits,
radio_band_limits))
+
+ try:
+ for start, end in radio._ranges:
+ for addr in range(start, end, 0x10):
+ block = radio._mmap[addr:addr+0x10]
+ _send(radio, 'W', addr, len(block), block)
+ status = chirp_common.Status()
+ status.cur = addr
+ status.max = end
+ status.msg = "Uploading to Radio"
+ radio.status_fn(status)
+ _finish(radio)
+ except errors.RadioError:
+ raise
+ except Exception as e:
+ _finish(radio)
+ raise errors.RadioError('Failed to upload to radio: %s' % e)
+
+
+#
+# The base class, extended for use with other models
+#
+class Rt98BaseRadio(chirp_common.CloneModeRadio,
+ chirp_common.ExperimentalRadio):
+ """Retevis RT98 Base"""
+ VENDOR = "Retevis"
+ MODEL = "RT98 Base"
+ BAUD_RATE = 9600
+
+ _memsize = 0x3E00
+ _ranges = [(0x0000, 0x3310),
+ (0x3320, 0x3390)]
+
+ @classmethod
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.experimental = ("The Retevis RT98 driver is an beta version."
+ "Proceed with Caution and backup your data")
+ return rp
+
+ def get_features(self):
+ _embedded = self._memobj.embedded_msg
+ rf = chirp_common.RadioFeatures()
+ rf.has_settings = True
+ rf.has_bank = False
+ rf.can_odd_split = True
+ rf.has_name = True
+ if _embedded.mode == 0: # PMR or FreeNet
+ rf.has_offset = False
+ else:
+ rf.has_offset = True
+ rf.has_ctone = True
+ rf.has_cross = True
+ rf.has_tuning_step = False
+ rf.has_dtcs = True
+ rf.has_rx_dtcs = True
+ rf.has_dtcs_polarity = True
+ rf.valid_skips = ["", "S"]
+ rf.memory_bounds = (1, 199)
+ rf.valid_name_length = 6
+ if _embedded.mode == 0: # PMR or FreeNet
+ rf.valid_duplexes = ['']
+ else:
+ rf.valid_duplexes = DUPLEXES + ['split', 'off']
+ rf.valid_characters = chirp_common.CHARSET_UPPER_NUMERIC + "- "
+ if _embedded.mode == 0: # PMR or FreeNet
+ rf.valid_modes = ['NFM']
+ else:
+ rf.valid_modes = ['FM', 'NFM']
+ rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
+ rf.valid_cross_modes = CROSS_MODES
+ if _embedded.mode == 0: # PMR or FreeNet
+ if str(_embedded.radio_type) == "RT98U":
+ rf.valid_power_levels = PMR_POWER_LEVELS
+ if str(_embedded.radio_type) == "RT98V":
+ rf.valid_power_levels = FREENET_POWER_LEVELS
+ else:
+ rf.valid_power_levels = POWER_LEVELS
+ rf.valid_dtcs_codes = chirp_common.ALL_DTCS_CODES
+
+ try:
+ rf.valid_bands = get_band_limits_Hz(
+ str(_embedded.radio_type),
+ int(_embedded.mode))
+ except TypeError as e:
+ # If we're asked without memory loaded, assume the most
permissive
+ rf.valid_bands = get_band_limits_Hz(str(_embedded.radio_type),
1)
+ except Exception as e:
+ LOG.error('Failed to get band limits for RT98: %s' % e)
+ rf.valid_bands = get_band_limits_Hz(str(_embedded.radio_type),
1)
+
+ rf.valid_tuning_steps = TUNING_STEPS
+ return rf
+
+ def validate_memory(self, mem):
+ _embedded = self._memobj.embedded_msg
+ msgs = ""
+ msgs = chirp_common.CloneModeRadio.validate_memory(self, mem)
+
+ # FreeNet and PMR radio types
+ if _embedded.mode == 0: # PMR or FreeNet
+ freq = float(mem.freq) / 1000000
+
+ # FreeNet
+ if str(_embedded.radio_type) == "RT98V":
+ if freq not in FREENET_FREQS:
+ _msg_freq = 'Memory location not a valid FreeNet
frequency'
+ # warn user invalid frequency
+ msgs.append(chirp_common.ValidationError(_msg_freq))
+
+ # PMR
+ if str(_embedded.radio_type) == "RT98U":
+ if freq not in PMR_FREQS:
+ _msg_freq = 'Memory location not a valid PMR frequency'
+ # warn user invalid frequency
+ msgs.append(chirp_common.ValidationError(_msg_freq))
+
+ return msgs
+
+ # Do a download of the radio from the serial port
+ def sync_in(self):
+ self._mmap = do_download(self)
+ self.process_mmap()
+
+ # Do an upload of the radio to the serial port
+ def sync_out(self):
+ do_upload(self)
+
+ def process_mmap(self):
+ self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+ # Return a raw representation of the memory object, which
+ # is very helpful for development
+ def get_raw_memory(self, number):
+ return repr(self._memobj.memory[number - 1])
+
+ def _get_dcs_index(self, _mem, which):
+ base = getattr(_mem, '%scode' % which)
+ extra = getattr(_mem, '%sdcsextra' % which)
+ return (int(extra) << 8) | int(base)
+
+ def _set_dcs_index(self, _mem, which, index):
+ base = getattr(_mem, '%scode' % which)
+ extra = getattr(_mem, '%sdcsextra' % which)
+ base.set_value(index & 0xFF)
+ extra.set_value(index >> 8)
+
+ # Extract a high-level memory object from the low-level memory map
+ # This is called to populate a memory in the UI
+ def get_memory(self, number):
+ _embedded = self._memobj.embedded_msg
+ # Get a low-level memory object mapped to the image
+ _mem = self._memobj.memory[number - 1]
+
+ # get flag info
+ cbyte = (number - 1) / 8
+ cbit = 7 - ((number - 1) % 8)
+ setflag = self._memobj.csetflag[cbyte].c[cbit]
+ skipflag = self._memobj.cskipflag[cbyte].c[cbit]
+
+ mem = chirp_common.Memory()
+
+ mem.number = number # Set the memory number
+
+ # We'll consider any blank (i.e. 0MHz frequency) to be empty
+ if _mem.freq == 0:
+ mem.empty = True
+ return mem
+
+ if setflag == 0:
+ mem.empty = True
+ return mem
+
+ if _mem.get_raw()[0] == "\xFF":
+ mem.empty = True
+ return mem
+
+ # set the name
+ mem.name = str(_mem.name).rstrip() # Set the alpha tag
+
+ # Convert your low-level frequency and offset to Hertz
+ mem.freq = int(_mem.freq) * 10
+ mem.offset = int(_mem.offset) * 10
+
+ # Set the duplex flags
+ if _mem.duplex == DUPLEX_POSSPLIT:
+ mem.duplex = '+'
+ elif _mem.duplex == DUPLEX_NEGSPLIT:
+ mem.duplex = '-'
+ elif _mem.duplex == DUPLEX_NOSPLIT:
+ mem.duplex = ''
+ elif _mem.duplex == DUPLEX_ODDSPLIT:
+ mem.duplex = 'split'
+ else:
+ LOG.error('%s: get_mem: unhandled duplex: %02x' %
+ (mem.name, _mem.duplex))
+
+ # handle tx off
+ if _mem.tx_off:
+ mem.duplex = 'off'
+
+ # Set the channel width
+ if _mem.channel_width == CHANNEL_WIDTH_12d5kHz:
+ mem.mode = 'NFM'
+ elif _embedded.mode == 0: # PMR or FreeNet
+ LOG.info('PMR and FreeNet channels must be Channel Width
12.5kHz')
+ mem.mode = 'NFM'
+ elif _mem.channel_width == CHANNEL_WIDTH_25kHz:
+ mem.mode = 'FM'
+ elif _mem.channel_width == CHANNEL_WIDTH_20kHz:
+ LOG.info(
+ '%s: get_mem: promoting 20kHz channel width to 25kHz' %
+ mem.name)
+ mem.mode = 'FM'
+ else:
+ LOG.error('%s: get_mem: unhandled channel width: 0x%02x' %
+ (mem.name, _mem.channel_width))
+
+ # set the power level
+ if _embedded.mode == 0: # PMR or FreeNet
+ if str(_embedded.radio_type) == "RT98U":
+ LOG.info('using PMR power levels')
+ _levels = PMR_POWER_LEVELS
+ if str(_embedded.radio_type) == "RT98V":
+ LOG.info('using FreeNet power levels')
+ _levels = FREENET_POWER_LEVELS
+ else: # COM or COMII
+ LOG.info('using general power levels')
+ _levels = POWER_LEVELS
+
+ if _mem.txpower == TXPOWER_LOW:
+ mem.power = _levels[0]
+ elif _embedded.mode == 0: # PMR or FreeNet
+ LOG.info('FreeNet or PMR channel is not set to TX Power Low')
+ LOG.info('Setting channel to TX Power Low')
+ mem.power = _levels[0]
+ elif _mem.txpower == TXPOWER_MED:
+ mem.power = _levels[1]
+ elif _mem.txpower == TXPOWER_HIGH:
+ mem.power = _levels[2]
+ else:
+ LOG.error('%s: get_mem: unhandled power level: 0x%02x' %
+ (mem.name, _mem.txpower))
+
+ # CTCSS Tones and DTCS Codes
+ rxtone = txtone = None
+
+ rxmode = TMODES[_mem.rxtmode]
+ txmode = TMODES[_mem.txtmode]
+
+ if rxmode == "Tone":
+ rxtone = TONES[_mem.rxtone]
+ elif rxmode == "DTCS":
+ rxtone = chirp_common.ALL_DTCS_CODES[self._get_dcs_index(
+ _mem, 'rx')]
+
+ if txmode == "Tone":
+ txtone = TONES[_mem.txtone]
+ elif txmode == "DTCS":
+ txtone = chirp_common.ALL_DTCS_CODES[self._get_dcs_index(
+ _mem, 'tx')]
+
+ rxpol = _mem.rxinv and "R" or "N"
+ txpol = _mem.txinv and "R" or "N"
+
+ chirp_common.split_tone_decode(mem,
+ (txmode, txtone, txpol),
+ (rxmode, rxtone, rxpol))
+
+ # Check if this memory is in the scan enabled list
+ mem.skip = "S" if skipflag == 0 else ""
+
+ # Extra
+ mem.extra = RadioSettingGroup("extra", "Extra")
+
+ rs = RadioSettingValueBoolean(bool(_mem.busychannellockout))
+ rset = RadioSetting("busychannellockout", "Busy channel lockout",
rs)
+ mem.extra.append(rset)
+
+ rs = RadioSettingValueBoolean(bool(_mem.reverse))
+ rset = RadioSetting("reverse", "Reverse", rs)
+ mem.extra.append(rset)
+
+ rs = RadioSettingValueBoolean(bool(_mem.talkaround))
+ rset = RadioSetting("talkaround", "Talk around", rs)
+ mem.extra.append(rset)
+
+ rs = RadioSettingValueBoolean(bool(_mem.squelch_mode))
+ rset = RadioSetting("squelch_mode", "Squelch mode", rs)
+ mem.extra.append(rset)
+
+ return mem
+
+ # Store details about a high-level memory to the memory map
+ # This is called when a user edits a memory in the UI
+ def set_memory(self, mem):
+ _embedded = self._memobj.embedded_msg
+ # Get a low-level memory object mapped to the image
+
+ _mem = self._memobj.memory[mem.number - 1]
+
+ cbyte = (mem.number - 1) / 8
+ cbit = 7 - ((mem.number - 1) % 8)
+
+ if mem.empty:
+ self._memobj.csetflag[cbyte].c[cbit] = 0
+ self._memobj.cskipflag[cbyte].c[cbit] = 0
+ _mem.set_raw('\xff' * (_mem.size() / 8))
+ return
+
+ _mem.set_raw('\x00' * (_mem.size() / 8))
+
+ # set the occupied bitfield
+ self._memobj.csetflag[cbyte].c[cbit] = 1
+ # set the scan add bitfield
+ self._memobj.cskipflag[cbyte].c[cbit] = 0 if (mem.skip == "S")
else 1
+
+ _mem.freq = mem.freq / 10 # Convert to low-level
frequency
+ _mem.offset = mem.offset / 10 # Convert to low-level
frequency
+
+ # Store the alpha tag
+ _mem.name = mem.name.ljust(6)[:6] # Store the alpha tag
+
+ # Set duplex bitfields
+ if mem.duplex == '+':
+ _mem.duplex = DUPLEX_POSSPLIT
+ elif mem.duplex == '-':
+ _mem.duplex = DUPLEX_NEGSPLIT
+ elif mem.duplex == '':
+ _mem.duplex = DUPLEX_NOSPLIT
+ elif mem.duplex == 'split':
+ diff = mem.offset - mem.freq
+ _mem.duplex = DUPLEXES.index("-") \
+ if diff < 0 else DUPLEXES.index("+")
+ _mem.offset = abs(diff) / 10
+ else:
+ LOG.error('%s: set_mem: unhandled duplex: %s' %
+ (mem.name, mem.duplex))
+
+ # handle tx off
+ _mem.tx_off = 0
+ if mem.duplex == 'off':
+ _mem.tx_off = 1
+
+ # Set the channel width - remember we promote 20kHz channels to FM
+ # on import, so don't handle them here
+ if mem.mode == 'FM':
+ _mem.channel_width = CHANNEL_WIDTH_25kHz
+ elif mem.mode == 'NFM':
+ _mem.channel_width = CHANNEL_WIDTH_12d5kHz
+ else:
+ LOG.error('%s: set_mem: unhandled mode: %s' % (
+ mem.name, mem.mode))
+
+ # CTCSS Tones and DTCS Codes
+ ((txmode, txtone, txpol),
+ (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
+
+ _mem.txtmode = TMODES.index(txmode)
+
+ _mem.rxtmode = TMODES.index(rxmode)
+
+ if txmode == "Tone":
+ _mem.txtone = TONES.index(txtone)
+ elif txmode == "DTCS":
+ self._set_dcs_index(_mem, 'tx',
+ chirp_common.ALL_DTCS_CODES.index(txtone))
+
+ _mem.squelch_mode = False
+ if rxmode == "Tone":
+ _mem.rxtone = TONES.index(rxtone)
+ _mem.squelch_mode = True
+ elif rxmode == "DTCS":
+ self._set_dcs_index(_mem, 'rx',
+ chirp_common.ALL_DTCS_CODES.index(rxtone))
+ _mem.squelch_mode = True
+
+ _mem.txinv = txpol == "R"
+ _mem.rxinv = rxpol == "R"
+
+ # set the power level
+ if mem.power == POWER_LEVELS[0]:
+ _mem.txpower = TXPOWER_LOW
+ elif mem.power == POWER_LEVELS[1]:
+ _mem.txpower = TXPOWER_MED
+ elif mem.power == POWER_LEVELS[2]:
+ _mem.txpower = TXPOWER_HIGH
+ else:
+ LOG.error('%s: set_mem: unhandled power level: %s' %
+ (mem.name, mem.power))
+
+ # extra settings
+ for setting in mem.extra:
+ setattr(_mem, setting.get_name(), setting.value)
+
+ def _get_settings(self):
+ _embedded = self._memobj.embedded_msg
+ _settings = self._memobj.settings
+ _settings2 = self._memobj.settings2
+ _settings3 = self._memobj.settings3
+ _slabel = self._memobj.slabel
+
+ function = RadioSettingGroup("function", "Function Setup")
+ group = RadioSettings(function)
+
+ # Function Setup
+ # MODE SET
+ rs = RadioSettingValueList(LIST_DISPLAY_MODE,
+
LIST_DISPLAY_MODE[_settings.display_mode])
+ rset = RadioSetting("display_mode", "Display Mode", rs)
+ function.append(rset)
+
+ rs = RadioSettingValueInteger(1, 199, _settings3.ch_number + 1)
+ rset = RadioSetting("settings3.ch_number", "Channel Number", rs)
+ function.append(rset)
+
+ # DISPLAY SET
+ def _filter(name):
+ filtered = ""
+ for char in str(name):
+ if char in chirp_common.CHARSET_ASCII:
+ filtered += char
+ else:
+ filtered += " "
+ return filtered
+
+ val = RadioSettingValueString(0, 6, _filter(_slabel.startname))
+ rs = RadioSetting("slabel.startname", "Startup Label", val)
+ function.append(rs)
+
+ # VOL SET
+ rs = RadioSettingValueBoolean(bool(_settings.beep))
+ rset = RadioSetting("beep", "Beep Prompt", rs)
+ function.append(rset)
+
+ rs = RadioSettingValueInteger(1, 30, _settings.volume)
+ rset = RadioSetting("volume", "Volume Level", rs)
+ function.append(rset)
+
+ rs = RadioSettingValueInteger(1, 16, _settings.mic_gain)
+ rset = RadioSetting("mic_gain", "Mic Gain", rs)
+ function.append(rset)
+
+ # ON/OFF SET
+ rs = RadioSettingValueList(LIST_APO,
+ LIST_APO[_settings.auto_power_off])
+ rset = RadioSetting("auto_power_off", "Auto Power Off", rs)
+ function.append(rset)
+
+ rs = RadioSettingValueList(LIST_AOP,
LIST_AOP[_settings.auto_power_on])
+ rset = RadioSetting("auto_power_on", "Power On Method", rs)
+ function.append(rset)
+
+ # STE SET
+ rs = RadioSettingValueList(LIST_STE_FREQ,
+ LIST_STE_FREQ[_settings.ste_frequency])
+ rset = RadioSetting("ste_frequency", "STE Frequency", rs)
+ function.append(rset)
+
+ rs = RadioSettingValueList(LIST_STE_TYPE,
+ LIST_STE_TYPE[_settings.ste_type])
+ rset = RadioSetting("ste_type", "STE Type", rs)
+ function.append(rset)
+
+ # FUNCTION SET
+ rs = RadioSettingValueList(LIST_STEP,
LIST_STEP[_settings.tuning_step])
+ rset = RadioSetting("tuning_step", "Tuning Step", rs)
+ function.append(rset)
+
+ rs = RadioSettingValueList(LIST_SQUELCH,
+ LIST_SQUELCH[_settings.squelch])
+ rset = RadioSetting("squelch", "Squelch Level", rs)
+ function.append(rset)
+
+ rs = RadioSettingValueBoolean(bool(_settings.sql_key_function))
+ rset = RadioSetting("sql_key_function", "SQL Key Function", rs)
+ function.append(rset)
+
+ rs = RadioSettingValueList(LIST_TIMEOUT,
+ LIST_TIMEOUT[_settings.timeout_timer])
+ rset = RadioSetting("timeout_timer", "Timeout Timer", rs)
+ function.append(rset)
+
+ # uncategorized
+ rs = RadioSettingValueBoolean(bool(_settings.save_chan_param))
+ rset = RadioSetting("save_chan_param", "Save Channel Parameters",
rs)
+ function.append(rset)
+
+ rs = RadioSettingValueBoolean(bool(_settings.forbid_chan_menu))
+ rset = RadioSetting("forbid_chan_menu", "Forbid Channel Menu", rs)
+ function.append(rset)
+
+ rs = RadioSettingValueBoolean(bool(not
_settings.forbid_initialize))
+ rset = RadioSetting("forbid_initialize", "Forbid Initialize", rs)
+ function.append(rset)
+
+ rs = RadioSettingValueBoolean(bool(_settings.forbid_setting))
+ rset = RadioSetting("forbid_setting", "Forbid Setting", rs)
+ function.append(rset)
+
+ # Information Of Scanning Channel
+ scanning = RadioSettingGroup("scanning", "Scanning Setup")
+ group.append(scanning)
+
+ rs = RadioSettingValueBoolean(bool(_settings2.scan_mode))
+ rset = RadioSetting("settings2.scan_mode", "Scan Mode", rs)
+ scanning.append(rset)
+
+ rs = RadioSettingValueList(LIST_PRIORITY_CH,
+
LIST_PRIORITY_CH[_settings2.priority_ch])
+ rset = RadioSetting("settings2.priority_ch", "Priority Channel",
rs)
+ scanning.append(rset)
+
+ rs = RadioSettingValueInteger(1, 199, _settings2.priority_ch1 + 1)
+ rset = RadioSetting("settings2.priority_ch1", "Priority Channel
1", rs)
+ scanning.append(rset)
+
+ rs = RadioSettingValueInteger(1, 199, _settings2.priority_ch2 + 1)
+ rset = RadioSetting("settings2.priority_ch2", "Priority Channel
2", rs)
+ scanning.append(rset)
+
+ rs = RadioSettingValueList(LIST_REVERT_CH,
+ LIST_REVERT_CH[_settings2.revert_ch])
+ rset = RadioSetting("settings2.revert_ch", "Revert Channel", rs)
+ scanning.append(rset)
+
+ rs = RadioSettingValueList(LIST_TIME46,
+
LIST_TIME46[_settings2.look_back_time_a])
+ rset = RadioSetting("settings2.look_back_time_a",
+ "Look Back Time A", rs)
+ scanning.append(rset)
+
+ rs = RadioSettingValueList(LIST_TIME46,
+
LIST_TIME46[_settings2.look_back_time_b])
+ rset = RadioSetting("settings2.look_back_time_b",
+ "Look Back Time B", rs)
+ scanning.append(rset)
+
+ rs = RadioSettingValueList(LIST_TIME50,
+
LIST_TIME50[_settings2.dropout_delay_time])
+ rset = RadioSetting("settings2.dropout_delay_time",
+ "Dropout Delay Time", rs)
+ scanning.append(rset)
+
+ rs = RadioSettingValueList(LIST_TIME50,
+ LIST_TIME50[_settings2.dwell_time])
+ rset = RadioSetting("settings2.dwell_time", "Dwell Time", rs)
+ scanning.append(rset)
+
+ # Embedded Message
+ embedded = RadioSettingGroup("embedded", "Embedded Message")
+ group.append(embedded)
+
+ rs = RadioSettingValueString(0, 5, _filter(_embedded.radio_type))
+ rs.set_mutable(False)
+ rset = RadioSetting("embedded_msg.radio_type", "Radio Type", rs)
+ embedded.append(rset)
+
+ if str(_embedded.radio_type) == "RT98V":
+ options = LIST_RT98V_MODES
+ else:
+ options = LIST_RT98U_MODES
+ rs = RadioSettingValueList(options, options[_embedded.mode])
+ rs.set_mutable(False)
+ rset = RadioSetting("embedded_msg.mode", "Mode", rs)
+ embedded.append(rset)
+
+ # frequency
+ if str(_embedded.radio_type) == "RT98V":
+ options = LIST_RT98V_FREQS
+ else:
+ options = LIST_RT98U_FREQS
+ rs = RadioSettingValueList(options, options[_settings3.bandlimit])
+ rs.set_mutable(False)
+ rset = RadioSetting("settings3.bandlimit", "Frequency", rs)
+ embedded.append(rset)
+
+ rs = RadioSettingValueString(0, 10, _filter(_embedded.date_mfg))
+ rs.set_mutable(False)
+ rset = RadioSetting("embedded_msg.date_mfg", "Production Date", rs)
+ embedded.append(rset)
+
+ rs = RadioSettingValueString(0, 4, _filter(_embedded.mcu_version))
+ rs.set_mutable(False)
+ rset = RadioSetting("embedded_msg.mcu_version", "MCU Version", rs)
+ embedded.append(rset)
+
+ return group
+
+ def get_settings(self):
+ try:
+ return self._get_settings()
+ except:
+ import traceback
+ LOG.error("failed to parse settings")
+ traceback.print_exc()
+ return None
+
+ 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 == "ch_number":
+ setattr(obj, setting, int(element.value) - 1)
+ elif setting == "forbid_initialize":
+ setattr(obj, setting, not int(element.value))
+ elif setting == "priority_ch1":
+ setattr(obj, setting, int(element.value) - 1)
+ elif setting == "priority_ch2":
+ 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):
+ # This radio has always been post-metadata, so never do
+ # old-school detection
+ return False
+
+
+(a)directory.register
+class Rt98Radio(Rt98BaseRadio):
+ """Retevis RT98"""
+ VENDOR = "Retevis"
+ MODEL = "RT98"
+ # Allowed radio types is a dict keyed by model of a list of version
+ # strings
+ ALLOWED_RADIO_TYPES = {'RT98V': ['V100'],
+ 'RT98U': ['V100']}
diff -r f586574bc878 -r 0eab8146b294 tools/cpep8.manifest
--- a/tools/cpep8.manifest Thu Jun 17 21:57:05 2021 -0400
+++ b/tools/cpep8.manifest Sat Jul 03 14:13:35 2021 -0400
@@ -83,6 +83,7 @@
./chirp/drivers/retevis_rt23.py
./chirp/drivers/retevis_rt26.py
./chirp/drivers/retevis_rt76p.py
+./chirp/drivers/retevis_rt98.py
./chirp/drivers/rfinder.py
./chirp/drivers/tdxone_tdq8a.py
./chirp/drivers/template.py
1
0
[chirp_devel] Fwd: [PATCH] [RT98] Add support for Retevis RT98 Single Band Mini Mobile Radios
by Jim Unroe 03 Jul '21
by Jim Unroe 03 Jul '21
03 Jul '21
CHIRP Radio Images (*.img) file for testing attached.
Jim KC9HI
---------- Forwarded message ---------
From: Jim Unroe <kc9hi(a)comcast.net>
Date: Sat, Jul 3, 2021 at 2:25 PM
Subject: [PATCH] [RT98] Add support for Retevis RT98 Single Band Mini
Mobile Radios
To: <Rock.Unroe(a)gmail.com>
# HG changeset patch
# User Jim Unroe <rock.unroe(a)gmail.com>
# Date 1625336015 14400
# Sat Jul 03 14:13:35 2021 -0400
# Node ID 0eab8146b294ef686ca4a49c17ea38abde54c7ab
# Parent f586574bc8786fd6bef1e5d54d08d381c81edd47
[RT98] Add support for Retevis RT98 Single Band Mini Mobile Radios
This patch adds support for the various Retevis RT98 mobile radio
models/modes.
VHF FreeNet
VHF COM
VHF COMII
UHF PMR
UHF COM
UHF COMII
#9181
diff -r f586574bc878 -r 0eab8146b294 chirp/drivers/retevis_rt98.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/chirp/drivers/retevis_rt98.py Sat Jul 03 14:13:35 2021 -0400
@@ -0,0 +1,1364 @@
+# Copyright 2021 Jim Unroe <rock.unroe(a)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 os
+import struct
+import time
+import logging
+
+from chirp import bitwise
+from chirp import chirp_common
+from chirp import directory
+from chirp import errors
+from chirp import memmap
+from chirp import util
+from chirp.settings import RadioSettingGroup, RadioSetting, RadioSettings,
\
+ RadioSettingValueList, RadioSettingValueString,
RadioSettingValueBoolean, \
+ RadioSettingValueInteger, RadioSettingValueString, \
+ RadioSettingValueFloat, InvalidValueError
+
+LOG = logging.getLogger(__name__)
+
+#
+# Chirp Driver for Retevis RT98 models: RT98V (136-174 Mhz)
+# RT98U (400-490 Mhz)
+#
+#
+#
+# Global Parameters
+#
+TONES = [62.5] + list(chirp_common.TONES)
+TMODES = ['', 'Tone', 'DTCS']
+DUPLEXES = ['', '+', '-']
+
+TXPOWER_LOW = 0x00
+TXPOWER_MED = 0x01
+TXPOWER_HIGH = 0x02
+
+DUPLEX_NOSPLIT = 0x00
+DUPLEX_POSSPLIT = 0x01
+DUPLEX_NEGSPLIT = 0x02
+
+CHANNEL_WIDTH_12d5kHz = 0x00
+CHANNEL_WIDTH_20kHz = 0x01
+CHANNEL_WIDTH_25kHz = 0x02
+
+TUNING_STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 20.0, 25.0, 30.0, 50.0]
+
+POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=5),
+ chirp_common.PowerLevel("Mid", watts=10),
+ chirp_common.PowerLevel("High", watts=15)]
+
+PMR_POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=0.5), ]
+
+FREENET_POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1), ]
+
+PMR_FREQS = [446.00625, 446.01875, 446.03125, 446.04375,
+ 446.05625, 446.06875, 446.08125, 446.09375,
+ 446.10625, 446.11875, 446.13125, 446.14375,
+ 446.15625, 446.16875, 446.18125, 446.19375]
+
+FREENET_FREQS = [149.02500, 149.03750, 149.05000,
+ 149.08750, 149.10000, 149.11250]
+
+CROSS_MODES = ["Tone->Tone", "DTCS->", "->DTCS", "Tone->DTCS",
"DTCS->Tone",
+ "->Tone", "DTCS->DTCS"]
+
+LIST_STEP = [str(x) for x in TUNING_STEPS]
+LIST_TIMEOUT = ["Off"] + ["%s min" % x for x in range(1, 31)]
+LIST_APO = ["Off", "30 min", "1 hr", "2 hrs"]
+LIST_SQUELCH = ["Off"] + ["Level %s" % x for x in range(1, 10)]
+LIST_DISPLAY_MODE = ["Channel", "Frequency", "Name"]
+LIST_AOP = ["Manual", "Auto"]
+LIST_STE_TYPE = ["Off", "Silent", "120 Degree", "180 Degree", "240 Degree"]
+LIST_STE_FREQ = ["Off", "55.2 Hz", "259.2 Hz"]
+
+LIST_PRIORITY_CH = ["Off", "Priority Channel 1", "Priority Channel 2",
+ "Priority Channel 1 + Priority Channel 2"]
+
+LIST_REVERT_CH = ["Selected", "Selected + TalkBack", "Priority Channel 1",
+ "Priority Channel 2", "Last Called", "Last Used",
+ "Priority Channel 1 + TalkBack",
+ "Priority Channel 2 + TalkBack"]
+
+LIST_TIME50 = ["0.1", "0.2", "0.3", "0.4", "0.5",
+ "0.6", "0.7", "0.8", "0.9", "1.0",
+ "1.1", "1.2", "1.3", "1.4", "1.5",
+ "1.6", "1.7", "1.8", "1.9", "2.0",
+ "2.1", "3.2", "2.3", "2.4", "2.5",
+ "2.6", "2.7", "2.8", "2.9", "3.0",
+ "3.1", "3.2", "3.3", "3.4", "3.5",
+ "3.6", "3.7", "3.8", "3.9", "4.0",
+ "4.1", "4.2", "4.3", "4.4", "4.5",
+ "4.6", "4.7", "4.8", "4.9", "5.0"]
+LIST_TIME46 = LIST_TIME50[4:]
+
+LIST_RT98V_MODES = ["FreeNet", "COM", "COMII"]
+LIST_RT98U_MODES = ["PMR", "COM", "COMII"]
+
+LIST_RT98V_FREQS = ["Rx(149 - 149.2 MHz) Tx(149 - 149.2 MHz)",
+ "Rx(136 - 174 MHz) Tx(136 - 174 MHz)",
+ "Rx(147 - 174 MHz) Tx(147 - 174 MHz)"]
+
+LIST_RT98U_FREQS = ["Rx(446 - 446.2 MHz) Tx(446 - 446.2 MHz)",
+ "Rx(400 - 470 MHz) Tx(400 - 470 MHz)",
+ "Rx(450 - 470 MHz) Tx(450 - 470 MHz)"]
+
+SETTING_LISTS = {
+ "tuning_step": LIST_STEP,
+ "timeout_timer": LIST_TIMEOUT,
+ "auto_power_off": LIST_APO,
+ "squelch": LIST_SQUELCH,
+ "display_mode": LIST_DISPLAY_MODE,
+ "auto_power_on": LIST_AOP,
+ "ste_type": LIST_STE_TYPE,
+ "ste_frequency": LIST_STE_FREQ,
+ "priority_ch": LIST_PRIORITY_CH,
+ "revert_ch": LIST_REVERT_CH,
+ "settings2.dropout_delay_time": LIST_TIME50,
+ "settings2.dwell_time": LIST_TIME50,
+ "settings2.look_back_time_a": LIST_TIME46,
+ "settings2.look_back_time_b": LIST_TIME46
+}
+
+# RT98 memory map
+# section: 1 Channel Bank
+# description of channel bank (199 channels , range 1-199)
+# Each 32 Byte (0x20 hex) record:
+# bytes:bit type description
+#
---------------------------------------------------------------------------
+# 4 bbcd freq[4] receive frequency in packed binary coded
+# decimal
+# 4 bbcd offset[4] transceive offset in packed binary coded
+# decimal (note: +/- direction set by
+# 'duplex' field)
+# 1 u8 unknown0
+# 1 u8
+# :1 reverse:1 reverse flag, 0=off, 1=on (reverses
+# transmit and receive freqencies)
+# :1 txoff:1 transmitt off flag, 0=transmit, 1=do not
+# transmit
+# :2 power:2 transmit power setting, value range 0-2,
+# 0=low, 1=middle, 2=high
+# :2 duplex:2 duplex settings, 0=simplex, 1=plus (+)
+# offset, 2=minus(-) offset (see offset
field)
+# :2 channel_width:2 channel spacing, 0=12.5kHz, 1=20kHz,
2=25kHz
+# 1 u8
+# :2 unknown1:2
+# :1 talkaround:1 talkaround flag, 0=off, 1=on
+# (bypasses repeater)
+# :1 squelch_mode:1 squelch mode flag, 0=carrier,
1=ctcss/dcs
+# :1 rxdcsextra:1 use with rxcode for index of rx DCS to
use
+# :1 rxinv:1 inverse DCS rx polarity flag, 0=N, 1=I
+# :1 txdcsextra:1 use with txcode for index of tx DCS to
use
+# :1 txinv:1 inverse DCS tx polarity flag, 0=N, 1=I
+# 1 u8
+# :4 unknown2:4
+# :2 rxtmode:2 rx tone mode, value range 0-2, 0=none,
+# 1=CTCSS, 2=DCS (ctcss tone in field
rxtone)
+# :2 txtmode:2 tx tone mode, value range 0-2, 0=none,
+# 1=CTCSS, 3=DCS (ctcss tone in field
txtone)
+# 1 u8
+# :2 unknown3:2
+# :6 txtone:6 tx ctcss tone, menu index
+# 1 u8
+# :2 unknown4:2
+# :6 rxtone:6 rx ctcss tone, menu index
+# 1 u8 txcode ?, not used for ctcss
+# 1 u8 rxcode ?, not used for ctcss
+# 1 u8
+# :6 unknown5:6
+# :1 busychannellockout:1 busy channel lockout flag, 0=off,
1=enabled
+# :1 unknown6:1
+# 6 char name[6] 6 byte char string for channel name
+# 9 u8 unknown7[9]
+#
+MEM_FORMAT = """
+#seekto 0x0000;
+struct {
+ bbcd freq[4];
+ bbcd offset[4];
+ u8 unknown0;
+ u8 reverse:1,
+ tx_off:1,
+ txpower:2,
+ duplex:2,
+ channel_width:2;
+ u8 unknown1:2,
+ talkaround:1,
+ squelch_mode:1,
+ rxdcsextra:1,
+ rxinv:1,
+ txdcsextra:1,
+ txinv:1;
+ u8 unknown2:4,
+ rxtmode:2,
+ txtmode:2;
+ u8 unknown3:2,
+ txtone:6;
+ u8 unknown4:2,
+ rxtone:6;
+ u8 txcode;
+ u8 rxcode;
+ u8 unknown5:6,
+ busychannellockout:1,
+ unknown6:1;
+ char name[6];
+ u8 unknown7[9];
+} memory[199];
+"""
+
+# RT98 memory map
+# section: 2 and 3 Channel Set/Skip Flags
+#
+# Channel Set (starts 0x3240) : Channel Set bit is value 0 if a memory
+# location in the channel bank is active.
+# Channel Skip (starts 0x3260): Channel Skip bit is value 0 if a memory
+# location in the channel bank is active.
+#
+# Both flag maps are a total 24 bytes in length, aligned on 32 byte
records.
+# bit = 0 channel not set/skip, 1 is channel set/no skip
+#
+# to index a channel:
+# cbyte = channel / 8 ;
+# cbit = channel % 8 ;
+# setflag = csetflag[cbyte].c[cbit] ;
+# skipflag = cskipflag[cbyte].c[cbit] ;
+#
+# channel range is 1-199, range is 32 bytes (last 7 unknown)
+#
+MEM_FORMAT = MEM_FORMAT + """
+#seekto 0x3240;
+struct {
+ bit c[8];
+} csetflag[32];
+
+#seekto 0x3260;
+struct {
+ bit c[8];
+} cskipflag[32];
+
+"""
+
+# RT98 memory map
+# section: 4 Startup Label
+#
+# bytes:bit type description
+#
---------------------------------------------------------------------------
+# 6 char start_label[6] label displayed at startup (usually
+# your call sign)
+#
+MEM_FORMAT = MEM_FORMAT + """
+#seekto 0x3300;
+struct {
+ char startname[6];
+} slabel;
+"""
+
+# RT98 memory map
+# section: 5, 6 and 7 Radio Options
+# used to set a number of radio options
+#
+# description of function setup options, starting at 0x3310 (settings3)
+#
+# bytes:bit type description
+#
---------------------------------------------------------------------------
+# 1 u8
+# :6 unknown:6
+# :2 bandlimit_3310:2 frequency ranges, range 0-2,
+# 0=freenet(vhf) or pmr(uhf), 1=com,
2=comii
+# rt98v - 00 FreeNet Rx(149 - 149.2 MHz) Tx(149 - 149.2
MHz)
+# 01 COM Rx(136 - 174 MHz) Tx(136 - 174 MHz)
+# 02 COMII Rx(147 - 174 MHz) Tx(147 - 174 MHz)
+# rt98u - 00 PMR Rx(446 - 446.2 MHz) Tx(446 - 446.2
MHz)
+# 01 COM Rx(400 - 470 MHz) Tx(400 - 470 MHz)
+# 02 COMII Rx(450 - 470 MHz) Tx(450 - 470 MHz)
+# 1 u8 ch_number; channel number, range 1-199
+#
+# description of function setup options, starting at 0x3340 (settings)
+#
+# bytes:bit type description
+#
---------------------------------------------------------------------------
+# 1 u8
+# :4 unknown_3340:4
+# :4 tuning_step:4 tuning step, menu index value from 0-8
+# 2.5, 5, 6.25, 10, 12.5, 20, 25, 30, 50
+# 1 u8
+# :7 unknown_3341:7
+# :1 beep:1 beep mode, range 0-1, 0=off, 1=on
+# 1 u8
+# :3 unknown_3342:3
+# :5 timeout_timer:5 timeout timer, range off (no timeout),
+# 1-30 minutes
+# 1 u8
+# :6 unknown_3343:6
+# :2 auto_power_off:2 auto power off, range 0-3, off, 30min,
+# 1hr, 2hr
+# 1 u8
+# :4 unknown_3344:4
+# :4 squelch:4 squelch level, range off, 1-9
+# 1 u8
+# :3 unknown_3345:3
+# :5 volume:5 volume level, range 1-30 (no zero)
+# 1 u8 unknown_3346
+# 1 u8 unknown_3347
+# 1 u8 0x3348 [12]
+# :6 unknown_3348:6
+# :2 display_mode display mode, range 0-2, 0=channel,
+# 1=frequency, 2=name
+# 1 u8
+# :7 unknown_3349:7
+# :1 auto_power_on:1 auto power on, range 0-1, 0=manual,
+# 1=auto
+# 1 u8
+# :3 unknown_334A:3
+# :5 mic_gain:5 mic gain, range 1-30 (no zero)
+# 1 u8
+# :5 unknown_334C:5
+# :3 ste_type:3 ste type, range 0-4, 0=off, 1=silent,
+# 2=120degree, 3=180degree, 4=240degree
+# 1 u8
+# :7 unknown_334D:7
+# :1 ste_frequency:1 ste frequency, range 0-2, 0=off,
+# 1=55.2Hz, 2=259.2Hz
+# 1 u8
+# :2 unknown_0x334E:2
+# :1 forbid_setting:1 forbid setting(optional function),
+# range 0-1, 0=disabled, 1=enabled
+# :1 forbid_initialize:1 forbid initialize operate, range 0-1,
+# 0=enabled, 1=disabled (inverted)
+# :1 save_chan_param:1 save channel parameters, range 0-1,
+# 0=disabled, 1=enabled
+# :1 forbid_chan_menu:1 forbid channel menu, range 0-1,
+# 0=disabled, 1=enabled
+# :1 sql_key_function:1 sql key function, range 0-1,
+# 0=squelch off momentary, 1=squelch off
+# :1 unknown:1
+#
+# description of function setup options, starting at 0x3380 (settings2)
+#
+# bytes:bit type description
+#
---------------------------------------------------------------------------
+# 1 u8
+# :7 unknown_3380:7
+# :1 scan_mode:1 scan mode, range 0-1, 0=off, 1=on
+# 1 u8
+# :6 unknown_3381:6
+# :2 priority_ch:2 priority channel, range 0-3, 0=off,
+# 1=priority channel 1,
+# 2=priority channel 2,
+# 3=priority channel 1 + priority
channel 2
+# 1 u8 priority_ch1 priority channel 1 number, range 1-199
+# 1 u8 priority_ch2 priority channel 2 number, range 1-199
+# 1 u8
+# :4 unknown_3384:4
+# :4 revert_ch:4 revert channel, range 0-3, 0=selected,
+# 1=selected + talkback, 2=last called,
+# 3=last used
+# 1 u8 look_back_time_a look back time a, range 0-45
+# 1 u8 look_back_time_b look back time b, range 0-45
+# 1 u8 dropout_delay_time dropout delay time, range 0-49
+# 1 u8 dwell_time dwell time, range 0-49
+#
+MEM_FORMAT = MEM_FORMAT + """
+#seekto 0x3310;
+struct {
+ u8 bandlimit;
+ u8 ch_number;
+} settings3;
+"""
+
+MEM_FORMAT = MEM_FORMAT + """
+#seekto 0x3340;
+struct {
+ u8 unknown_3340:4,
+ tuning_step:4;
+ u8 unknown_3341:7,
+ beep:1;
+ u8 unknown_3342:3,
+ timeout_timer:5;
+ u8 unknown_3343:6,
+ auto_power_off:2;
+ u8 unknown_3344:4,
+ squelch:4;
+ u8 unknown_3345:3,
+ volume:5;
+ u8 unknown_3346;
+ u8 unknown_3347;
+ u8 unknown_3348:6,
+ display_mode:2;
+ u8 unknown_3349:7,
+ auto_power_on:1;
+ u8 unknown_334A:3,
+ mic_gain:5;
+ u8 unknown_334B;
+ u8 unknown_334C:5,
+ ste_type:3;
+ u8 unknown_334D:6,
+ ste_frequency:2;
+ u8 unknown_334E:1,
+ forbid_setting:1,
+ unknown1:1,
+ forbid_initialize:1,
+ save_chan_param:1,
+ forbid_chan_menu:1,
+ sql_key_function:1,
+ unknown2:1;
+} settings;
+"""
+
+MEM_FORMAT = MEM_FORMAT + """
+#seekto 0x3380;
+struct {
+ u8 unknown_3380:7,
+ scan_mode:1;
+ u8 unknown_3381:6,
+ priority_ch:2;
+ u8 priority_ch1;
+ u8 priority_ch2;
+ u8 unknown_3384:4,
+ revert_ch:4;
+ u8 look_back_time_a;
+ u8 look_back_time_b;
+ u8 dropout_delay_time;
+ u8 dwell_time;
+} settings2;
+"""
+
+# RT98 memory map
+# section: 8 Embedded Messages
+#
+# bytes:bit type description
+#
---------------------------------------------------------------------------
+# 6 char radio_type[5] radio type, vhf=rt98v, uhf=rt98u
+# 2 u8 unknown1[2]
+# 4 char mcu_version[4] mcu version, [x.xx]
+# 2 u8 unknown2[2]
+# 1 u8 mode rt98u mode: 0=pmr, 1=com, 2=comii
+# rt98v mode: 0=freenet, 1=com, 2=comii
+# 1 u8 unknown3
+# 10 u8 unused1[10]
+# 4 u8 unknown4[4]
+# 3 u8 unused2[3]
+# 16 u8 unknown5[16]
+# 10 char date_mfg[16] date manufactured, [yyyy-mm-dd]
+#
+MEM_FORMAT = MEM_FORMAT + """
+#seekto 0x3D00;
+struct {
+char radio_type[5];
+u8 unknown1[2];
+char mcu_version[4];
+u8 unknown2[2];
+u8 mode;
+u8 unknown3;
+u8 unused1[10];
+u8 unknown4[4];
+u8 unused2[3];
+u8 unknown5[16];
+char date_mfg[10];
+} embedded_msg;
+"""
+
+
+# Format for the version messages returned by the radio
+VER_FORMAT = '''
+u8 hdr;
+char model[5];
+u8 unknown[2];
+u8 bandlimit;
+char version[6];
+u8 ack;
+'''
+
+
+# Radio supports upper case and symbols
+CHARSET_ASCII_PLUS = chirp_common.CHARSET_UPPER_NUMERIC + '- '
+
+# Band limits as defined by the band byte in ver_response, defined in Hz,
for
+# VHF and UHF, used for RX and TX.
+RT98V_BAND_LIMITS = {0x00: [(149000000, 149200000)],
+ 0x01: [(136000000, 174000000)],
+ 0x02: [(147000000, 174000000)]}
+
+RT98U_BAND_LIMITS = {0x00: [(446000000, 446200000)],
+ 0x01: [(400000000, 470000000)],
+ 0x02: [(450000000, 470000000)]}
+
+
+# Get band limits from a band limit value
+def get_band_limits_Hz(radio_type, limit_value):
+ if radio_type == "RT98U":
+ if limit_value not in RT98U_BAND_LIMITS:
+ limit_value = 0x01
+ LOG.warning('Unknown band limit value 0x%02x, default to 0x01')
+ bandlimitfrequencies = RT98U_BAND_LIMITS[limit_value]
+ elif radio_type == "RT98V":
+ if limit_value not in RT98V_BAND_LIMITS:
+ limit_value = 0x01
+ LOG.warning('Unknown band limit value 0x%02x, default to 0x01')
+ bandlimitfrequencies = RT98V_BAND_LIMITS[limit_value]
+ return bandlimitfrequencies
+
+
+def _echo_write(radio, data):
+ try:
+ radio.pipe.write(data)
+ radio.pipe.read(len(data))
+ except Exception, e:
+ LOG.error("Error writing to radio: %s" % e)
+ raise errors.RadioError("Unable to write to radio")
+
+
+def _checksum(data):
+ cs = 0
+ for byte in data:
+ cs += ord(byte)
+ return cs % 256
+
+
+def _read(radio, length):
+ try:
+ data = radio.pipe.read(length)
+ except Exception, e:
+ _finish(radio)
+ LOG.error("Error reading from radio: %s" % e)
+ raise errors.RadioError("Unable to read from radio")
+
+ if len(data) != length:
+ _finish(radio)
+ LOG.error("Short read from radio (%i, expected %i)" %
+ (len(data), length))
+ LOG.debug(util.hexprint(data))
+ raise errors.RadioError("Short read from radio")
+ return data
+
+
+# strip trailing 0x00 to convert a string returned by bitwise.parse into a
+# python string
+def cstring_to_py_string(cstring):
+ return "".join(c for c in cstring if c != '\x00')
+
+
+# Check the radio version reported to see if it's one we support,
+# returns bool version supported, and the band index
+def check_ver(ver_response, allowed_types):
+ ''' Check the returned radio version is one we approve of '''
+
+ LOG.debug('ver_response = ')
+ LOG.debug(util.hexprint(ver_response))
+
+ resp = bitwise.parse(VER_FORMAT, ver_response)
+ verok = False
+
+ if resp.hdr == 0x49 and resp.ack == 0x06:
+ model, version =
[cstring_to_py_string(bitwise.get_string(s)).strip()
+ for s in (resp.model, resp.version)]
+ LOG.debug('radio model: \'%s\' version: \'%s\'' %
+ (model, version))
+ LOG.debug('allowed_types = %s' % allowed_types)
+
+ if model in allowed_types:
+ LOG.debug('model in allowed_types')
+
+ if version in allowed_types[model]:
+ LOG.debug('version in allowed_types[model]')
+ verok = True
+ else:
+ _finish(radio)
+ raise errors.RadioError('Failed to parse version response')
+
+ return verok, str(resp.model), int(resp.bandlimit)
+
+
+def _ident(radio):
+ radio.pipe.timeout = 1
+ _echo_write(radio, "PROGRAM")
+ response = radio.pipe.read(3)
+ if response != "QX\06":
+ _finish(radio)
+ LOG.debug("Response was :\n%s" % util.hexprint(response))
+ raise errors.RadioError("Radio did not respond. Check connection.")
+ _echo_write(radio, "\x02")
+ ver_response = radio.pipe.read(16)
+ LOG.debug(util.hexprint(ver_response))
+
+ verok, model, bandlimit = check_ver(ver_response,
+ radio.ALLOWED_RADIO_TYPES)
+ if not verok:
+ _finish(radio)
+ raise errors.RadioError(
+ 'Radio version not in allowed list for %s-%s: %s' %
+ (radio.VENDOR, radio.MODEL, util.hexprint(ver_response)))
+
+ return model, bandlimit
+
+
+def _send(radio, cmd, addr, length, data=None):
+ frame = struct.pack(">cHb", cmd, addr, length)
+ if data:
+ frame += data
+ frame += chr(_checksum(frame[1:]))
+ frame += "\x06"
+ _echo_write(radio, frame)
+ LOG.debug("Sent:\n%s" % util.hexprint(frame))
+ if data:
+ result = radio.pipe.read(1)
+ if result != "\x06":
+ _finish(radio)
+ LOG.debug("Ack was: %s" % repr(result))
+ raise errors.RadioError("Radio did not accept block at %04x"
+ % addr)
+ return
+ result = _read(radio, length + 6)
+ LOG.debug("Got:\n%s" % util.hexprint(result))
+ header = result[0:4]
+ data = result[4:-2]
+ ack = result[-1]
+ if ack != "\x06":
+ _finish(radio)
+ LOG.debug("Ack was: %s" % repr(ack))
+ raise errors.RadioError("Radio NAK'd block at %04x" % addr)
+ _cmd, _addr, _length = struct.unpack(">cHb", header)
+ if _addr != addr or _length != _length:
+ _finish(radio)
+ LOG.debug("Expected/Received:")
+ LOG.debug(" Length: %02x/%02x" % (length, _length))
+ LOG.debug(" Addr: %04x/%04x" % (addr, _addr))
+ raise errors.RadioError("Radio send unexpected block")
+ cs = _checksum(result[1:-2])
+ if cs != ord(result[-2]):
+ _finish(radio)
+ LOG.debug("Calculated: %02x" % cs)
+ LOG.debug("Actual: %02x" % ord(result[-2]))
+ raise errors.RadioError("Block at 0x%04x failed checksum" % addr)
+ return data
+
+
+def _finish(radio):
+ endframe = "\x45\x4E\x44"
+ _echo_write(radio, endframe)
+ result = radio.pipe.read(1)
+ if result != "\x06":
+ LOG.error("Got:\n%s" % util.hexprint(result))
+ raise errors.RadioError("Radio did not finish cleanly")
+
+
+def do_download(radio):
+
+ _ident(radio)
+
+ _memobj = None
+ data = ""
+
+ for addr in range(0, radio._memsize, 0x10):
+ block = _send(radio, 'R', addr, 0x10)
+ data += block
+ status = chirp_common.Status()
+ status.cur = len(data)
+ status.max = radio._memsize
+ status.msg = "Downloading from radio"
+ radio.status_fn(status)
+
+ _finish(radio)
+
+ return memmap.MemoryMap(data)
+
+
+def do_upload(radio):
+ model, bandlimit = _ident(radio)
+ _embedded = radio._memobj.embedded_msg
+
+ if model != str(_embedded.radio_type):
+ LOG.warning('radio and image model types differ')
+ LOG.warning('model type (radio): %s' % str(model))
+ LOG.warning('model type (image): %s' % str(_embedded.radio_type))
+
+ _finish(radio)
+
+ msg = ("The upload was stopped because the radio type "
+ "of the image (%s) does not match that "
+ "of the radio (%s).")
+ raise errors.RadioError(msg % (str(_embedded.radio_type),
str(model)))
+
+ if bandlimit != int(_embedded.mode):
+ if str(_embedded.radio_type) == "RT98U":
+ image_band_limits = LIST_RT98U_FREQS[int(_embedded.mode)]
+ if str(_embedded.radio_type) == "RT98V":
+ image_band_limits = LIST_RT98V_FREQS[int(_embedded.mode)]
+ if model == "RT98U":
+ radio_band_limits = LIST_RT98U_FREQS[int(bandlimit)]
+ if model == "RT98V":
+ radio_band_limits = LIST_RT98V_FREQS[int(bandlimit)]
+
+ LOG.warning('radio and image band limits differ')
+ LOG.warning('image band limits: %s' % image_band_limits)
+ LOG.warning('radio band limits: %s' % radio_band_limits)
+
+ _finish(radio)
+
+ msg = ("The upload was stopped because the band limits "
+ "of the image (%s) does not match that "
+ "of the radio (%s).")
+ raise errors.RadioError(msg % (image_band_limits,
radio_band_limits))
+
+ try:
+ for start, end in radio._ranges:
+ for addr in range(start, end, 0x10):
+ block = radio._mmap[addr:addr+0x10]
+ _send(radio, 'W', addr, len(block), block)
+ status = chirp_common.Status()
+ status.cur = addr
+ status.max = end
+ status.msg = "Uploading to Radio"
+ radio.status_fn(status)
+ _finish(radio)
+ except errors.RadioError:
+ raise
+ except Exception as e:
+ _finish(radio)
+ raise errors.RadioError('Failed to upload to radio: %s' % e)
+
+
+#
+# The base class, extended for use with other models
+#
+class Rt98BaseRadio(chirp_common.CloneModeRadio,
+ chirp_common.ExperimentalRadio):
+ """Retevis RT98 Base"""
+ VENDOR = "Retevis"
+ MODEL = "RT98 Base"
+ BAUD_RATE = 9600
+
+ _memsize = 0x3E00
+ _ranges = [(0x0000, 0x3310),
+ (0x3320, 0x3390)]
+
+ @classmethod
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.experimental = ("The Retevis RT98 driver is an beta version."
+ "Proceed with Caution and backup your data")
+ return rp
+
+ def get_features(self):
+ _embedded = self._memobj.embedded_msg
+ rf = chirp_common.RadioFeatures()
+ rf.has_settings = True
+ rf.has_bank = False
+ rf.can_odd_split = True
+ rf.has_name = True
+ if _embedded.mode == 0: # PMR or FreeNet
+ rf.has_offset = False
+ else:
+ rf.has_offset = True
+ rf.has_ctone = True
+ rf.has_cross = True
+ rf.has_tuning_step = False
+ rf.has_dtcs = True
+ rf.has_rx_dtcs = True
+ rf.has_dtcs_polarity = True
+ rf.valid_skips = ["", "S"]
+ rf.memory_bounds = (1, 199)
+ rf.valid_name_length = 6
+ if _embedded.mode == 0: # PMR or FreeNet
+ rf.valid_duplexes = ['']
+ else:
+ rf.valid_duplexes = DUPLEXES + ['split', 'off']
+ rf.valid_characters = chirp_common.CHARSET_UPPER_NUMERIC + "- "
+ if _embedded.mode == 0: # PMR or FreeNet
+ rf.valid_modes = ['NFM']
+ else:
+ rf.valid_modes = ['FM', 'NFM']
+ rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
+ rf.valid_cross_modes = CROSS_MODES
+ if _embedded.mode == 0: # PMR or FreeNet
+ if str(_embedded.radio_type) == "RT98U":
+ rf.valid_power_levels = PMR_POWER_LEVELS
+ if str(_embedded.radio_type) == "RT98V":
+ rf.valid_power_levels = FREENET_POWER_LEVELS
+ else:
+ rf.valid_power_levels = POWER_LEVELS
+ rf.valid_dtcs_codes = chirp_common.ALL_DTCS_CODES
+
+ try:
+ rf.valid_bands = get_band_limits_Hz(
+ str(_embedded.radio_type),
+ int(_embedded.mode))
+ except TypeError as e:
+ # If we're asked without memory loaded, assume the most
permissive
+ rf.valid_bands = get_band_limits_Hz(str(_embedded.radio_type),
1)
+ except Exception as e:
+ LOG.error('Failed to get band limits for RT98: %s' % e)
+ rf.valid_bands = get_band_limits_Hz(str(_embedded.radio_type),
1)
+
+ rf.valid_tuning_steps = TUNING_STEPS
+ return rf
+
+ def validate_memory(self, mem):
+ _embedded = self._memobj.embedded_msg
+ msgs = ""
+ msgs = chirp_common.CloneModeRadio.validate_memory(self, mem)
+
+ # FreeNet and PMR radio types
+ if _embedded.mode == 0: # PMR or FreeNet
+ freq = float(mem.freq) / 1000000
+
+ # FreeNet
+ if str(_embedded.radio_type) == "RT98V":
+ if freq not in FREENET_FREQS:
+ _msg_freq = 'Memory location not a valid FreeNet
frequency'
+ # warn user invalid frequency
+ msgs.append(chirp_common.ValidationError(_msg_freq))
+
+ # PMR
+ if str(_embedded.radio_type) == "RT98U":
+ if freq not in PMR_FREQS:
+ _msg_freq = 'Memory location not a valid PMR frequency'
+ # warn user invalid frequency
+ msgs.append(chirp_common.ValidationError(_msg_freq))
+
+ return msgs
+
+ # Do a download of the radio from the serial port
+ def sync_in(self):
+ self._mmap = do_download(self)
+ self.process_mmap()
+
+ # Do an upload of the radio to the serial port
+ def sync_out(self):
+ do_upload(self)
+
+ def process_mmap(self):
+ self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+ # Return a raw representation of the memory object, which
+ # is very helpful for development
+ def get_raw_memory(self, number):
+ return repr(self._memobj.memory[number - 1])
+
+ def _get_dcs_index(self, _mem, which):
+ base = getattr(_mem, '%scode' % which)
+ extra = getattr(_mem, '%sdcsextra' % which)
+ return (int(extra) << 8) | int(base)
+
+ def _set_dcs_index(self, _mem, which, index):
+ base = getattr(_mem, '%scode' % which)
+ extra = getattr(_mem, '%sdcsextra' % which)
+ base.set_value(index & 0xFF)
+ extra.set_value(index >> 8)
+
+ # Extract a high-level memory object from the low-level memory map
+ # This is called to populate a memory in the UI
+ def get_memory(self, number):
+ _embedded = self._memobj.embedded_msg
+ # Get a low-level memory object mapped to the image
+ _mem = self._memobj.memory[number - 1]
+
+ # get flag info
+ cbyte = (number - 1) / 8
+ cbit = 7 - ((number - 1) % 8)
+ setflag = self._memobj.csetflag[cbyte].c[cbit]
+ skipflag = self._memobj.cskipflag[cbyte].c[cbit]
+
+ mem = chirp_common.Memory()
+
+ mem.number = number # Set the memory number
+
+ # We'll consider any blank (i.e. 0MHz frequency) to be empty
+ if _mem.freq == 0:
+ mem.empty = True
+ return mem
+
+ if setflag == 0:
+ mem.empty = True
+ return mem
+
+ if _mem.get_raw()[0] == "\xFF":
+ mem.empty = True
+ return mem
+
+ # set the name
+ mem.name = str(_mem.name).rstrip() # Set the alpha tag
+
+ # Convert your low-level frequency and offset to Hertz
+ mem.freq = int(_mem.freq) * 10
+ mem.offset = int(_mem.offset) * 10
+
+ # Set the duplex flags
+ if _mem.duplex == DUPLEX_POSSPLIT:
+ mem.duplex = '+'
+ elif _mem.duplex == DUPLEX_NEGSPLIT:
+ mem.duplex = '-'
+ elif _mem.duplex == DUPLEX_NOSPLIT:
+ mem.duplex = ''
+ elif _mem.duplex == DUPLEX_ODDSPLIT:
+ mem.duplex = 'split'
+ else:
+ LOG.error('%s: get_mem: unhandled duplex: %02x' %
+ (mem.name, _mem.duplex))
+
+ # handle tx off
+ if _mem.tx_off:
+ mem.duplex = 'off'
+
+ # Set the channel width
+ if _mem.channel_width == CHANNEL_WIDTH_12d5kHz:
+ mem.mode = 'NFM'
+ elif _embedded.mode == 0: # PMR or FreeNet
+ LOG.info('PMR and FreeNet channels must be Channel Width
12.5kHz')
+ mem.mode = 'NFM'
+ elif _mem.channel_width == CHANNEL_WIDTH_25kHz:
+ mem.mode = 'FM'
+ elif _mem.channel_width == CHANNEL_WIDTH_20kHz:
+ LOG.info(
+ '%s: get_mem: promoting 20kHz channel width to 25kHz' %
+ mem.name)
+ mem.mode = 'FM'
+ else:
+ LOG.error('%s: get_mem: unhandled channel width: 0x%02x' %
+ (mem.name, _mem.channel_width))
+
+ # set the power level
+ if _embedded.mode == 0: # PMR or FreeNet
+ if str(_embedded.radio_type) == "RT98U":
+ LOG.info('using PMR power levels')
+ _levels = PMR_POWER_LEVELS
+ if str(_embedded.radio_type) == "RT98V":
+ LOG.info('using FreeNet power levels')
+ _levels = FREENET_POWER_LEVELS
+ else: # COM or COMII
+ LOG.info('using general power levels')
+ _levels = POWER_LEVELS
+
+ if _mem.txpower == TXPOWER_LOW:
+ mem.power = _levels[0]
+ elif _embedded.mode == 0: # PMR or FreeNet
+ LOG.info('FreeNet or PMR channel is not set to TX Power Low')
+ LOG.info('Setting channel to TX Power Low')
+ mem.power = _levels[0]
+ elif _mem.txpower == TXPOWER_MED:
+ mem.power = _levels[1]
+ elif _mem.txpower == TXPOWER_HIGH:
+ mem.power = _levels[2]
+ else:
+ LOG.error('%s: get_mem: unhandled power level: 0x%02x' %
+ (mem.name, _mem.txpower))
+
+ # CTCSS Tones and DTCS Codes
+ rxtone = txtone = None
+
+ rxmode = TMODES[_mem.rxtmode]
+ txmode = TMODES[_mem.txtmode]
+
+ if rxmode == "Tone":
+ rxtone = TONES[_mem.rxtone]
+ elif rxmode == "DTCS":
+ rxtone = chirp_common.ALL_DTCS_CODES[self._get_dcs_index(
+ _mem, 'rx')]
+
+ if txmode == "Tone":
+ txtone = TONES[_mem.txtone]
+ elif txmode == "DTCS":
+ txtone = chirp_common.ALL_DTCS_CODES[self._get_dcs_index(
+ _mem, 'tx')]
+
+ rxpol = _mem.rxinv and "R" or "N"
+ txpol = _mem.txinv and "R" or "N"
+
+ chirp_common.split_tone_decode(mem,
+ (txmode, txtone, txpol),
+ (rxmode, rxtone, rxpol))
+
+ # Check if this memory is in the scan enabled list
+ mem.skip = "S" if skipflag == 0 else ""
+
+ # Extra
+ mem.extra = RadioSettingGroup("extra", "Extra")
+
+ rs = RadioSettingValueBoolean(bool(_mem.busychannellockout))
+ rset = RadioSetting("busychannellockout", "Busy channel lockout",
rs)
+ mem.extra.append(rset)
+
+ rs = RadioSettingValueBoolean(bool(_mem.reverse))
+ rset = RadioSetting("reverse", "Reverse", rs)
+ mem.extra.append(rset)
+
+ rs = RadioSettingValueBoolean(bool(_mem.talkaround))
+ rset = RadioSetting("talkaround", "Talk around", rs)
+ mem.extra.append(rset)
+
+ rs = RadioSettingValueBoolean(bool(_mem.squelch_mode))
+ rset = RadioSetting("squelch_mode", "Squelch mode", rs)
+ mem.extra.append(rset)
+
+ return mem
+
+ # Store details about a high-level memory to the memory map
+ # This is called when a user edits a memory in the UI
+ def set_memory(self, mem):
+ _embedded = self._memobj.embedded_msg
+ # Get a low-level memory object mapped to the image
+
+ _mem = self._memobj.memory[mem.number - 1]
+
+ cbyte = (mem.number - 1) / 8
+ cbit = 7 - ((mem.number - 1) % 8)
+
+ if mem.empty:
+ self._memobj.csetflag[cbyte].c[cbit] = 0
+ self._memobj.cskipflag[cbyte].c[cbit] = 0
+ _mem.set_raw('\xff' * (_mem.size() / 8))
+ return
+
+ _mem.set_raw('\x00' * (_mem.size() / 8))
+
+ # set the occupied bitfield
+ self._memobj.csetflag[cbyte].c[cbit] = 1
+ # set the scan add bitfield
+ self._memobj.cskipflag[cbyte].c[cbit] = 0 if (mem.skip == "S")
else 1
+
+ _mem.freq = mem.freq / 10 # Convert to low-level
frequency
+ _mem.offset = mem.offset / 10 # Convert to low-level
frequency
+
+ # Store the alpha tag
+ _mem.name = mem.name.ljust(6)[:6] # Store the alpha tag
+
+ # Set duplex bitfields
+ if mem.duplex == '+':
+ _mem.duplex = DUPLEX_POSSPLIT
+ elif mem.duplex == '-':
+ _mem.duplex = DUPLEX_NEGSPLIT
+ elif mem.duplex == '':
+ _mem.duplex = DUPLEX_NOSPLIT
+ elif mem.duplex == 'split':
+ diff = mem.offset - mem.freq
+ _mem.duplex = DUPLEXES.index("-") \
+ if diff < 0 else DUPLEXES.index("+")
+ _mem.offset = abs(diff) / 10
+ else:
+ LOG.error('%s: set_mem: unhandled duplex: %s' %
+ (mem.name, mem.duplex))
+
+ # handle tx off
+ _mem.tx_off = 0
+ if mem.duplex == 'off':
+ _mem.tx_off = 1
+
+ # Set the channel width - remember we promote 20kHz channels to FM
+ # on import, so don't handle them here
+ if mem.mode == 'FM':
+ _mem.channel_width = CHANNEL_WIDTH_25kHz
+ elif mem.mode == 'NFM':
+ _mem.channel_width = CHANNEL_WIDTH_12d5kHz
+ else:
+ LOG.error('%s: set_mem: unhandled mode: %s' % (
+ mem.name, mem.mode))
+
+ # CTCSS Tones and DTCS Codes
+ ((txmode, txtone, txpol),
+ (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
+
+ _mem.txtmode = TMODES.index(txmode)
+
+ _mem.rxtmode = TMODES.index(rxmode)
+
+ if txmode == "Tone":
+ _mem.txtone = TONES.index(txtone)
+ elif txmode == "DTCS":
+ self._set_dcs_index(_mem, 'tx',
+ chirp_common.ALL_DTCS_CODES.index(txtone))
+
+ _mem.squelch_mode = False
+ if rxmode == "Tone":
+ _mem.rxtone = TONES.index(rxtone)
+ _mem.squelch_mode = True
+ elif rxmode == "DTCS":
+ self._set_dcs_index(_mem, 'rx',
+ chirp_common.ALL_DTCS_CODES.index(rxtone))
+ _mem.squelch_mode = True
+
+ _mem.txinv = txpol == "R"
+ _mem.rxinv = rxpol == "R"
+
+ # set the power level
+ if mem.power == POWER_LEVELS[0]:
+ _mem.txpower = TXPOWER_LOW
+ elif mem.power == POWER_LEVELS[1]:
+ _mem.txpower = TXPOWER_MED
+ elif mem.power == POWER_LEVELS[2]:
+ _mem.txpower = TXPOWER_HIGH
+ else:
+ LOG.error('%s: set_mem: unhandled power level: %s' %
+ (mem.name, mem.power))
+
+ # extra settings
+ for setting in mem.extra:
+ setattr(_mem, setting.get_name(), setting.value)
+
+ def _get_settings(self):
+ _embedded = self._memobj.embedded_msg
+ _settings = self._memobj.settings
+ _settings2 = self._memobj.settings2
+ _settings3 = self._memobj.settings3
+ _slabel = self._memobj.slabel
+
+ function = RadioSettingGroup("function", "Function Setup")
+ group = RadioSettings(function)
+
+ # Function Setup
+ # MODE SET
+ rs = RadioSettingValueList(LIST_DISPLAY_MODE,
+
LIST_DISPLAY_MODE[_settings.display_mode])
+ rset = RadioSetting("display_mode", "Display Mode", rs)
+ function.append(rset)
+
+ rs = RadioSettingValueInteger(1, 199, _settings3.ch_number + 1)
+ rset = RadioSetting("settings3.ch_number", "Channel Number", rs)
+ function.append(rset)
+
+ # DISPLAY SET
+ def _filter(name):
+ filtered = ""
+ for char in str(name):
+ if char in chirp_common.CHARSET_ASCII:
+ filtered += char
+ else:
+ filtered += " "
+ return filtered
+
+ val = RadioSettingValueString(0, 6, _filter(_slabel.startname))
+ rs = RadioSetting("slabel.startname", "Startup Label", val)
+ function.append(rs)
+
+ # VOL SET
+ rs = RadioSettingValueBoolean(bool(_settings.beep))
+ rset = RadioSetting("beep", "Beep Prompt", rs)
+ function.append(rset)
+
+ rs = RadioSettingValueInteger(1, 30, _settings.volume)
+ rset = RadioSetting("volume", "Volume Level", rs)
+ function.append(rset)
+
+ rs = RadioSettingValueInteger(1, 16, _settings.mic_gain)
+ rset = RadioSetting("mic_gain", "Mic Gain", rs)
+ function.append(rset)
+
+ # ON/OFF SET
+ rs = RadioSettingValueList(LIST_APO,
+ LIST_APO[_settings.auto_power_off])
+ rset = RadioSetting("auto_power_off", "Auto Power Off", rs)
+ function.append(rset)
+
+ rs = RadioSettingValueList(LIST_AOP,
LIST_AOP[_settings.auto_power_on])
+ rset = RadioSetting("auto_power_on", "Power On Method", rs)
+ function.append(rset)
+
+ # STE SET
+ rs = RadioSettingValueList(LIST_STE_FREQ,
+ LIST_STE_FREQ[_settings.ste_frequency])
+ rset = RadioSetting("ste_frequency", "STE Frequency", rs)
+ function.append(rset)
+
+ rs = RadioSettingValueList(LIST_STE_TYPE,
+ LIST_STE_TYPE[_settings.ste_type])
+ rset = RadioSetting("ste_type", "STE Type", rs)
+ function.append(rset)
+
+ # FUNCTION SET
+ rs = RadioSettingValueList(LIST_STEP,
LIST_STEP[_settings.tuning_step])
+ rset = RadioSetting("tuning_step", "Tuning Step", rs)
+ function.append(rset)
+
+ rs = RadioSettingValueList(LIST_SQUELCH,
+ LIST_SQUELCH[_settings.squelch])
+ rset = RadioSetting("squelch", "Squelch Level", rs)
+ function.append(rset)
+
+ rs = RadioSettingValueBoolean(bool(_settings.sql_key_function))
+ rset = RadioSetting("sql_key_function", "SQL Key Function", rs)
+ function.append(rset)
+
+ rs = RadioSettingValueList(LIST_TIMEOUT,
+ LIST_TIMEOUT[_settings.timeout_timer])
+ rset = RadioSetting("timeout_timer", "Timeout Timer", rs)
+ function.append(rset)
+
+ # uncategorized
+ rs = RadioSettingValueBoolean(bool(_settings.save_chan_param))
+ rset = RadioSetting("save_chan_param", "Save Channel Parameters",
rs)
+ function.append(rset)
+
+ rs = RadioSettingValueBoolean(bool(_settings.forbid_chan_menu))
+ rset = RadioSetting("forbid_chan_menu", "Forbid Channel Menu", rs)
+ function.append(rset)
+
+ rs = RadioSettingValueBoolean(bool(not
_settings.forbid_initialize))
+ rset = RadioSetting("forbid_initialize", "Forbid Initialize", rs)
+ function.append(rset)
+
+ rs = RadioSettingValueBoolean(bool(_settings.forbid_setting))
+ rset = RadioSetting("forbid_setting", "Forbid Setting", rs)
+ function.append(rset)
+
+ # Information Of Scanning Channel
+ scanning = RadioSettingGroup("scanning", "Scanning Setup")
+ group.append(scanning)
+
+ rs = RadioSettingValueBoolean(bool(_settings2.scan_mode))
+ rset = RadioSetting("settings2.scan_mode", "Scan Mode", rs)
+ scanning.append(rset)
+
+ rs = RadioSettingValueList(LIST_PRIORITY_CH,
+
LIST_PRIORITY_CH[_settings2.priority_ch])
+ rset = RadioSetting("settings2.priority_ch", "Priority Channel",
rs)
+ scanning.append(rset)
+
+ rs = RadioSettingValueInteger(1, 199, _settings2.priority_ch1 + 1)
+ rset = RadioSetting("settings2.priority_ch1", "Priority Channel
1", rs)
+ scanning.append(rset)
+
+ rs = RadioSettingValueInteger(1, 199, _settings2.priority_ch2 + 1)
+ rset = RadioSetting("settings2.priority_ch2", "Priority Channel
2", rs)
+ scanning.append(rset)
+
+ rs = RadioSettingValueList(LIST_REVERT_CH,
+ LIST_REVERT_CH[_settings2.revert_ch])
+ rset = RadioSetting("settings2.revert_ch", "Revert Channel", rs)
+ scanning.append(rset)
+
+ rs = RadioSettingValueList(LIST_TIME46,
+
LIST_TIME46[_settings2.look_back_time_a])
+ rset = RadioSetting("settings2.look_back_time_a",
+ "Look Back Time A", rs)
+ scanning.append(rset)
+
+ rs = RadioSettingValueList(LIST_TIME46,
+
LIST_TIME46[_settings2.look_back_time_b])
+ rset = RadioSetting("settings2.look_back_time_b",
+ "Look Back Time B", rs)
+ scanning.append(rset)
+
+ rs = RadioSettingValueList(LIST_TIME50,
+
LIST_TIME50[_settings2.dropout_delay_time])
+ rset = RadioSetting("settings2.dropout_delay_time",
+ "Dropout Delay Time", rs)
+ scanning.append(rset)
+
+ rs = RadioSettingValueList(LIST_TIME50,
+ LIST_TIME50[_settings2.dwell_time])
+ rset = RadioSetting("settings2.dwell_time", "Dwell Time", rs)
+ scanning.append(rset)
+
+ # Embedded Message
+ embedded = RadioSettingGroup("embedded", "Embedded Message")
+ group.append(embedded)
+
+ rs = RadioSettingValueString(0, 5, _filter(_embedded.radio_type))
+ rs.set_mutable(False)
+ rset = RadioSetting("embedded_msg.radio_type", "Radio Type", rs)
+ embedded.append(rset)
+
+ if str(_embedded.radio_type) == "RT98V":
+ options = LIST_RT98V_MODES
+ else:
+ options = LIST_RT98U_MODES
+ rs = RadioSettingValueList(options, options[_embedded.mode])
+ rs.set_mutable(False)
+ rset = RadioSetting("embedded_msg.mode", "Mode", rs)
+ embedded.append(rset)
+
+ # frequency
+ if str(_embedded.radio_type) == "RT98V":
+ options = LIST_RT98V_FREQS
+ else:
+ options = LIST_RT98U_FREQS
+ rs = RadioSettingValueList(options, options[_settings3.bandlimit])
+ rs.set_mutable(False)
+ rset = RadioSetting("settings3.bandlimit", "Frequency", rs)
+ embedded.append(rset)
+
+ rs = RadioSettingValueString(0, 10, _filter(_embedded.date_mfg))
+ rs.set_mutable(False)
+ rset = RadioSetting("embedded_msg.date_mfg", "Production Date", rs)
+ embedded.append(rset)
+
+ rs = RadioSettingValueString(0, 4, _filter(_embedded.mcu_version))
+ rs.set_mutable(False)
+ rset = RadioSetting("embedded_msg.mcu_version", "MCU Version", rs)
+ embedded.append(rset)
+
+ return group
+
+ def get_settings(self):
+ try:
+ return self._get_settings()
+ except:
+ import traceback
+ LOG.error("failed to parse settings")
+ traceback.print_exc()
+ return None
+
+ 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 == "ch_number":
+ setattr(obj, setting, int(element.value) - 1)
+ elif setting == "forbid_initialize":
+ setattr(obj, setting, not int(element.value))
+ elif setting == "priority_ch1":
+ setattr(obj, setting, int(element.value) - 1)
+ elif setting == "priority_ch2":
+ 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):
+ # This radio has always been post-metadata, so never do
+ # old-school detection
+ return False
+
+
+(a)directory.register
+class Rt98Radio(Rt98BaseRadio):
+ """Retevis RT98"""
+ VENDOR = "Retevis"
+ MODEL = "RT98"
+ # Allowed radio types is a dict keyed by model of a list of version
+ # strings
+ ALLOWED_RADIO_TYPES = {'RT98V': ['V100'],
+ 'RT98U': ['V100']}
diff -r f586574bc878 -r 0eab8146b294 tools/cpep8.manifest
--- a/tools/cpep8.manifest Thu Jun 17 21:57:05 2021 -0400
+++ b/tools/cpep8.manifest Sat Jul 03 14:13:35 2021 -0400
@@ -83,6 +83,7 @@
./chirp/drivers/retevis_rt23.py
./chirp/drivers/retevis_rt26.py
./chirp/drivers/retevis_rt76p.py
+./chirp/drivers/retevis_rt98.py
./chirp/drivers/rfinder.py
./chirp/drivers/tdxone_tdq8a.py
./chirp/drivers/template.py
1
0
24 Jun '21
# HG changeset patch
# User Kosta A. <ve7kcy(a)gmail.com>
# Date 1624595990 25200
# Thu Jun 24 21:39:50 2021 -0700
# Branch open-recent
# Node ID bea11de983007eac96d69b3a0aeccfdcaab1c2a7
# Parent f586574bc8786fd6bef1e5d54d08d381c81edd47
Add support for clearing recently opened files. #9127
o Add Clear Recently Open files menu item
o Order recently opened files list by most recent
o Normalize camel case accross all menu items
diff --git a/chirp/ui/mainapp.py b/chirp/ui/mainapp.py
--- a/chirp/ui/mainapp.py
+++ b/chirp/ui/mainapp.py
@@ -339,6 +339,10 @@
if not fname:
return
+ if not os.path.exists(fname):
+ LOG.error("Unable to find file %s" % fname)
+ return
+
self.record_recent_file(fname)
if icf.is_icf_file(fname):
@@ -531,54 +535,61 @@
return recent
def _set_recent_list(self, recent):
- for fn in recent:
- CONF.set("recent%i" % recent.index(fn), fn, "state")
+ for index in range(0, KEEP_RECENT):
+ key = "recent%i" % index
+ if (index < len(recent)):
+ fn = recent[index]
+ CONF.set(key, fn, "state")
+ else:
+ CONF.remove_option(key, "state")
def update_recent_files(self):
- i = 0
- for fname in self._get_recent_list():
- action_name = "recent%i" % i
+ recent_files = self._get_recent_list()
+ for index in range(0, KEEP_RECENT):
+ action_name = "recent%i" % index
path = "/MenuBar/file/recent"
old_action = self.menu_ag.get_action(action_name)
if old_action:
+ old_action.set_visible(False)
self.menu_ag.remove_action(old_action)
- file_basename = os.path.basename(fname).replace("_", "__")
- widget_name = action_name
- widget_label = "_%i. %s" % (i + 1, file_basename)
- widget_tip = _("Open recent file") + (" {name}").format(name=fname)
- widget_icon = ""
- action = gtk.Action(widget_name, widget_label,
- widget_tip, widget_icon)
+ if (index < len(recent_files)):
+ fname = recent_files[index]
+ widget_label = os.path.basename(fname).replace("_", "__")
+ widget_tip = _("Open recent file") + (" {name}").format(name=fname)
+ widget_path = path + "/" + action_name
- action.connect("activate", lambda a, f: self.do_open(f), fname)
- mid = self.menu_uim.new_merge_id()
- self.menu_uim.add_ui(mid, path,
- action_name, action_name,
- gtk.UI_MANAGER_MENUITEM, False)
- self.menu_ag.add_action(action)
+ action = gtk.Action(action_name, widget_label, widget_tip, "")
+ action.connect("activate", lambda a, f: self.do_open(f), fname)
+
+ mid = self.menu_uim.new_merge_id()
+ self.menu_uim.add_ui(mid, path,
+ action_name, action_name,
+ gtk.UI_MANAGER_MENUITEM, True)
+ self.menu_ag.add_action(action)
- widget_uim_path = path + "/" + widget_name
- try:
- widget_item = self.menu_uim.get_widget(widget_uim_path)
- widget_item.set_tooltip_text(widget_tip)
- except:
- pass
-
- i += 1
+ try:
+ widget = self.menu_uim.get_widget(widget_path)
+ widget.set_tooltip_text(tip)
+ except:
+ pass
- def record_recent_file(self, filename):
-
+ def record_recent_file(self, fname):
recent_files = self._get_recent_list()
- if filename not in recent_files:
- if len(recent_files) == KEEP_RECENT:
- del recent_files[-1]
- recent_files.insert(0, filename)
- self._set_recent_list(recent_files)
+ if fname in recent_files:
+ recent_files.remove(fname)
+ if len(recent_files) == KEEP_RECENT:
+ recent_files.pop(0)
+ recent_files.append(fname)
+ self._set_recent_list(recent_files)
self.update_recent_files()
+ def clear_recent_files(self):
+ self._set_recent_list([])
+ self.update_recent_files();
+
def import_stock_config(self, action, config):
eset = self.get_current_editorset()
count = eset.do_import(config)
@@ -621,9 +632,9 @@
"")
action.connect("activate", self.import_stock_config, config)
mid = self.menu_uim.new_merge_id()
- mid = self.menu_uim.add_ui(mid, path,
- action_name, action_name,
- gtk.UI_MANAGER_MENUITEM, False)
+ self.menu_uim.add_ui(mid, path,
+ action_name, action_name,
+ gtk.UI_MANAGER_MENUITEM, False)
self.menu_ag.add_action(action)
def _do_open_action(config):
@@ -637,9 +648,9 @@
"")
action.connect("activate", lambda a, c: self.do_open(c), config)
mid = self.menu_uim.new_merge_id()
- mid = self.menu_uim.add_ui(mid, path,
- action_name, action_name,
- gtk.UI_MANAGER_MENUITEM, False)
+ self.menu_uim.add_ui(mid, path,
+ action_name, action_name,
+ gtk.UI_MANAGER_MENUITEM, False)
self.menu_ag.add_action(action)
configs = glob(os.path.join(stock_dir, "*.csv"))
@@ -881,6 +892,9 @@
count = eset.do_import(filen)
reporting.report_model_usage(eset.rthread.radio, "import", count > 0)
+ def do_clear_recently_opened(self):
+ self.clear_recent_files()
+
def do_dmrmarc_prompt(self):
fields = {"1City": (gtk.Entry(), lambda x: x),
"2State": (gtk.Entry(), lambda x: x),
@@ -1805,6 +1819,8 @@
self.do_close()
elif action == "import":
self.do_import()
+ elif action == "clearrecent":
+ self.do_clear_recently_opened()
elif action in ["qdmrmarc", "idmrmarc"]:
self.do_dmrmarc(action[0] == "i")
elif action in ["qrfinder", "irfinder"]:
@@ -1868,7 +1884,10 @@
<menuitem action="new"/>
<menuitem action="open"/>
<menu action="openstock" name="openstock"/>
- <menu action="recent" name="recent"/>
+ <menu action="recent" name="recent">
+ <separator/>
+ <menuitem action="clearrecent"/>
+ </menu>
<menuitem action="save"/>
<menuitem action="saveas"/>
<menuitem action="loadmod"/>
@@ -1961,8 +1980,9 @@
('file', None, _("_File"), None, None, self.mh),
('new', gtk.STOCK_NEW, None, None, None, self.mh),
('open', gtk.STOCK_OPEN, None, None, None, self.mh),
- ('openstock', None, _("Open stock config"), None, None, self.mh),
- ('recent', None, _("_Recent"), None, None, self.mh),
+ ('openstock', None, _("Open Stock Config"), None, None, self.mh),
+ ('recent', None, _("Open _Recent"), None, None, self.mh),
+ ('clearrecent', None, _("Clear Recently Opened"), None, None, self.mh),
('save', gtk.STOCK_SAVE, None, None, None, self.mh),
('saveas', gtk.STOCK_SAVE_AS, None, None, None, self.mh),
('loadmod', None, _("Load Module"), None, None, self.mh),
@@ -1985,13 +2005,13 @@
('view', None, _("_View"), None, None, self.mh),
('columns', None, _("Columns"), None, None, self.mh),
('viewdeveloper', None, _("Developer"), None, None, self.mh),
- ('devshowraw', None, _('Show raw memory'),
+ ('devshowraw', None, _('Show Raw Memory'),
"%s<Shift>r" % CTRL_KEY, None, self.mh),
- ('devdiffraw', None, _("Diff raw memories"),
+ ('devdiffraw', None, _("Diff Raw Memories"),
"%s<Shift>d" % CTRL_KEY, None, self.mh),
- ('devdifftab', None, _("Diff tabs"),
+ ('devdifftab', None, _("Diff Tabs"),
"%s<Shift>t" % CTRL_KEY, None, self.mh),
- ('language', None, _("Change language"), None, None, self.mh),
+ ('language', None, _("Change Language"), None, None, self.mh),
('radio', None, _("_Radio"), None, None, self.mh),
('download', None, _("Download From Radio"),
"%sd" % ALT_KEY, None, self.mh),
@@ -1999,7 +2019,7 @@
"%su" % ALT_KEY, None, self.mh),
('import', None, _("Import"), "%si" % ALT_KEY, None, self.mh),
('export', None, _("Export"), "%se" % ALT_KEY, None, self.mh),
- ('importsrc', None, _("Import from data source"),
+ ('importsrc', None, _("Import From Data Source"),
None, None, self.mh),
('idmrmarc', None, _("DMR-MARC Repeaters"), None, None, self.mh),
('iradioref', None, _("RadioReference"), None, None, self.mh),
@@ -2009,12 +2029,12 @@
None, None, self.mh),
('irfinder', None, _("RFinder"), None, None, self.mh),
('irbook', None, _("RepeaterBook"), None, None, self.mh),
- ('irbookpolitical', None, _("RepeaterBook political query"), None,
+ ('irbookpolitical', None, _("RepeaterBook Political Query"), None,
None, self.mh),
- ('irbookproximity', None, _("RepeaterBook proximity query"), None,
+ ('irbookproximity', None, _("RepeaterBook Proximity Query"), None,
None, self.mh),
('ipr', None, _("przemienniki.net"), None, None, self.mh),
- ('querysrc', None, _("Query data source"), None, None, self.mh),
+ ('querysrc', None, _("Query Data Source"), None, None, self.mh),
('qdmrmarc', None, _("DMR-MARC Repeaters"), None, None, self.mh),
('qradioref', None, _("RadioReference"), None, None, self.mh),
('qradioreference', None, _("RadioReference.com US"),
@@ -2024,16 +2044,16 @@
('qrfinder', None, _("RFinder"), None, None, self.mh),
('qpr', None, _("przemienniki.net"), None, None, self.mh),
('qrbook', None, _("RepeaterBook"), None, None, self.mh),
- ('qrbookpolitical', None, _("RepeaterBook political query"), None,
+ ('qrbookpolitical', None, _("RepeaterBook Political Query"), None,
None, self.mh),
- ('qrbookproximity', None, _("RepeaterBook proximity query"), None,
+ ('qrbookproximity', None, _("RepeaterBook Proximity Query"), None,
None, self.mh),
('export_chirp', None, _("CHIRP Native File"),
None, None, self.mh),
('export_csv', None, _("CSV File"), None, None, self.mh),
- ('stock', None, _("Import from stock config"),
+ ('stock', None, _("Import From Stock Config"),
None, None, self.mh),
- ('channel_defaults', None, _("Channel defaults"),
+ ('channel_defaults', None, _("Channel Defaults"),
None, None, self.mh),
('cancelq', gtk.STOCK_STOP, None, "Escape", None, self.mh),
('help', None, _('Help'), None, None, self.mh),
@@ -2254,8 +2274,6 @@
vbox = gtk.VBox(False, 2)
- self._recent = []
-
self.menu_ag = None
mbar = self.make_menubar()
1
0
[chirp_devel] [PATCH] Optimizing generic csv files load times by removing duplicate load for an empty list. Fixes #8991
by Kosta A. 23 Jun '21
by Kosta A. 23 Jun '21
23 Jun '21
# HG changeset patch
# User Kosta A. <ve7kcy(a)gmail.com>
# Date 1624429267 25200
# Tue Jun 22 23:21:07 2021 -0700
# Branch generic-csv
# Node ID 6b816f380d29518e9d45fe6a6a8fa32d3e96b805
# Parent f586574bc8786fd6bef1e5d54d08d381c81edd47
Optimizing generic csv files load times by removing duplicate load for an empty list. Fixes #8991
diff --git a/chirp/chirp_common.py b/chirp/chirp_common.py
--- a/chirp/chirp_common.py
+++ b/chirp/chirp_common.py
@@ -268,11 +268,11 @@
# or an empty list if none
extra = []
- def __init__(self):
+ def __init__(self, number=0, empty=False, name=""):
self.freq = 0
- self.number = 0
+ self.number = number
self.extd_number = ""
- self.name = ""
+ self.name = name
self.vfo = 0
self.rtone = 88.5
self.ctone = 88.5
@@ -290,7 +290,7 @@
self.comment = ""
- self.empty = False
+ self.empty = empty
self.immutable = []
diff --git a/chirp/drivers/generic_csv.py b/chirp/drivers/generic_csv.py
--- a/chirp/drivers/generic_csv.py
+++ b/chirp/drivers/generic_csv.py
@@ -73,14 +73,12 @@
"Comment": (str, "comment"),
}
- def _blank(self):
+ def _blank(self, setDefault=False):
self.errors = []
- self.memories = []
- for i in range(0, 1000):
- mem = chirp_common.Memory()
- mem.number = i
- mem.empty = True
- self.memories.append(mem)
+ self.memories = [chirp_common.Memory(i, True) for i in range(0, 1000)]
+ if (setDefault):
+ self.memories[0].empty = False
+ self.memories[0].freq = 146010000
def __init__(self, pipe):
chirp_common.FileBackedRadio.__init__(self, None)
@@ -92,7 +90,7 @@
if self._filename and os.path.exists(self._filename):
self.load()
else:
- self._blank()
+ self._blank(True)
def get_features(self):
rf = chirp_common.RadioFeatures()
diff --git a/chirp/ui/editorset.py b/chirp/ui/editorset.py
--- a/chirp/ui/editorset.py
+++ b/chirp/ui/editorset.py
@@ -386,19 +386,6 @@
"export: {error}").format(error=e),
self)
- def prime(self):
- # NOTE: this is only called to prime new CSV files, so assume
- # only one memory editor for now
- mem = chirp_common.Memory()
- mem.freq = 146010000
-
- def cb(*args):
- gobject.idle_add(self.editors["memedit0"].prefill)
-
- job = common.RadioJob(cb, "set_memory", mem)
- job.set_desc(_("Priming memory"))
- self.rthread.submit(job)
-
def tab_selected(self, notebook, foo, pagenum):
widget = notebook.get_nth_page(pagenum)
for k, v in self.editors.items():
diff --git a/chirp/ui/mainapp.py b/chirp/ui/mainapp.py
--- a/chirp/ui/mainapp.py
+++ b/chirp/ui/mainapp.py
@@ -271,7 +271,6 @@
def do_new(self):
eset = editorset.EditorSet(_("Untitled") + ".csv", self)
self._connect_editorset(eset)
- eset.prime()
eset.show()
tab = self.tabs.append_page(eset, eset.get_tab_label())
diff --git a/chirp/ui/memedit.py b/chirp/ui/memedit.py
--- a/chirp/ui/memedit.py
+++ b/chirp/ui/memedit.py
@@ -1062,10 +1062,7 @@
if not mem.empty or self.show_empty:
gobject.idle_add(self.set_memory, mem)
else:
- mem = chirp_common.Memory()
- mem.number = number
- mem.name = "ERROR"
- mem.empty = True
+ mem = chirp_common.Memory(number, True, "Error")
gobject.idle_add(self.set_memory, mem)
for i in range(lo, hi+1):
1
0
# HG changeset patch
# User Jim Unroe <rock.unroe(a)gmail.com>
# Date 1623981425 14400
# Thu Jun 17 21:57:05 2021 -0400
# Node ID f586574bc8786fd6bef1e5d54d08d381c81edd47
# Parent 7a13867d60472789687f5320c62b00d034846cbc
[KT8900D] Add MCU version for QYT KT8900D
This patch adds an additional MCU version to support the QYT KT8900D
dual-band mobile radio.
fixes #9139
diff --git a/chirp/drivers/btech.py b/chirp/drivers/btech.py
--- a/chirp/drivers/btech.py
+++ b/chirp/drivers/btech.py
@@ -1,4 +1,4 @@
-# Copyright 2016-2020:
+# Copyright 2016-2021:
# * Pavel Milanes CO7WT, <pavelmc(a)gmail.com>
# * Jim Unroe KC9HI, <rock.unroe(a)gmail.com>
#
@@ -245,6 +245,7 @@
# KT8900D (dual band)
KT8900D_fp = "VC2002"
KT8900D_fp1 = "VC8632"
+KT8900D_fp2 = "VC3402"
# LUITON LT-588UV
LT588UV_fp = "V2G1F4"
@@ -3939,7 +3940,7 @@
_vhf_range = (136000000, 175000000)
_uhf_range = (400000000, 481000000)
_magic = MSTRING_KT8900D
- _fileid = [KT8900D_fp, KT8900D_fp1]
+ _fileid = [KT8900D_fp2, KT8900D_fp1, KT8900D_fp]
# Clones
ALIASES = [OTGRadioV1]
2
1
22 Jun '21
# HG changeset patch
# User Kosta A. <ve7kcy(a)gmail.com>
# Date 1624429345 25200
# Tue Jun 22 23:22:25 2021 -0700
# Branch misc
# Node ID d156d3a46b7e6cbce985dc14186f0e07b5d3016b
# Parent f586574bc8786fd6bef1e5d54d08d381c81edd47
Reduce additional spew by forcing bandplan log to info.
diff --git a/chirp/ui/bandplans.py b/chirp/ui/bandplans.py
--- a/chirp/ui/bandplans.py
+++ b/chirp/ui/bandplans.py
@@ -47,7 +47,7 @@
# Check for duplicates.
duplicates = [x for x in plan.BANDS if x == band]
if len(duplicates) > 1:
- LOG.warn("Bandplan %s has duplicates %s" %
+ LOG.info("Bandplan %s has duplicates %s" %
(name, duplicates))
# Add repeater inputs.
rpt_input = band.inverse()
1
0
[chirp_devel] [PATCH] Removing default support for Icom Dstar from generic csv radios. #8991
by Kosta A. 22 Jun '21
by Kosta A. 22 Jun '21
22 Jun '21
# HG changeset patch
# User Kosta A. <ve7kcy(a)gmail.com>
# Date 1624428411 25200
# Tue Jun 22 23:06:51 2021 -0700
# Branch no-dstar
# Node ID b5f407175d8797bd41020e59c2024777a9e490e8
# Parent f586574bc8786fd6bef1e5d54d08d381c81edd47
Removing default support for Icom Dstar from generic csv radios. #8991
diff --git a/chirp/drivers/generic_csv.py b/chirp/drivers/generic_csv.py
--- a/chirp/drivers/generic_csv.py
+++ b/chirp/drivers/generic_csv.py
@@ -47,7 +47,7 @@
@directory.register
-class CSVRadio(chirp_common.FileBackedRadio, chirp_common.IcomDstarSupport):
+class CSVRadio(chirp_common.FileBackedRadio):
"""A driver for Generic CSV files"""
VENDOR = "Generic"
MODEL = "CSV"
@@ -67,9 +67,6 @@
"Mode": (str, "mode"),
"TStep": (float, "tuning_step"),
"Skip": (str, "skip"),
- "URCALL": (str, "dv_urcall"),
- "RPT1CALL": (str, "dv_rpt1call"),
- "RPT2CALL": (str, "dv_rpt2call"),
"Comment": (str, "comment"),
}
1
0
# HG changeset patch
# User Kosta A. <ve7kcy(a)gmail.com>
# Date 1624378359 25200
# Tue Jun 22 09:12:39 2021 -0700
# Branch generic
# Node ID e1e3497b8f7be6f6641a53843479be161c51d0f4
# Parent f586574bc8786fd6bef1e5d54d08d381c81edd47
Memory bounds are inclusive. #8991
diff --git a/chirp/drivers/generic_csv.py b/chirp/drivers/generic_csv.py
--- a/chirp/drivers/generic_csv.py
+++ b/chirp/drivers/generic_csv.py
@@ -99,7 +99,7 @@
rf.has_bank = False
rf.requires_call_lists = False
rf.has_implicit_calls = False
- rf.memory_bounds = (0, len(self.memories))
+ rf.memory_bounds = (0, len(self.memories)-1)
rf.has_infinite_number = True
rf.has_nostep_tuning = True
rf.has_comment = True
1
0