Polar H10: Add configuration settings

This commit is contained in:
José Rebelo
2026-04-18 17:44:19 +01:00
parent b73077a566
commit dd46ba54be
7 changed files with 180 additions and 0 deletions
@@ -575,6 +575,11 @@ public class DeviceSettingsPreferenceConst {
public static final String PREF_SONY_ADAPTIVE_VOLUME_CONTROL = "pref_adaptive_volume_control";
public static final String PREF_SONY_WIDE_AREA_TAP = "pref_wide_area_tap";
public static final String PREF_GYMLINK_ENABLED = "gymlink_enabled";
public static final String PREF_ANTPLUS_ENABLED = "antplus_enabled";
public static final String PREF_HR_BROADCAST = "hr_broadcast";
public static final String PREF_DUAL_CONNECTION = "dual_connection";
public static final String PREF_MEDIA_SOURCE = "pref_media_source";
public static final String PREF_MEDIA_PLAYBACK_MODE = "pref_media_playback_mode";
public static final String PREF_SHOKZ_EQUALIZER_BLUETOOTH = "pref_shokz_equalizer_bluetooth";
@@ -857,6 +857,11 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i
addPreferenceHandlerFor(PREF_SONY_ADAPTIVE_VOLUME_CONTROL);
addPreferenceHandlerFor(PREF_SONY_WIDE_AREA_TAP);
addPreferenceHandlerFor(PREF_GYMLINK_ENABLED);
addPreferenceHandlerFor(PREF_ANTPLUS_ENABLED);
addPreferenceHandlerFor(PREF_HR_BROADCAST);
addPreferenceHandlerFor(PREF_DUAL_CONNECTION);
addPreferenceHandlerFor(PREF_MEDIA_SOURCE);
addPreferenceHandlerFor(PREF_MEDIA_PLAYBACK_MODE);
addPreferenceHandlerFor(PREF_SHOKZ_EQUALIZER_BLUETOOTH);
@@ -3,6 +3,8 @@ package nodomain.freeyourgadget.gadgetbridge.devices.polar;
import java.util.regex.Pattern;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class PolarH10DeviceCoordinator extends AbstractPolarDeviceCoordinator {
@Override
@@ -14,4 +16,11 @@ public class PolarH10DeviceCoordinator extends AbstractPolarDeviceCoordinator {
protected Pattern getSupportedDeviceName() {
return Pattern.compile("^Polar H10.*");
}
@Override
public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_polar);
return deviceSpecificSettings;
}
}
@@ -597,6 +597,7 @@ public class BleNamesResolver {
mServices.put("000056ff-0000-1000-8000-00805f9b34fb", "(Propr: GloryFit Data");
mServices.put("af9df7a1-e595-11e3-96b4-0002a5d5c51b", "(Propr: OneTouch");
mServices.put("9b012401-bc30-ce9a-e111-0f67e491abde", "(Propr: Garmin GFDI V0)");
mServices.put("6217ff4b-fb31-1140-ad5a-a45545d7ecf3", "(Propr: Polar Settings)");
mServices.put("6a4e2401-667b-11e3-949a-0800200c9a66", "(Propr: Garmin GFDI V1)");
mServices.put("6a4e2800-667b-11e3-949a-0800200c9a66", "(Propr: Garmin ML)");
mServices.put("86f61000-f706-58a0-95b2-1fb9261e4dc7", "(Propr: Ultrahuman State)");
@@ -1194,6 +1195,7 @@ public class BleNamesResolver {
mCharacteristics.put("86f65002-f706-58a0-95b2-1fb9261e4dc7", "(Propr: Ultrahuman Response)");
mCharacteristics.put("86f66001-f706-58a0-95b2-1fb9261e4dc7", "(Propr: Ultrahuman Data)");
mCharacteristics.put("da2e7828-fbce-4e01-ae9e-261174997c48", "(Propr: SMP - Simple Management Protocol)");
mCharacteristics.put("6217ff4d-91bb-91d0-7e2a-7cd3bda8a1f3", "(Propr: Polar Settings)");
mCharacteristics.put("6e400002-b5a3-f393-e0a9-e50e24dcca9e", "(Propr: Nordic UART TX)");
mCharacteristics.put("6e400003-b5a3-f393-e0a9-e50e24dcca9e", "(Propr: Nordic UART RX)");
mCharacteristics.put("de5bf729-d711-4e47-af26-65e3012a5dc7", "(Propr: Yawell Notify)");
@@ -1,5 +1,10 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.polar;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_ANTPLUS_ENABLED;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DUAL_CONNECTION;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_GYMLINK_ENABLED;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HR_BROADCAST;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
@@ -18,6 +23,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
import nodomain.freeyourgadget.gadgetbridge.devices.HeartRrIntervalSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.polar.PolarH10ActivitySampleProvider;
@@ -50,12 +56,26 @@ public class PolarH10DeviceSupport extends AbstractBTLESingleDeviceSupport {
public static final UUID UUID_SERVICE_HEART_RATE = GattService.UUID_SERVICE_HEART_RATE;
public static final UUID UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT = GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT;
public static final UUID UUID_SERVICE_POLAR_SETTINGS = UUID.fromString("6217ff4b-fb31-1140-ad5a-a45545d7ecf3");
public static final UUID UUID_CHARACTERISTIC_POLAR_SETTINGS = UUID.fromString("6217ff4d-91bb-91d0-7e2a-7cd3bda8a1f3");
public static final byte CMD_HR_BROADCAST_SET = (byte) 0x01;
public static final byte CMD_HR_BROADCAST_GET = (byte) 0x02;
public static final byte CMD_GYM_LINK_SET = (byte) 0x03;
public static final byte CMD_GYM_LINK_GET = (byte) 0x04;
public static final byte CMD_DUAL_CONNECTION_SET = (byte) 0x08;
public static final byte CMD_DUAL_CONNECTION_GET = (byte) 0x09;
public static final byte CMD_ANT_SET = (byte) 0x0a;
public static final byte CMD_ANT_GET = (byte) 0x0b;
public static final byte CMD_RESPONSE = (byte) 0xf0;
public PolarH10DeviceSupport() {
super(LOG);
addSupportedService(UUID_SERVICE_DEVICE_INFORMATION);
addSupportedService(UUID_SERVICE_BATTERY_SERVICE);
addSupportedService(UUID_SERVICE_HEART_RATE);
addSupportedService(UUID_SERVICE_POLAR_SETTINGS);
IntentListener mListener = intent -> {
String action = intent.getAction();
@@ -101,6 +121,12 @@ public class PolarH10DeviceSupport extends AbstractBTLESingleDeviceSupport {
heartRateProfile.enableNotify(builder, true);
builder.notify(UUID_CHARACTERISTIC_POLAR_SETTINGS, true);
builder.write(UUID_CHARACTERISTIC_POLAR_SETTINGS, CMD_HR_BROADCAST_GET);
builder.write(UUID_CHARACTERISTIC_POLAR_SETTINGS, CMD_GYM_LINK_GET);
builder.write(UUID_CHARACTERISTIC_POLAR_SETTINGS, CMD_DUAL_CONNECTION_GET);
builder.write(UUID_CHARACTERISTIC_POLAR_SETTINGS, CMD_ANT_GET);
// Set defaults
getDevice().setFirmwareVersion("N/A");
getDevice().setFirmwareVersion2("N/A");
@@ -142,11 +168,106 @@ public class PolarH10DeviceSupport extends AbstractBTLESingleDeviceSupport {
return true;
}
if (UUID_CHARACTERISTIC_POLAR_SETTINGS.equals(characteristic.getUuid())) {
handlePolarSetting(value);
return true;
}
LOG.warn("Unhandled characteristic change: {} = {}", characteristic.getUuid(), GB.hexdump(value));
return false;
}
@Override
public void onSendConfiguration(final String config) {
final byte preferenceCommand;
final boolean preferenceValue;
switch (config) {
case PREF_GYMLINK_ENABLED:
preferenceCommand = CMD_GYM_LINK_SET;
preferenceValue = getDevicePrefs().getBoolean(config, false);
break;
case PREF_ANTPLUS_ENABLED:
preferenceCommand = CMD_ANT_SET;
preferenceValue = getDevicePrefs().getBoolean(config, false);
break;
case PREF_HR_BROADCAST:
preferenceCommand = CMD_HR_BROADCAST_SET;
preferenceValue = getDevicePrefs().getBoolean(config, false);
break;
case PREF_DUAL_CONNECTION:
preferenceCommand = CMD_DUAL_CONNECTION_SET;
preferenceValue = getDevicePrefs().getBoolean(config, false);
break;
default:
super.onSendConfiguration(config);
return;
}
LOG.debug("Setting {} = {}", config, preferenceValue);
final TransactionBuilder builder = createTransactionBuilder("set " + config + " = " + preferenceValue);
builder.write(UUID_CHARACTERISTIC_POLAR_SETTINGS, preferenceCommand, (byte) (preferenceValue ? 1 : 0));
builder.queue();
}
private void handlePolarSetting(final byte[] value) {
if (value[0] != CMD_RESPONSE) {
LOG.warn("Unexpected first byte {}", String.format("0x%02x", value[0]));
return;
}
switch (value[1]) {
case CMD_HR_BROADCAST_SET:
LOG.debug("HR broadcast set response, success = {}", value[2]);
break;
case CMD_HR_BROADCAST_GET:
if (value[2] != 0x01) {
LOG.warn("HR broadcast get failed, status = {}", String.format("0x%02x", value[2]));
return;
}
LOG.debug("Got HR broadcast setting, value = {}", value[3] != 0);
evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences(PREF_HR_BROADCAST, value[3] != 0));
break;
case CMD_GYM_LINK_SET:
LOG.debug("GymLink set response, success = {}", value[2]);
break;
case CMD_GYM_LINK_GET:
if (value[2] != 0x01) {
LOG.warn("GymLink get failed, status = {}", String.format("0x%02x", value[2]));
return;
}
LOG.debug("Got GymLink setting, value = {}", value[3] != 0);
evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences(PREF_GYMLINK_ENABLED, value[3] != 0));
break;
case CMD_DUAL_CONNECTION_SET:
LOG.debug("Dual connection set response, success = {}", value[2]);
break;
case CMD_DUAL_CONNECTION_GET:
if (value[2] != 0x01) {
LOG.warn("Dual connection get failed, status = {}", String.format("0x%02x", value[2]));
return;
}
LOG.debug("Got dual connection setting, value = {}", value[3] != 0);
evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences(PREF_DUAL_CONNECTION, value[3] != 0));
break;
case CMD_ANT_SET:
LOG.debug("ANT+ set response, success = {}", value[2]);
break;
case CMD_ANT_GET:
if (value[2] != 0x01) {
LOG.warn("ANT+ get failed, status = {}", String.format("0x%02x", value[2]));
return;
}
LOG.debug("Got ANT+ setting, value = {}", value[3] != 0);
evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences(PREF_ANTPLUS_ENABLED, value[3] != 0));
break;
default:
LOG.warn("Unknown response byte: {}", String.format("0x%02x", value[1]));
}
}
private void handleDeviceInfo(nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo info) {
LOG.warn("Device info: {}", info);
+8
View File
@@ -4769,4 +4769,12 @@
<string name="qhybrid_button_function_step_goal">Step goal progress</string>
<string name="qhybrid_button_function_control_music">Control music player</string>
<string name="qhybrid_button_function_ring_phone">Ring phone</string>
<string name="gymlink_title">GymLink</string>
<string name="gymlink_summary">Allow GymLink-compatible equipment to connect to the device.</string>
<string name="antplus_title">ANT+</string>
<string name="antplus_summary">Nearby compatible ANT+ devices can pick up your heart rate.</string>
<string name="broadcast_heart_rate_title">Broadcast heart rate</string>
<string name="broadcast_heart_rate_summary">Any nearby device or app can pick up your heart rate.</string>
<string name="dual_connection_title">Dual connection</string>
<string name="dual_connection_summary">Allow two apps or devices to connect simultaneously.</string>
</resources>
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="gymlink_enabled"
android:layout="@layout/preference_checkbox"
android:summary="@string/gymlink_summary"
android:title="@string/gymlink_title" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="antplus_enabled"
android:layout="@layout/preference_checkbox"
android:summary="@string/antplus_summary"
android:title="@string/antplus_title" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="hr_broadcast"
android:layout="@layout/preference_checkbox"
android:summary="@string/broadcast_heart_rate_summary"
android:title="@string/broadcast_heart_rate_title" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="dual_connection"
android:layout="@layout/preference_checkbox"
android:summary="@string/dual_connection_summary"
android:title="@string/dual_connection_title" />
</androidx.preference.PreferenceScreen>