1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-06-08 22:28:20 +02:00

very basic makibes hr3 support

This commit is contained in:
Cre3per 2019-10-02 22:57:31 +02:00 committed by Andreas Shimokawa
parent 8df99bf5bd
commit 7409281c5a
8 changed files with 961 additions and 0 deletions

View File

@ -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 <http://www.gnu.org/licenses/>. */
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";
}

View File

@ -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 <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3;
/*
* @author Alejandro Ladera Chamorro &lt;11555126+tiparega@users.noreply.github.com&gt;
*/
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<? extends Activity> getPairingActivity() {
return null;
}
@Override
public boolean supportsActivityDataFetching() {
return false;
}
@Override
public boolean supportsActivityTracking() {
return false;
}
@Override
public SampleProvider<? extends ActivitySample> 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<? extends Activity> getAppsManagementActivity() {
return null;
}
}

View File

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

View File

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

View File

@ -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<? extends Alarm> 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;
}
}

View File

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

View File

@ -186,6 +186,8 @@
<string name="pref_title_screentime">Screen on duration</string>
<string name="prefs_title_all_day_heart_rate">All day heart rate measurement</string>
<string name="preferences_hplus_settings">HPlus/Makibes settings</string>
<!-- Makibes HR3 Preferences -->
<string name="preferences_makibes_hr3_settings">Makibes HR3 settings</string>
<!-- ID115 Preferences -->
<string name="preferences_id115_settings">ID115 settings</string>
<string name="prefs_screen_orientation">Screen orientation</string>
@ -673,6 +675,7 @@
<string name="devicetype_miscale2">Mi Scale 2</string>
<string name="devicetype_bfh16">BFH-16</string>
<string name="devicetype_mijia_lywsd02">Mijia Smart Clock</string>
<string name="devicetype_makibes_hr3">Makibes HR3</string>
<string name="choose_auto_export_location">Choose export location</string>
<string name="notification_channel_name">Gadgetbridge notifications</string>
<!-- Menus on the smart device -->

View File

@ -586,6 +586,27 @@
</PreferenceCategory>
</PreferenceScreen>
<PreferenceScreen
android:icon="@drawable/ic_device_hplus"
android:key="pref_key_hplus"
android:title="@string/preferences_makibes_hr3_settings">
<PreferenceCategory
android:key="pref_category_makibes_hr3_general"
android:title="@string/pref_header_general">
<ListPreference
android:defaultValue="24h"
android:entries="@array/pref_timeformat_entries"
android:entryValues="@array/pref_timeformat_values"
android:key="makibes_hr3_timeformat"
android:title="@string/pref_title_timeformat"
android:summary="%s" />
</PreferenceCategory>
</PreferenceScreen>
<PreferenceScreen
android:icon="@drawable/ic_device_h30_h10"
android:key="pref_key_id115"