mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
feat(withings,scanwatch): Add ECG and AFib support
This commit is contained in:
+4
@@ -32,11 +32,15 @@ public class WithingsScanwatchSettingsCustomizer implements DeviceSpecificSettin
|
||||
|
||||
static final String PREF_SCREENS_SORTABLE = "withings_scanwatch_screens_sortable";
|
||||
static final String PREF_SHORTCUT_ACTION = "withings_scanwatch_shortcut_action";
|
||||
static final String PREF_ECG_ENABLED = "withings_scanwatch_ecg_enabled";
|
||||
static final String PREF_AFIB_ENABLED = "withings_scanwatch_afib_enabled";
|
||||
|
||||
@Override
|
||||
public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String rootKey) {
|
||||
handler.addPreferenceHandlerFor(PREF_SCREENS_SORTABLE);
|
||||
handler.addPreferenceHandlerFor(PREF_SHORTCUT_ACTION);
|
||||
handler.addPreferenceHandlerFor(PREF_ECG_ENABLED);
|
||||
handler.addPreferenceHandlerFor(PREF_AFIB_ENABLED);
|
||||
|
||||
final ListPreference shortcutPref = handler.findPreference(PREF_SHORTCUT_ACTION);
|
||||
if (shortcutPref != null) {
|
||||
|
||||
+88
@@ -36,6 +36,9 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.With
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.WithingsUUIDs;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation.GetShortcutHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.EndOfTransmission;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.FeatureTagDeprecated;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.FeatureTagsUserId;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.LocalNotification;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ScreenSettings;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ShortcutAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsScreenId;
|
||||
@@ -67,6 +70,11 @@ public class WithingsScanwatchDeviceSupport extends WithingsBaseDeviceSupport {
|
||||
|
||||
private static final String PREF_SHORTCUT_ACTION = "withings_scanwatch_shortcut_action";
|
||||
|
||||
/** Whether the ECG measurement feature is enabled on the watch (feature tag activation). */
|
||||
static final String PREF_ECG_ENABLED = "withings_scanwatch_ecg_enabled";
|
||||
/** Whether AFib detection (and night AFib) is enabled on the watch. */
|
||||
static final String PREF_AFIB_ENABLED = "withings_scanwatch_afib_enabled";
|
||||
|
||||
@Override
|
||||
protected void addExtraSyncCommands() {
|
||||
addSimpleConversationToQueue(
|
||||
@@ -100,9 +108,89 @@ public class WithingsScanwatchDeviceSupport extends WithingsBaseDeviceSupport {
|
||||
sendQueue();
|
||||
return true;
|
||||
}
|
||||
if (PREF_ECG_ENABLED.equals(config)) {
|
||||
final SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress());
|
||||
final boolean enabled = prefs.getBoolean(PREF_ECG_ENABLED, false);
|
||||
clearQueue();
|
||||
addFeatureTagsCommand(enabled, prefs.getBoolean(PREF_AFIB_ENABLED, false));
|
||||
addLocalNotificationsCommand(enabled, prefs.getBoolean(PREF_AFIB_ENABLED, false));
|
||||
sendQueue();
|
||||
return true;
|
||||
}
|
||||
if (PREF_AFIB_ENABLED.equals(config)) {
|
||||
final SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress());
|
||||
final boolean afibEnabled = prefs.getBoolean(PREF_AFIB_ENABLED, false);
|
||||
clearQueue();
|
||||
addFeatureTagsCommand(prefs.getBoolean(PREF_ECG_ENABLED, false), afibEnabled);
|
||||
addLocalNotificationsCommand(prefs.getBoolean(PREF_ECG_ENABLED, false), afibEnabled);
|
||||
sendQueue();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues a {@code CMD_FEATURE_TAGS_SET_DEPRECATED_V2} (0x0987) message activating the
|
||||
* feature tags required for ECG and/or AFib.
|
||||
*
|
||||
* <p>The full tag set observed from HCI captures:
|
||||
* <ul>
|
||||
* <li>ECG enabled: tags 0x0004, 0x000F, 0x0035, 0x0058</li>
|
||||
* <li>AFib enabled (in addition to ECG tags): 0x000A, 0x000B</li>
|
||||
* </ul>
|
||||
* All tags use startTime=0 / endTime=0 (always active).
|
||||
*
|
||||
* <p>Note: the Withings app also sends 0x0035 and 0x0058 (purpose unknown) as a baseline.
|
||||
*/
|
||||
private void addFeatureTagsCommand(final boolean ecgEnabled, final boolean afibEnabled) {
|
||||
final WithingsMessage msg = new WithingsMessage(WithingsMessageType.SET_FEATURE_TAGS_DEPRECATED);
|
||||
msg.addDataStructure(new FeatureTagsUserId()); // userId = 0
|
||||
|
||||
if (ecgEnabled) {
|
||||
msg.addDataStructure(new FeatureTagDeprecated(FeatureTagDeprecated.TAG_ECG_TERMS));
|
||||
msg.addDataStructure(new FeatureTagDeprecated(FeatureTagDeprecated.TAG_ECG_MEAS));
|
||||
}
|
||||
if (afibEnabled) {
|
||||
msg.addDataStructure(new FeatureTagDeprecated(FeatureTagDeprecated.TAG_AFIB_EXTRA));
|
||||
msg.addDataStructure(new FeatureTagDeprecated(FeatureTagDeprecated.TAG_AFIB_NIGHT));
|
||||
}
|
||||
if (ecgEnabled || afibEnabled) {
|
||||
msg.addDataStructure(new FeatureTagDeprecated(FeatureTagDeprecated.TAG_0x0035));
|
||||
msg.addDataStructure(new FeatureTagDeprecated(FeatureTagDeprecated.TAG_0x0058));
|
||||
}
|
||||
msg.addDataStructure(new EndOfTransmission());
|
||||
addSimpleConversationToQueue(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues a {@code CMD_LOCAL_NOTIFICATIONS_CONFIG_SET} (0x0990) message configuring the
|
||||
* five on-watch notification slots.
|
||||
*
|
||||
* <p>All five slots must always be sent together. Slot mapping from HCI captures:
|
||||
* <ol>
|
||||
* <li>PPG_AFIB - enabled when {@code afibEnabled}</li>
|
||||
* <li>ECG - always disabled (managed via feature tags)</li>
|
||||
* <li>UNKNOWN_3 - always disabled</li>
|
||||
* <li>HIGH_LOW_HR - enabled when {@code ecgEnabled} (observed in ECG capture)</li>
|
||||
* <li>PPG_AFIB_NIGHT - enabled when {@code afibEnabled}</li>
|
||||
* </ol>
|
||||
*/
|
||||
private void addLocalNotificationsCommand(final boolean ecgEnabled, final boolean afibEnabled) {
|
||||
final WithingsMessage msg = new WithingsMessage(WithingsMessageType.SET_LOCAL_NOTIFICATIONS);
|
||||
msg.addDataStructure(new LocalNotification(LocalNotification.NOTIF_PPG_AFIB,
|
||||
afibEnabled ? LocalNotification.STATUS_ENABLED : LocalNotification.STATUS_DISABLED));
|
||||
msg.addDataStructure(new LocalNotification(LocalNotification.NOTIF_ECG,
|
||||
LocalNotification.STATUS_DISABLED));
|
||||
msg.addDataStructure(new LocalNotification(LocalNotification.NOTIF_UNKNOWN_3,
|
||||
LocalNotification.STATUS_DISABLED));
|
||||
msg.addDataStructure(new LocalNotification(LocalNotification.NOTIF_HIGH_LOW_HR,
|
||||
ecgEnabled ? LocalNotification.STATUS_ENABLED : LocalNotification.STATUS_DISABLED));
|
||||
msg.addDataStructure(new LocalNotification(LocalNotification.NOTIF_PPG_AFIB_NIGHT,
|
||||
afibEnabled ? LocalNotification.STATUS_ENABLED : LocalNotification.STATUS_DISABLED));
|
||||
msg.addDataStructure(new EndOfTransmission());
|
||||
addSimpleConversationToQueue(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the Scanwatch screen list from the user's drag-sort preference.
|
||||
*
|
||||
|
||||
+9
@@ -131,6 +131,15 @@ public class DataStructureFactory {
|
||||
case WithingsStructureType.SHORTCUT_ACTION:
|
||||
structure = new ShortcutAction();
|
||||
break;
|
||||
case WithingsStructureType.FEATURE_TAG_DEPRECATED:
|
||||
structure = new FeatureTagDeprecated();
|
||||
break;
|
||||
case WithingsStructureType.FEATURE_TAGS_USER_ID:
|
||||
structure = new FeatureTagsUserId();
|
||||
break;
|
||||
case WithingsStructureType.LOCAL_NOTIFICATION:
|
||||
structure = new LocalNotification();
|
||||
break;
|
||||
default:
|
||||
structure = null;
|
||||
logger.info("Received yet unknown structure type: " + structureTypeFromResponse);
|
||||
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
/* Copyright (C) 2024 Gadgetbridge contributors
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge 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 Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Encodes one entry in the feature-tags-deprecated list (TLV type {@code 0x099C}).
|
||||
*
|
||||
* <p>Wire format: 2-byte type + 2-byte length (10) + 10-byte payload:
|
||||
* <pre>
|
||||
* id (uint16 BE) - feature tag identifier
|
||||
* startTime (uint32 BE) - Unix timestamp; 0 = always active
|
||||
* endTime (uint32 BE) - Unix timestamp; 0 = no expiry
|
||||
* </pre>
|
||||
*
|
||||
* <p>Known feature tag IDs observed from HCI captures:
|
||||
* <ul>
|
||||
* <li>{@link #TAG_ECG_TERMS} (0x0004) - ECG terms & conditions accepted</li>
|
||||
* <li>{@link #TAG_AFIB_WINDOW} (0x0009) - AFib detection time window (uses timestamps)</li>
|
||||
* <li>{@link #TAG_AFIB_EXTRA} (0x000A) - AFib detection (always active)</li>
|
||||
* <li>{@link #TAG_AFIB_NIGHT} (0x000B) - Night-time AFib detection</li>
|
||||
* <li>{@link #TAG_ECG_MEAS} (0x000F) - ECG measurement enabled</li>
|
||||
* <li>{@link #TAG_IRREGULAR_HR}(0x0013) - Irregular heart rate detection</li>
|
||||
* <li>{@link #TAG_HIGH_HR} (0x0014) - High heart rate notification</li>
|
||||
* <li>{@link #TAG_LOW_HR} (0x0016) - Low heart rate notification</li>
|
||||
* <li>{@link #TAG_0x0035} (0x0035) - Present during ECG activation (purpose unknown)</li>
|
||||
* <li>{@link #TAG_0x0058} (0x0058) - Present during ECG activation (purpose unknown)</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class FeatureTagDeprecated extends WithingsStructure {
|
||||
|
||||
// ---- Known feature tag identifiers ----
|
||||
|
||||
/** ECG terms & conditions accepted by the user. */
|
||||
public static final short TAG_ECG_TERMS = 0x0004;
|
||||
/** AFib detection time window (populated with actual timestamps). */
|
||||
public static final short TAG_AFIB_WINDOW = 0x0009;
|
||||
/** AFib continuous detection (always-active, timestamps = 0). */
|
||||
public static final short TAG_AFIB_EXTRA = (short) 0x000A;
|
||||
/** Night-time AFib detection. */
|
||||
public static final short TAG_AFIB_NIGHT = (short) 0x000B;
|
||||
/** ECG measurement feature enabled. */
|
||||
public static final short TAG_ECG_MEAS = (short) 0x000F;
|
||||
/** Irregular heart rate detection. */
|
||||
public static final short TAG_IRREGULAR_HR = (short) 0x0013;
|
||||
/** High heart rate notification. */
|
||||
public static final short TAG_HIGH_HR = (short) 0x0014;
|
||||
/** Low heart rate notification. */
|
||||
public static final short TAG_LOW_HR = (short) 0x0016;
|
||||
/** Unknown; observed during ECG activation (always-active). */
|
||||
public static final short TAG_0x0035 = 0x0035;
|
||||
/** Unknown; observed during ECG activation (always-active). */
|
||||
public static final short TAG_0x0058 = 0x0058;
|
||||
|
||||
// ---- Payload ----
|
||||
|
||||
private short tagId;
|
||||
private int startTime; // 0 = always active
|
||||
private int endTime; // 0 = no expiry
|
||||
|
||||
/** No-arg constructor required by {@link DataStructureFactory}. */
|
||||
public FeatureTagDeprecated() {}
|
||||
|
||||
/**
|
||||
* Creates a feature tag that is always active (start/end = 0).
|
||||
*
|
||||
* @param tagId one of the {@code TAG_*} constants
|
||||
*/
|
||||
public FeatureTagDeprecated(short tagId) {
|
||||
this(tagId, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a feature tag with an explicit activation window.
|
||||
*
|
||||
* @param tagId one of the {@code TAG_*} constants
|
||||
* @param startTime Unix epoch seconds; 0 = always active
|
||||
* @param endTime Unix epoch seconds; 0 = no expiry
|
||||
*/
|
||||
public FeatureTagDeprecated(short tagId, int startTime, int endTime) {
|
||||
this.tagId = tagId;
|
||||
this.startTime = startTime;
|
||||
this.endTime = endTime;
|
||||
}
|
||||
|
||||
public short getTagId() { return tagId; }
|
||||
public int getStartTime() { return startTime; }
|
||||
public int getEndTime() { return endTime; }
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
// 4-byte TLV header + 10-byte payload (u16 + u32 + u32)
|
||||
return 14;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
buffer.putShort(tagId);
|
||||
buffer.putInt(startTime);
|
||||
buffer.putInt(endTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillFromRawDataAsBuffer(ByteBuffer buffer) {
|
||||
if (buffer.remaining() >= 10) {
|
||||
tagId = buffer.getShort();
|
||||
startTime = buffer.getInt();
|
||||
endTime = buffer.getInt();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.FEATURE_TAG_DEPRECATED;
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/* Copyright (C) 2024 Gadgetbridge contributors
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge 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 Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Encodes the user-ID header TLV (type {@code 0x0145}) that precedes the feature-tag list in
|
||||
* {@code CMD_FEATURE_TAGS_SET_DEPRECATED_V2} (0x0987) messages.
|
||||
*
|
||||
* <p>Wire format: 2-byte type + 2-byte length (4) + 4-byte uint32 user-ID (always {@code 0} in
|
||||
* captures - may be a Withings account ID that Gadgetbridge does not have).
|
||||
*/
|
||||
public class FeatureTagsUserId extends WithingsStructure {
|
||||
|
||||
private int userId;
|
||||
|
||||
/** Constructs with userId = 0 (Gadgetbridge does not have a Withings account ID). */
|
||||
public FeatureTagsUserId() {
|
||||
this.userId = 0;
|
||||
}
|
||||
|
||||
public FeatureTagsUserId(int userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
// 4-byte header + 4-byte uint32
|
||||
return 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
buffer.putInt(userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillFromRawDataAsBuffer(ByteBuffer buffer) {
|
||||
if (buffer.remaining() >= 4) {
|
||||
userId = buffer.getInt();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.FEATURE_TAGS_USER_ID;
|
||||
}
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/* Copyright (C) 2024 Gadgetbridge contributors
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge 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 Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Encodes one on-device notification slot configuration (TLV type {@code 0x09A8}).
|
||||
*
|
||||
* <p>Wire format: 2-byte type + 2-byte length (5) + 5-byte payload:
|
||||
* <pre>
|
||||
* pad (uint16 BE) - always 0x0000
|
||||
* pad (uint8) - always 0x00
|
||||
* notifId (uint8) - notification slot identifier (see {@code NOTIF_*} constants)
|
||||
* status (uint8) - 0x00 = disabled, 0x01 = enabled
|
||||
* </pre>
|
||||
*
|
||||
* <p>Known notification slot IDs observed from HCI captures:
|
||||
* <ul>
|
||||
* <li>{@link #NOTIF_PPG_AFIB} (1) - AFib detection alert</li>
|
||||
* <li>{@link #NOTIF_ECG} (2) - ECG-related notification (slot seen as disabled)</li>
|
||||
* <li>{@link #NOTIF_UNKNOWN_3} (3) - Unknown (always seen as disabled)</li>
|
||||
* <li>{@link #NOTIF_HIGH_LOW_HR} (4) - High/low heart rate alert (enabled during ECG setup)</li>
|
||||
* <li>{@link #NOTIF_PPG_AFIB_NIGHT} (5) - Night-time AFib detection alert</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>The full 5-slot configuration must always be sent together when using
|
||||
* {@code CMD_LOCAL_NOTIFICATIONS_CONFIG_SET} (0x0990).
|
||||
*/
|
||||
public class LocalNotification extends WithingsStructure {
|
||||
|
||||
// ---- Notification slot identifiers ----
|
||||
|
||||
/** AFib detection alert. */
|
||||
public static final byte NOTIF_PPG_AFIB = 1;
|
||||
/** ECG notification slot. */
|
||||
public static final byte NOTIF_ECG = 2;
|
||||
/** Unknown notification slot (appears always disabled). */
|
||||
public static final byte NOTIF_UNKNOWN_3 = 3;
|
||||
/** High/low heart-rate alert. */
|
||||
public static final byte NOTIF_HIGH_LOW_HR = 4;
|
||||
/** Night-time AFib detection alert. */
|
||||
public static final byte NOTIF_PPG_AFIB_NIGHT = 5;
|
||||
|
||||
// ---- Status values ----
|
||||
|
||||
public static final byte STATUS_DISABLED = 0x00;
|
||||
public static final byte STATUS_ENABLED = 0x01;
|
||||
|
||||
// ---- Payload ----
|
||||
|
||||
private byte notifId;
|
||||
private byte status;
|
||||
|
||||
/** No-arg constructor required by {@link DataStructureFactory}. */
|
||||
public LocalNotification() {}
|
||||
|
||||
/**
|
||||
* @param notifId one of the {@code NOTIF_*} constants
|
||||
* @param status {@link #STATUS_ENABLED} or {@link #STATUS_DISABLED}
|
||||
*/
|
||||
public LocalNotification(byte notifId, byte status) {
|
||||
this.notifId = notifId;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public byte getNotifId() { return notifId; }
|
||||
public byte getStatus() { return status; }
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
// 4-byte TLV header + 5-byte payload
|
||||
return 9;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
buffer.putShort((short) 0x0000); // pad
|
||||
buffer.put((byte) 0x00); // pad
|
||||
buffer.put(notifId);
|
||||
buffer.put(status);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillFromRawDataAsBuffer(ByteBuffer buffer) {
|
||||
if (buffer.remaining() >= 5) {
|
||||
buffer.getShort(); // skip pad
|
||||
buffer.get(); // skip pad
|
||||
notifId = buffer.get();
|
||||
status = buffer.get();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.LOCAL_NOTIFICATION;
|
||||
}
|
||||
}
|
||||
+9
@@ -71,5 +71,14 @@ public class WithingsStructureType {
|
||||
/** Long-press crown shortcut action (type 0x09A1 = 2465). */
|
||||
public static final short SHORTCUT_ACTION = (short) 0x09A1; // 2465
|
||||
|
||||
/** One entry in the feature-tags-deprecated list (type 0x099C = 2460). */
|
||||
public static final short FEATURE_TAG_DEPRECATED = (short) 0x099C; // 2460
|
||||
|
||||
/** User-ID header preceding feature-tag lists in CMD_FEATURE_TAGS_SET_DEPRECATED_V2 (type 0x0145 = 325). */
|
||||
public static final short FEATURE_TAGS_USER_ID = 0x0145; // 325
|
||||
|
||||
/** Local (on-watch) notification slot configuration (type 0x09A8 = 2472). */
|
||||
public static final short LOCAL_NOTIFICATION = (short) 0x09A8; // 2472
|
||||
|
||||
private WithingsStructureType() {}
|
||||
}
|
||||
|
||||
+16
@@ -69,5 +69,21 @@ public final class WithingsMessageType {
|
||||
/** Set the long-press crown shortcut action (cmd 0x0989). */
|
||||
public static final short SET_SHORTCUT = (short) 0x0989; // 2441
|
||||
|
||||
/**
|
||||
* Set feature-tags-deprecated list (cmd 0x0987).
|
||||
* Enables/disables ECG, AFib detection and related health features on the watch.
|
||||
* The message body contains a {@code FeatureTagsUserId} header followed by one or more
|
||||
* {@code FeatureTagDeprecated} TLVs and a final {@code EndOfTransmission}.
|
||||
*/
|
||||
public static final short SET_FEATURE_TAGS_DEPRECATED = (short) 0x0987; // 2439
|
||||
|
||||
/**
|
||||
* Set local notification slot configuration (cmd 0x0990).
|
||||
* Configures which on-watch health alerts (AFib, high/low HR, etc.) are enabled.
|
||||
* The message body contains five {@code LocalNotification} TLVs (one per slot) and a final
|
||||
* {@code EndOfTransmission}.
|
||||
*/
|
||||
public static final short SET_LOCAL_NOTIFICATIONS = (short) 0x0990; // 2448
|
||||
|
||||
private WithingsMessageType() {}
|
||||
}
|
||||
|
||||
@@ -3753,6 +3753,12 @@
|
||||
<string name="withings_scanwatch_screen_breathe">Breathe</string>
|
||||
<string name="withings_scanwatch_screen_clock">Clock</string>
|
||||
|
||||
<!-- ECG and AFib health feature settings -->
|
||||
<string name="withings_scanwatch_pref_ecg_title">ECG Measurement</string>
|
||||
<string name="withings_scanwatch_pref_ecg_summary">Enable ECG measurement feature on the watch</string>
|
||||
<string name="withings_scanwatch_pref_afib_title">AFib Detection</string>
|
||||
<string name="withings_scanwatch_pref_afib_summary">Enable atrial fibrillation detection and night AFib monitoring</string>
|
||||
|
||||
<!-- Shortcut (long-press crown) settings -->
|
||||
<string name="withings_scanwatch_pref_shortcut_title">Long-press crown shortcut</string>
|
||||
<string name="withings_scanwatch_pref_shortcut_summary">Action launched by holding the crown button</string>
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
|
||||
<!-- Long-press crown shortcut -->
|
||||
<ListPreference
|
||||
android:icon="@drawable/ic_shortcut"
|
||||
android:defaultValue="0"
|
||||
android:entries="@array/pref_withings_scanwatch_shortcut_entries"
|
||||
android:entryValues="@array/pref_withings_scanwatch_shortcut_values"
|
||||
@@ -42,4 +43,22 @@
|
||||
android:summary="@string/withings_scanwatch_pref_shortcut_summary"
|
||||
android:title="@string/withings_scanwatch_pref_shortcut_title" />
|
||||
|
||||
<!-- ECG measurement feature -->
|
||||
<SwitchPreference
|
||||
android:icon="@drawable/ic_ecg"
|
||||
android:defaultValue="false"
|
||||
android:key="withings_scanwatch_ecg_enabled"
|
||||
android:persistent="true"
|
||||
android:summary="@string/withings_scanwatch_pref_ecg_summary"
|
||||
android:title="@string/withings_scanwatch_pref_ecg_title" />
|
||||
|
||||
<!-- AFib detection -->
|
||||
<SwitchPreference
|
||||
android:icon="@drawable/ic_heartrate"
|
||||
android:defaultValue="false"
|
||||
android:key="withings_scanwatch_afib_enabled"
|
||||
android:persistent="true"
|
||||
android:summary="@string/withings_scanwatch_pref_afib_summary"
|
||||
android:title="@string/withings_scanwatch_pref_afib_title" />
|
||||
|
||||
</androidx.preference.PreferenceScreen>
|
||||
|
||||
Reference in New Issue
Block a user