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 new file mode 100644 index 000000000..0c8b09936 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/makibeshr3/MakibesHR3Constants.java @@ -0,0 +1,215 @@ +/* Copyright (C) 2016-2019 Andreas Shimokawa, Carsten Pfeiffer, João + Paulo Barraca + + 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 java.util.UUID; + +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 + + // Services and Characteristics + // 00001801-0000-1000-8000-00805f9b34fb + // 00002a05-0000-1000-8000-00805f9b34fb + // 00001800-0000-1000-8000-00805f9b34fb + // 00002a00-0000-1000-8000-00805f9b34fb + // 00002a01-0000-1000-8000-00805f9b34fb + // 00002a02-0000-1000-8000-00805f9b34fb + // 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 + // 0000fee7-0000-1000-8000-00805f9b34fb + // 0000fec9-0000-1000-8000-00805f9b34fb + // 0000fea1-0000-1000-8000-00805f9b34fb + // 0000fea2-0000-1000-8000-00805f9b34fb + + // Command structure + // ab 00 [argument_count] ff [command] 80 [arguments] + // where [argument_count] is [arguments].length + 3 + + // refresh sends + // 51 + // 52 + // 93 (CMD_SET_DATE_TIME) + + public static final byte[] DATA_TEMPLATE = { + (byte) 0xab, + (byte) 0x00, + (byte) 0, // argument_count + (byte) 0xff, + (byte) 0, // command + (byte) 0x80 +// ,arguments + }; + + public static final int DATA_ARGUMENT_COUNT_INDEX = 2; + public static final int DATA_COMMAND_INDEX = 4; + public static final int DATA_ARGUMENTS_INDEX = 6; + + + // 00 + public static final byte CMD_FACTORY_RESET = (byte) 0x23; + + + // 00 + // year (+2000) + // month + // day + // 0b + // 00 + // year (+2000) + // month + // day + // 0b + // 19 + public static final byte CMD_UNKNOWN_51 = (byte) 0x51; + + // this is the last command sent on sync + // 00 + // year (+2000) + // month + // 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_FIND_DEVICE = (byte) 0x71; + + + 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; + public static final byte ARG_SEND_NOTIFICATION_SOURCE_QQ = (byte) 0x07; + public static final byte ARG_SEND_NOTIFICATION_SOURCE_WECHAT = (byte) 0x09; + public static final byte ARG_SEND_NOTIFICATION_SOURCE_WHATSAPP = (byte) 0x0a; + public static final byte ARG_SEND_NOTIFICATION_SOURCE_LINE = (byte) 0x0e; + public static final byte ARG_SEND_NOTIFICATION_SOURCE_TWITTER = (byte) 0x0f; + public static final byte ARG_SEND_NOTIFICATION_SOURCE_FACEBOOK = (byte) 0x10; + public static final byte ARG_SEND_NOTIFICATION_SOURCE_FACEBOOK2 = (byte) 0x11; + 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 + // ASCII + public static final byte CMD_SEND_NOTIFICATION = (byte) 0x72; + + + public static final byte ARG_SET_ALARM_REMINDER_REPEAT_WEEKDAY = (byte) 0x1F; + public static final byte ARG_SET_ALARM_REMINDER_REPEAT_CUSTOM = (byte) 0x40; + public static final byte ARG_SET_ALARM_REMINDER_REPEAT_EVERY_DAY = (byte) 0x7F; + public static final byte ARG_SET_ALARM_REMINDER_REPEAT_ONE_TIME = (byte) 0x80; + // reminder id starting at 0 + // enable (00/01) + // hour + // minute + // ARG_SET_ALARM_REMINDER_REPEAT_* + public static final byte CMD_SET_ALARM_REMINDER = (byte) 0x73; + + + public static final byte ARG_SET_PERSONAL_INFORMATION_UNIT_DISTANCE_MILES = (byte) 0x00; + public static final byte ARG_SET_PERSONAL_INFORMATION_UNIT_DISTANCE_KILOMETERS = (byte) 0x01; + public static final byte ARG_SET_PERSONAL_INFORMATION_UNIT_LENGTH_INCHES = (byte) 0x00; + public static final byte ARG_SET_PERSONAL_INFORMATION_UNIT_LENGTH_CENTIMETERS = (byte) 0x01; + // step length (in/cm) + // age (years) + // height (in/cm) + // weight (lb/kg) + // ARG_SET_PERSONAL_INFORMATION_UNIT_DISTANCE_* + // target step count (kilo) + // 5a + // 82 + // 3c + // 5a + // 28 + // b4 + // 5d + // 64 + public static final byte CMD_SET_PERSONAL_INFORMATION = (byte) 0x74; + + + // enable (00/01) + // start hour + // start minute + // end hour + // end minute + // 2d + public static final byte CMD_SET_SEDENTARY_REMINDER = (byte) 0x75; + + + // enable (00/01) + // start hour + // start minute + // end hour + // end minute + public static final byte CMD_SET_QUITE_HOURS = (byte) 0x76; + + + // enable (00/01) + public static final byte CMD_SET_HEADS_UP_SCREEN = (byte) 0x77; + + + // 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; + + + // enable (00/01) + public static final byte CMD_SET_LOST_REMINDER = (byte) 0x7a; + + + // 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; + + + // 00 + // year hi + // year lo + // month + // day + // hour + // minute + // second + public static final byte CMD_SET_DATE_TIME = (byte) 0x93; + + + // 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; + + public static final String PREF_TIMEFORMAT = "makibes_hr3_timeformat"; +} 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 new file mode 100644 index 000000000..aeb5a1572 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/makibeshr3/MakibesHR3Coordinator.java @@ -0,0 +1,182 @@ +/* Copyright (C) 2017-2019 Daniele Gobbetti, João Paulo Barraca, tiparega + + 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; + +/* + * @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.net.Uri; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.slf4j.LoggerFactory; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.GBException; +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator; +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.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; +import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; +import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.util.Prefs; + +import static nodomain.freeyourgadget.gadgetbridge.GBApplication.getContext; + +/** + * Pseudo Coordinator for the Q8, a sub type of the HPLUS devices + */ +public class MakibesHR3Coordinator extends AbstractDeviceCoordinator { + + protected static Prefs prefs = GBApplication.getPrefs(); + + public static byte getTimeMode(String address) { + String tmode = prefs.getString(MakibesHR3Constants.PREF_TIMEFORMAT, getContext().getString(R.string.p_timeformat_24h)); + + LoggerFactory.getLogger(MakibesHR3Coordinator.class).debug("tmode is " + tmode); + + if(tmode.equals(getContext().getString(R.string.p_timeformat_24h))) { + return MakibesHR3Constants.ARG_SET_TIMEMODE_24H; + }else{ + return MakibesHR3Constants.ARG_SET_TIMEMODE_12H; + } + } + + @NonNull + @Override + public DeviceType getSupportedType(GBDeviceCandidate candidate) { + String name = candidate.getDevice().getName(); + + // TODO: + if ((name != null) && name.equals("Y808")) { + return DeviceType.MAKIBESHR3; + } + + return DeviceType.UNKNOWN; + } + + @Override + protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException { + + } + + @Override + public int getBondingStyle(GBDevice deviceCandidate) { + return BONDING_STYLE_NONE; + } + + @Override + public boolean supportsCalendarEvents() { + return false; + } + + @Override + public boolean supportsRealtimeData() { + return false; + } + + @Override + public boolean supportsWeather() { + return false; + } + + @Override + public boolean supportsFindDevice() { + return true; + } + + @Override + public DeviceType getDeviceType() { + return DeviceType.MAKIBESHR3; + } + + @Nullable + @Override + public Class getPairingActivity() { + return null; + } + + @Override + public boolean supportsActivityDataFetching() { + return false; + } + + @Override + public boolean supportsActivityTracking() { + return false; + } + + @Override + public SampleProvider getSampleProvider(GBDevice device, DaoSession session) { + return null; + } + + @Override + public InstallHandler findInstallHandler(Uri uri, Context context) { + return null; + } + + @Override + public boolean supportsScreenshots() { + return false; + } + + @Override + public int getAlarmSlotCount() { + // TODO: + return 5; + } + + @Override + public boolean supportsSmartWakeup(GBDevice device) { + return false; + } + + @Override + public boolean supportsHeartRateMeasurement(GBDevice device) { + return true; + } + + @Override + public String getManufacturer() { + return "Makibes"; + } + + @Override + public boolean supportsAppsManagement() { + return false; + } + + @Override + public Class getAppsManagementActivity() { + return null; + } + +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java index d81751ba8..340ce1bfa 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java @@ -56,6 +56,7 @@ public enum DeviceType { CASIOGB6900(120, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_casiogb6900), MISCALE2(131, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_miscale2), BFH16(140, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_bfh16), + MAKIBESHR3(150, R.drawable.ic_device_default, R.drawable.ic_device_hplus_disabled, R.string.devicetype_makibes_hr3), MIJIA_LYWSD02(200, R.drawable.ic_device_pebble, R.drawable.ic_device_pebble_disabled, R.string.devicetype_mijia_lywsd02), TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_test); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java index dfa4e83ce..4442691f1 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java @@ -42,6 +42,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.id115.ID115Support; import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.BFH16DeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.TeclastH30Support; import nodomain.freeyourgadget.gadgetbridge.service.devices.liveview.LiveviewSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.makibeshr3.MakibesHR3DeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.mijia_lywsd02.MijiaLywsd02Support; import nodomain.freeyourgadget.gadgetbridge.service.devices.miscale2.MiScale2DeviceSupport; @@ -195,6 +196,10 @@ public class DeviceSupportFactory { break; case MIJIA_LYWSD02: deviceSupport = new ServiceDeviceSupport(new MijiaLywsd02Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); + break; + case MAKIBESHR3: + deviceSupport = new ServiceDeviceSupport(new MakibesHR3DeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); + break; } if (deviceSupport != null) { deviceSupport.setContext(gbDevice, mBtAdapter, mContext); 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 new file mode 100644 index 000000000..ecbe3a5ed --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/makibeshr3/MakibesHR3DeviceSupport.java @@ -0,0 +1,532 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.makibeshr3; + +import android.bluetooth.BluetoothGattCharacteristic; +import android.net.Uri; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3.MakibesHR3Constants; +import nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3.MakibesHR3Coordinator; +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.MusicSpec; +import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; +import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.btle.Transaction; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol; + +public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport { + + private static final Logger LOG = LoggerFactory.getLogger(MakibesHR3DeviceSupport.class); + + public BluetoothGattCharacteristic ctrlCharacteristic = null; + + public MakibesHR3DeviceSupport() { + super(LOG); + + addSupportedService(MakibesHR3Constants.UUID_SERVICE); + } + + @Override + public boolean useAutoConnect() { + return false; + } + + @Override + public void onNotification(NotificationSpec notificationSpec) { + TransactionBuilder transactionBuilder = this.createTransactionBuilder("onnotificaiton"); + + byte sender; + + switch (notificationSpec.type) { + case FACEBOOK: + case FACEBOOK_MESSENGER: + sender = MakibesHR3Constants.ARG_SEND_NOTIFICATION_SOURCE_FACEBOOK; + break; + case LINE: + sender = MakibesHR3Constants.ARG_SEND_NOTIFICATION_SOURCE_LINE; + break; + case TELEGRAM: + sender = MakibesHR3Constants.ARG_SEND_NOTIFICATION_SOURCE_MESSAGE; + break; + case TWITTER: + sender = MakibesHR3Constants.ARG_SEND_NOTIFICATION_SOURCE_TWITTER; + break; + case WECHAT: + sender = MakibesHR3Constants.ARG_SEND_NOTIFICATION_SOURCE_WECHAT; + break; + case WHATSAPP: + sender = MakibesHR3Constants.ARG_SEND_NOTIFICATION_SOURCE_WHATSAPP; + break; + case KAKAO_TALK: + sender = MakibesHR3Constants.ARG_SEND_NOTIFICATION_SOURCE_KAKOTALK; + break; + + default: + sender = MakibesHR3Constants.ARG_SEND_NOTIFICATION_SOURCE_MESSAGE; + break; + } + + this.sendNotification(transactionBuilder, + sender, notificationSpec.title + ": " + notificationSpec.body); + + try { + this.performConnected(transactionBuilder.getTransaction()); + } catch (Exception ex) { + LoggerFactory.getLogger(this.getClass()).error("notification failed"); + } + } + + @Override + public void onDeleteNotification(int id) { + + } + + @Override + public void onSetTime() { + TransactionBuilder transactionBuilder = this.createTransactionBuilder("settime"); + + this.setDateTime(transactionBuilder); + + try { + this.performConnected(transactionBuilder.getTransaction()); + } catch (Exception ex) { + LoggerFactory.getLogger(this.getClass()).error("factory reset failed"); + } + } + + @Override + public void onSetAlarms(ArrayList alarms) { + + TransactionBuilder transactionBuilder = this.createTransactionBuilder("setalarms"); + + for (int i = 0; i < alarms.size(); ++i) { + Alarm alarm = alarms.get(i); + + // Should we use @alarm.getPosition() rather than @i? + this.setAlarmReminder( + transactionBuilder, + i, + alarm.getEnabled(), + alarm.getHour(), + alarm.getMinute(), + MakibesHR3Constants.ARG_SET_ALARM_REMINDER_REPEAT_CUSTOM); + } + + try { + this.performConnected(transactionBuilder.getTransaction()); + } catch (Exception ex) { + LoggerFactory.getLogger(this.getClass()).error("setalarms failed"); + } + } + + @Override + public void onSetCallState(CallSpec callSpec) { + TransactionBuilder transactionBuilder = this.createTransactionBuilder("callstate"); + LOG.debug("callSpec " + callSpec.command); + 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); + } + + try { + this.performConnected(transactionBuilder.getTransaction()); + } catch (Exception ex) { + LoggerFactory.getLogger(this.getClass()).error("call state failed"); + } + } + + @Override + public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) { + + } + + @Override + public void onSetMusicState(MusicStateSpec stateSpec) { + + } + + @Override + public void onSetMusicInfo(MusicSpec musicSpec) { + + } + + @Override + public void onEnableRealtimeSteps(boolean enable) { + + } + + @Override + public void onInstallApp(Uri uri) { + + } + + @Override + public void onAppInfoReq() { + + } + + @Override + public void onAppStart(UUID uuid, boolean start) { + + } + + @Override + public void onAppDelete(UUID uuid) { + + } + + @Override + public void onAppConfiguration(UUID appUuid, String config, Integer id) { + + } + + @Override + public void onAppReorder(UUID[] uuids) { + + } + + @Override + public void onFetchRecordedData(int dataTypes) { + + } + + @Override + public void onReset(int flags) { + + if ((flags & GBDeviceProtocol.RESET_FLAGS_FACTORY_RESET) != 0) { + TransactionBuilder transactionBuilder = this.createTransactionBuilder("reset"); + this.factoryReset(transactionBuilder); + + try { + this.performConnected(transactionBuilder.getTransaction()); + } catch (Exception ex) { + LoggerFactory.getLogger(this.getClass()).error("factory reset failed"); + } + } else if ((flags & GBDeviceProtocol.RESET_FLAGS_REBOOT) != 0) { + TransactionBuilder transactionBuilder = this.createTransactionBuilder("reboot"); + this.reboot(transactionBuilder); + + try { + this.performConnected(transactionBuilder.getTransaction()); + } catch (Exception ex) { + LoggerFactory.getLogger(this.getClass()).error("factory reset failed"); + } + } + } + + @Override + public void onHeartRateTest() { + + } + + @Override + public void onEnableRealtimeHeartRateMeasurement(boolean enable) { + + } + + @Override + public void onFindDevice(boolean start) { + if (!start) { + return; + } + + TransactionBuilder transactionBuilder = this.createTransactionBuilder("finddevice"); + + this.findDevice(transactionBuilder); + + try { + this.performConnected(transactionBuilder.getTransaction()); + } catch (Exception e) { + LOG.debug("ERROR"); + } + } + + @Override + public void onSetConstantVibration(int integer) { + + } + + @Override + public void onScreenshotReq() { + + } + + @Override + public void onEnableHeartRateSleepSupport(boolean enable) { + + } + + @Override + public void onSetHeartRateMeasurementInterval(int seconds) { + + } + + @Override + public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) { + + } + + @Override + public void onDeleteCalendarEvent(byte type, long id) { + + } + + @Override + public void onSendConfiguration(String config) { + + } + + @Override + public void onReadConfiguration(String config) { + + } + + @Override + public void onTestNewFunction() { + + } + + @Override + public void onSendWeather(WeatherSpec weatherSpec) { + + } + + 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; + } + + private MakibesHR3DeviceSupport syncPreferences(TransactionBuilder transaction) { + + this.setTimeMode(transaction); + this.setDateTime(transaction); + // setDayOfWeek(transaction); + // setTimeMode(transaction); + + // setGender(transaction); + // setAge(transaction); + // setWeight(transaction); + // setHeight(transaction); + + // setGoal(transaction); + // setLanguage(transaction); + // setScreenTime(transaction); + // setUnit(transaction); + // setAllDayHeart(transaction); + + return this; + } + + @Override + protected TransactionBuilder initializeDevice(TransactionBuilder builder) { + gbDevice.setState(GBDevice.State.INITIALIZING); + gbDevice.sendDeviceUpdateIntent(getContext()); + + this.ctrlCharacteristic = getCharacteristic(MakibesHR3Constants.UUID_CHARACTERISTIC_CONTROL); + + builder.setGattCallback(this); + + // Allow modifications + builder.write(this.ctrlCharacteristic, new byte[]{0x01, 0x00}); + + // Initialize device + sendUserInfo(builder); //Sync preferences + + gbDevice.setState(GBDevice.State.INITIALIZED); + gbDevice.sendDeviceUpdateIntent(getContext()); + + getDevice().setFirmwareVersion("N/A"); + getDevice().setFirmwareVersion2("N/A"); + + return builder; + } + + /** + * @param command + * @param data + * @return + */ + private byte[] craftData(byte command, byte[] data) { + byte[] result = new byte[MakibesHR3Constants.DATA_TEMPLATE.length + data.length]; + + for (int i = 0; i < MakibesHR3Constants.DATA_TEMPLATE.length; ++i) { + result[i] = MakibesHR3Constants.DATA_TEMPLATE[i]; + } + + result[MakibesHR3Constants.DATA_ARGUMENT_COUNT_INDEX] = (byte) (data.length + 3); + result[MakibesHR3Constants.DATA_COMMAND_INDEX] = command; + + for (int i = 0; i < data.length; ++i) { + result[MakibesHR3Constants.DATA_ARGUMENTS_INDEX + i] = data[i]; + } + + return result; + } + + + private byte[] craftData(byte command) { + return this.craftData(command, new byte[]{}); + } + + private void writeSafe(BluetoothGattCharacteristic characteristic, TransactionBuilder builder, byte[] data) { + final int maxMessageLength = 20; + + // For every split, we need 1 byte extra. + int extraBytes = (((data.length - maxMessageLength) / maxMessageLength) + 1); + + int totalDataLength = (data.length + extraBytes); + + int segmentCount = (((totalDataLength - 1) / maxMessageLength) + 1); + + byte[] indexedData = new byte[totalDataLength]; + + int it = 0; + int segmentIndex = 0; + for (int i = 0; i < data.length; ++i) { + if ((i != 0) && ((it % maxMessageLength) == 0)) { + indexedData[it++] = (byte) segmentIndex++; + } + + indexedData[it++] = data[i]; + } + + for (int i = 0; i < segmentCount; ++i) { + int segmentStart = (i * maxMessageLength); + int segmentLength; + + if (i == (segmentCount - 1)) { + segmentLength = (indexedData.length - segmentStart); + } else { + segmentLength = maxMessageLength; + } + + byte[] segment = new byte[segmentLength]; + + for (int j = 0; j < segmentLength; ++j) { + segment[j] = indexedData[segmentStart + j]; + } + + builder.write(characteristic, segment); + } + } + + private MakibesHR3DeviceSupport factoryReset(TransactionBuilder transaction) { + transaction.write(this.ctrlCharacteristic, this.craftData(MakibesHR3Constants.CMD_FACTORY_RESET)); + + return this.reboot(transaction); + } + + private MakibesHR3DeviceSupport findDevice(TransactionBuilder transaction) { + transaction.write(this.ctrlCharacteristic, this.craftData(MakibesHR3Constants.CMD_FIND_DEVICE)); + + return this; + } + + private MakibesHR3DeviceSupport sendNotification(TransactionBuilder transaction, + byte source, String message) { + byte[] data = new byte[message.length() + 2]; + data[0] = source; + data[1] = (byte) 0x02; + + for (int i = 0; i < message.length(); ++i) { + data[i + 2] = (byte) message.charAt(i); + } + + this.writeSafe( + this.ctrlCharacteristic, + transaction, + this.craftData(MakibesHR3Constants.CMD_SEND_NOTIFICATION, data)); + + return this; + } + + private MakibesHR3DeviceSupport setAlarmReminder(TransactionBuilder transaction, + int id, boolean enable, int hour, int minute, byte repeat) { + transaction.write(this.ctrlCharacteristic, + this.craftData(MakibesHR3Constants.CMD_SET_ALARM_REMINDER, new byte[]{ + (byte) id, + (byte) (enable ? 0x01 : 0x00), + (byte) hour, + (byte) minute, + repeat + })); + + return this; + } + + private MakibesHR3DeviceSupport setTimeMode(TransactionBuilder transaction) { + byte value = MakibesHR3Coordinator.getTimeMode(getDevice().getAddress()); + + byte[] data = this.craftData(MakibesHR3Constants.CMD_SET_TIMEMODE, new byte[]{value}); + + transaction.write(this.ctrlCharacteristic, data); + + return this; + } + + private MakibesHR3DeviceSupport setDateTime(TransactionBuilder transaction, + int year, + int month, + int day, + int hour, + int minute, + int second) { + + byte[] data = this.craftData(MakibesHR3Constants.CMD_SET_DATE_TIME, + new byte[]{ + (byte) 0x00, + (byte) (year & 0xff00), + (byte) (year & 0x00ff), + (byte) month, + (byte) day, + (byte) hour, + (byte) minute, + (byte) second + }); + + transaction.write(this.ctrlCharacteristic, data); + + return this; + } + + private MakibesHR3DeviceSupport setDateTime(TransactionBuilder transaction) { + + Calendar calendar = Calendar.getInstance(); + + return this.setDateTime(transaction, + calendar.get(Calendar.YEAR), + calendar.get(Calendar.MONTH) + 1, + calendar.get(Calendar.DAY_OF_MONTH), + calendar.get(Calendar.HOUR_OF_DAY), + calendar.get(Calendar.MINUTE), + calendar.get(Calendar.SECOND) + ); + } + + private MakibesHR3DeviceSupport reboot(TransactionBuilder transaction) { + transaction.write(this.ctrlCharacteristic, this.craftData(MakibesHR3Constants.CMD_REBOOT)); + + return this; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java index 1d6eb6f79..7b65757e1 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java @@ -45,6 +45,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.casiogb6900.CasioGB6900Devic import nodomain.freeyourgadget.gadgetbridge.devices.hplus.EXRIZUK8Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.hplus.MakibesF68Coordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3.MakibesHR3Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.hplus.Q8Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitcor.AmazfitCorCoordinator; @@ -228,6 +229,7 @@ public class DeviceHelper { result.add(new CasioGB6900DeviceCoordinator()); result.add(new BFH16DeviceCoordinator()); result.add(new MijiaLywsd02Coordinator()); + result.add(new MakibesHR3Coordinator()); return result; } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bccabd969..b19720dd4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -186,6 +186,8 @@ Screen on duration All day heart rate measurement HPlus/Makibes settings + + Makibes HR3 settings ID115 settings Screen orientation @@ -673,6 +675,7 @@ Mi Scale 2 BFH-16 Mijia Smart Clock + Makibes HR3 Choose export location Gadgetbridge notifications diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 0a7ea295e..f1f4ae71c 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -586,6 +586,27 @@ + + + + + + + + + +