mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-12-29 12:05:53 +01:00
makibes hr3.
fixed long notifications a a nullptrexception. added vibration timeout. added step history download.
This commit is contained in:
parent
da1a72c6c6
commit
cd3558cd50
@ -96,21 +96,23 @@ public final class MakibesHR3Constants {
|
||||
// heart rate
|
||||
public static final byte[] RPRT_HEART_RATE_SAMPLE = new byte[]{ (byte) 0x51, (byte) 0x11 };
|
||||
|
||||
// year
|
||||
// WearFit says "walking" in the step details. This is probably also in here, but
|
||||
// I don't run :O
|
||||
// year (+2000)
|
||||
// month
|
||||
// day
|
||||
// hour?
|
||||
// 00
|
||||
// 01
|
||||
// 39
|
||||
// hour (start of measurement. interval is 1h. Might be longer when running.)
|
||||
// 00 (either used for steps or minute)
|
||||
// accumulated steps (hi)
|
||||
// accumulated steps (lo)
|
||||
// 00
|
||||
// 00
|
||||
// 0d
|
||||
// ?? (changes whenever steps change. Ranges from 00 to 16.)
|
||||
// 00
|
||||
// 00
|
||||
// 00
|
||||
// 00
|
||||
public static final byte[] RPRT_51_20 = new byte[]{ (byte) 0x51, (byte) 0x20 };
|
||||
public static final byte[] RPRT_STEPS_SAMPLE = new byte[]{ (byte) 0x51, (byte) 0x20 };
|
||||
|
||||
|
||||
// enable (00/01)
|
||||
@ -155,23 +157,27 @@ public final class MakibesHR3Constants {
|
||||
// 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'
|
||||
// device replies with
|
||||
// {@link MakibesHR3Constants#RPRT_HEART_RATE_SAMPLE}
|
||||
// {@link MakibesHR3Constants#RPRT_STEPS_SAMPLE} (Only if steps are non-zero)
|
||||
// {@link MakibesHR3Constants#RPRT_FITNESS}
|
||||
// there are also multiple 6 * 00 reports
|
||||
// 00
|
||||
// year (+2000)
|
||||
// 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). Multiple of 5)
|
||||
// 19 (this is >= (B). Multiple of 5)
|
||||
// year (+2000) steps after
|
||||
// month steps after
|
||||
// day steps after
|
||||
// hour steps after
|
||||
// minute steps after
|
||||
// year (+2000) heart rate after
|
||||
// month heart rate after
|
||||
// day heart rate after
|
||||
// hour heart rate after
|
||||
// minute heart rate after
|
||||
public static final byte CMD_REQUEST_FITNESS = (byte) 0x51;
|
||||
|
||||
|
||||
// this looks like a request for the heart rate history
|
||||
// Manually sending this doesn't yield a reply. The heart rate history is sent in response to
|
||||
// CMD_CMD_REQUEST_FITNESS.
|
||||
// 00
|
||||
// year (+2000)
|
||||
// month (not current!)
|
||||
@ -181,6 +187,7 @@ public final class MakibesHR3Constants {
|
||||
public static final byte CMD_52 = (byte) 0x52;
|
||||
|
||||
|
||||
// vibrates 6 times
|
||||
public static final byte CMD_FIND_DEVICE = (byte) 0x71;
|
||||
|
||||
|
||||
|
@ -1,11 +1,10 @@
|
||||
// TODO: WearFit sometimes resets today's step count when it's used after GB.
|
||||
// TODO: GB sometimes fails to connect until a connection with WearFit was made. This must be caused
|
||||
// TODO: by GB, not by makibes hr3 support.
|
||||
|
||||
// TODO: Where can I view today's steps in GB?
|
||||
// TODO: GB accumulates all step samples, even if they're part of the same day.
|
||||
|
||||
// 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.
|
||||
|
||||
@ -21,6 +20,7 @@ import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.widget.Toast;
|
||||
@ -30,7 +30,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.sql.Date;
|
||||
import java.util.Date;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
@ -73,6 +73,7 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport implement
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MakibesHR3DeviceSupport.class);
|
||||
|
||||
private Handler mVibrationHandler = new Handler();
|
||||
private Vibrator mVibrator;
|
||||
|
||||
private BluetoothGattCharacteristic mControlCharacteristic = null;
|
||||
@ -136,8 +137,16 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport implement
|
||||
break;
|
||||
}
|
||||
|
||||
String message = "";
|
||||
|
||||
if (notificationSpec.title != null) {
|
||||
message += (notificationSpec.title + ": ");
|
||||
}
|
||||
|
||||
message += notificationSpec.body;
|
||||
|
||||
this.sendNotification(transactionBuilder,
|
||||
sender, notificationSpec.title + ": " + notificationSpec.body);
|
||||
sender, message);
|
||||
|
||||
try {
|
||||
this.performConnected(transactionBuilder.getTransaction());
|
||||
@ -224,7 +233,7 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport implement
|
||||
if (callSpec.command == CallSpec.CALL_INCOMING) {
|
||||
this.sendNotification(transactionBuilder, MakibesHR3Constants.ARG_SEND_NOTIFICATION_SOURCE_CALL, callSpec.name);
|
||||
} else {
|
||||
this.sendNotification(transactionBuilder, MakibesHR3Constants.ARG_SEND_NOTIFICATION_SOURCE_STOP_CALL, callSpec.name);
|
||||
this.sendNotification(transactionBuilder, MakibesHR3Constants.ARG_SEND_NOTIFICATION_SOURCE_STOP_CALL, "");
|
||||
}
|
||||
|
||||
try {
|
||||
@ -325,21 +334,35 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport implement
|
||||
}
|
||||
|
||||
private void onReverseFindDevice(boolean start) {
|
||||
final long[] PATTERN = new long[]{
|
||||
100, 100,
|
||||
100, 100,
|
||||
100, 100,
|
||||
500
|
||||
};
|
||||
if (this.mVibrator.hasVibrator()) {
|
||||
final long[] PATTERN = new long[]{
|
||||
100, 100,
|
||||
100, 100,
|
||||
100, 100,
|
||||
500
|
||||
};
|
||||
|
||||
if (start) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
this.mVibrator.vibrate(VibrationEffect.createWaveform(PATTERN, 0));
|
||||
} else {
|
||||
this.mVibrator.vibrate(PATTERN, 0);
|
||||
}
|
||||
|
||||
// In case the connection is closed while we're searching for the device.
|
||||
|
||||
this.mVibrationHandler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mVibrator.cancel();
|
||||
}
|
||||
}, 1100 * 6);
|
||||
|
||||
if (start) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
this.mVibrator.vibrate(VibrationEffect.createWaveform(PATTERN, 0));
|
||||
} else {
|
||||
this.mVibrator.vibrate(PATTERN, 0);
|
||||
this.mVibrator.cancel();
|
||||
}
|
||||
} else {
|
||||
this.mVibrator.cancel();
|
||||
// TODO: Alternative handling. Don't use sound, the connection isn't secure.
|
||||
}
|
||||
}
|
||||
|
||||
@ -477,8 +500,7 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport implement
|
||||
// Initialize device
|
||||
sendUserInfo(builder); //Sync preferences
|
||||
|
||||
// TODO: arguments
|
||||
this.requestFitness(builder, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0);
|
||||
this.requestFitness(builder);
|
||||
|
||||
gbDevice.setState(GBDevice.State.INITIALIZED);
|
||||
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||
@ -521,20 +543,21 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport implement
|
||||
}
|
||||
|
||||
private void addGBActivitySample(MakibesHR3ActivitySample sample) {
|
||||
this.addGBActivitySamples(new 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);
|
||||
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
|
||||
}
|
||||
|
||||
private void onReceiveFitness(int steps) {
|
||||
@ -590,6 +613,26 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport implement
|
||||
this.addGBActivitySample(sample);
|
||||
}
|
||||
|
||||
/**
|
||||
* The time is the start of the measurement. Each measurement lasts 1h.
|
||||
*/
|
||||
private void onReceiveStepsSample(int year, int month, int day, int hour, int minute, int steps) {
|
||||
LOG.debug("received steps sample " + year + "-" + month + "-" + day + " " + hour + ":" + minute + " " + steps);
|
||||
|
||||
MakibesHR3ActivitySample sample = new MakibesHR3ActivitySample();
|
||||
|
||||
Calendar calendar = new GregorianCalendar(year, month - 1, day, hour + 1, minute);
|
||||
|
||||
int timeStamp = (int) (calendar.getTimeInMillis() / 1000);
|
||||
|
||||
sample.setSteps(steps);
|
||||
sample.setTimestamp(timeStamp);
|
||||
|
||||
sample.setRawKind(ActivityKind.TYPE_ACTIVITY);
|
||||
|
||||
this.addGBActivitySample(sample);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCharacteristicChanged(BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic) {
|
||||
@ -611,7 +654,7 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport implement
|
||||
arguments[i] = value[i + 6];
|
||||
}
|
||||
|
||||
byte[] report = new byte[]{ value[4], value[5] };
|
||||
byte[] report = new byte[]{value[4], value[5]};
|
||||
|
||||
switch (report[0]) {
|
||||
case MakibesHR3Constants.RPRT_REVERSE_FIND_DEVICE:
|
||||
@ -639,14 +682,19 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport implement
|
||||
break;
|
||||
default: // Non-80 reports
|
||||
if (Arrays.equals(report, MakibesHR3Constants.RPRT_FITNESS)) {
|
||||
this.onReceiveFitness(
|
||||
(int) arguments[1] * 0xff + arguments[2]
|
||||
);
|
||||
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]);
|
||||
} else if (Arrays.equals(report, MakibesHR3Constants.RPRT_STEPS_SAMPLE)) {
|
||||
this.onReceiveStepsSample(
|
||||
arguments[0] + 2000, arguments[1], arguments[2],
|
||||
arguments[3], 0,
|
||||
(arguments[5] * 0xff) + arguments[6]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -684,7 +732,11 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport implement
|
||||
final int maxMessageLength = 20;
|
||||
|
||||
// For every split, we need 1 byte extra.
|
||||
int extraBytes = (((data.length - maxMessageLength) / maxMessageLength) + 1);
|
||||
int extraBytes = 0;
|
||||
|
||||
if (data.length > 20) {
|
||||
extraBytes = (((data.length - maxMessageLength) / maxMessageLength) + 1);
|
||||
}
|
||||
|
||||
int totalDataLength = (data.length + extraBytes);
|
||||
|
||||
@ -726,24 +778,32 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport implement
|
||||
return this.reboot(transaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ugly because I don't like Date.
|
||||
* All non-zero records after the given times will be returned via
|
||||
* {@link MakibesHR3Constants#RPRT_HEART_RATE_SAMPLE},
|
||||
* {@link MakibesHR3Constants#RPRT_STEPS_SAMPLE},
|
||||
* {@link MakibesHR3Constants#RPRT_FITNESS}
|
||||
*/
|
||||
private MakibesHR3DeviceSupport requestFitness(TransactionBuilder transaction,
|
||||
int yearStart, int monthStart, int dayStart,
|
||||
int a4, int a5,
|
||||
int yearEnd, int monthEnd, int dayEnd,
|
||||
int a9, int a10) {
|
||||
int yearStepsAfter, int monthStepsAfter, int dayStepsAfter,
|
||||
int hourStepsAfter, int minuteStepsAfter,
|
||||
int yearHeartRateAfter, int monthHeartRateAfter, int dayHeartRateAfter,
|
||||
int hourHeartRateAfter, int minuteHeartRateAfter) {
|
||||
|
||||
byte[] data = this.craftData(MakibesHR3Constants.CMD_REQUEST_FITNESS,
|
||||
new byte[]{
|
||||
(byte) (yearStart - 2000),
|
||||
(byte) monthStart,
|
||||
(byte) dayStart,
|
||||
(byte) a4,
|
||||
(byte) a5,
|
||||
(byte) (yearEnd - 2000),
|
||||
(byte) monthEnd,
|
||||
(byte) dayEnd,
|
||||
(byte) a9,
|
||||
(byte) a10
|
||||
(byte) 0x00,
|
||||
(byte) (yearStepsAfter - 2000),
|
||||
(byte) monthStepsAfter,
|
||||
(byte) dayStepsAfter,
|
||||
(byte) hourStepsAfter,
|
||||
(byte) minuteStepsAfter,
|
||||
(byte) (yearHeartRateAfter - 2000),
|
||||
(byte) monthHeartRateAfter,
|
||||
(byte) dayHeartRateAfter,
|
||||
(byte) hourHeartRateAfter,
|
||||
(byte) minuteHeartRateAfter
|
||||
});
|
||||
|
||||
transaction.write(this.mControlCharacteristic, data);
|
||||
@ -751,6 +811,47 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport implement
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ugly because I don't like Date.
|
||||
* All non-zero records after the given times will be returned via
|
||||
* {@link MakibesHR3Constants#RPRT_HEART_RATE_SAMPLE},
|
||||
* {@link MakibesHR3Constants#RPRT_STEPS_SAMPLE},
|
||||
* {@link MakibesHR3Constants#RPRT_FITNESS}
|
||||
*/
|
||||
private MakibesHR3DeviceSupport requestFitness(TransactionBuilder transaction) {
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
|
||||
MakibesHR3SampleProvider provider = new MakibesHR3SampleProvider(this.getDevice(), dbHandler.getDaoSession());
|
||||
|
||||
MakibesHR3ActivitySample latestSample = provider.getLatestActivitySample();
|
||||
|
||||
if (latestSample == null) {
|
||||
this.requestFitness(transaction,
|
||||
0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0);
|
||||
} else {
|
||||
// getInstance is unnecessary, we're overriding.
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(new Date(latestSample.getTimestamp() * 1000l));
|
||||
|
||||
int year = calendar.get(Calendar.YEAR);
|
||||
int month = (calendar.get(Calendar.MONTH) + 1);
|
||||
int day = calendar.get(Calendar.DAY_OF_MONTH);
|
||||
int hour = calendar.get(Calendar.HOUR_OF_DAY);
|
||||
int minute = calendar.get(Calendar.MINUTE);
|
||||
|
||||
this.requestFitness(transaction,
|
||||
year, month, day, hour, minute,
|
||||
year, month, day, hour, minute);
|
||||
}
|
||||
|
||||
} catch (Exception ex) {
|
||||
LOG.error(ex.getMessage());
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private MakibesHR3DeviceSupport findDevice(TransactionBuilder transaction) {
|
||||
transaction.write(this.mControlCharacteristic, this.craftData(MakibesHR3Constants.CMD_FIND_DEVICE));
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user