mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2025-01-12 10:55:49 +01:00
makibes hr3.
implemented deleteDevice. implemented heart rate history download. cleaned up sample handling.
This commit is contained in:
parent
c0a6566410
commit
da1a72c6c6
@ -72,7 +72,6 @@ public final class MakibesHR3Constants {
|
||||
public static final byte[] RPRT_SINGLE_BLOOD_OXYGEN = new byte[]{ (byte) 0x31, (byte) 0x11 };
|
||||
|
||||
|
||||
// This is also used with different parameters.
|
||||
// steps take up more bytes. I don't know which ones yet.
|
||||
// Only sent after we send CMD_51
|
||||
// 00 (maybe also used for steps)
|
||||
@ -86,7 +85,32 @@ public final class MakibesHR3Constants {
|
||||
// 00
|
||||
// 00
|
||||
// 00
|
||||
public static final byte RPRT_FITNESS = (byte) 0x51;
|
||||
public static final byte[] RPRT_FITNESS = new byte[]{ (byte) 0x51, 0x08 };
|
||||
|
||||
// year (+2000)
|
||||
// month
|
||||
// day
|
||||
// hour
|
||||
// minute
|
||||
// heart rate
|
||||
// heart rate
|
||||
public static final byte[] RPRT_HEART_RATE_SAMPLE = new byte[]{ (byte) 0x51, (byte) 0x11 };
|
||||
|
||||
// year
|
||||
// month
|
||||
// day
|
||||
// hour?
|
||||
// 00
|
||||
// 01
|
||||
// 39
|
||||
// 00
|
||||
// 00
|
||||
// 0d
|
||||
// 00
|
||||
// 00
|
||||
// 00
|
||||
// 00
|
||||
public static final byte[] RPRT_51_20 = new byte[]{ (byte) 0x51, (byte) 0x20 };
|
||||
|
||||
|
||||
// enable (00/01)
|
||||
@ -123,34 +147,35 @@ public final class MakibesHR3Constants {
|
||||
public static final byte CMD_FACTORY_RESET = (byte) 0x23;
|
||||
|
||||
|
||||
// enable (00/01)
|
||||
public static final byte[] CMD_SET_REAL_TIME_BLOOD_OXYGEN = new byte[]{ (byte) 0x31, (byte) 0x12 };
|
||||
|
||||
|
||||
// After disabling, the watch replies with RPRT_SINGLE_BLOOD_OXYGEN
|
||||
// enable (00/01)
|
||||
public static final byte[] CMD_SET_SINGLE_BLOOD_OXYGEN = new byte[]{ (byte) 0x31, (byte) 0x11 };
|
||||
|
||||
|
||||
// The times in here are probably 'from' and 'until'
|
||||
// 00
|
||||
// year (+2000)
|
||||
// month (not current! but close)
|
||||
// day (not current! but close)
|
||||
// month (not current! but close. Multiple of 5)
|
||||
// day (not current! but close. Multiple of 5)
|
||||
// 0b (A)
|
||||
// 00 (B)
|
||||
// year (+2000)
|
||||
// month (not current! but close)
|
||||
// day (not current! but close)
|
||||
// 0b (this is >= (A))
|
||||
// 19 (this is >= (B))
|
||||
// 0b (this is >= (A). Multiple of 5)
|
||||
// 19 (this is >= (B). Multiple of 5)
|
||||
public static final byte CMD_REQUEST_FITNESS = (byte) 0x51;
|
||||
|
||||
// I don't think the watch can do this, but it replies.
|
||||
// enable (00/01)
|
||||
public static final byte[] CMD_SET_REAL_TIME_BLOOD_OXYGEN = new byte[]{ (byte) 0x31, (byte) 0x12 };
|
||||
|
||||
|
||||
// When disabling, the watch replies with RPRT_SINGLE_BLOOD_OXYGEN
|
||||
// enable (00/01)
|
||||
public static final byte[] CMD_SET_SINGLE_BLOOD_OXYGEN = new byte[]{ (byte) 0x31, (byte) 0x11 };
|
||||
|
||||
|
||||
// this is the last command sent on sync
|
||||
// this looks like a request for the heart rate history
|
||||
// 00
|
||||
// year (+2000)
|
||||
// month (not current!)
|
||||
// 14 this isn't the current day
|
||||
// day (not current!)
|
||||
// hour (current)
|
||||
// minute (current)
|
||||
public static final byte CMD_52 = (byte) 0x52;
|
||||
|
@ -28,6 +28,7 @@ import androidx.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import de.greenrobot.dao.query.QueryBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
@ -37,6 +38,8 @@ import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.MakibesHR3ActivitySampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.No1F1ActivitySampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
@ -50,11 +53,9 @@ public class MakibesHR3Coordinator extends AbstractDeviceCoordinator {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MakibesHR3Coordinator.class);
|
||||
|
||||
public static byte getTimeMode(SharedPreferences sharedPrefs) {
|
||||
String tmode = sharedPrefs.getString(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT, getContext().getString(R.string.p_timeformat_24h));
|
||||
String timeMode = sharedPrefs.getString(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT, getContext().getString(R.string.p_timeformat_24h));
|
||||
|
||||
LOG.debug("tmode is " + tmode);
|
||||
|
||||
if (tmode.equals(getContext().getString(R.string.p_timeformat_24h))) {
|
||||
if (timeMode.equals(getContext().getString(R.string.p_timeformat_24h))) {
|
||||
return MakibesHR3Constants.ARG_SET_TIMEMODE_24H;
|
||||
} else {
|
||||
return MakibesHR3Constants.ARG_SET_TIMEMODE_12H;
|
||||
@ -82,7 +83,9 @@ public class MakibesHR3Coordinator extends AbstractDeviceCoordinator {
|
||||
|
||||
@Override
|
||||
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
|
||||
|
||||
Long deviceId = device.getId();
|
||||
QueryBuilder<?> qb = session.getMakibesHR3ActivitySampleDao().queryBuilder();
|
||||
qb.where(MakibesHR3ActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,8 +1,11 @@
|
||||
// TODO: WearFit sometimes resets today's step count when it's used after GB.
|
||||
// TODO: Where can I view today's steps in GB?
|
||||
// TODO: GB adds the step count to the week's total every time we broadcast a sample.
|
||||
// TODO: GB accumulates all step samples, even if they're part of the same day.
|
||||
|
||||
// TODO: Read activity history from device
|
||||
// TODO: Activity history download progress.
|
||||
// TODO: Remove downloaded history from the device.
|
||||
|
||||
// TODO: Request and handle step history from the device.
|
||||
|
||||
// TODO: All the commands that aren't supported by GB should be added to device specific settings.
|
||||
|
||||
@ -27,8 +30,11 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.sql.Date;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
@ -43,6 +49,8 @@ import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.MakibesHR3ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
|
||||
@ -485,7 +493,7 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport implement
|
||||
return builder;
|
||||
}
|
||||
|
||||
private void broadcastActivity(Integer heartRate, Integer steps) {
|
||||
private void addGBActivitySamples(MakibesHR3ActivitySample[] samples) {
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
|
||||
User user = DBHelper.getUser(dbHandler.getDaoSession());
|
||||
@ -493,45 +501,93 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport implement
|
||||
|
||||
MakibesHR3SampleProvider provider = new MakibesHR3SampleProvider(this.getDevice(), dbHandler.getDaoSession());
|
||||
|
||||
int timeStamp = (int) (System.currentTimeMillis() / 1000);
|
||||
for (MakibesHR3ActivitySample sample : samples) {
|
||||
sample.setDevice(device);
|
||||
sample.setUser(user);
|
||||
sample.setProvider(provider);
|
||||
|
||||
MakibesHR3ActivitySample sample = this.createActivitySample(device, user, timeStamp, provider);
|
||||
sample.setRawIntensity(ActivitySample.NOT_MEASURED);
|
||||
|
||||
if (heartRate != null) {
|
||||
sample.setHeartRate(heartRate);
|
||||
provider.addGBActivitySample(sample);
|
||||
}
|
||||
|
||||
if (steps != null) {
|
||||
sample.setSteps(steps);
|
||||
}
|
||||
|
||||
// I saw this somewhere else and it works.
|
||||
sample.setRawKind(-1);
|
||||
|
||||
provider.addGBActivitySample(sample);
|
||||
|
||||
// TODO: steps aren't real time.
|
||||
Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
|
||||
.putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample)
|
||||
.putExtra(DeviceService.EXTRA_TIMESTAMP, timeStamp);
|
||||
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
|
||||
|
||||
} catch (Exception ex) {
|
||||
GB.toast(getContext(), "Error saving steps data: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
|
||||
// Why is this a toast? The user doesn't care about the error.
|
||||
GB.toast(getContext(), "Error saving samples: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
|
||||
GB.updateTransferNotification(null, "Data transfer failed", false, 0, getContext());
|
||||
|
||||
LOG.error(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void addGBActivitySample(MakibesHR3ActivitySample sample) {
|
||||
this.addGBActivitySamples(new MakibesHR3ActivitySample[]{ sample });
|
||||
}
|
||||
|
||||
/**
|
||||
* Should only be called after the sample has been populated by
|
||||
* {@link MakibesHR3DeviceSupport#addGBActivitySample} or
|
||||
* {@link MakibesHR3DeviceSupport#addGBActivitySamples}
|
||||
* @param sample
|
||||
*/
|
||||
private void broadcastSample(MakibesHR3ActivitySample sample) {
|
||||
Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
|
||||
.putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample)
|
||||
.putExtra(DeviceService.EXTRA_TIMESTAMP, sample.getTimestamp());
|
||||
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
|
||||
}
|
||||
|
||||
private void onReceiveFitness(int steps) {
|
||||
LOG.info("steps: " + steps);
|
||||
|
||||
this.broadcastActivity(null, steps);
|
||||
MakibesHR3ActivitySample sample = new MakibesHR3ActivitySample();
|
||||
|
||||
sample.setSteps(steps);
|
||||
sample.setTimestamp((int) (System.currentTimeMillis() / 1000));
|
||||
|
||||
sample.setRawKind(ActivityKind.TYPE_ACTIVITY);
|
||||
this.addGBActivitySample(sample);
|
||||
}
|
||||
|
||||
private void onReceiveHeartRate(int heartRate) {
|
||||
LOG.info("heart rate: " + heartRate);
|
||||
|
||||
this.broadcastActivity(heartRate, null);
|
||||
MakibesHR3ActivitySample sample = new MakibesHR3ActivitySample();
|
||||
|
||||
if (heartRate > 0) {
|
||||
sample.setHeartRate(heartRate);
|
||||
sample.setTimestamp((int) (System.currentTimeMillis() / 1000));
|
||||
sample.setRawKind(ActivityKind.TYPE_ACTIVITY);
|
||||
} else {
|
||||
if (heartRate == MakibesHR3Constants.ARG_HEARTRATE_NO_TARGET) {
|
||||
sample.setRawKind(ActivityKind.TYPE_NOT_WORN);
|
||||
} else if (heartRate == MakibesHR3Constants.ARG_HEARTRATE_NO_READING) {
|
||||
sample.setRawKind(ActivityKind.TYPE_NOT_MEASURED);
|
||||
} else {
|
||||
LOG.warn("invalid heart rate reading: " + heartRate);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.addGBActivitySample(sample);
|
||||
this.broadcastSample(sample);
|
||||
}
|
||||
|
||||
private void onReceiveHeartRateSample(int year, int month, int day, int hour, int minute, int heartRate) {
|
||||
LOG.debug("received heart rate sample " + year + "-" + month + "-" + day + " " + hour + ":" + minute + " " + heartRate);
|
||||
|
||||
MakibesHR3ActivitySample sample = new MakibesHR3ActivitySample();
|
||||
|
||||
Calendar calendar = new GregorianCalendar(year, month - 1, day, hour, minute);
|
||||
|
||||
int timeStamp = (int) (calendar.getTimeInMillis() / 1000);
|
||||
|
||||
sample.setHeartRate(heartRate);
|
||||
sample.setTimestamp(timeStamp);
|
||||
|
||||
sample.setRawKind(ActivityKind.TYPE_ACTIVITY);
|
||||
|
||||
this.addGBActivitySample(sample);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -555,18 +611,9 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport implement
|
||||
arguments[i] = value[i + 6];
|
||||
}
|
||||
|
||||
byte report = value[4];
|
||||
byte[] report = new byte[]{ value[4], value[5] };
|
||||
|
||||
LOG.debug("report: " + Integer.toHexString((int) report));
|
||||
|
||||
switch (report) {
|
||||
case MakibesHR3Constants.RPRT_FITNESS:
|
||||
if (value.length == 17) {
|
||||
this.onReceiveFitness(
|
||||
(int) arguments[1] * 0xff + arguments[2]
|
||||
);
|
||||
}
|
||||
break;
|
||||
switch (report[0]) {
|
||||
case MakibesHR3Constants.RPRT_REVERSE_FIND_DEVICE:
|
||||
this.onReverseFindDevice(arguments[0] == 0x01);
|
||||
break;
|
||||
@ -590,6 +637,18 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport implement
|
||||
this.getDevice().setFirmwareVersion(((int) arguments[0]) + "." + ((int) arguments[1]));
|
||||
}
|
||||
break;
|
||||
default: // Non-80 reports
|
||||
if (Arrays.equals(report, MakibesHR3Constants.RPRT_FITNESS)) {
|
||||
this.onReceiveFitness(
|
||||
(int) arguments[1] * 0xff + arguments[2]
|
||||
);
|
||||
} else if (Arrays.equals(report, MakibesHR3Constants.RPRT_HEART_RATE_SAMPLE)) {
|
||||
this.onReceiveHeartRateSample(
|
||||
arguments[0] + 2000, arguments[1], arguments[2],
|
||||
arguments[3], arguments[4],
|
||||
arguments[5]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user