mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
docs(withings,scanwatch): Collected sync process
This commit is contained in:
+8
@@ -363,6 +363,10 @@ public abstract class WithingsBaseDeviceSupport extends AbstractBTLESingleDevice
|
||||
message.addDataStructure(new TypeVersion());
|
||||
addSimpleConversationToQueue(message, activitySampleHandler);
|
||||
|
||||
// Official-app-like stored-measure sync is split into two pieces:
|
||||
// 1) queue polling for signal types 0x0001/0x0004/0x0005, always starting from cursor 0
|
||||
// 2) a separate ECG discovery flow that enumerates stored ECG record keys before fetching
|
||||
// waveforms. Mixed 0x0004 pages can still surface ECG metadata, so both paths cooperate.
|
||||
message = new WithingsMessage(WithingsMessageType.GET_STORED_MEASURE_SIGNAL, ExpectedResponse.EOT);
|
||||
message.addDataStructure(new StoredSignalMeta(0x0001, 0));
|
||||
addSimpleConversationToQueue(message, new StoredMeasureSignalHandler(this, gbDevice, 0x0001));
|
||||
@@ -813,6 +817,8 @@ public abstract class WithingsBaseDeviceSupport extends AbstractBTLESingleDevice
|
||||
Message deleteMessage = new WithingsMessage(WithingsMessageType.DELETE_STORED_MEASURE_SIGNAL, ExpectedResponse.SIMPLE);
|
||||
// Official captures include a 0x0145 TLV before 0x0143, and successful multi-page
|
||||
// manual SpO2 syncs increment it across successive delete requests in the same session.
|
||||
// The cursor in 0x0143 is treated as an opaque delete token for the current head page,
|
||||
// not as a "next page" pointer.
|
||||
deleteMessage.addDataStructure(new FeatureTagsUserId(deleteRequestId));
|
||||
deleteMessage.addDataStructure(new StoredSignalMeta(signalType, signalFlags, cursor));
|
||||
addSimpleConversationFirst(deleteMessage, handler);
|
||||
@@ -833,6 +839,8 @@ public abstract class WithingsBaseDeviceSupport extends AbstractBTLESingleDevice
|
||||
final int cursor,
|
||||
final ResponseHandler handler) {
|
||||
Message getMessage = new WithingsMessage(WithingsMessageType.GET_STORED_MEASURE_SIGNAL, ExpectedResponse.EOT);
|
||||
// For stored-measure queues the official app repeatedly re-reads the queue head with
|
||||
// cursor 0. The returned StoredSignalMeta carries the delete key for the current head item.
|
||||
getMessage.addDataStructure(new StoredSignalMeta(signalType, signalFlags, cursor));
|
||||
addSimpleConversationFirst(getMessage, handler);
|
||||
}
|
||||
|
||||
+13
@@ -62,6 +62,16 @@ public class StoredMeasureSignalHandler implements ResponseHandler {
|
||||
private int lastDeletedSpo2 = -1;
|
||||
private int repeatedPageRetries;
|
||||
|
||||
/**
|
||||
* Handles the head-based stored-measure queues used for SpO2 and mixed stored signals.
|
||||
*
|
||||
* Based on official app HCI captures, these queues do not behave like classic pagination.
|
||||
* Gadgetbridge re-requests cursor 0, parses the returned head page, stores any SpO2 / HR
|
||||
* samples, and then deletes that exact page using the returned 0x0143 key. Some head pages
|
||||
* are mixed and contain both ECG metadata and SpO2 values, so this handler may hand off ECG
|
||||
* retrieval to {@link WithingsEcgHandler} before resuming deletion of the same queue head.
|
||||
*/
|
||||
|
||||
public StoredMeasureSignalHandler(final WithingsBaseDeviceSupport support, final GBDevice device, final int signalType) {
|
||||
this.support = support;
|
||||
this.device = device;
|
||||
@@ -221,6 +231,9 @@ public class StoredMeasureSignalHandler implements ResponseHandler {
|
||||
logger.info("Stored-measure page signalType={} cursor={} contains an already-seen ECG record; continuing with page deletion instead of re-fetching it again",
|
||||
signalType, currentCursor);
|
||||
} else {
|
||||
// Mixed signalType=0x0004 pages can contain an ECG record key plus SpO2 data.
|
||||
// Match the official app by fetching the ECG waveform first, then returning to
|
||||
// the same stored-measure queue head so the mixed page can still be deleted.
|
||||
logger.info("Stored-measure page signalType={} cursor={} contains ECG markers; scheduling ECG fetch before continuing this stored-measure loop",
|
||||
signalType, currentCursor);
|
||||
support.notifyEcgRecordDiscovered(ecgMetaToNotify, signalType);
|
||||
|
||||
+14
@@ -71,6 +71,16 @@ public class WithingsEcgHandler implements ResponseHandler {
|
||||
private EcgWaveformHandler activeWaveformHandler;
|
||||
private int repeatedSpo2FallbackRetries;
|
||||
|
||||
/**
|
||||
* Mirrors the official ECG sync split seen in HCI captures:
|
||||
* first discover stored ECG record keys via MEASURE_* responses, then fetch each waveform by
|
||||
* replaying the exact 0x0116 key returned by discovery.
|
||||
*
|
||||
* This discovery stage means ECG progress is theoretically knowable because the watch tells
|
||||
* us how many ECG record keys exist up front. The current implementation uses discovery for
|
||||
* fetching order and dedupe, but it does not yet surface a user-visible 0-100 progress value.
|
||||
*/
|
||||
|
||||
public WithingsEcgHandler(final WithingsBaseDeviceSupport support, final GBDevice device) {
|
||||
this.support = support;
|
||||
this.device = device;
|
||||
@@ -199,6 +209,8 @@ public class WithingsEcgHandler implements ResponseHandler {
|
||||
if (timestampMs > 0 && !seenRecordTimestamps.contains(timestampMs)) {
|
||||
seenRecordTimestamps.add(timestampMs);
|
||||
}
|
||||
// Discovery gives us the same record key the official app later replays in
|
||||
// GET_STORED_MEASURE_SIGNAL to fetch the full waveform.
|
||||
logger.info("Discovered Withings ECG record ts={} type={}", recordKey.getTimestampMs(), recordKey.getMeasurementType());
|
||||
waveformFetchQueued = true;
|
||||
activeWaveformHandler = new EcgWaveformHandler(recordKey, true, false, false, -1);
|
||||
@@ -379,6 +391,8 @@ public class WithingsEcgHandler implements ResponseHandler {
|
||||
logger.warn("Stopping repeated ECG fallback for ts={} after {} missing-delete-key retries; not advancing cursor because official app behavior appears head-page based",
|
||||
startTimestampMs, repeatedSpo2FallbackRetries - 1);
|
||||
} else {
|
||||
// Retry from cursor 0 because mixed stored-signal pages behave as a queue head,
|
||||
// not a pageable list. Advancing the cursor risks skipping a stubborn head page.
|
||||
support.queueGetStoredMeasureSignal(originatingSignalType, 0, new StoredMeasureSignalHandler(support, device, originatingSignalType));
|
||||
}
|
||||
} else if (discoveryPagesSeen < MAX_DISCOVERY_PAGES) {
|
||||
|
||||
Reference in New Issue
Block a user