1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-10-03 19:57:08 +02:00

makibes hr3.

implemented deleteDevice.
implemented heart rate history download.
cleaned up sample handling.
This commit is contained in:
Cre3per 2019-10-07 15:28:54 +02:00 committed by Andreas Shimokawa
parent c0a6566410
commit da1a72c6c6
3 changed files with 145 additions and 58 deletions

View File

@ -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;

View File

@ -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

View File

@ -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;
}
}