mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2025-02-16 12:26:47 +01:00
add support for Hama Fit6900 watch
This commit is contained in:
parent
7cafbc2002
commit
2ec568bec7
@ -0,0 +1,28 @@
|
||||
/*
|
||||
Copyright (C) 2024 enoint
|
||||
|
||||
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.hama.fit6900;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class HamaFit6900Constants {
|
||||
public static final UUID UUID_SERVICE_RXTX = UUID.fromString("c3e6fea0-e966-1000-8000-be99c223df6a");
|
||||
public static final UUID UUID_CHARACTERISTIC_TX = UUID.fromString("c3e6fea1-e966-1000-8000-be99c223df6a");
|
||||
public static final UUID UUID_CHARACTERISTIC_RX = UUID.fromString("c3e6fea2-e966-1000-8000-be99c223df6a");
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
/*
|
||||
Copyright (C) 2024 enoint
|
||||
|
||||
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.hama.fit6900;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.hama.fit6900.HamaFit6900DeviceSupport;
|
||||
|
||||
public final class HamaFit6900DeviceCoordinator extends AbstractBLEDeviceCoordinator {
|
||||
@Override
|
||||
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern getSupportedDeviceName() {
|
||||
return Pattern.compile("Fit6900");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBondingStyle() {
|
||||
return BONDING_STYLE_NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAlarmSlotCount(GBDevice device) {
|
||||
return 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsFindDevice() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||
return new int[]{
|
||||
R.xml.devicesettings_allow_accept_reject_calls, // reject only
|
||||
R.xml.devicesettings_camera_remote,
|
||||
R.xml.devicesettings_find_phone,
|
||||
R.xml.devicesettings_liftwrist_display_no_on,
|
||||
R.xml.devicesettings_notifications_enable,
|
||||
R.xml.devicesettings_timeformat,
|
||||
R.xml.devicesettings_transliteration,
|
||||
R.xml.devicesettings_donotdisturb_no_auto,
|
||||
R.xml.devicesettings_autoheartrate,
|
||||
R.xml.devicesettings_hydration_reminder
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedLanguageSettings(GBDevice device) {
|
||||
return new String[]{
|
||||
"auto",
|
||||
"en_US",
|
||||
"es_ES",
|
||||
"de_DE",
|
||||
"it_IT",
|
||||
"fr_FR",
|
||||
"sv_SE"
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturer() {
|
||||
return "Hama";
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Class<? extends DeviceSupport> getDeviceSupportClass() {
|
||||
return HamaFit6900DeviceSupport.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDeviceNameResource() {
|
||||
return R.string.devicetype_hama_fit6900;
|
||||
}
|
||||
}
|
@ -0,0 +1,440 @@
|
||||
/*
|
||||
Copyright (C) 2024 enoint
|
||||
|
||||
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.hama.fit6900;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||
|
||||
public final class Message {
|
||||
private static byte encodeBoolean(boolean value) {
|
||||
return (byte) ((value) ? 1 : 0);
|
||||
}
|
||||
|
||||
private static void encodeInt16(byte[] data, int offset, int value) {
|
||||
data[offset + 0] = (byte) (value & 0xFF);
|
||||
data[offset + 1] = (byte) ((value >> 8) & 0xFF);
|
||||
}
|
||||
|
||||
private static void encodeInt32(byte[] data, int offset, int value) {
|
||||
data[offset + 0] = (byte) (value & 0xFF);
|
||||
data[offset + 1] = (byte) ((value >> 8) & 0xFF);
|
||||
data[offset + 2] = (byte) ((value >> 16) & 0xFF);
|
||||
data[offset + 3] = (byte) ((value >> 24) & 0xFF);
|
||||
}
|
||||
|
||||
private static void encodeInt32Goal(byte[] data, int offset, int value) {
|
||||
encodeInt32(data, offset, value);
|
||||
data[offset + 3] = 0;
|
||||
}
|
||||
|
||||
private static byte encodeTimeFormat(TimeFormat tf) {
|
||||
return (byte) ((tf == TimeFormat.Format12H) ? 1 : 0);
|
||||
}
|
||||
|
||||
public static int decodeInt32(byte[] data, int offset) {
|
||||
return ((data[offset] & 0xFF) << 24) | ((data[offset + 1] & 0xFF) << 16) | ((data[offset + 2] & 0xFF) << 8) | (data[offset + 3] & 0xFF);
|
||||
}
|
||||
|
||||
private static byte[] byteArrayAdd(byte[] b1, byte[] b2) {
|
||||
if (b2 == null || b2.length == 0)
|
||||
return b1;
|
||||
|
||||
byte[] result = new byte[b1.length + b2.length];
|
||||
System.arraycopy(b1, 0, result, 0, b1.length);
|
||||
System.arraycopy(b2, 0, result, b1.length, b2.length);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static int calculateCrc(byte[] data) {
|
||||
int crc = 0xFF;
|
||||
for (byte b : data) {
|
||||
crc ^= b & 0xFF;
|
||||
for (int k = 0; k < 8; k++) {
|
||||
if ((crc & 1) != 0) {
|
||||
crc = (crc >> 1) ^ 184;
|
||||
} else {
|
||||
crc = (crc >> 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
private static final byte HEADER_MAGIC_NUMBER = (byte) 186;
|
||||
private static final byte PROTOCOL_VERSION_0 = (byte) 0;
|
||||
|
||||
private static byte[] encodeMessage(byte[] commandData) {
|
||||
// msgType | 1: single message or end of multi-part message, 3: one part of a multi-part message
|
||||
byte msgType = 1;
|
||||
byte unknown1 = 0; // 1 bit
|
||||
byte unknown2 = 0; // 4 bits
|
||||
int length = commandData.length;
|
||||
int crc = calculateCrc(commandData);
|
||||
int msgCounter = 0; // returned with the response
|
||||
|
||||
byte[] header = new byte[8];
|
||||
header[0] = HEADER_MAGIC_NUMBER;
|
||||
header[1] = (byte) ((msgType << 5) | (unknown1 << 4) | unknown2);
|
||||
header[2] = (byte) ((length >> 8) & 0xFF);
|
||||
header[3] = (byte) (length & 0xFF);
|
||||
header[4] = (byte) ((crc >> 8) & 0xFF);
|
||||
header[5] = (byte) (crc & 0xFF);
|
||||
header[6] = (byte) ((msgCounter >> 8) & 0xFF);
|
||||
header[7] = (byte) (msgCounter & 0xFF);
|
||||
|
||||
return byteArrayAdd(header, commandData);
|
||||
}
|
||||
|
||||
|
||||
private static byte[] encodeCommand(byte cmd, byte cmdKey, byte[] argsData) {
|
||||
int length = (argsData != null) ? argsData.length : 0;
|
||||
|
||||
byte[] header = new byte[5];
|
||||
header[0] = cmd;
|
||||
header[1] = PROTOCOL_VERSION_0;
|
||||
header[2] = cmdKey;
|
||||
header[3] = (byte) ((length >> 8) & 1);
|
||||
header[4] = (byte) (length & 0xFF);
|
||||
|
||||
return byteArrayAdd(header, argsData);
|
||||
}
|
||||
|
||||
public static byte[] encodeCommandMessage(int cmd, int cmdKey, byte[] cmdArgsData) {
|
||||
assert (cmd > 0);
|
||||
assert (cmd <= 0xFF);
|
||||
assert (cmdKey > 0);
|
||||
assert (cmdKey <= 0xFF);
|
||||
return encodeMessage(encodeCommand((byte) cmd, (byte) cmdKey, cmdArgsData));
|
||||
}
|
||||
|
||||
public static class CommandMessage { // decoded message
|
||||
public byte msgType;
|
||||
|
||||
public int cmd;
|
||||
public int key;
|
||||
public byte[] cmdArgs;
|
||||
}
|
||||
|
||||
|
||||
public static CommandMessage decodeCommandMessage(byte[] data) {
|
||||
final int MESSAGE_HEADER_SIZE = 8;
|
||||
final int COMMAND_HEADER_SIZE = 5;
|
||||
|
||||
if (data[0] != HEADER_MAGIC_NUMBER) {
|
||||
return null;
|
||||
}
|
||||
|
||||
byte messageType = (byte) (data[1] >> 5);
|
||||
int messageLength = ((data[2] & 0xFF) << 8) | (data[3] & 0xFF);
|
||||
int crcReceived = ((data[4] & 0xFF) << 8) | (data[5] & 0xFF);
|
||||
//int msgCounter = ((data[6] & 0xFF) << 8) | (data[7] & 0xFF);
|
||||
|
||||
if (data.length != MESSAGE_HEADER_SIZE + messageLength) {
|
||||
return null;
|
||||
}
|
||||
|
||||
{
|
||||
byte[] commandData = new byte[messageLength];
|
||||
System.arraycopy(data, MESSAGE_HEADER_SIZE, commandData, 0, messageLength);
|
||||
|
||||
int crc = calculateCrc(commandData);
|
||||
if (crc != crcReceived) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
int cmd = data[MESSAGE_HEADER_SIZE + 0] & 0xFF;
|
||||
int version = data[MESSAGE_HEADER_SIZE + 1] & 0xFF;
|
||||
int key = data[MESSAGE_HEADER_SIZE + 2] & 0xFF;
|
||||
int commandLength = ((data[MESSAGE_HEADER_SIZE + 3] & 1) << 8) | (data[MESSAGE_HEADER_SIZE + 4] & 0xFF);
|
||||
|
||||
if (version != PROTOCOL_VERSION_0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (data.length != MESSAGE_HEADER_SIZE + COMMAND_HEADER_SIZE + commandLength) {
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] commandArgs = new byte[commandLength];
|
||||
System.arraycopy(data, MESSAGE_HEADER_SIZE + COMMAND_HEADER_SIZE, commandArgs, 0, commandLength);
|
||||
|
||||
CommandMessage result = new CommandMessage();
|
||||
result.msgType = messageType;
|
||||
result.cmd = cmd;
|
||||
result.key = key;
|
||||
result.cmdArgs = commandArgs;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static byte[] encodeGetFirmwareVersion() {
|
||||
return encodeCommandMessage(1, 18, null);
|
||||
}
|
||||
|
||||
public static byte[] encodeGetBatteryStatus() {
|
||||
return encodeCommandMessage(4, 64, null);
|
||||
}
|
||||
|
||||
private static void encodeDateTime(byte[] data, int dataOffset, int year, int month, int day, int hour, int minute, int second) {
|
||||
data[dataOffset + 0] = (byte) ((year % 100) & 0xFF);
|
||||
data[dataOffset + 1] = (byte) (month & 0xFF);
|
||||
data[dataOffset + 2] = (byte) (day & 0xFF);
|
||||
data[dataOffset + 3] = (byte) (hour & 0xFF);
|
||||
data[dataOffset + 4] = (byte) (minute & 0xFF);
|
||||
data[dataOffset + 5] = (byte) (second & 0xFF);
|
||||
}
|
||||
|
||||
public enum TimeFormat {
|
||||
Format12H,
|
||||
Format24H
|
||||
}
|
||||
|
||||
public static byte[] encodeSetDateTime(Calendar dt, TimeFormat timeFormat) {
|
||||
byte[] args = new byte[7];
|
||||
encodeDateTime(args, 0, dt.get(Calendar.YEAR), dt.get(Calendar.MONTH) + 1,
|
||||
dt.get(Calendar.DAY_OF_MONTH), dt.get(Calendar.HOUR_OF_DAY), dt.get(Calendar.MINUTE), dt.get(Calendar.SECOND));
|
||||
// time format value is optional. shorter message is also accepted
|
||||
args[6] = encodeTimeFormat(timeFormat);
|
||||
return encodeCommandMessage(2, 32, args);
|
||||
}
|
||||
|
||||
public enum Gender {
|
||||
FEMALE,
|
||||
MALE
|
||||
}
|
||||
|
||||
public static byte[] encodeSetUserInfo(Gender gender, int age, int heightCm, int weightKg, int stepsGoal) {
|
||||
byte[] args = new byte[8];
|
||||
args[0] = (byte) ((gender == Gender.MALE) ? 1 : 0);
|
||||
args[1] = (byte) (age & 0xFF);
|
||||
args[2] = (byte) (heightCm & 0xFF);
|
||||
args[3] = (byte) (weightKg & 0xFF);
|
||||
encodeInt32Goal(args, 4, stepsGoal);
|
||||
|
||||
return encodeCommandMessage(2, 35, args);
|
||||
}
|
||||
|
||||
public static byte[] encodeFindDevice() {
|
||||
return encodeCommandMessage(5, 80, null);
|
||||
}
|
||||
|
||||
public enum NotificationType {
|
||||
INCOMING_CALL(0), // shows popup with hang up button
|
||||
|
||||
SMS(1),
|
||||
MQQ(2),
|
||||
WEIXIN(3),
|
||||
FACEBOOK(4),
|
||||
TWITTER(6),
|
||||
WHATSAPP(7),
|
||||
INSTAGRAM(8),
|
||||
LINKEDIN(9),
|
||||
|
||||
CALL_REJECT(15), // closes INCOMING_CALL notification
|
||||
CALL_ACCEPT(16), // closes INCOMING call notification
|
||||
|
||||
UNKNOWN(255);
|
||||
|
||||
private final byte value;
|
||||
|
||||
NotificationType(final int value) {
|
||||
this.value = (byte) value;
|
||||
}
|
||||
|
||||
public byte getValue() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] encodeShowNotification(NotificationType type, String text) {
|
||||
final int TEXT_LENGTH_MAX = 64;
|
||||
|
||||
text = text.trim();
|
||||
if (text.length() > TEXT_LENGTH_MAX) {
|
||||
text = StringUtils.truncate(text, TEXT_LENGTH_MAX);
|
||||
text = text.trim(); // trim again so text is centered on screen
|
||||
}
|
||||
|
||||
byte[] args = byteArrayAdd(new byte[]{type.getValue()}, text.getBytes(StandardCharsets.UTF_16LE));
|
||||
return encodeCommandMessage(6, 96, args);
|
||||
}
|
||||
|
||||
public static byte[] encodeSetAlarms(ArrayList<? extends Alarm> alarms) {
|
||||
final int ENTRY_COUNT = 5;
|
||||
final int ENTRY_SIZE = 5;
|
||||
|
||||
byte[] args = new byte[ENTRY_COUNT * ENTRY_SIZE];
|
||||
Arrays.fill(args, (byte) 0);
|
||||
|
||||
int offset = 0;
|
||||
for (Alarm alarm : alarms) {
|
||||
// When all properties of an alarm are 0, it will not be listed in the watch UI.
|
||||
// Disabled alarms with non-0 properties will be shown in disabled state.
|
||||
// Show only enabled:
|
||||
if (alarm.getEnabled() && !alarm.getUnused()) {
|
||||
args[offset + 0] = (byte) alarm.getHour();
|
||||
args[offset + 1] = (byte) alarm.getMinute();
|
||||
args[offset + 2] = (byte) alarm.getRepetition();
|
||||
args[offset + 3] = (byte) 1;
|
||||
args[offset + 4] = encodeBoolean(alarm.getEnabled());
|
||||
|
||||
offset += ENTRY_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
return encodeCommandMessage(2, 33, args);
|
||||
}
|
||||
|
||||
private static final Map<String, Integer> LANGUAGES = new HashMap<String, Integer>() {{
|
||||
put("en", 1);
|
||||
put("es", 3);
|
||||
put("de", 4);
|
||||
put("it", 5);
|
||||
put("fr", 6);
|
||||
put("sv", 18);
|
||||
|
||||
put("ru", 2);
|
||||
put("pt", 7);
|
||||
put("pl", 8);
|
||||
put("nl", 9);
|
||||
put("el", 10);
|
||||
put("tr", 11);
|
||||
put("ro", 12);
|
||||
put("ja", 13);
|
||||
put("he", 15);
|
||||
put("da", 16);
|
||||
put("sr", 17);
|
||||
put("cs", 19);
|
||||
put("sk", 20);
|
||||
put("hu", 21);
|
||||
put("ar", 22);
|
||||
put("bg", 23);
|
||||
put("th", 24);
|
||||
put("uk", 25);
|
||||
put("fi", 26);
|
||||
put("nb", 27);
|
||||
put("ko", 28);
|
||||
put("id", 29);
|
||||
put("lv", 30);
|
||||
put("lt", 31);
|
||||
put("et", 32);
|
||||
put("my", 33);
|
||||
put("vi", 34);
|
||||
put("hr", 35);
|
||||
}};
|
||||
|
||||
private static int resolveLanguageId(String language_, String country_) {
|
||||
String language = language_.toLowerCase();
|
||||
String country = country_.toLowerCase();
|
||||
|
||||
if (language.equals("zh")) {
|
||||
switch (country) {
|
||||
case "cn":
|
||||
return 0;
|
||||
case "tw":
|
||||
case "hk":
|
||||
case "mo":
|
||||
return 14;
|
||||
}
|
||||
} else if (language.equals("pt") && country.equals("br")) {
|
||||
return 36;
|
||||
} else {
|
||||
Integer languageId = LANGUAGES.get(language);
|
||||
if (languageId != null)
|
||||
return languageId;
|
||||
}
|
||||
|
||||
return LANGUAGES.get("en");
|
||||
}
|
||||
|
||||
public static byte[] encodeSetSystemData(String lang, String country, TimeFormat timeFormat) {
|
||||
byte[] args = new byte[4];
|
||||
args[0] = (byte) resolveLanguageId(lang, country);
|
||||
args[1] = encodeTimeFormat(timeFormat);
|
||||
args[2] = (byte) 60; // screen. unclear
|
||||
args[3] = (byte) 0; // pair. 1 has no effect
|
||||
|
||||
return encodeCommandMessage(2, 39, args);
|
||||
}
|
||||
|
||||
public static byte[] encodeSetDoNotDisturb(boolean enable, int startHour, int startMinute, int endHour, int endMinute) {
|
||||
byte[] args = new byte[5];
|
||||
args[0] = encodeBoolean(enable);
|
||||
args[1] = (byte) startHour;
|
||||
args[2] = (byte) startMinute;
|
||||
args[3] = (byte) endHour;
|
||||
args[4] = (byte) endMinute;
|
||||
|
||||
return encodeCommandMessage(6, 100, args);
|
||||
}
|
||||
|
||||
public static byte[] encodeSetUnit(boolean isMetric) {
|
||||
byte[] args = new byte[2];
|
||||
args[0] = args[1] = (byte) ((isMetric) ? 0 : 1); // 0: metric, 1: imperial
|
||||
return encodeCommandMessage(2, 1, args);
|
||||
}
|
||||
|
||||
public static byte[] encodeSetLiftWristDisplayOn(boolean enable) {
|
||||
byte[] args = new byte[3];
|
||||
args[0] = (byte) 1; // hand
|
||||
args[1] = encodeBoolean(enable); // raise
|
||||
args[2] = encodeBoolean(enable); // wrist
|
||||
|
||||
return encodeCommandMessage(4, 74, args);
|
||||
}
|
||||
|
||||
public static byte[] encodeSetAutoHeartRate(boolean enable, int startHour, int startMinute, int endHour, int endMinute, int intervalMinutes) {
|
||||
byte[] args = new byte[7];
|
||||
args[0] = encodeBoolean(enable);
|
||||
args[1] = (byte) startHour;
|
||||
args[2] = (byte) startMinute;
|
||||
args[3] = (byte) endHour;
|
||||
args[4] = (byte) endMinute;
|
||||
encodeInt16(args, 5, intervalMinutes);
|
||||
|
||||
return encodeCommandMessage(9, 146, args);
|
||||
}
|
||||
|
||||
public static byte[] encodeSetHydrationReminder(boolean enable, int startHour, int startMinute, int endHour, int endMinute, int intervalMinutes) {
|
||||
byte[] args = new byte[8];
|
||||
args[0] = encodeBoolean(enable);
|
||||
args[1] = (byte) startHour;
|
||||
args[2] = (byte) startMinute;
|
||||
args[3] = (byte) endHour;
|
||||
args[4] = (byte) endMinute;
|
||||
args[5] = (byte) 0x7F; // repeat: bitmask for days of week, bit 0=Monday
|
||||
encodeInt16(args, 6, intervalMinutes);
|
||||
|
||||
return encodeCommandMessage(2, 40, args);
|
||||
}
|
||||
|
||||
public static byte[] encodeFactoryReset() {
|
||||
return encodeCommandMessage(2, 199, null);
|
||||
}
|
||||
}
|
@ -74,6 +74,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.vivoactive.Ga
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.vivomove.GarminVivomoveHrCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.vivomove.GarminVivomoveStyleCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.vivosmart.GarminVivosmart5Coordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.hama.fit6900.HamaFit6900DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.EXRIZUK8Coordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.MakibesF68Coordinator;
|
||||
@ -436,6 +437,7 @@ public enum DeviceType {
|
||||
SONY_WENA_3(SonyWena3Coordinator.class),
|
||||
FEMOMETER_VINCA2(FemometerVinca2DeviceCoordinator.class),
|
||||
PIXOO(PixooCoordinator.class),
|
||||
HAMA_FIT6900(HamaFit6900DeviceCoordinator.class),
|
||||
SCANNABLE(ScannableDeviceCoordinator.class),
|
||||
CYCLING_SENSOR(CyclingSensorCoordinator.class),
|
||||
TEST(TestDeviceCoordinator.class);
|
||||
|
@ -0,0 +1,625 @@
|
||||
/*
|
||||
Copyright (C) 2024 enoint
|
||||
|
||||
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.service.devices.hama.fit6900;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_FIND_PHONE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_FIND_PHONE_DURATION;
|
||||
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Handler;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCameraRemote;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.hama.fit6900.HamaFit6900Constants;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Locale;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.hama.fit6900.Message;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||
|
||||
public final class HamaFit6900DeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HamaFit6900DeviceSupport.class);
|
||||
|
||||
private BluetoothGattCharacteristic writeCharacteristic;
|
||||
|
||||
private int notificationCount = 0;
|
||||
private final Handler findPhoneStopNotificationHandler = new Handler();
|
||||
|
||||
public HamaFit6900DeviceSupport() {
|
||||
super(LOG);
|
||||
addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE);
|
||||
addSupportedService(HamaFit6900Constants.UUID_SERVICE_RXTX);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionBuilder initializeDevice(TransactionBuilder builder) {
|
||||
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
|
||||
|
||||
writeCharacteristic = getCharacteristic(HamaFit6900Constants.UUID_CHARACTERISTIC_TX);
|
||||
|
||||
builder.notify(getCharacteristic(HamaFit6900Constants.UUID_CHARACTERISTIC_RX), true);
|
||||
builder.setCallback(this);
|
||||
|
||||
builder.write(writeCharacteristic, Message.encodeGetBatteryStatus());
|
||||
builder.write(writeCharacteristic, Message.encodeGetFirmwareVersion());
|
||||
|
||||
if (GBApplication.getPrefs().getBoolean("datetime_synconconnect", true)) {
|
||||
builder.write(writeCharacteristic, makeSetDateTimeMessage());
|
||||
}
|
||||
// sync all preferences to device
|
||||
builder.write(writeCharacteristic, makeSetSystemDataMessage());
|
||||
builder.write(writeCharacteristic, makeSetUnitMessage());
|
||||
builder.write(writeCharacteristic, makeSetUserInfoMessage());
|
||||
builder.write(writeCharacteristic, makeSetAutoHeartRate());
|
||||
builder.write(writeCharacteristic, makeSetDoNotDisturbMessage());
|
||||
builder.write(writeCharacteristic, makeSetHydrationReminderMessage());
|
||||
builder.write(writeCharacteristic, makeSetLiftWristMessage());
|
||||
builder.write(writeCharacteristic, Message.encodeSetAlarms(new ArrayList(DBHelper.getAlarms(gbDevice))));
|
||||
|
||||
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendConfiguration(final String config) {
|
||||
switch (config) {
|
||||
case DeviceSettingsPreferenceConst.PREF_LANGUAGE:
|
||||
case DeviceSettingsPreferenceConst.PREF_TIMEFORMAT:
|
||||
sendMessage("update-language+timeformat", makeSetSystemDataMessage());
|
||||
return;
|
||||
|
||||
case SettingsActivity.PREF_MEASUREMENT_SYSTEM:
|
||||
sendMessage("update-units", makeSetUnitMessage());
|
||||
return;
|
||||
|
||||
case ActivityUser.PREF_USER_WEIGHT_KG:
|
||||
case ActivityUser.PREF_USER_GENDER:
|
||||
case ActivityUser.PREF_USER_HEIGHT_CM:
|
||||
case ActivityUser.PREF_USER_YEAR_OF_BIRTH:
|
||||
case DeviceSettingsPreferenceConst.PREF_USER_FITNESS_GOAL:
|
||||
sendMessage("update-user-info", makeSetUserInfoMessage());
|
||||
return;
|
||||
|
||||
case DeviceSettingsPreferenceConst.PREF_AUTOHEARTRATE_SWITCH:
|
||||
case DeviceSettingsPreferenceConst.PREF_AUTOHEARTRATE_START:
|
||||
case DeviceSettingsPreferenceConst.PREF_AUTOHEARTRATE_END:
|
||||
case DeviceSettingsPreferenceConst.PREF_AUTOHEARTRATE_INTERVAL: {
|
||||
byte[] msg = makeSetAutoHeartRate();
|
||||
if (msg != null) {
|
||||
sendMessage("update-auto-heart-rate", msg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO:
|
||||
case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO_START:
|
||||
case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO_END: {
|
||||
byte[] msg = makeSetDoNotDisturbMessage();
|
||||
if (msg != null)
|
||||
sendMessage("update-do-not-disturb", msg);
|
||||
return;
|
||||
}
|
||||
|
||||
case DeviceSettingsPreferenceConst.PREF_HYDRATION_SWITCH:
|
||||
case DeviceSettingsPreferenceConst.PREF_HYDRATION_PERIOD: {
|
||||
byte[] msg = makeSetHydrationReminderMessage();
|
||||
if (msg != null)
|
||||
sendMessage("update-hydration-reminder", msg);
|
||||
return;
|
||||
}
|
||||
|
||||
case DeviceSettingsPreferenceConst.PREF_ACTIVATE_DISPLAY_ON_LIFT:
|
||||
sendMessage("update-lift-wrist", makeSetLiftWristMessage());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCharacteristicChanged(BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic) {
|
||||
if (super.onCharacteristicChanged(gatt, characteristic)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!characteristic.getUuid().equals(HamaFit6900Constants.UUID_CHARACTERISTIC_RX)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
byte[] receivedData = characteristic.getValue();
|
||||
|
||||
Message.CommandMessage cmdMsg = Message.decodeCommandMessage(receivedData);
|
||||
if (cmdMsg == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final byte[] cmdArgs = cmdMsg.cmdArgs;
|
||||
switch (cmdMsg.cmd) {
|
||||
case 1:
|
||||
switch (cmdMsg.key) {
|
||||
case 19: // response to GetFirmwareVersion
|
||||
final String fwVersion = String.format("%d.%d.%d", cmdArgs[0] & 0xFF, cmdArgs[1] & 0xFF, cmdArgs[2] & 0xFF);
|
||||
// data[3]: bracelet type
|
||||
// data[>3]: ?
|
||||
handleFirmwareVersion(fwVersion);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
switch (cmdMsg.key) {
|
||||
case 1: // response to SetUnit; no args
|
||||
case 32: // response to SetDateTime; no args
|
||||
case 33: // response to SetAlarms; no args
|
||||
case 35: // response to SetUserInfo; no args
|
||||
case 39: // response to SetSystemData; no args
|
||||
case 40: // response to SetHydrationReminder; no args
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 4:
|
||||
switch (cmdMsg.key) {
|
||||
case 70: // notification msg: camera - open
|
||||
handleCameraRemote(GBDeviceEventCameraRemote.Event.OPEN_CAMERA);
|
||||
return true;
|
||||
case 71: // notification msg: camera - capture
|
||||
handleCameraRemote(GBDeviceEventCameraRemote.Event.TAKE_PICTURE);
|
||||
return true;
|
||||
case 72: // notification msg: camera - close
|
||||
handleCameraRemote(GBDeviceEventCameraRemote.Event.CLOSE_CAMERA);
|
||||
return true;
|
||||
|
||||
case 74: // response to SetLiftWriteDisplayOn; no args
|
||||
return true;
|
||||
|
||||
case 65: // response to GetBatteryStatus
|
||||
handleBatteryStatus(cmdArgs);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 5:
|
||||
switch (cmdMsg.key) {
|
||||
case 80: // response to FindDevice; length=1, [0]
|
||||
return true;
|
||||
case 81: // notification msg: find phone; watch sends no notification to stop it
|
||||
handleFindPhone(true);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 6:
|
||||
switch (cmdMsg.key) {
|
||||
case 96: // response to ShowNotification; no args
|
||||
case 100: // response to SetDoNotDisturb; no args
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 9:
|
||||
switch (cmdMsg.key) {
|
||||
case 146: // response to SetAutoHeartRate; no args
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 10:
|
||||
switch (cmdMsg.key) {
|
||||
case 171: // notification msg: heart rate update
|
||||
// int heartRate = cmdArgs[0] & 0xFF;
|
||||
break;
|
||||
case 172: // notification msg: steps update
|
||||
// int32 steps; float calories [kCal]; float distance [km]
|
||||
//int steps = Message.decodeInt32(cmdArgs, 0);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 13:
|
||||
switch (cmdMsg.key) {
|
||||
case 2: // notification msg: hang up call
|
||||
// in case of ShowNotification(INCOMING_CALL,..) the watch shows a hang up
|
||||
// button which triggers this notification. Accepting a call is not supported.
|
||||
handleCallReject();
|
||||
return true;
|
||||
|
||||
case 4: // notification msg: media player - play
|
||||
case 5: // notification msg: media player - pause
|
||||
// Watch only shows a single play/pause button and has no idea of media player playing state.
|
||||
// So just toggle between play and pause.
|
||||
handleMusicControl(GBDeviceEventMusicControl.Event.PLAYPAUSE);
|
||||
return true;
|
||||
case 6: // notification msg: media player - previous
|
||||
handleMusicControl(GBDeviceEventMusicControl.Event.PREVIOUS);
|
||||
return true;
|
||||
case 7: // notification msg: media player - next
|
||||
handleMusicControl(GBDeviceEventMusicControl.Event.NEXT);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
//LOG.debug(" command {},{} | NOT HANDLED", cmdMsg.cmd, cmdMsg.key);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotification(NotificationSpec notificationSpec) {
|
||||
if (!getDevicePrefsNotificationEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Message.NotificationType type = Message.NotificationType.UNKNOWN;
|
||||
switch (notificationSpec.type) {
|
||||
case FACEBOOK:
|
||||
case FACEBOOK_MESSENGER:
|
||||
type = Message.NotificationType.FACEBOOK;
|
||||
break;
|
||||
case GENERIC_SMS:
|
||||
type = Message.NotificationType.SMS;
|
||||
break;
|
||||
case INSTAGRAM:
|
||||
type = Message.NotificationType.INSTAGRAM;
|
||||
break;
|
||||
case LINKEDIN:
|
||||
type = Message.NotificationType.LINKEDIN;
|
||||
break;
|
||||
case TWITTER:
|
||||
type = Message.NotificationType.TWITTER;
|
||||
break;
|
||||
case WHATSAPP:
|
||||
type = Message.NotificationType.WHATSAPP;
|
||||
break;
|
||||
}
|
||||
final String notificationMsg = StringUtils.getFirstOf(notificationSpec.sender, notificationSpec.title);
|
||||
|
||||
final String uniqueTaskName = "notification" + notificationCount;
|
||||
notificationCount++;
|
||||
|
||||
sendMessage(uniqueTaskName, Message.encodeShowNotification(type, notificationMsg));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetTime() {
|
||||
sendMessage("set-datetime", makeSetDateTimeMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
|
||||
sendMessage("set-alarms", Message.encodeSetAlarms(alarms));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetCallState(CallSpec callSpec) {
|
||||
switch (callSpec.command) {
|
||||
case CallSpec.CALL_INCOMING: {
|
||||
if (getDevicePrefsNotificationEnabled()) {
|
||||
final String text = StringUtils.getFirstOf(callSpec.name, callSpec.number);
|
||||
sendMessage("notification-call-incoming",
|
||||
Message.encodeShowNotification(Message.NotificationType.INCOMING_CALL, text));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CallSpec.CALL_ACCEPT:
|
||||
// aborts INCOMING_CALL notification
|
||||
sendMessage("notification-call-accept",
|
||||
Message.encodeShowNotification(Message.NotificationType.CALL_ACCEPT, ""));
|
||||
break;
|
||||
case CallSpec.CALL_REJECT:
|
||||
case CallSpec.CALL_END:
|
||||
// aborts INCOMING_CALL notification
|
||||
sendMessage("notification-call-reject",
|
||||
Message.encodeShowNotification(Message.NotificationType.CALL_REJECT, ""));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFindDevice(boolean start) {
|
||||
if (start) {
|
||||
sendMessage("find-device", Message.encodeFindDevice());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useAutoConnect() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean sendMessage(String taskName, byte[] message) {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized(taskName);
|
||||
builder.write(writeCharacteristic, message);
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException ex) {
|
||||
LOG.error(taskName, ex);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean getDevicePrefsNotificationEnabled() {
|
||||
final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
|
||||
return prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_NOTIFICATION_ENABLE, false);
|
||||
}
|
||||
|
||||
private Message.TimeFormat getDevicePrefsTimeFormat() {
|
||||
GBPrefs gbPrefs = new GBPrefs(new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress())));
|
||||
Message.TimeFormat timeFormat = null;
|
||||
switch (gbPrefs.getTimeFormat()) {
|
||||
case DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_24H:
|
||||
timeFormat = Message.TimeFormat.Format24H;
|
||||
break;
|
||||
case DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_12H:
|
||||
timeFormat = Message.TimeFormat.Format12H;
|
||||
break;
|
||||
}
|
||||
return timeFormat;
|
||||
}
|
||||
|
||||
private byte[] makeSetDateTimeMessage() {
|
||||
return Message.encodeSetDateTime(Calendar.getInstance(), getDevicePrefsTimeFormat());
|
||||
}
|
||||
|
||||
private byte[] makeSetSystemDataMessage() {
|
||||
final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
|
||||
final String localeString = prefs.getString(DeviceSettingsPreferenceConst.PREF_LANGUAGE, DeviceSettingsPreferenceConst.PREF_LANGUAGE_AUTO);
|
||||
|
||||
String language;
|
||||
String country;
|
||||
if (localeString.equals(DeviceSettingsPreferenceConst.PREF_LANGUAGE_AUTO)) {
|
||||
language = Locale.getDefault().getLanguage();
|
||||
country = Locale.getDefault().getCountry();
|
||||
} else {
|
||||
language = localeString.substring(0, 2);
|
||||
country = localeString.substring(3, 5);
|
||||
}
|
||||
|
||||
return Message.encodeSetSystemData(language, country, getDevicePrefsTimeFormat());
|
||||
}
|
||||
|
||||
private byte[] makeSetUnitMessage() {
|
||||
final Prefs prefs = GBApplication.getPrefs();
|
||||
String unit = prefs.getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, "metric");
|
||||
|
||||
return Message.encodeSetUnit(unit.equals("metric"));
|
||||
}
|
||||
|
||||
private byte[] makeSetUserInfoMessage() {
|
||||
final ActivityUser activityUser = new ActivityUser();
|
||||
return Message.encodeSetUserInfo(
|
||||
(activityUser.getGender() == ActivityUser.GENDER_MALE) ? Message.Gender.MALE : Message.Gender.FEMALE,
|
||||
activityUser.getAge(),
|
||||
activityUser.getHeightCm(),
|
||||
activityUser.getWeightKg(),
|
||||
activityUser.getStepsGoal());
|
||||
}
|
||||
|
||||
private byte[] makeSetDoNotDisturbMessage() {
|
||||
final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
|
||||
|
||||
final String enabled = prefs.getString(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO, "off");
|
||||
if (!enabled.equals(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_SCHEDULED)) {
|
||||
return Message.encodeSetDoNotDisturb(false, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
final String start = prefs.getString(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO_START, "22:00");
|
||||
final String end = prefs.getString(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO_END, "6:00");
|
||||
|
||||
final Calendar startCalendar = GregorianCalendar.getInstance();
|
||||
final Calendar endCalendar = GregorianCalendar.getInstance();
|
||||
final DateFormat df = new SimpleDateFormat("HH:mm");
|
||||
|
||||
try {
|
||||
startCalendar.setTime(df.parse(start));
|
||||
endCalendar.setTime(df.parse(end));
|
||||
} catch (ParseException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Message.encodeSetDoNotDisturb(true, startCalendar.get(Calendar.HOUR_OF_DAY), startCalendar.get(Calendar.MINUTE),
|
||||
endCalendar.get(Calendar.HOUR_OF_DAY), endCalendar.get(Calendar.MINUTE));
|
||||
}
|
||||
|
||||
private byte[] makeSetAutoHeartRate() {
|
||||
final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
|
||||
|
||||
final boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_AUTOHEARTRATE_SWITCH, false);
|
||||
if (!enabled) {
|
||||
return Message.encodeSetAutoHeartRate(false, 0, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
// PREF_AUTOHEARTRATE_SLEEP is not supported
|
||||
final String start = prefs.getString(DeviceSettingsPreferenceConst.PREF_AUTOHEARTRATE_START, "22:00");
|
||||
final String end = prefs.getString(DeviceSettingsPreferenceConst.PREF_AUTOHEARTRATE_END, "6:00");
|
||||
final String intervalStr = prefs.getString(DeviceSettingsPreferenceConst.PREF_AUTOHEARTRATE_INTERVAL, "2");
|
||||
|
||||
final Calendar startCalendar = GregorianCalendar.getInstance();
|
||||
final Calendar endCalendar = GregorianCalendar.getInstance();
|
||||
final DateFormat df = new SimpleDateFormat("HH:mm");
|
||||
final int intervalMinutes;
|
||||
|
||||
try {
|
||||
startCalendar.setTime(df.parse(start));
|
||||
endCalendar.setTime(df.parse(end));
|
||||
} catch (ParseException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
intervalMinutes = Integer.parseInt(intervalStr);
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Message.encodeSetAutoHeartRate(true, startCalendar.get(Calendar.HOUR_OF_DAY), startCalendar.get(Calendar.MINUTE),
|
||||
endCalendar.get(Calendar.HOUR_OF_DAY), endCalendar.get(Calendar.MINUTE), intervalMinutes);
|
||||
}
|
||||
|
||||
private byte[] makeSetHydrationReminderMessage() {
|
||||
final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
|
||||
|
||||
boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HYDRATION_SWITCH, false);
|
||||
final String intervalStr = prefs.getString(DeviceSettingsPreferenceConst.PREF_HYDRATION_PERIOD, "60");
|
||||
int intervalMinutes;
|
||||
try {
|
||||
intervalMinutes = Integer.parseInt(intervalStr);
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (intervalMinutes == 0) {
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
// Drink reminder notifications honor the do-not-disturb time range.
|
||||
// Start time must be > then end time; e.g. start=19, end=1 won't work.
|
||||
// enable=true and start=end=0 behaves as disabled.
|
||||
|
||||
int startHour = 0;
|
||||
int startMin = 0;
|
||||
int endHour = 23;
|
||||
int endMin = 59;
|
||||
|
||||
return Message.encodeSetHydrationReminder(enabled, startHour, startMin, endHour, endMin, intervalMinutes);
|
||||
}
|
||||
|
||||
private byte[] makeSetLiftWristMessage() {
|
||||
final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
|
||||
final String enabled = prefs.getString(DeviceSettingsPreferenceConst.PREF_ACTIVATE_DISPLAY_ON_LIFT, "off");
|
||||
|
||||
final boolean isEnabled = !enabled.equals("off");
|
||||
return Message.encodeSetLiftWristDisplayOn(isEnabled);
|
||||
}
|
||||
|
||||
private void handleFirmwareVersion(String fwVersion) {
|
||||
final GBDeviceEventVersionInfo event = new GBDeviceEventVersionInfo();
|
||||
event.fwVersion = fwVersion;
|
||||
event.fwVersion2 = null;
|
||||
event.hwVersion = null;
|
||||
evaluateGBDeviceEvent(event);
|
||||
}
|
||||
|
||||
private void handleCallReject() {
|
||||
final GBDeviceEventCallControl event = new GBDeviceEventCallControl();
|
||||
event.event = GBDeviceEventCallControl.Event.REJECT;
|
||||
evaluateGBDeviceEvent(event);
|
||||
}
|
||||
|
||||
private void handleCameraRemote(GBDeviceEventCameraRemote.Event eventType) {
|
||||
final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
|
||||
if (!prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_CAMERA_REMOTE, false))
|
||||
return;
|
||||
|
||||
final GBDeviceEventCameraRemote event = new GBDeviceEventCameraRemote();
|
||||
event.event = eventType;
|
||||
evaluateGBDeviceEvent(event);
|
||||
}
|
||||
|
||||
private void handleMusicControl(GBDeviceEventMusicControl.Event eventType) {
|
||||
final GBDeviceEventMusicControl event = new GBDeviceEventMusicControl();
|
||||
event.event = eventType;
|
||||
evaluateGBDeviceEvent(event);
|
||||
}
|
||||
|
||||
private void handleFindPhone(boolean start) {
|
||||
if (start) {
|
||||
SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
|
||||
String findPhone = sharedPreferences.getString(PREF_FIND_PHONE, getContext().getString(R.string.p_off));
|
||||
if (findPhone.equals("off"))
|
||||
return;
|
||||
|
||||
String durationSecStr = sharedPreferences.getString(PREF_FIND_PHONE_DURATION, "");
|
||||
int durationSec;
|
||||
try {
|
||||
durationSec = Integer.parseInt(durationSecStr);
|
||||
} catch (Exception ex) {
|
||||
durationSec = 60;
|
||||
}
|
||||
if (durationSec <= 0)
|
||||
return;
|
||||
|
||||
GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
|
||||
findPhoneEvent.event = GBDeviceEventFindPhone.Event.START;
|
||||
evaluateGBDeviceEvent(findPhoneEvent);
|
||||
|
||||
try {
|
||||
this.findPhoneStopNotificationHandler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handleFindPhone(false);
|
||||
}
|
||||
}, durationSec * 1000);
|
||||
|
||||
} catch (Exception ex) {
|
||||
handleFindPhone(false);
|
||||
}
|
||||
} else {
|
||||
GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
|
||||
findPhoneEvent.event = GBDeviceEventFindPhone.Event.STOP;
|
||||
evaluateGBDeviceEvent(findPhoneEvent);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleBatteryStatus(byte[] cmdArgs) {
|
||||
/*
|
||||
final int level = cmdArgs[0] & 0xFF;
|
||||
final int type = cmdArgs[1] & 0xFF;
|
||||
|
||||
Disabled since watch always returns a level value of 60
|
||||
Also missing: status needs to be polled regularly
|
||||
|
||||
GBDeviceEventBatteryInfo event = new GBDeviceEventBatteryInfo();
|
||||
event.level = level;
|
||||
evaluateGBDeviceEvent(event); */
|
||||
}
|
||||
}
|
@ -1618,6 +1618,7 @@
|
||||
<string name="devicetype_sg2">Lemfo SG2</string>
|
||||
<string name="devicetype_lefun">Lefun</string>
|
||||
<string name="devicetype_bohemic_smart_bracelet">Bohemic Smart Bracelet</string>
|
||||
<string name="devicetype_hama_fit6900">Hama Fit6900</string>
|
||||
<!-- Menus on the smart device -->
|
||||
<string name="menuitem_nothing">Nothing</string>
|
||||
<string name="menuitem_status">Status</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user