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