From 8b7d8530973679267dddaa5a12d6e8f2d01365d6 Mon Sep 17 00:00:00 2001 From: Cre3per Date: Thu, 3 Oct 2019 11:52:46 +0200 Subject: [PATCH] merged. added makibes hr3 OnSharedPreferenceChangeListener. added makibes hr3 reverse find device (find phone). added makibes hr3 heart rate/steps/firmware version. --- .../gadgetbridge/daogen/GBDaoGenerator.java | 15 +- app/src/main/AndroidManifest.xml | 1 + .../makibeshr3/MakibesHR3Constants.java | 112 +++++--- .../makibeshr3/MakibesHR3Coordinator.java | 28 +- .../makibeshr3/MakibesHR3SampleProvider.java | 84 ++++++ .../makibeshr3/MakibesHR3DeviceSupport.java | 244 +++++++++++++++++- 6 files changed, 439 insertions(+), 45 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/makibeshr3/MakibesHR3SampleProvider.java diff --git a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java index 3a55d6e58..13868d787 100644 --- a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java +++ b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java @@ -45,7 +45,7 @@ public class GBDaoGenerator { public static void main(String[] args) throws Exception { - Schema schema = new Schema(20, MAIN_PACKAGE + ".entities"); + Schema schema = new Schema(21, MAIN_PACKAGE + ".entities"); Entity userAttributes = addUserAttributes(schema); Entity user = addUserInfo(schema, userAttributes); @@ -60,6 +60,7 @@ public class GBDaoGenerator { Entity tag = addTag(schema); Entity userDefinedActivityOverlay = addActivityDescription(schema, tag, user); + addMakibesHR3ActivitySample(schema, user, device); addMiBandActivitySample(schema, user, device); addPebbleHealthActivitySample(schema, user, device); addPebbleHealthActivityKindOverlay(schema, user, device); @@ -186,6 +187,16 @@ public class GBDaoGenerator { return deviceAttributes; } + private static Entity addMakibesHR3ActivitySample(Schema schema, Entity user, Entity device) { + Entity activitySample = addEntity(schema, "MakibesHR3ActivitySample"); + activitySample.implementsSerializable(); + addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device); + activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE); + activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE); + addHeartRateProperties(activitySample); + return activitySample; + } + private static Entity addMiBandActivitySample(Schema schema, Entity user, Entity device) { Entity activitySample = addEntity(schema, "MiBandActivitySample"); activitySample.implementsSerializable(); @@ -363,7 +374,7 @@ public class GBDaoGenerator { alarm.addBooleanProperty("smartWakeup").notNull(); alarm.addIntProperty("repetition").notNull().codeBeforeGetter( "public boolean isRepetitive() { return getRepetition() != ALARM_ONCE; } " + - "public boolean getRepetition(int dow) { return (this.repetition & dow) > 0; }" + "public boolean getRepetition(int dow) { return (this.repetition & dow) > 0; }" ); alarm.addIntProperty("hour").notNull(); alarm.addIntProperty("minute").notNull(); diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8aecd746f..eb7b6d8de 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,6 +21,7 @@ + diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/makibeshr3/MakibesHR3Constants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/makibeshr3/MakibesHR3Constants.java index 7805d8ac7..9424959b9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/makibeshr3/MakibesHR3Constants.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/makibeshr3/MakibesHR3Constants.java @@ -24,15 +24,7 @@ public final class MakibesHR3Constants { public static final UUID UUID_SERVICE = UUID.fromString("6e400001-b5a3-f393-e0a9-e50e24dcca9e"); public static final UUID UUID_CHARACTERISTIC_CONTROL = UUID.fromString("6e400002-b5a3-f393-e0a9-e50e24dcca9e"); - - // time - // mode ab:00:04:ff:7c:80:** (00: 24h, 01: 12h) - - // confirm write? - // ab:00:09:ff:52:80:00:13:06:09:0f:0b - - // disconnect? - // ab:00:03:ff:ff:80 + public static final UUID UUID_CHARACTERISTIC_REPORT = UUID.fromString("6e400003-b5a3-f393-e0a9-e50e24dcca9e"); // Services and Characteristics // 00001801-0000-1000-8000-00805f9b34fb @@ -44,8 +36,8 @@ public final class MakibesHR3Constants { // 00002a04-0000-1000-8000-00805f9b34fb // 00002aa6-0000-1000-8000-00805f9b34fb // 6e400001-b5a3-f393-e0a9-e50e24dcca9e // Nordic UART Service - // 6e400002-b5a3-f393-e0a9-e50e24dcca9e // control - // 6e400003-b5a3-f393-e0a9-e50e24dcca9e + // 6e400002-b5a3-f393-e0a9-e50e24dcca9e // control (RX) + // 6e400003-b5a3-f393-e0a9-e50e24dcca9e // report // 0000fee7-0000-1000-8000-00805f9b34fb // 0000fec9-0000-1000-8000-00805f9b34fb // 0000fea1-0000-1000-8000-00805f9b34fb @@ -55,10 +47,8 @@ public final class MakibesHR3Constants { // ab 00 [argument_count] ff [command] 80 [arguments] // where [argument_count] is [arguments].length + 3 - // refresh sends - // 51 - // 52 - // 93 (CMD_SET_DATE_TIME) + // Report structure is the same but 80 might by different + public static final byte[] DATA_TEMPLATE = { (byte) 0xab, @@ -75,36 +65,78 @@ public final class MakibesHR3Constants { public static final int DATA_ARGUMENTS_INDEX = 6; + // 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) + // [steps hi] + // [steps lo] + // 00 + // 00 + // 01 (also was 0b. Maybe minutes of activity.) + // 00 + // 00 + // 00 + // 00 + // 00 + public static final byte RPRT_FITNESS = (byte) 0x51; + + + // enable (00/01) + public static final byte RPRT_REVERSE_FIND_DEVICE = (byte) 0x7d; + + + // heart rate + public static final byte RPRT_HEARTRATE = (byte) 0x84; + + + // 2 arguments. + public static final byte RPRT_91 = (byte) 0x91; + + // firmware_major + // firmware_minor + // 37 + // 00 + // 00 + // 00 + // 00 + // 00 + // 00 + // 20 + // 0e + public static final byte RPRT_SOFTWARE = (byte) 0x92; + // 00 public static final byte CMD_FACTORY_RESET = (byte) 0x23; // 00 // year (+2000) - // month - // day - // 0b - // 00 + // month (not current! but close) + // day (not current! but close) + // 0b (A) + // 00 (B) // year (+2000) - // month - // day - // 0b - // 19 - public static final byte CMD_UNKNOWN_51 = (byte) 0x51; + // month (not current! but close) + // day (not current! but close) + // 0b (this is >= (A)) + // 19 (this is >= (B)) + public static final byte CMD_REQUEST_FITNESS = (byte) 0x51; // this is the last command sent on sync // 00 // year (+2000) - // month + // month (not current!) // 14 this isn't the current day // hour (current) // minute (current) - public static final byte CMD_UNKNOWN_52 = (byte) 0x52; + public static final byte CMD_52 = (byte) 0x52; public static final byte CMD_FIND_DEVICE = (byte) 0x71; + // WearFit writes uses other sources as well. They don't do anything though. public static final byte ARG_SEND_NOTIFICATION_SOURCE_CALL = (byte) 0x01; public static final byte ARG_SEND_NOTIFICATION_SOURCE_STOP_CALL = (byte) 0x02; public static final byte ARG_SEND_NOTIFICATION_SOURCE_MESSAGE = (byte) 0x03; @@ -118,7 +150,7 @@ public final class MakibesHR3Constants { public static final byte ARG_SEND_NOTIFICATION_SOURCE_WEIBO = (byte) 0x13; public static final byte ARG_SEND_NOTIFICATION_SOURCE_KAKOTALK = (byte) 0x14; // ARG_SET_NOTIFICATION_SOURCE_* - // 02 + // 02 (This is 00 and 01 during connection. I don't know what it does. Maybe clears notifications?) // ASCII public static final byte CMD_SEND_NOTIFICATION = (byte) 0x72; @@ -177,6 +209,10 @@ public final class MakibesHR3Constants { public static final byte CMD_SET_HEADS_UP_SCREEN = (byte) 0x77; + // Looks like enable/disable. + public static final byte CMD_78 = (byte) 0x78; + + // The watch enters photograph mode, but doesn't appear to send a trigger signal. // enable (00/01) public static final byte CMD_SET_PHOTOGRAPH_MODE = (byte) 0x79; @@ -188,14 +224,24 @@ public final class MakibesHR3Constants { // 7b has 1 argument. Looks like enable/disable. - // 7e has 14 arguments. - public static final byte ARG_SET_TIMEMODE_24H = 0x00; public static final byte ARG_SET_TIMEMODE_12H = 0x01; // ARG_SET_TIMEMODE_* public static final byte CMD_SET_TIMEMODE = (byte) 0x7c; + // 5 arguments. + public static final byte CMD_7f = (byte) 0x7f; + + + // enable (00/01) + public static final byte CMD_SET_REAL_TIME_HEART_RATE = (byte) 0x84; + + + // looks like enable/disable. + public static final byte CMD_85 = (byte) 0x85; + + // 00 // year hi // year lo @@ -207,6 +253,14 @@ public final class MakibesHR3Constants { public static final byte CMD_SET_DATE_TIME = (byte) 0x93; + // looks like enable/disable. + public static final byte CMD_96 = (byte) 0x96; + + + // looks like enable/disable. + public static final byte CMD_e5 = (byte) 0xe5; + + // If this is sent after {@link CMD_FACTORY_RESET}, it's a shutdown, not a reboot. // Rebooting resets the watch face and wallpaper. public static final byte CMD_REBOOT = (byte) 0xff; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/makibeshr3/MakibesHR3Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/makibeshr3/MakibesHR3Coordinator.java index a81c789a9..e867d1a2d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/makibeshr3/MakibesHR3Coordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/makibeshr3/MakibesHR3Coordinator.java @@ -16,10 +16,18 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3; +/* + * @author Alejandro Ladera Chamorro <11555126+tiparega@users.noreply.github.com> + */ + + import android.app.Activity; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGattCharacteristic; import android.content.Context; -import android.content.SharedPreferences; import android.net.Uri; +import android.util.Log; +import android.content.SharedPreferences; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -48,20 +56,24 @@ public class MakibesHR3Coordinator extends AbstractDeviceCoordinator { private static final Logger LOG = LoggerFactory.getLogger(MakibesHR3Coordinator.class); - public static byte getTimeMode(String deviceAddress) { - SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress); - + public static byte getTimeMode(SharedPreferences sharedPrefs) { String tmode = sharedPrefs.getString(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT, getContext().getString(R.string.p_timeformat_24h)); LOG.debug("tmode is " + tmode); - if (getContext().getString(R.string.p_timeformat_24h).equals(tmode)) { + if (tmode.equals(getContext().getString(R.string.p_timeformat_24h))) { return MakibesHR3Constants.ARG_SET_TIMEMODE_24H; } else { return MakibesHR3Constants.ARG_SET_TIMEMODE_12H; } } + public static byte getTimeMode(String deviceAddress) { + SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress); + + return getTimeMode(sharedPrefs); + } + @NonNull @Override public DeviceType getSupportedType(GBDeviceCandidate candidate) { @@ -92,7 +104,7 @@ public class MakibesHR3Coordinator extends AbstractDeviceCoordinator { @Override public boolean supportsRealtimeData() { - return false; + return true; } @Override @@ -123,12 +135,12 @@ public class MakibesHR3Coordinator extends AbstractDeviceCoordinator { @Override public boolean supportsActivityTracking() { - return false; + return true; } @Override public SampleProvider getSampleProvider(GBDevice device, DaoSession session) { - return null; + return new MakibesHR3SampleProvider(device, session); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/makibeshr3/MakibesHR3SampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/makibeshr3/MakibesHR3SampleProvider.java new file mode 100644 index 000000000..fd6f4ee01 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/makibeshr3/MakibesHR3SampleProvider.java @@ -0,0 +1,84 @@ +/* Copyright (C) 2018-2019 Daniele Gobbetti, Sebastian Kranz + + 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 . */ +package nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import de.greenrobot.dao.AbstractDao; +import de.greenrobot.dao.Property; +import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider; +import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; +import nodomain.freeyourgadget.gadgetbridge.entities.MakibesHR3ActivitySample; +import nodomain.freeyourgadget.gadgetbridge.entities.MakibesHR3ActivitySampleDao; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; + +public class MakibesHR3SampleProvider extends AbstractSampleProvider { + + private GBDevice mDevice; + private DaoSession mSession; + + public MakibesHR3SampleProvider(GBDevice device, DaoSession session) { + super(device, session); + + mSession = session; + mDevice = device; + } + + @Override + public int normalizeType(int rawType) { + return rawType; + } + + @Override + public int toRawActivityKind(int activityKind) { + return activityKind; + } + + @Override + public float normalizeIntensity(int rawIntensity) { + return rawIntensity; + } + + @Override + public MakibesHR3ActivitySample createActivitySample() { + return new MakibesHR3ActivitySample(); + } + + @Override + public AbstractDao getSampleDao() { + return getSession().getMakibesHR3ActivitySampleDao(); + } + + @Nullable + @Override + protected Property getRawKindSampleProperty() { + return MakibesHR3ActivitySampleDao.Properties.RawKind; + } + + @NonNull + @Override + protected Property getTimestampSampleProperty() { + return MakibesHR3ActivitySampleDao.Properties.Timestamp; + } + + @NonNull + @Override + protected Property getDeviceIdentifierSampleProperty() { + return MakibesHR3ActivitySampleDao.Properties.DeviceId; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/makibeshr3/MakibesHR3DeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/makibeshr3/MakibesHR3DeviceSupport.java index 6a782481e..246019739 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/makibeshr3/MakibesHR3DeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/makibeshr3/MakibesHR3DeviceSupport.java @@ -1,7 +1,26 @@ +// TODO: WearFit resets today's step count when it's used after GB. + +// TODO: Battery level + +// TODO: ALARM REMINDER REPETITION + +// TODO: It'd be cool if we could change the language. There's no official way to do so, but the +// TODO: watch is sold as chinese/english. + package nodomain.freeyourgadget.gadgetbridge.service.devices.makibeshr3; +import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; import android.net.Uri; +import android.os.Build; +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.widget.Toast; + +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -10,13 +29,23 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.UUID; +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; +import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; +import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3.MakibesHR3Constants; import nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3.MakibesHR3Coordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3.MakibesHR3SampleProvider; +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.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; +import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; @@ -24,12 +53,20 @@ import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol; +import nodomain.freeyourgadget.gadgetbridge.util.GB; -public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport { +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TIMEFORMAT; +import static nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3.MakibesHR3Constants.RPRT_SOFTWARE; + +public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport implements SharedPreferences.OnSharedPreferenceChangeListener { private static final Logger LOG = LoggerFactory.getLogger(MakibesHR3DeviceSupport.class); - private BluetoothGattCharacteristic ctrlCharacteristic = null; + private Vibrator mVibrator; + + public BluetoothGattCharacteristic ctrlCharacteristic = null; + public BluetoothGattCharacteristic rprtCharacteristic = null; + public MakibesHR3DeviceSupport() { super(LOG); @@ -42,6 +79,17 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport { return false; } + public MakibesHR3ActivitySample createActivitySample(Device device, User user, int timestampInSeconds, SampleProvider provider) { + MakibesHR3ActivitySample sample = new MakibesHR3ActivitySample(); + sample.setDevice(device); + sample.setUser(user); + sample.setTimestamp(timestampInSeconds); + sample.setProvider(provider); + + return sample; + } + + @Override public void onNotification(NotificationSpec notificationSpec) { TransactionBuilder transactionBuilder = this.createTransactionBuilder("onnotificaiton"); @@ -233,7 +281,34 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport { @Override public void onEnableRealtimeHeartRateMeasurement(boolean enable) { + TransactionBuilder transactionBuilder = this.createTransactionBuilder("finddevice"); + this.setEnableRealTimeHeartRate(transactionBuilder, enable); + + try { + this.performConnected(transactionBuilder.getTransaction()); + } catch (Exception e) { + LOG.debug("ERROR"); + } + } + + private void onReverseFindDevice(boolean start) { + 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); + } + } else { + this.mVibrator.cancel(); + } } @Override @@ -304,12 +379,8 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport { } private MakibesHR3DeviceSupport sendUserInfo(TransactionBuilder builder) { - // builder.write(ctrlCharacteristic, MakibesHR3Constants.CMD_SET_PREF_START); - // builder.write(ctrlCharacteristic, MakibesHR3Constants.CMD_SET_PREF_START1); - syncPreferences(builder); - // builder.write(ctrlCharacteristic, new byte[]{MakibesHR3Constants.CMD_SET_CONF_END}); return this; } @@ -334,30 +405,158 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport { return this; } + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + LOG.debug(key + " changed"); + TransactionBuilder transactionBuilder = this.createTransactionBuilder("onSharedPreferenceChanged"); + + if (key.equals(PREF_TIMEFORMAT)) { + this.setTimeMode(transactionBuilder); + } else { + return; + } + + try { + this.performConnected(transactionBuilder.getTransaction()); + } catch (Exception ex) { + LOG.warn(ex.getMessage()); + } + } + @Override protected TransactionBuilder initializeDevice(TransactionBuilder builder) { gbDevice.setState(GBDevice.State.INITIALIZING); gbDevice.sendDeviceUpdateIntent(getContext()); this.ctrlCharacteristic = getCharacteristic(MakibesHR3Constants.UUID_CHARACTERISTIC_CONTROL); + this.rprtCharacteristic = getCharacteristic(MakibesHR3Constants.UUID_CHARACTERISTIC_REPORT); + this.mVibrator = (Vibrator) this.getContext().getSystemService(Context.VIBRATOR_SERVICE); + + builder.notify(this.rprtCharacteristic, true); builder.setGattCallback(this); + // Allow modifications builder.write(this.ctrlCharacteristic, new byte[]{0x01, 0x00}); // Initialize device sendUserInfo(builder); //Sync preferences + this.requestFitness(builder, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0); + gbDevice.setState(GBDevice.State.INITIALIZED); gbDevice.sendDeviceUpdateIntent(getContext()); getDevice().setFirmwareVersion("N/A"); getDevice().setFirmwareVersion2("N/A"); + SharedPreferences preferences = GBApplication.getDeviceSpecificSharedPrefs(this.getDevice().getAddress()); + + // TODO: Why doesn't this work? + preferences.registerOnSharedPreferenceChangeListener(this); + return builder; } + private void broadcastActivity(Integer heartRate, Integer steps) { + try (DBHandler dbHandler = GBApplication.acquireDB()) { + + User user = DBHelper.getUser(dbHandler.getDaoSession()); + Device device = DBHelper.getDevice(this.getDevice(), dbHandler.getDaoSession()); + + MakibesHR3SampleProvider provider = new MakibesHR3SampleProvider(this.getDevice(), dbHandler.getDaoSession()); + + int timeStamp = (int) (System.currentTimeMillis() / 1000); + + MakibesHR3ActivitySample sample = this.createActivitySample(device, user, timeStamp, provider); + + if (heartRate != null) { + sample.setHeartRate(heartRate); + } + + if (steps != null) { + sample.setSteps(steps); + } + + sample.setRawKind(-1); + + provider.addGBActivitySample(sample); + + 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); + GB.updateTransferNotification(null, "Data transfer failed", false, 0, getContext()); + } + } + + private void onReceiveFitness(int steps) { + LOG.info("steps: " + steps); + + this.broadcastActivity(null, steps); + } + + private void onReceiveHeartRate(int heartRate) { + LOG.info("heart rate: " + heartRate); + + this.broadcastActivity(heartRate, null); + } + + @Override + public boolean onCharacteristicChanged(BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic) { + if (super.onCharacteristicChanged(gatt, characteristic)) { + return true; + } + + byte[] data = characteristic.getValue(); + if (data.length < 6) + return true; + + UUID characteristicUuid = characteristic.getUuid(); + + if (characteristicUuid.equals(rprtCharacteristic.getUuid())) { + byte[] value = characteristic.getValue(); + byte[] arguments = new byte[value.length - 6]; + + for (int i = 0; i < arguments.length; ++i) { + arguments[i] = value[i + 6]; + } + + byte report = value[4]; + + 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; + case MakibesHR3Constants.RPRT_REVERSE_FIND_DEVICE: + this.onReverseFindDevice(arguments[0] == 0x01); + break; + case MakibesHR3Constants.RPRT_HEARTRATE: + if (value.length == 7) { + this.onReceiveHeartRate((int) arguments[0]); + } + break; + case RPRT_SOFTWARE: + if (arguments.length == 11) { + this.getDevice().setFirmwareVersion(((int) arguments[0]) + "." + ((int) arguments[1])); + } + break; + } + } + + return false; + } + /** * @param command * @param data @@ -427,6 +626,31 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport { return this.reboot(transaction); } + 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) { + + 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 + }); + + transaction.write(this.ctrlCharacteristic, data); + + return this; + } + private MakibesHR3DeviceSupport findDevice(TransactionBuilder transaction) { transaction.write(this.ctrlCharacteristic, this.craftData(MakibesHR3Constants.CMD_FIND_DEVICE)); @@ -475,6 +699,14 @@ public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport { return this; } + private MakibesHR3DeviceSupport setEnableRealTimeHeartRate(TransactionBuilder transaction, boolean enable) { + byte[] data = this.craftData(MakibesHR3Constants.CMD_SET_REAL_TIME_HEART_RATE, new byte[]{(byte) (enable ? 0x01 : 0x00)}); + + transaction.write(this.ctrlCharacteristic, data); + + return this; + } + private MakibesHR3DeviceSupport setDateTime(TransactionBuilder transaction, int year, int month,