mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2025-01-10 17:11:56 +01:00
Bangle.js: Fetch activity data
This commit is contained in:
parent
f97250d46b
commit
a95820d09e
@ -43,7 +43,7 @@ public class GBDaoGenerator {
|
|||||||
|
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
final Schema schema = new Schema(50, MAIN_PACKAGE + ".entities");
|
final Schema schema = new Schema(51, MAIN_PACKAGE + ".entities");
|
||||||
|
|
||||||
Entity userAttributes = addUserAttributes(schema);
|
Entity userAttributes = addUserAttributes(schema);
|
||||||
Entity user = addUserInfo(schema, userAttributes);
|
Entity user = addUserInfo(schema, userAttributes);
|
||||||
@ -613,6 +613,7 @@ public class GBDaoGenerator {
|
|||||||
Entity activitySample = addEntity(schema, "BangleJSActivitySample");
|
Entity activitySample = addEntity(schema, "BangleJSActivitySample");
|
||||||
activitySample.implementsSerializable();
|
activitySample.implementsSerializable();
|
||||||
addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
|
addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
|
||||||
|
activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
||||||
activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
||||||
activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
||||||
addHeartRateProperties(activitySample);
|
addHeartRateProperties(activitySample);
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
/* Copyright (C) 2023 José Rebelo
|
||||||
|
|
||||||
|
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 <http://www.gnu.org/licenses/>. */
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge.database.schema;
|
||||||
|
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.BangleJSActivitySampleDao;
|
||||||
|
|
||||||
|
public class GadgetbridgeUpdate_51 implements DBUpdateScript {
|
||||||
|
@Override
|
||||||
|
public void upgradeSchema(final SQLiteDatabase db) {
|
||||||
|
if (!DBHelper.existsColumn(BangleJSActivitySampleDao.TABLENAME, BangleJSActivitySampleDao.Properties.RawIntensity.columnName, db)) {
|
||||||
|
final String sqlAddColumnRawIntensity = "ALTER TABLE " + BangleJSActivitySampleDao.TABLENAME + " ADD COLUMN "
|
||||||
|
+ BangleJSActivitySampleDao.Properties.RawIntensity.columnName + " INTEGER DEFAULT -1 NOT NULL";
|
||||||
|
db.execSQL(sqlAddColumnRawIntensity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void downgradeSchema(final SQLiteDatabase db) {
|
||||||
|
}
|
||||||
|
}
|
@ -25,4 +25,8 @@ public final class BangleJSConstants {
|
|||||||
public static final UUID UUID_CHARACTERISTIC_NORDIC_UART_TX = UUID.fromString("6e400002-b5a3-f393-e0a9-e50e24dcca9e");
|
public static final UUID UUID_CHARACTERISTIC_NORDIC_UART_TX = UUID.fromString("6e400002-b5a3-f393-e0a9-e50e24dcca9e");
|
||||||
public static final UUID UUID_CHARACTERISTIC_NORDIC_UART_RX = UUID.fromString("6e400003-b5a3-f393-e0a9-e50e24dcca9e");
|
public static final UUID UUID_CHARACTERISTIC_NORDIC_UART_RX = UUID.fromString("6e400003-b5a3-f393-e0a9-e50e24dcca9e");
|
||||||
|
|
||||||
|
public static final String PREF_BANGLEJS_ACTIVITY_FULL_SYNC_TRIGGER = "pref_banglejs_activity_full_sync_trigger";
|
||||||
|
public static final String PREF_BANGLEJS_ACTIVITY_FULL_SYNC_STATUS = "pref_banglejs_activity_full_sync_status";
|
||||||
|
public static final String PREF_BANGLEJS_ACTIVITY_FULL_SYNC_START = "pref_banglejs_activity_full_sync_start";
|
||||||
|
public static final String PREF_BANGLEJS_ACTIVITY_FULL_SYNC_STOP = "pref_banglejs_activity_full_sync_stop";
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,7 @@ import java.util.List;
|
|||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
|
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||||
@ -111,7 +112,7 @@ public class BangleJSCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsActivityDataFetching() {
|
public boolean supportsActivityDataFetching() {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -188,6 +189,7 @@ public class BangleJSCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int[] getSupportedDeviceSpecificSettings(final GBDevice device) {
|
public int[] getSupportedDeviceSpecificSettings(final GBDevice device) {
|
||||||
final List<Integer> settings = new ArrayList<>();
|
final List<Integer> settings = new ArrayList<>();
|
||||||
|
|
||||||
@ -205,6 +207,8 @@ public class BangleJSCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
if (BuildConfig.INTERNET_ACCESS)
|
if (BuildConfig.INTERNET_ACCESS)
|
||||||
settings.add(R.xml.devicesettings_device_internet_access);
|
settings.add(R.xml.devicesettings_device_internet_access);
|
||||||
|
|
||||||
|
settings.add(R.xml.devicesettings_banglejs_activity);
|
||||||
|
|
||||||
settings.add(R.xml.devicesettings_header_developer);
|
settings.add(R.xml.devicesettings_header_developer);
|
||||||
settings.add(R.xml.devicesettings_banglejs_apploader);
|
settings.add(R.xml.devicesettings_banglejs_apploader);
|
||||||
settings.add(R.xml.devicesettings_device_intents);
|
settings.add(R.xml.devicesettings_device_intents);
|
||||||
@ -212,6 +216,11 @@ public class BangleJSCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return ArrayUtils.toPrimitive(settings.toArray(new Integer[0]));
|
return ArrayUtils.toPrimitive(settings.toArray(new Integer[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
|
||||||
|
return new BangleJSSettingsCustomizer(device);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsNavigation() {
|
public boolean supportsNavigation() {
|
||||||
return true;
|
return true;
|
||||||
|
@ -19,18 +19,23 @@ package nodomain.freeyourgadget.gadgetbridge.devices.banglejs;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import de.greenrobot.dao.AbstractDao;
|
import de.greenrobot.dao.AbstractDao;
|
||||||
import de.greenrobot.dao.Property;
|
import de.greenrobot.dao.Property;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
|
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.BangleJSActivitySample;
|
import nodomain.freeyourgadget.gadgetbridge.entities.BangleJSActivitySample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.BangleJSActivitySampleDao;
|
import nodomain.freeyourgadget.gadgetbridge.entities.BangleJSActivitySampleDao;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.ID115ActivitySample;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.ID115ActivitySampleDao;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||||
|
|
||||||
public class BangleJSSampleProvider extends AbstractSampleProvider<BangleJSActivitySample> {
|
public class BangleJSSampleProvider extends AbstractSampleProvider<BangleJSActivitySample> {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(BangleJSSampleProvider.class);
|
||||||
|
|
||||||
public BangleJSSampleProvider(GBDevice device, DaoSession session) {
|
public BangleJSSampleProvider(GBDevice device, DaoSession session) {
|
||||||
super(device, session);
|
super(device, session);
|
||||||
}
|
}
|
||||||
@ -77,11 +82,54 @@ public class BangleJSSampleProvider extends AbstractSampleProvider<BangleJSActiv
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float normalizeIntensity(int rawIntensity) {
|
public float normalizeIntensity(int rawIntensity) {
|
||||||
return rawIntensity;
|
return rawIntensity / 256.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BangleJSActivitySample createActivitySample() {
|
public BangleJSActivitySample createActivitySample() {
|
||||||
return new BangleJSActivitySample();
|
return new BangleJSActivitySample();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upserts a sample in the database, avoiding duplicated samples if a sample already exists in a
|
||||||
|
* close timestamp (within 2 minutes);
|
||||||
|
*/
|
||||||
|
public void upsertSample(final BangleJSActivitySample sample) {
|
||||||
|
final List<BangleJSActivitySample> nearSamples = getGBActivitySamples(
|
||||||
|
sample.getTimestamp() - 60 * 2,
|
||||||
|
sample.getTimestamp() + 60 * 2,
|
||||||
|
normalizeType(sample.getRawKind())
|
||||||
|
);
|
||||||
|
|
||||||
|
if (nearSamples.isEmpty()) {
|
||||||
|
// No nearest sample, just insert
|
||||||
|
LOG.debug("No duplicate found at {}, inserting", sample.getTimestamp());
|
||||||
|
addGBActivitySample(sample);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BangleJSActivitySample nearestSample = nearSamples.get(0);
|
||||||
|
|
||||||
|
for (final BangleJSActivitySample s : nearSamples) {
|
||||||
|
final int curDist = Math.abs(nearestSample.getTimestamp() - s.getTimestamp());
|
||||||
|
final int newDist = Math.abs(sample.getTimestamp() - s.getTimestamp());
|
||||||
|
if (newDist < curDist) {
|
||||||
|
nearestSample = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug("Found {} duplicates for {}, updating nearest sample at {}", nearSamples.size(), sample.getTimestamp(), nearestSample.getTimestamp());
|
||||||
|
|
||||||
|
if (sample.getHeartRate() != 0) {
|
||||||
|
nearestSample.setHeartRate(sample.getHeartRate());
|
||||||
|
}
|
||||||
|
if (sample.getSteps() != 0) {
|
||||||
|
nearestSample.setSteps(sample.getSteps());
|
||||||
|
}
|
||||||
|
if (sample.getRawIntensity() != 0) {
|
||||||
|
nearestSample.setRawIntensity(sample.getRawIntensity());
|
||||||
|
}
|
||||||
|
|
||||||
|
addGBActivitySample(nearestSample);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,141 @@
|
|||||||
|
/* Copyright (C) 2023 José Rebelo
|
||||||
|
|
||||||
|
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 <http://www.gnu.org/licenses/>. */
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge.devices.banglejs;
|
||||||
|
|
||||||
|
import static nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSConstants.PREF_BANGLEJS_ACTIVITY_FULL_SYNC_START;
|
||||||
|
import static nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSConstants.PREF_BANGLEJS_ACTIVITY_FULL_SYNC_STATUS;
|
||||||
|
import static nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSConstants.PREF_BANGLEJS_ACTIVITY_FULL_SYNC_STOP;
|
||||||
|
import static nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSConstants.PREF_BANGLEJS_ACTIVITY_FULL_SYNC_TRIGGER;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.ProgressDialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.os.Parcel;
|
||||||
|
|
||||||
|
import androidx.preference.EditTextPreference;
|
||||||
|
import androidx.preference.Preference;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||||
|
|
||||||
|
public class BangleJSSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
|
||||||
|
private ProgressDialog activityFullSyncDialog;
|
||||||
|
|
||||||
|
final GBDevice device;
|
||||||
|
|
||||||
|
public BangleJSSettingsCustomizer(final GBDevice device) {
|
||||||
|
this.device = device;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) {
|
||||||
|
// Handle full sync status
|
||||||
|
if (preference.getKey().equals(PREF_BANGLEJS_ACTIVITY_FULL_SYNC_STATUS)) {
|
||||||
|
final EditTextPreference fullSyncStatusPreference = (EditTextPreference) preference;
|
||||||
|
final String statusValue = fullSyncStatusPreference.getText();
|
||||||
|
|
||||||
|
if (activityFullSyncDialog != null) {
|
||||||
|
switch (statusValue) {
|
||||||
|
case "start":
|
||||||
|
activityFullSyncDialog.setMessage(handler.getContext().getString(R.string.busy_task_fetch_activity_data));
|
||||||
|
break;
|
||||||
|
case "end":
|
||||||
|
activityFullSyncDialog.dismiss();
|
||||||
|
activityFullSyncDialog = null;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
activityFullSyncDialog.setMessage(statusValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs) {
|
||||||
|
final Preference fullSyncPref = handler.findPreference(PREF_BANGLEJS_ACTIVITY_FULL_SYNC_TRIGGER);
|
||||||
|
if (fullSyncPref != null) {
|
||||||
|
fullSyncPref.setOnPreferenceClickListener(preference -> {
|
||||||
|
if (activityFullSyncDialog != null) {
|
||||||
|
// Already syncing
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Context context = preference.getContext();
|
||||||
|
|
||||||
|
new AlertDialog.Builder(context)
|
||||||
|
.setTitle(R.string.pref_activity_full_sync_trigger_title)
|
||||||
|
.setMessage(R.string.pref_activity_full_sync_trigger_warning)
|
||||||
|
.setIcon(R.drawable.ic_refresh)
|
||||||
|
.setPositiveButton(R.string.start, (dialog, whichButton) -> {
|
||||||
|
handler.notifyPreferenceChanged(PREF_BANGLEJS_ACTIVITY_FULL_SYNC_START);
|
||||||
|
|
||||||
|
activityFullSyncDialog = new ProgressDialog(context);
|
||||||
|
activityFullSyncDialog.setCancelable(false);
|
||||||
|
activityFullSyncDialog.setMessage(context.getString(R.string.sony_anc_optimizer_status_starting));
|
||||||
|
activityFullSyncDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||||
|
activityFullSyncDialog.setProgress(0);
|
||||||
|
activityFullSyncDialog.setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(R.string.Cancel), (dialog1, which) -> {
|
||||||
|
dialog1.dismiss();
|
||||||
|
activityFullSyncDialog = null;
|
||||||
|
handler.notifyPreferenceChanged(PREF_BANGLEJS_ACTIVITY_FULL_SYNC_STOP);
|
||||||
|
});
|
||||||
|
|
||||||
|
activityFullSyncDialog.show();
|
||||||
|
})
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.show();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getPreferenceKeysWithSummary() {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(final Parcel dest, final int flags) {
|
||||||
|
dest.writeParcelable(device, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<BangleJSSettingsCustomizer> CREATOR = new Creator<BangleJSSettingsCustomizer>() {
|
||||||
|
@Override
|
||||||
|
public BangleJSSettingsCustomizer createFromParcel(final Parcel in) {
|
||||||
|
final GBDevice device = in.readParcelable(BangleJSSettingsCustomizer.class.getClassLoader());
|
||||||
|
return new BangleJSSettingsCustomizer(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BangleJSSettingsCustomizer[] newArray(final int size) {
|
||||||
|
return new BangleJSSettingsCustomizer[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -4,7 +4,6 @@ import androidx.annotation.NonNull;
|
|||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
|
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesSettingsCustomizer;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
|
import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
|
||||||
|
@ -25,6 +25,8 @@ import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.Dev
|
|||||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DEVICE_INTENTS;
|
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DEVICE_INTENTS;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DEVICE_INTERNET_ACCESS;
|
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DEVICE_INTERNET_ACCESS;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.database.DBHelper.getUser;
|
import static nodomain.freeyourgadget.gadgetbridge.database.DBHelper.getUser;
|
||||||
|
import static nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSConstants.PREF_BANGLEJS_ACTIVITY_FULL_SYNC_START;
|
||||||
|
import static nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSConstants.PREF_BANGLEJS_ACTIVITY_FULL_SYNC_STATUS;
|
||||||
|
|
||||||
import android.bluetooth.BluetoothGatt;
|
import android.bluetooth.BluetoothGatt;
|
||||||
import android.bluetooth.BluetoothGattCharacteristic;
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
@ -32,6 +34,7 @@ import android.content.BroadcastReceiver;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
@ -72,6 +75,7 @@ import java.text.SimpleDateFormat;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -89,6 +93,7 @@ import io.wax911.emojify.EmojiManager;
|
|||||||
import io.wax911.emojify.EmojiUtils;
|
import io.wax911.emojify.EmojiUtils;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
|
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
|
||||||
@ -96,6 +101,7 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallContro
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl;
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSConstants;
|
import nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSConstants;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSSampleProvider;
|
import nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSSampleProvider;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.BangleJSActivitySample;
|
import nodomain.freeyourgadget.gadgetbridge.entities.BangleJSActivitySample;
|
||||||
@ -105,6 +111,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationManager;
|
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationManager;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.LocationProviderType;
|
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.LocationProviderType;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
|
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||||
@ -117,6 +124,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
|
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEQueue;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEQueue;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.EmojiConverter;
|
import nodomain.freeyourgadget.gadgetbridge.util.EmojiConverter;
|
||||||
@ -328,27 +336,6 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
getDevice().setFirmwareVersion2("N/A");
|
getDevice().setFirmwareVersion2("N/A");
|
||||||
lastBatteryPercent = -1;
|
lastBatteryPercent = -1;
|
||||||
|
|
||||||
/* Here we get the last Activity info saved from Bangle.js, and then send
|
|
||||||
its timestamp. Bangle.js can then look back at its history and can try and
|
|
||||||
send any missing data. */
|
|
||||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
|
||||||
BangleJSSampleProvider provider = new BangleJSSampleProvider(getDevice(), dbHandler.getDaoSession());
|
|
||||||
BangleJSActivitySample sample = provider.getLatestActivitySample();
|
|
||||||
if (sample!=null) {
|
|
||||||
LOG.info("Send 'actlast' with last activity's timestamp: "+sample.getTimestamp());
|
|
||||||
try {
|
|
||||||
JSONObject o = new JSONObject();
|
|
||||||
o.put("t", "actlast");
|
|
||||||
o.put("time", sample.getTimestamp());
|
|
||||||
uartTxJSON("actlast", o);
|
|
||||||
} catch (JSONException e) {
|
|
||||||
LOG.info("JSONException: " + e.getLocalizedMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
LOG.warn("Error getting last activity: " + ex.getLocalizedMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG.info("Initialization Done");
|
LOG.info("Initialization Done");
|
||||||
|
|
||||||
requestBangleGPSPowerStatus();
|
requestBangleGPSPowerStatus();
|
||||||
@ -547,6 +534,9 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
case "notify" :
|
case "notify" :
|
||||||
handleNotificationControl(json);
|
handleNotificationControl(json);
|
||||||
break;
|
break;
|
||||||
|
case "actfetch":
|
||||||
|
handleActivityFetch(json);
|
||||||
|
break;
|
||||||
case "act":
|
case "act":
|
||||||
handleActivity(json);
|
handleActivity(json);
|
||||||
break;
|
break;
|
||||||
@ -626,17 +616,36 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
evaluateGBDeviceEvent(deviceEvtNotificationControl);
|
evaluateGBDeviceEvent(deviceEvtNotificationControl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleActivityFetch(final JSONObject json) throws JSONException {
|
||||||
|
final String state = json.getString("state");
|
||||||
|
if ("start".equals(state)) {
|
||||||
|
GB.updateTransferNotification(getContext().getString(R.string.busy_task_fetch_activity_data),"", true, 0, getContext());
|
||||||
|
getDevice().setBusyTask(getContext().getString(R.string.busy_task_fetch_activity_data));
|
||||||
|
} else if ("end".equals(state)) {
|
||||||
|
saveLastSyncTimestamp(System.currentTimeMillis() - 1000L * 60);
|
||||||
|
getDevice().unsetBusyTask();
|
||||||
|
GB.updateTransferNotification(null, "", false, 100, getContext());
|
||||||
|
} else {
|
||||||
|
LOG.warn("Unknown actfetch state {}", state);
|
||||||
|
}
|
||||||
|
|
||||||
|
final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
|
||||||
|
.withPreference(PREF_BANGLEJS_ACTIVITY_FULL_SYNC_STATUS, state);
|
||||||
|
evaluateGBDeviceEvent(event);
|
||||||
|
|
||||||
|
getDevice().sendDeviceUpdateIntent(getContext());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle "act" packet, used to send activity reports
|
* Handle "act" packet, used to send activity reports
|
||||||
*/
|
*/
|
||||||
private void handleActivity(JSONObject json) throws JSONException {
|
private void handleActivity(JSONObject json) throws JSONException {
|
||||||
BangleJSActivitySample sample = new BangleJSActivitySample();
|
BangleJSActivitySample sample = new BangleJSActivitySample();
|
||||||
sample.setTimestamp((int) (System.currentTimeMillis() / 1000L));
|
int timestamp = (int) (json.optLong("ts", System.currentTimeMillis()) / 1000);
|
||||||
int hrm = 0;
|
int hrm = json.optInt("hrm", 0);
|
||||||
int steps = 0;
|
int steps = json.optInt("stp", 0);
|
||||||
if (json.has("time")) sample.setTimestamp(json.getInt("time"));
|
int intensity = json.optInt("mov", ActivitySample.NOT_MEASURED);
|
||||||
if (json.has("hrm")) hrm = json.getInt("hrm");
|
boolean realtime = json.optInt("rt", 0) == 1;
|
||||||
if (json.has("stp")) steps = json.getInt("stp");
|
|
||||||
int activity = BangleJSSampleProvider.TYPE_ACTIVITY;
|
int activity = BangleJSSampleProvider.TYPE_ACTIVITY;
|
||||||
/*if (json.has("act")) {
|
/*if (json.has("act")) {
|
||||||
String actName = "TYPE_" + json.getString("act").toUpperCase();
|
String actName = "TYPE_" + json.getString("act").toUpperCase();
|
||||||
@ -651,21 +660,26 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
LOG.info("JSON activity '"+actName+"' not found");
|
LOG.info("JSON activity '"+actName+"' not found");
|
||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
|
sample.setTimestamp(timestamp);
|
||||||
sample.setRawKind(activity);
|
sample.setRawKind(activity);
|
||||||
sample.setHeartRate(hrm);
|
sample.setHeartRate(hrm);
|
||||||
sample.setSteps(steps);
|
sample.setSteps(steps);
|
||||||
|
sample.setRawIntensity(intensity);
|
||||||
|
if (!realtime) {
|
||||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||||
Long userId = getUser(dbHandler.getDaoSession()).getId();
|
final Long userId = getUser(dbHandler.getDaoSession()).getId();
|
||||||
Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
|
final Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
|
||||||
BangleJSSampleProvider provider = new BangleJSSampleProvider(getDevice(), dbHandler.getDaoSession());
|
BangleJSSampleProvider provider = new BangleJSSampleProvider(getDevice(), dbHandler.getDaoSession());
|
||||||
sample.setDeviceId(deviceId);
|
sample.setDeviceId(deviceId);
|
||||||
sample.setUserId(userId);
|
sample.setUserId(userId);
|
||||||
provider.addGBActivitySample(sample);
|
provider.upsertSample(sample);
|
||||||
} catch (Exception ex) {
|
} catch (final Exception ex) {
|
||||||
LOG.warn("Error saving activity: " + ex.getLocalizedMessage());
|
LOG.warn("Error saving activity: " + ex.getLocalizedMessage());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// push realtime data
|
// push realtime data
|
||||||
if (realtimeHRM || realtimeStep) {
|
if (realtime && (realtimeHRM || realtimeStep)) {
|
||||||
Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
|
Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
|
||||||
.putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample);
|
.putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample);
|
||||||
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
|
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
|
||||||
@ -942,6 +956,17 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
return intent;
|
return intent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSendConfiguration(final String config) {
|
||||||
|
switch (config) {
|
||||||
|
case PREF_BANGLEJS_ACTIVITY_FULL_SYNC_START:
|
||||||
|
fetchActivityData(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.warn("Unknown config changed: {}", config);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCharacteristicChanged(BluetoothGatt gatt,
|
public boolean onCharacteristicChanged(BluetoothGatt gatt,
|
||||||
BluetoothGattCharacteristic characteristic) {
|
BluetoothGattCharacteristic characteristic) {
|
||||||
@ -1306,7 +1331,11 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFetchRecordedData(int dataTypes) {
|
public void onFetchRecordedData(int dataTypes) {
|
||||||
if (dataTypes == RecordedDataTypes.TYPE_DEBUGLOGS) {
|
if ((dataTypes & RecordedDataTypes.TYPE_ACTIVITY) != 0) {
|
||||||
|
fetchActivityData(getLastSuccessfulSyncTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((dataTypes & RecordedDataTypes.TYPE_DEBUGLOGS) != 0) {
|
||||||
File dir;
|
File dir;
|
||||||
try {
|
try {
|
||||||
dir = FileUtils.getExternalFilesDir();
|
dir = FileUtils.getExternalFilesDir();
|
||||||
@ -1329,6 +1358,37 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void fetchActivityData(final long timestampMillis) {
|
||||||
|
try {
|
||||||
|
JSONObject o = new JSONObject();
|
||||||
|
o.put("t", "actfetch");
|
||||||
|
o.put("ts", timestampMillis);
|
||||||
|
uartTxJSON("fetch activity data", o);
|
||||||
|
} catch (final JSONException e) {
|
||||||
|
LOG.warn("Failed to fetch activity data", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getLastSyncTimeKey() {
|
||||||
|
return "lastSyncTimeMillis";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void saveLastSyncTimestamp(final long timestamp) {
|
||||||
|
final SharedPreferences.Editor editor = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()).edit();
|
||||||
|
editor.putLong(getLastSyncTimeKey(), timestamp);
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected long getLastSuccessfulSyncTime() {
|
||||||
|
long timeStampMillis = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()).getLong(getLastSyncTimeKey(), 0);
|
||||||
|
if (timeStampMillis != 0) {
|
||||||
|
return timeStampMillis;
|
||||||
|
}
|
||||||
|
final GregorianCalendar calendar = BLETypeConversions.createCalendar();
|
||||||
|
calendar.add(Calendar.DAY_OF_MONTH, -1);
|
||||||
|
return calendar.getTimeInMillis();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
|
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
|
||||||
if (enable == realtimeHRM) return;
|
if (enable == realtimeHRM) return;
|
||||||
|
@ -2222,4 +2222,7 @@
|
|||||||
<string name="withings_bt_calibration_next">Next</string>
|
<string name="withings_bt_calibration_next">Next</string>
|
||||||
<string name="drag_handle">drag handle</string>
|
<string name="drag_handle">drag handle</string>
|
||||||
<string name="find_my_phone_found_it">FOUND IT</string>
|
<string name="find_my_phone_found_it">FOUND IT</string>
|
||||||
|
<string name="pref_activity_full_sync_trigger_summary">Trigger a full sync of all activity data</string>
|
||||||
|
<string name="pref_activity_full_sync_trigger_title">Full sync</string>
|
||||||
|
<string name="pref_activity_full_sync_trigger_warning">This will trigger a full sync of all activity data from the device. It may take a few minutes to complete.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
23
app/src/main/res/xml/devicesettings_banglejs_activity.xml
Normal file
23
app/src/main/res/xml/devicesettings_banglejs_activity.xml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<PreferenceCategory
|
||||||
|
android:key="pref_key_header_banglejs_activity"
|
||||||
|
android:title="@string/Activity">
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:icon="@drawable/ic_refresh"
|
||||||
|
android:key="pref_banglejs_activity_full_sync_trigger"
|
||||||
|
android:summary="@string/pref_activity_full_sync_trigger_summary"
|
||||||
|
android:title="@string/pref_activity_full_sync_trigger_title" />
|
||||||
|
|
||||||
|
<!-- dummy preference, just to trigger customizer notifications -->
|
||||||
|
<EditTextPreference
|
||||||
|
android:defaultValue="NOT_RUNNING"
|
||||||
|
android:enabled="false"
|
||||||
|
android:key="pref_banglejs_activity_full_sync_status"
|
||||||
|
android:shouldDisableView="false"
|
||||||
|
android:title="pref_banglejs_activity_full_sync_status"
|
||||||
|
app:isPreferenceVisible="false" />
|
||||||
|
</PreferenceCategory>
|
||||||
|
</androidx.preference.PreferenceScreen>
|
Loading…
Reference in New Issue
Block a user