diff --git a/chirp/drivers/baofeng_uv17.py b/chirp/drivers/baofeng_uv17.py
index 09b7da0ec..d5fde1670 100644
--- a/chirp/drivers/baofeng_uv17.py
+++ b/chirp/drivers/baofeng_uv17.py
@@ -179,6 +179,7 @@ class UV17(baofeng_uv17Pro.UV17Pro):
(b"\x06", 1)]
_fingerprint = b"\x06" + b"UV15999"
_scode_offset = 1
+ _mem_positions = ()
_tri_band = False
POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1.00),
diff --git a/chirp/drivers/baofeng_uv17Pro.py b/chirp/drivers/baofeng_uv17Pro.py
index 99e7af1ed..0c91e92fd 100644
--- a/chirp/drivers/baofeng_uv17Pro.py
+++ b/chirp/drivers/baofeng_uv17Pro.py
@@ -33,6 +33,8 @@
MSTRING_UV17PROGPS = b"PROGRAMCOLORPROU"
# Baofeng GM-5RH magic string
MSTRING_GM5RH = b"PROGRAMBFGMRS05U"
+# BTECH BF-F8HP Pro magic string
+MSTRING_BFF8HPPRO = b"PROGRAMBF5RTECHU"
DTMF_CHARS = "0123456789 *#ABCD"
STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 20.0, 25.0, 50.0]
@@ -48,13 +50,9 @@
LIST_PILOT_TONE = ["1000 Hz", "1450 Hz", "1750 Hz", "2100 Hz"]
LIST_VOICE = ["Off", "English", "Chinese"]
LIST_WORKMODE = ["Frequency", "Channel"]
-LIST_BEEP = ["Off", "Beep", "Voice", "Both"]
LIST_SCANMODE = ["Time", "Carrier", "Search"]
LIST_ALARMMODE = ["Local", "Send Tone", "Send Code"]
-LIST_MENU_QUIT_TIME = ["%s sec" % x for x in range(5, 55, 5)] + ["60 sec"]
-LIST_ID_DELAY = ["%s ms" % x for x in range(100, 3100, 100)]
LIST_QT_SAVEMODE = ["Both", "RX", "TX"]
-LIST_SKEY2_SHORT = ["FM", "Scan", "Search", "Vox"]
LIST_RPT_TAIL_CLEAR = ["%s ms" % x for x in range(0, 1100, 100)]
LIST_VOX_DELAY_TIME = ["%s ms" % x for x in range(500, 2100, 100)]
LIST_VOX_LEVEL = ["Off"] + ["%s" % x for x in range(1, 10, 1)]
@@ -151,7 +149,7 @@ def _download(radio):
d = bfc._rawrecv(radio, radio.BLOCK_SIZE + 4)
LOG.debug("Response Data= " + util.hexprint(d))
- d = _crypt(1, d[4:])
+ d = _crypt(radio._encrsym, d[4:])
# Aggregate the data
data += d
@@ -183,7 +181,7 @@ def _upload(radio):
for addr in range(MEM_START, MEM_START + MEM_SIZE,
radio.BLOCK_SIZE):
data = radio_mem[data_addr:data_addr + radio.BLOCK_SIZE]
- data = _crypt(1, data)
+ data = _crypt(radio._encrsym, data)
data_addr += radio.BLOCK_SIZE
frame = radio._make_frame(b"W", addr, radio.BLOCK_SIZE, data)
@@ -248,6 +246,9 @@ class UV17Pro(bfc.BaofengCommonHT):
_has_send_id_delay = False
_has_skey2_short = False
_scode_offset = 0
+ _encrsym = 1
+ _has_voice = True
+ _mem_positions = (0x8080, 0x80A0, 0x8280)
MODES = ["NFM", "FM"]
VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \
@@ -282,6 +283,12 @@ class UV17Pro(bfc.BaofengCommonHT):
LIST_BACKLIGHT_TIMER = ["Always On"] + ["%s sec"
% x for x in range(5, 25, 5)]
LIST_MODE = ["Name", "Frequency", "Channel Number"]
+ LIST_BEEP = ["Off", "Beep", "Voice", "Both"]
+ LIST_MENU_QUIT_TIME = ["%s sec" % x for x in range(5, 55, 5)] + ["60 sec"]
+ LIST_ID_DELAY = ["%s ms" % x for x in range(100, 3100, 100)]
+ LIST_SEPARATE_CODE = ["A", "B", "C", "D", "*", "#"]
+ LIST_GROUP_CALL_CODE = ["Off"] + LIST_SEPARATE_CODE
+ LIST_SKEY2_SHORT = ["FM", "Scan", "Search", "Vox"]
CHANNELS = 1000
@@ -371,7 +378,9 @@ class UV17Pro(bfc.BaofengCommonHT):
u8 unknown4[2];
u8 voxdlytime;
u8 menuquittime;
- u8 unknown5[6];
+ u8 unknown5[2];
+ u8 dispani;
+ u8 unknown11[3];
u8 totalarm;
u8 unknown6[2];
u8 ctsdcsscantype;
@@ -383,13 +392,18 @@ class UV17Pro(bfc.BaofengCommonHT):
u8 key2short;
u8 unknown8[2];
u8 rstmenu;
- u8 unknown9;
+ u8 singlewatch;
u8 hangup;
u8 voxsw;
u8 gpstimezone;
+ u8 unknown10;
+ u8 inputdtmf;
+ u8 gpsunits;
+ u8 pontime;
+ char stationid[8];
} settings;
- #seekto 0x8080;
+ #seekto 0x%04X;
struct {
u8 code[5];
u8 unknown[1];
@@ -397,16 +411,27 @@ class UV17Pro(bfc.BaofengCommonHT):
aniid:2;
u8 dtmfon;
u8 dtmfoff;
+ u8 separatecode;
+ u8 groupcallcode;
} ani;
- #seekto 0x80A0;
+ #seekto 0x%04X;
struct {
u8 code[5];
- u8 name[10];
+ char name[10];
u8 unused;
} pttid[20];
- #seekto 0x8280;
+ struct {
+ u8 unknown32[32];
+ u8 code[16];
+ } upcode;
+
+ struct {
+ u8 code[16];
+ } downcode;
+
+ #seekto 0x%04X;
struct {
char name[16];
} bank_name[10];
@@ -453,7 +478,8 @@ def get_prompts(cls):
def process_mmap(self):
"""Process the mem map into the mem object"""
# make lines shorter for style check.
- self._memobj = bitwise.parse(self.MEM_FORMAT, self._mmap)
+ fmt = self.MEM_FORMAT % self._mem_positions
+ self._memobj = bitwise.parse(fmt, self._mmap)
# DTMF settings
def apply_code(self, setting, obj, length):
@@ -465,8 +491,22 @@ def apply_code(self, setting, obj, length):
code.append(0xFF)
obj.code = code
+ def _filterCodeName(self, name):
+ fname = b""
+ for char in name:
+ if ord(str(char)) in [0, 255]:
+ break
+ fname += int(char).to_bytes(1, 'big')
+ return fname.decode('gb2312').strip()
+
+ def apply_codename(self, setting, obj):
+ codename = (str(setting.value).encode('gb2312')[:10].ljust(10,
+ b"\xFF"))
+ obj.name = codename
+
def get_settings_common_dtmf(self, dtmfe, _mem):
for i in range(0, len(self.SCODE_LIST)):
+ # Signal Code
_codeobj = self._memobj.pttid[i].code
_code = "".join([
DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
@@ -474,14 +514,33 @@ def get_settings_common_dtmf(self, dtmfe, _mem):
val.set_charset(DTMF_CHARS)
pttid = RadioSetting("pttid/%i.code" % i,
"Signal Code %i" % (i + 1), val)
+ if self.MODEL == "BF-F8HP-PRO":
+ pttid.set_doc("3 characters maximum")
pttid.set_apply_callback(self.apply_code, self._memobj.pttid[i], 5)
dtmfe.append(pttid)
+ # Signal Code Name
+ _nameobj = self._memobj.pttid[i]
+ try:
+ rs = RadioSetting(
+ "pttid/%i.name" % i,
+ "Signal Code %i Name" % (i + 1),
+ RadioSettingValueString(
+ 0, 10, self._filterCodeName(_nameobj.name),
+ False, CHARSET_GB2312))
+ rs.set_apply_callback(self.apply_codename, _nameobj)
+ dtmfe.append(rs)
+ except AttributeError:
+ # UV17, et al do not have pttid.name
+ pass
+
_codeobj = self._memobj.ani.code
_code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
val = RadioSettingValueString(0, 5, _code, False)
val.set_charset(DTMF_CHARS)
rs = RadioSetting("ani.code", "ANI Code", val)
+ if self.MODEL == "BF-F8HP-PRO":
+ rs.set_doc("3 characters maximum")
rs.set_apply_callback(self.apply_code, self._memobj.ani, 5)
dtmfe.append(rs)
@@ -555,14 +614,15 @@ def get_settings_common_basic(self, basic, _mem):
_mem.settings.powerondistype]))
basic.append(rs)
- if _mem.settings.voice >= len(self.LIST_VOICE):
- val = 0x01
- else:
- val = _mem.settings.voice
- rs = RadioSetting("settings.voice", "Voice Prompt",
- RadioSettingValueList(
- self.LIST_VOICE, self.LIST_VOICE[val]))
- basic.append(rs)
+ if self._has_voice:
+ if _mem.settings.voice >= len(self.LIST_VOICE):
+ val = 0x01
+ else:
+ val = _mem.settings.voice
+ rs = RadioSetting("settings.voice", "Voice Prompt",
+ RadioSettingValueList(
+ self.LIST_VOICE, self.LIST_VOICE[val]))
+ basic.append(rs)
rs = RadioSetting("settings.voicesw", "Enable Voice",
RadioSettingValueBoolean(_mem.settings.voicesw))
@@ -584,7 +644,8 @@ def get_settings_common_basic(self, basic, _mem):
rs = RadioSetting("settings.beep", "Beep",
RadioSettingValueList(
- LIST_BEEP, LIST_BEEP[_mem.settings.beep]))
+ self.LIST_BEEP,
+ self.LIST_BEEP[_mem.settings.beep]))
basic.append(rs)
rs = RadioSetting("settings.roger", "Roger",
@@ -659,16 +720,16 @@ def get_settings_pro_basic(self, basic, _mem):
rs = RadioSetting("settings.menuquittime", "Menu Quit Timer",
RadioSettingValueList(
- LIST_MENU_QUIT_TIME,
- LIST_MENU_QUIT_TIME[
+ self.LIST_MENU_QUIT_TIME,
+ self.LIST_MENU_QUIT_TIME[
_mem.settings.menuquittime]))
basic.append(rs)
if self._has_send_id_delay:
rs = RadioSetting("settings.pttdly", "Send ID Delay",
RadioSettingValueList(
- LIST_ID_DELAY,
- LIST_ID_DELAY[_mem.settings.pttdly]))
+ self.LIST_ID_DELAY,
+ self.LIST_ID_DELAY[_mem.settings.pttdly]))
basic.append(rs)
rs = RadioSetting("settings.ctsdcsscantype", "QT Save Mode",
@@ -681,7 +742,8 @@ def getKey2shortIndex(value):
key_to_index = {0x07: 0,
0x1C: 1,
0x1D: 2,
- 0x2D: 3}
+ 0x2D: 3,
+ 0x0A: 4}
return key_to_index.get(int(value), 0)
def apply_Key2short(setting, obj):
@@ -689,14 +751,15 @@ def apply_Key2short(setting, obj):
key_to_index = {'FM': 0x07,
'Scan': 0x1C,
'Search': 0x1D,
- 'Vox': 0x2D}
+ 'Vox': 0x2D,
+ 'TX Power': 0x0A}
obj.key2short = key_to_index.get(val, 0x07)
if self._has_skey2_short:
rs = RadioSetting("settings.key2short", "Skey2 Short",
RadioSettingValueList(
- LIST_SKEY2_SHORT,
- LIST_SKEY2_SHORT[
+ self.LIST_SKEY2_SHORT,
+ self.LIST_SKEY2_SHORT[
getKey2shortIndex(
_mem.settings.key2short)]))
rs.set_apply_callback(apply_Key2short, _mem.settings)
@@ -947,6 +1010,9 @@ def apply_bankname(setting, obj):
RadioSettingValueString(
0, 16, _filterName(_nameobj.name), False,
CHARSET_GB2312))
+ if self.MODEL == "BF-F8HP-PRO":
+ rs.set_doc("12 characters maximum (only the first 6 show "
+ "up on the main display)")
rs.set_apply_callback(apply_bankname, _nameobj)
bank.append(rs)
@@ -1189,6 +1255,15 @@ def get_memory_common(self, _mem, name, mem):
scode]))
mem.extra.append(rs)
+ if self.MODEL == "BF-F8HP-PRO":
+ rs = RadioSetting("sqmode", "RX DTMF",
+ RadioSettingValueBoolean(_mem.sqmode))
+ mem.extra.append(rs)
+
+ rs = RadioSetting("fhss", "FHSS",
+ RadioSettingValueBoolean(_mem.fhss))
+ mem.extra.append(rs)
+
mem.name = str(name).replace('\xFF', ' ').replace('\x00', ' ').rstrip()
def _get_raw_memory(self, number):
@@ -1371,3 +1446,123 @@ class GM5RH(UV17Pro):
class UV5GPlus(GM5RH):
VENDOR = "Radioddity"
MODEL = "UV-5G Plus"
+
+
+@directory.register
+class F8HPPro(UV17Pro):
+ VENDOR = "Baofeng"
+ MODEL = "BF-F8HP-PRO"
+
+ _magic = MSTRING_BFF8HPPRO
+ _magics = [(b"\x46", 16),
+ (b"\x4d", 6),
+ (b"\x53\x45\x4E\x44\x12\x0D\x0A\x0A\x10\x03\x0D\x02\x11\x0C" +
+ b"\x12\x0A\x11\x06\x04\x0E\x02\x09\x0D\x00\x00", 1)]
+ _encrsym = 3
+ VALID_BANDS = [UV17Pro._airband, UV17Pro._vhf_range, UV17Pro._vhf2_range,
+ UV17Pro._uhf_range, UV17Pro._uhf2_range]
+ LIST_POWER_ON_TIME = ['3 Seconds', '5 Seconds', '10 Seconds']
+ LIST_GPS_UNITS = ['km/h', 'MPH', 'kn']
+ LIST_POWERON_DISPLAY_TYPE = ["LOGO", "BATT voltage", "Station ID"]
+ LIST_BEEP = ["Off", "On"]
+ LIST_MENU_QUIT_TIME = ["10 sec", "20 sec", "30 sec", "60 sec", "Off"]
+ LIST_BACKLIGHT_TIMER = ["Always On", "5 sec", "10 sec", "15 sec",
+ "20 sec", "30 sec", "60 sec"]
+ LIST_ID_DELAY = ["%s ms" % x for x in range(0, 1600, 100)]
+ LIST_SKEY2_SHORT = ["FM", "Scan", "Search", "Vox", "TX Power"]
+
+ _has_support_for_banknames = True
+ _vfoscan = True
+ _has_gps = True
+ _has_voxsw = True
+ _has_pilot_tone = True
+ _has_send_id_delay = True
+ _has_skey2_short = True
+ _has_voice = False
+ _has_when_to_send_aniid = False
+
+ MEM_STARTS = [0x0000, 0x9000, 0xA000, 0xD000]
+ MEM_SIZES = [0x8040, 0x0080, 0x02C0, 0x00C0]
+
+ MEM_TOTAL = 0x8440
+ _mem_positions = (0x80C0, 0x80E0, 0x8380)
+
+ def get_settings_pro_dtmf(self, dtmfe, _mem):
+ super().get_settings_pro_dtmf(dtmfe, _mem)
+
+ rs = RadioSetting("ani.separatecode", "Separate Code",
+ RadioSettingValueList(
+ self.LIST_SEPARATE_CODE,
+ self.LIST_SEPARATE_CODE[_mem.ani.separatecode]))
+ dtmfe.append(rs)
+
+ rs = RadioSetting("ani.groupcallcode", "Group Call Code",
+ RadioSettingValueList(
+ self.LIST_GROUP_CALL_CODE,
+ self.LIST_GROUP_CALL_CODE[_mem.ani.groupcallcode]))
+ dtmfe.append(rs)
+
+ _codeobj = self._memobj.upcode.code
+ _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+ val = RadioSettingValueString(0, 16, _code, False)
+ val.set_charset(DTMF_CHARS)
+ rs = RadioSetting("upcode.code", "Up Code", val)
+ rs.set_apply_callback(self.apply_code, self._memobj.upcode, 16)
+ dtmfe.append(rs)
+
+ _codeobj = self._memobj.downcode.code
+ _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+ val = RadioSettingValueString(0, 16, _code, False)
+ val.set_charset(DTMF_CHARS)
+ rs = RadioSetting("downcode.code", "Down Code", val)
+ rs.set_apply_callback(self.apply_code, self._memobj.downcode, 16)
+ dtmfe.append(rs)
+
+ def get_settings_pro_basic(self, basic, _mem):
+ super().get_settings_pro_basic(basic, _mem)
+
+ rs = RadioSetting("settings.dispani", "Display ANI",
+ RadioSettingValueBoolean(_mem.settings.dispani))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.pontime", "Power on Time",
+ RadioSettingValueList(
+ self.LIST_POWER_ON_TIME,
+ self.LIST_POWER_ON_TIME[_mem.settings.pontime]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.gpsunits", "GPS Speed Units",
+ RadioSettingValueList(
+ self.LIST_GPS_UNITS,
+ self.LIST_GPS_UNITS[_mem.settings.gpsunits]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.inputdtmf", "Input DTMF",
+ RadioSettingValueBoolean(_mem.settings.inputdtmf))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.singlewatch", "Single Watch",
+ RadioSettingValueBoolean(_mem.settings.singlewatch))
+ basic.append(rs)
+
+ def _filterStationID(name):
+ fname = b""
+ for char in name:
+ if ord(str(char)) in [0, 255]:
+ break
+ fname += int(char).to_bytes(1, 'big')
+ return fname.decode('gb2312').strip()
+
+ def apply_stationid(setting, obj):
+ stationid = (str(setting.value).encode('gb2312')[:8].ljust(8,
+ b"\xFF"))
+ obj.stationid = stationid
+
+ _nameobj = self._memobj.settings
+ rs = RadioSetting("settings.stationid",
+ "StationID",
+ RadioSettingValueString(
+ 0, 8, _filterStationID(_nameobj.stationid),
+ False, CHARSET_GB2312))
+ rs.set_apply_callback(apply_stationid, _nameobj)
+ basic.append(rs)
diff --git a/tests/Python3_Driver_Testing.md b/tests/Python3_Driver_Testing.md
index 5a9a1d77b..09100e6ed 100644
--- a/tests/Python3_Driver_Testing.md
+++ b/tests/Python3_Driver_Testing.md
@@ -55,6 +55,7 @@
| Baofeng_BF-A58 | [@KC9HI](https://github.com/KC9HI) | 3-Dec-2022 | Yes | **1.46%** |
| Baofeng_BF-A58S | [@KC9HI](https://github.com/KC9HI) | 3-Dec-2022 | Yes | 0.59% |
| Baofeng_BF-F8HP | [@KC9HI](https://github.com/KC9HI) | 18-Nov-2022 | Yes | **6.33%** |
+| Baofeng_BF-F8HP-PRO | | | Yes | |
| Baofeng_BF-M4 | [@cetinajero](https://github.com/cetinajero) | 8-May-2023 | Yes | |
| Baofeng_BF-T1 | [@KC9HI](https://github.com/KC9HI) | 10-Dec-2022 | Yes | 0.76% |
| Baofeng_BF-T20 | [@KC9HI](https://github.com/KC9HI) | 15-Dec-2023 | Yes | |
@@ -467,11 +468,11 @@
| Zastone_ZT-X6 | [Implied by Retevis_RT22](#user-content-Retevis_RT22) | 9-Dec-2022 | Yes | 0.11% |
## Stats
-**Drivers:** 464
+**Drivers:** 465
-**Tested:** 87% (406/58) (93% of usage stats)
+**Tested:** 87% (406/59) (93% of usage stats)
-**Byte clean:** 91% (425/39)
+**Byte clean:** 91% (426/39)
## Meaning of this testing
diff --git a/tests/images/Baofeng_BF-F8HP-PRO.img b/tests/images/Baofeng_BF-F8HP-PRO.img
new file mode 100644
index 000000000..0b8bdd58c
Binary files /dev/null and b/tests/images/Baofeng_BF-F8HP-PRO.img differ