mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-06-09 22:57:54 +02:00
1560 lines
70 KiB
Java
1560 lines
70 KiB
Java
/* Copyright (C) 2016-2020 Petr Vaněk
|
|
|
|
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.fitpro;
|
|
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_ALARM;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_DND;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_FIND_BAND;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_GET_HW_INFO;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_GROUP_BAND_INFO;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_GROUP_BIND;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_GROUP_GENERAL;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_GROUP_HEARTRATE_SETTINGS;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_GROUP_RECEIVE_BUTTON_DATA;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_GROUP_RECEIVE_SPORTS_DATA;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_GROUP_REQUEST_DATA;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_GROUP_RESET;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_HEART_RATE_MEASUREMENT;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_NOTIFICATIONS_ENABLE;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_NOTIFICATION_CALL;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_NOTIFICATION_MESSAGE;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_REQUEST_STEPS_DATA0x10;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_REQUEST_STEPS_DATA0x7;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_REQUEST_STEPS_DATA0x8;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_REQUEST_STEPS_DATA1;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_RESET;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_RX_BAND_INFO;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_SET_ARM;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_SET_DATE_TIME;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_SET_DEVICE_VIBRATIONS;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_SET_DISPLAY_ON_LIFT;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_SET_LANGUAGE;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_SET_LONG_SIT_REMINDER;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_SET_SLEEP_TIMES;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_SET_STEP_GOAL;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_SET_USER_DATA;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_UNBIND;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_WEATHER;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.GENDER_FEMALE;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.GENDER_MALE;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.NOTIFICATION_ICON_FACEBOOK;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.NOTIFICATION_ICON_INSTAGRAM;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.NOTIFICATION_ICON_LINE;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.NOTIFICATION_ICON_QQ;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.NOTIFICATION_ICON_SMS;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.NOTIFICATION_ICON_TWITTER;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.NOTIFICATION_ICON_WECHAT;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.NOTIFICATION_ICON_WHATSAPP;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.RX_CAMERA1;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.RX_CAMERA2;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.RX_CAMERA3;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.RX_FIND_PHONE;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.RX_HEART_RATE_DATA;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.RX_MEDIA_BACK;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.RX_MEDIA_FORW;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.RX_MEDIA_PLAY_PAUSE;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.RX_SLEEP_DATA;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.RX_SPORTS_DAY_DATA;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.RX_STEP_DATA;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.UNIT_IMPERIAL;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.UNIT_METRIC;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.UUID_CHARACTERISTIC_RX;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.UUID_CHARACTERISTIC_TX;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.VALUE_OFF;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.VALUE_ON;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.VALUE_SET_ARM_LEFT;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.VALUE_SET_ARM_RIGHT;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.VALUE_SET_DEVICE_VIBRATIONS_DISABLE;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.VALUE_SET_DEVICE_VIBRATIONS_ENABLE;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.VALUE_SET_LONG_SIT_REMINDER_OFF;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.VALUE_SET_LONG_SIT_REMINDER_ON;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.VALUE_SET_NOTIFICATIONS_ENABLE_OFF;
|
|
import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.VALUE_SET_NOTIFICATIONS_ENABLE_ON;
|
|
|
|
import android.bluetooth.BluetoothGatt;
|
|
import android.bluetooth.BluetoothGattCharacteristic;
|
|
import android.content.Intent;
|
|
import android.net.Uri;
|
|
import android.widget.Toast;
|
|
|
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|
|
|
import org.apache.commons.lang3.ArrayUtils;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.IOException;
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.text.DateFormat;
|
|
import java.text.ParseException;
|
|
import java.text.SimpleDateFormat;
|
|
import java.util.ArrayList;
|
|
import java.util.Calendar;
|
|
import java.util.Date;
|
|
import java.util.GregorianCalendar;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.UUID;
|
|
|
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
|
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
|
import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
|
|
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
|
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
|
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
|
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
|
|
import nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants;
|
|
import nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProSampleProvider;
|
|
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
|
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
|
import nodomain.freeyourgadget.gadgetbridge.entities.FitProActivitySample;
|
|
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
|
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
|
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
|
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
|
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
|
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
|
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
|
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
|
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
|
import nodomain.freeyourgadget.gadgetbridge.model.Weather;
|
|
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
|
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.SetDeviceBusyAction;
|
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
|
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener;
|
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfoProfile;
|
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo;
|
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile;
|
|
import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
|
|
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
|
|
|
public class FitProDeviceSupport extends AbstractBTLEDeviceSupport {
|
|
private static final Logger LOG = LoggerFactory.getLogger(FitProDeviceSupport.class);
|
|
public final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo();
|
|
public final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
|
|
public final DeviceInfoProfile<FitProDeviceSupport> deviceInfoProfile;
|
|
public final BatteryInfoProfile<FitProDeviceSupport> batteryInfoProfile;
|
|
|
|
public BluetoothGattCharacteristic readCharacteristic;
|
|
public BluetoothGattCharacteristic writeCharacteristic;
|
|
private static final boolean debugEnabled = false;
|
|
|
|
public FitProDeviceSupport() {
|
|
super(LOG);
|
|
addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION);
|
|
addSupportedService(GattService.UUID_SERVICE_BATTERY_SERVICE);
|
|
|
|
IntentListener mListener = new IntentListener() {
|
|
@Override
|
|
public void notify(Intent intent) {
|
|
String action = intent.getAction();
|
|
if (DeviceInfoProfile.ACTION_DEVICE_INFO.equals(action)) {
|
|
handleDeviceInfo((nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo) intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO));
|
|
} else if (BatteryInfoProfile.ACTION_BATTERY_INFO.equals(action)) {
|
|
handleBatteryInfo((nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfo) intent.getParcelableExtra(BatteryInfoProfile.EXTRA_BATTERY_INFO));
|
|
}
|
|
}
|
|
};
|
|
|
|
deviceInfoProfile = new DeviceInfoProfile<>(this);
|
|
deviceInfoProfile.addListener(mListener);
|
|
addSupportedProfile(deviceInfoProfile);
|
|
|
|
batteryInfoProfile = new BatteryInfoProfile<>(this);
|
|
batteryInfoProfile.addListener(mListener);
|
|
addSupportedProfile(batteryInfoProfile);
|
|
addSupportedService(FitProConstants.UUID_CHARACTERISTIC_RX);
|
|
addSupportedService(FitProConstants.UUID_CHARACTERISTIC_UART);
|
|
}
|
|
|
|
@Override
|
|
public TransactionBuilder initializeDevice(TransactionBuilder builder) {
|
|
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
|
|
readCharacteristic = getCharacteristic(UUID_CHARACTERISTIC_RX);
|
|
writeCharacteristic = getCharacteristic(UUID_CHARACTERISTIC_TX);
|
|
|
|
builder.notify(getCharacteristic(UUID_CHARACTERISTIC_RX), true);
|
|
builder.notify(getCharacteristic(GattService.UUID_SERVICE_BATTERY_SERVICE), true);
|
|
builder.setGattCallback(this);
|
|
|
|
deviceInfoProfile.requestDeviceInfo(builder);
|
|
batteryInfoProfile.requestBatteryInfo(builder);
|
|
batteryInfoProfile.enableNotify(builder, true);
|
|
deviceInfoProfile.enableNotify(builder, true);
|
|
|
|
// this sequence seems to be important as without it:
|
|
// - fetch steps doesn't work
|
|
// - band seems to drain battery really fast
|
|
// - the wait time is needed as the band must process each command
|
|
// - (implementation based on individual requests did not work, the wait is still needed)
|
|
|
|
builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, FitProConstants.CMD_INIT1, (byte) 0x2));
|
|
setTime(builder);
|
|
builder.wait(200);
|
|
builder.write(writeCharacteristic, craftData(CMD_GROUP_REQUEST_DATA, FitProConstants.CMD_INIT1));
|
|
builder.wait(200);
|
|
builder.write(writeCharacteristic, craftData(CMD_GROUP_REQUEST_DATA, FitProConstants.CMD_INIT2));
|
|
builder.wait(200);
|
|
setLanguage(builder);
|
|
builder.wait(200);
|
|
builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, FitProConstants.CMD_INIT3, VALUE_ON));
|
|
builder.wait(200);
|
|
builder.write(writeCharacteristic, craftData(CMD_GROUP_REQUEST_DATA, VALUE_ON));
|
|
builder.wait(200);
|
|
builder.write(writeCharacteristic, craftData(CMD_GROUP_REQUEST_DATA, (byte) 0xf));
|
|
builder.wait(200);
|
|
builder.write(writeCharacteristic, craftData(CMD_GROUP_REQUEST_DATA, CMD_GET_HW_INFO));
|
|
builder.wait(200);
|
|
builder.write(writeCharacteristic, craftData(CMD_GROUP_BAND_INFO, CMD_RX_BAND_INFO));
|
|
builder.wait(200);
|
|
|
|
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
|
|
return builder;
|
|
}
|
|
|
|
public void handleDeviceInfo(DeviceInfo info) {
|
|
LOG.debug("fitpro device info: " + info);
|
|
versionCmd.hwVersion = "FitPro";
|
|
versionCmd.fwVersion = info.getFirmwareRevision();
|
|
handleGBDeviceEvent(versionCmd);
|
|
}
|
|
|
|
public void handleDeviceInfo(byte[] value) {
|
|
LOG.debug("FitPro device info2");
|
|
//test this 0xCD 0x00 0x11 0x15 0x01 0x02 0x00 0x0C 0x2B 0x27 0x00 0x01 0x33 0xA5 0x02 0x79 0x0A 0x68 0x56 0x06
|
|
debugPrintArray(value, "Device info:");
|
|
if (value.length < 20) {
|
|
return;
|
|
}
|
|
int start = 14;
|
|
int data_len = (int) value[start];
|
|
|
|
byte[] name = new byte[data_len];
|
|
System.arraycopy(value, start + 1, name, 0, data_len);
|
|
String sName = new String(name, StandardCharsets.UTF_8); //unused for now
|
|
|
|
start = start + data_len + 1;
|
|
data_len = (int) value[start];
|
|
byte[] hwname = new byte[data_len];
|
|
System.arraycopy(value, start + 1, hwname, 0, data_len);
|
|
String sHWName = new String(hwname, StandardCharsets.UTF_8);
|
|
LOG.debug("Device info: " + versionCmd);
|
|
versionCmd.hwVersion = sHWName;
|
|
handleGBDeviceEvent(versionCmd);
|
|
}
|
|
|
|
public byte[] craftData(byte command_group, byte command, byte[] data) {
|
|
//0xCD 0x00 0x09 0x12 0x01 0x01 0x00 0x04 0xA5 0x83 0x73 0xDB
|
|
byte[] result = new byte[FitProConstants.DATA_TEMPLATE.length + data.length];
|
|
System.arraycopy(FitProConstants.DATA_TEMPLATE, 0, result, 0, FitProConstants.DATA_TEMPLATE.length);
|
|
result[2] = (byte) (FitProConstants.DATA_TEMPLATE.length + data.length - 3);
|
|
result[3] = command_group;
|
|
result[5] = command;
|
|
result[7] = (byte) data.length;
|
|
System.arraycopy(data, 0, result, 8, data.length);
|
|
//debug
|
|
debugPrintArray(result, "crafted packet");
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public boolean onCharacteristicChanged(BluetoothGatt gatt,
|
|
BluetoothGattCharacteristic characteristic) {
|
|
super.onCharacteristicChanged(gatt, characteristic);
|
|
UUID characteristicUUID = characteristic.getUuid();
|
|
byte[] data = characteristic.getValue();
|
|
debugPrintArray(data, "FitPro received value");
|
|
if (data[0] != FitProConstants.DATA_HEADER) {
|
|
if (debugEnabled) {
|
|
LOG.info("FitPro, packet not starting with 0xcd: " + data[0]);
|
|
debugPrintArray(new byte[]{data[0]}, "first byte");
|
|
LOG.info("Characteristic changed UUID: " + characteristicUUID);
|
|
LOG.info("Characteristic changed service: " + characteristic.getService().getCharacteristics());
|
|
debugPrintArray(data, "value bytes");
|
|
}
|
|
indicateFinishedFetchingOperation();
|
|
return false;
|
|
}
|
|
|
|
if (data != null && data.length > 5) {
|
|
byte command = data[3];
|
|
byte param = data[5];
|
|
|
|
switch (command) {
|
|
case CMD_GROUP_RECEIVE_BUTTON_DATA:
|
|
switch (param) {
|
|
case RX_FIND_PHONE:
|
|
handleFindPhone();
|
|
break;
|
|
case RX_MEDIA_BACK:
|
|
case RX_MEDIA_FORW:
|
|
case RX_MEDIA_PLAY_PAUSE:
|
|
handleMediaButton(param);
|
|
break;
|
|
case RX_CAMERA1:
|
|
case RX_CAMERA2:
|
|
case RX_CAMERA3:
|
|
handleCamera(param);
|
|
break;
|
|
default:
|
|
}
|
|
break;
|
|
case CMD_GROUP_RECEIVE_SPORTS_DATA:
|
|
switch (param) {
|
|
case RX_HEART_RATE_DATA:
|
|
handleHR(data);
|
|
break;
|
|
case RX_SPORTS_DAY_DATA:
|
|
indicateStartingFetchingOperation();
|
|
handleDayTotalsData(data);
|
|
indicateFinishedFetchingOperation();
|
|
break;
|
|
case RX_SLEEP_DATA:
|
|
indicateStartingFetchingOperation();
|
|
handleSleepData(data);
|
|
indicateFinishedFetchingOperation();
|
|
break;
|
|
case RX_STEP_DATA:
|
|
indicateStartingFetchingOperation();
|
|
handleStepData(data);
|
|
indicateFinishedFetchingOperation();
|
|
break;
|
|
case CMD_REQUEST_STEPS_DATA0x7:
|
|
case CMD_REQUEST_STEPS_DATA0x8:
|
|
case CMD_REQUEST_STEPS_DATA0x10:
|
|
//acking this makes the band to send data
|
|
sendAck(data[3], data[1], data[2], data[5]);
|
|
break;
|
|
}
|
|
break;
|
|
case CMD_GROUP_BAND_INFO:
|
|
switch (param) {
|
|
case CMD_RX_BAND_INFO:
|
|
handleDeviceInfo(data);
|
|
break;
|
|
}
|
|
sendAck(data[3], data[1], data[2], data[5]);
|
|
break;
|
|
case CMD_GROUP_REQUEST_DATA:
|
|
switch (param) {
|
|
case CMD_GET_HW_INFO:
|
|
handleHardwareDetails(data);
|
|
break;
|
|
}
|
|
sendAck(data[3], data[1], data[2], data[5]);
|
|
break;
|
|
}
|
|
|
|
LOG.info("Characteristic changed UUID: " + characteristicUUID);
|
|
LOG.info("Characteristic changed service: " + characteristic.getService().getCharacteristics());
|
|
debugPrintArray(data, "value bytes");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void indicateFinishedFetchingOperation() {
|
|
//LOG.debug("download finish announced");
|
|
GB.updateTransferNotification(null, "", false, 100, getContext());
|
|
GB.signalActivityDataFinish();
|
|
unsetBusy();
|
|
}
|
|
|
|
public void indicateStartingFetchingOperation() {
|
|
GB.updateTransferNotification(null, getContext().getString(R.string.busy_task_fetch_activity_data), true, 10, getContext());
|
|
}
|
|
|
|
protected void unsetBusy() {
|
|
if (getDevice().isBusy()) {
|
|
getDevice().unsetBusyTask();
|
|
getDevice().sendDeviceUpdateIntent(getContext());
|
|
}
|
|
}
|
|
|
|
public void handleHardwareDetails(byte[] value) {
|
|
LOG.debug("FitPro hardware details");
|
|
debugPrintArray(value, "Device info:");
|
|
if (value.length < 20) {
|
|
return;
|
|
}
|
|
int start = 8;
|
|
int data_len = (int) value[start];
|
|
|
|
byte[] led = new byte[data_len];
|
|
System.arraycopy(value, start + 1, led, 0, data_len);
|
|
String sLED = new String(led, StandardCharsets.UTF_8);
|
|
|
|
start = start + data_len + 1;
|
|
data_len = (int) value[start];
|
|
byte[] gsensor = new byte[data_len];
|
|
System.arraycopy(value, start + 1, gsensor, 0, data_len);
|
|
String sGsensor = new String(gsensor, StandardCharsets.UTF_8);
|
|
|
|
gbDevice.setFirmwareVersion2(sGsensor + " " + sLED);
|
|
|
|
//the band does not like to answer when asked together for both hw info, so ask now,
|
|
// after data is already received
|
|
|
|
TransactionBuilder builder = new TransactionBuilder("notification");
|
|
builder.write(writeCharacteristic, craftData(CMD_GROUP_BAND_INFO, CMD_RX_BAND_INFO));
|
|
builder.queue(getQueue());
|
|
|
|
}
|
|
|
|
public void handleHR(byte[] value) {
|
|
LOG.debug("FitPro handle heart rate measurement");
|
|
debugPrintArray(value, "value");
|
|
if (value.length < 17) {
|
|
LOG.debug("FitPro heartrate measurement payload too short");
|
|
return;
|
|
}
|
|
|
|
int heartRate = (int) value[19];
|
|
int pressureLow = (int) value[18];
|
|
int pressureHigh = (int) value[17];
|
|
int spo2 = (int) value[13];
|
|
int seconds = ByteBuffer.wrap(value, 12, 4).getInt();
|
|
sendAck(value[3], value[1], value[2], value[5]);
|
|
|
|
if (!(heartRate > 0)) {
|
|
return;
|
|
}
|
|
handleHR(seconds, heartRate, pressureLow, pressureHigh, spo2);
|
|
}
|
|
|
|
@Override
|
|
public void onSetCallState(CallSpec callSpec) {
|
|
LOG.debug("FitPro send call notification");
|
|
TransactionBuilder builder = new TransactionBuilder("CALL");
|
|
|
|
if (callSpec.command == CallSpec.CALL_INCOMING) {
|
|
|
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
|
try {
|
|
outputStream.write(0x1);
|
|
outputStream.write(0x0);
|
|
outputStream.write(0x0);
|
|
|
|
if (callSpec.name != null) {
|
|
outputStream.write(callSpec.name.getBytes(StandardCharsets.UTF_8));
|
|
outputStream.write(0x20);
|
|
}
|
|
if (callSpec.number != null) {
|
|
outputStream.write(callSpec.number.getBytes(StandardCharsets.UTF_8));
|
|
outputStream.write(0x20);
|
|
}
|
|
|
|
} catch (IOException e) {
|
|
LOG.error("error sending call notification: " + e);
|
|
}
|
|
debugPrintArray(craftData(CMD_GROUP_GENERAL, CMD_NOTIFICATION_CALL, outputStream.toByteArray()), "crafted call notify");
|
|
builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_NOTIFICATION_CALL, outputStream.toByteArray()));
|
|
} else {
|
|
builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_NOTIFICATION_CALL, VALUE_OFF));
|
|
}
|
|
builder.queue(getQueue());
|
|
}
|
|
|
|
@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 onSendConfiguration(String config) {
|
|
|
|
LOG.debug("FitPro on send config: " + config);
|
|
try {
|
|
TransactionBuilder builder = performInitialized("sendConfiguration");
|
|
switch (config) {
|
|
case DeviceSettingsPreferenceConst.PREF_LANGUAGE:
|
|
setLanguage(builder);
|
|
break;
|
|
case DeviceSettingsPreferenceConst.PREF_LONGSIT_PERIOD:
|
|
case DeviceSettingsPreferenceConst.PREF_LONGSIT_SWITCH:
|
|
case DeviceSettingsPreferenceConst.PREF_LONGSIT_START:
|
|
case DeviceSettingsPreferenceConst.PREF_LONGSIT_END:
|
|
setLongSitReminder(builder);
|
|
break;
|
|
case DeviceSettingsPreferenceConst.PREF_ACTIVATE_DISPLAY_ON_LIFT:
|
|
case DeviceSettingsPreferenceConst.PREF_DISPLAY_ON_LIFT_START:
|
|
case DeviceSettingsPreferenceConst.PREF_DISPLAY_ON_LIFT_END:
|
|
setDisplayOnLift(builder);
|
|
break;
|
|
case SettingsActivity.PREF_MEASUREMENT_SYSTEM:
|
|
case ActivityUser.PREF_USER_WEIGHT_KG:
|
|
case ActivityUser.PREF_USER_GENDER:
|
|
case ActivityUser.PREF_USER_HEIGHT_CM:
|
|
case ActivityUser.PREF_USER_YEAR_OF_BIRTH:
|
|
setUserData(builder);
|
|
break;
|
|
case ActivityUser.PREF_USER_STEPS_GOAL:
|
|
setStepsGoal(builder);
|
|
break;
|
|
case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO:
|
|
case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO_START:
|
|
case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO_END:
|
|
setDoNotDisturb(builder);
|
|
break;
|
|
case DeviceSettingsPreferenceConst.PREF_SLEEP_TIME:
|
|
case DeviceSettingsPreferenceConst.PREF_SLEEP_TIME_START:
|
|
case DeviceSettingsPreferenceConst.PREF_SLEEP_TIME_END:
|
|
setSleepTime(builder);
|
|
break;
|
|
case DeviceSettingsPreferenceConst.PREF_WEARLOCATION:
|
|
setWearLocation(builder);
|
|
break;
|
|
case DeviceSettingsPreferenceConst.PREF_VIBRATION_ENABLE:
|
|
setVibrations(builder);
|
|
break;
|
|
case DeviceSettingsPreferenceConst.PREF_NOTIFICATION_ENABLE:
|
|
setNotifications(builder);
|
|
break;
|
|
case DeviceSettingsPreferenceConst.PREF_AUTOHEARTRATE_SWITCH:
|
|
case DeviceSettingsPreferenceConst.PREF_AUTOHEARTRATE_SLEEP:
|
|
case DeviceSettingsPreferenceConst.PREF_AUTOHEARTRATE_INTERVAL:
|
|
case DeviceSettingsPreferenceConst.PREF_AUTOHEARTRATE_START:
|
|
case DeviceSettingsPreferenceConst.PREF_AUTOHEARTRATE_END:
|
|
setAutoHeartRate(builder);
|
|
break;
|
|
}
|
|
builder.queue(getQueue());
|
|
} catch (IOException e) {
|
|
GB.toast(getContext(), "Error sending configuration: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onReadConfiguration(String config) {
|
|
|
|
}
|
|
|
|
public void sendAck(byte command_group, byte length_high, byte length_low, byte command) {
|
|
LOG.debug(" ACKing data: " + nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils.arrayToString(new byte[]{command_group}) + " " + nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils.arrayToString(new byte[]{command}));
|
|
TransactionBuilder builder = new TransactionBuilder("notification");
|
|
short size = (short) (ByteBuffer.wrap(new byte[]{length_high, length_low}).getShort() + 3);
|
|
byte[] sizeArray = ByteBuffer.allocate(2).putShort(size).array();
|
|
builder.write(writeCharacteristic, new byte[]{FitProConstants.DATA_HEADER_ACK, 0, 5, command_group, 1, sizeArray[0], sizeArray[1], 1});
|
|
builder.queue(getQueue());
|
|
}
|
|
|
|
@Override
|
|
public void onTestNewFunction() {
|
|
LOG.debug("Hello FitPro Test function");
|
|
}
|
|
|
|
@Override
|
|
public void onSendWeather(WeatherSpec weatherSpec) {
|
|
LOG.debug("FitPro send weather");
|
|
short todayMax = (short) (weatherSpec.todayMaxTemp - 273);
|
|
short todayMin = (short) (weatherSpec.todayMinTemp - 273);
|
|
byte weatherUnit = 0;
|
|
String units = GBApplication.getPrefs().getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, GBApplication.getContext().getString(R.string.p_unit_metric));
|
|
if (units.equals(GBApplication.getContext().getString(R.string.p_unit_imperial))) {
|
|
todayMax = (short) (todayMax * 1.8f + 32);
|
|
todayMin = (short) (todayMin * 1.8f + 32);
|
|
weatherUnit = 1;
|
|
}
|
|
|
|
byte currentConditionCode = Weather.mapToFitProCondition(weatherSpec.currentConditionCode);
|
|
TransactionBuilder builder = new TransactionBuilder("weather");
|
|
builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_WEATHER, new byte[]{(byte) todayMin, (byte) todayMax, (byte) currentConditionCode, (byte) weatherUnit}));
|
|
builder.queue(getQueue());
|
|
}
|
|
|
|
@Override
|
|
public boolean useAutoConnect() {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public void onNotification(NotificationSpec notificationSpec) {
|
|
LOG.debug("FitPro notification: " + notificationSpec.type);
|
|
TransactionBuilder builder = new TransactionBuilder("notification");
|
|
byte icon = NOTIFICATION_ICON_SMS;
|
|
switch (notificationSpec.type) {
|
|
case GENERIC_SMS:
|
|
icon = NOTIFICATION_ICON_SMS;
|
|
break;
|
|
case FACEBOOK:
|
|
case FACEBOOK_MESSENGER:
|
|
icon = NOTIFICATION_ICON_FACEBOOK;
|
|
break;
|
|
case LINE:
|
|
icon = NOTIFICATION_ICON_LINE;
|
|
break;
|
|
case WHATSAPP:
|
|
icon = NOTIFICATION_ICON_WHATSAPP;
|
|
break;
|
|
case TWITTER:
|
|
icon = NOTIFICATION_ICON_TWITTER;
|
|
break;
|
|
case SIGNAL:
|
|
case VIBER:
|
|
case CONVERSATIONS:
|
|
icon = NOTIFICATION_ICON_QQ;
|
|
break;
|
|
case WECHAT:
|
|
case GMAIL:
|
|
icon = NOTIFICATION_ICON_WECHAT;
|
|
break;
|
|
case INSTAGRAM:
|
|
icon = NOTIFICATION_ICON_INSTAGRAM;
|
|
break;
|
|
default:
|
|
icon = NOTIFICATION_ICON_SMS;
|
|
break;
|
|
}
|
|
|
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
|
try {
|
|
outputStream.write(icon);
|
|
outputStream.write(0x0);
|
|
outputStream.write(0x0);
|
|
|
|
if (notificationSpec.sender != null) {
|
|
outputStream.write(notificationSpec.sender.getBytes(StandardCharsets.UTF_8));
|
|
outputStream.write(0x20);
|
|
} else {
|
|
if (notificationSpec.phoneNumber != null) { //use number only if there is no sender
|
|
outputStream.write(notificationSpec.phoneNumber.getBytes(StandardCharsets.UTF_8));
|
|
outputStream.write(0x20);
|
|
}
|
|
}
|
|
|
|
if (notificationSpec.subject != null) {
|
|
outputStream.write(notificationSpec.subject.getBytes(StandardCharsets.UTF_8));
|
|
outputStream.write(0x20);
|
|
}
|
|
if (notificationSpec.body != null) {
|
|
outputStream.write(notificationSpec.body.getBytes(StandardCharsets.UTF_8));
|
|
outputStream.write(0x20);
|
|
}
|
|
|
|
} catch (IOException e) {
|
|
LOG.error("FitPro error sending notification: " + e);
|
|
}
|
|
String output = outputStream.toString();
|
|
if (outputStream.toString().length() > 60) {
|
|
output = outputStream.toString().substring(0, 60);
|
|
}
|
|
|
|
builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_NOTIFICATION_MESSAGE, output.getBytes(StandardCharsets.UTF_8)));
|
|
builder.queue(getQueue());
|
|
}
|
|
|
|
@Override
|
|
public void onDeleteNotification(int id) {
|
|
|
|
}
|
|
|
|
public FitProDeviceSupport setLanguage(TransactionBuilder builder) {
|
|
LOG.debug("FitPro set language");
|
|
String localeString = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("language", "auto");
|
|
if (localeString == null || localeString.equals("auto")) {
|
|
String language = Locale.getDefault().getLanguage();
|
|
String country = Locale.getDefault().getCountry();
|
|
|
|
if (country == null) {
|
|
country = language;
|
|
}
|
|
localeString = language + "_" + country.toUpperCase();
|
|
}
|
|
LOG.info("Setting device to locale: " + localeString);
|
|
|
|
byte languageCode = FitProConstants.LANG_ENGLISH;
|
|
|
|
switch (localeString.substring(0, 2)) {
|
|
case "zh":
|
|
languageCode = FitProConstants.LANG_CHINESE;
|
|
break;
|
|
case "it":
|
|
languageCode = FitProConstants.LANG_ITALIAN;
|
|
break;
|
|
case "cs":
|
|
languageCode = FitProConstants.LANG_CZECH;
|
|
break;
|
|
case "en":
|
|
languageCode = FitProConstants.LANG_ENGLISH;
|
|
break;
|
|
case "tr":
|
|
languageCode = FitProConstants.LANG_TURKISH;
|
|
break;
|
|
case "ru":
|
|
languageCode = FitProConstants.LANG_RUSSIAN;
|
|
break;
|
|
case "pl":
|
|
languageCode = FitProConstants.LANG_POLISH;
|
|
break;
|
|
case "nl":
|
|
languageCode = FitProConstants.LANG_NETHERLANDS;
|
|
break;
|
|
case "fr":
|
|
languageCode = FitProConstants.LANG_FRENCH;
|
|
break;
|
|
case "es":
|
|
languageCode = FitProConstants.LANG_SPANISH;
|
|
break;
|
|
case "de":
|
|
languageCode = FitProConstants.LANG_GERMAN;
|
|
break;
|
|
case "pt":
|
|
languageCode = FitProConstants.LANG_PORTUGUESE;
|
|
break;
|
|
|
|
default:
|
|
languageCode = FitProConstants.LANG_ENGLISH;
|
|
}
|
|
|
|
builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_SET_LANGUAGE, languageCode));
|
|
|
|
return this;
|
|
}
|
|
|
|
public FitProDeviceSupport setUserData(TransactionBuilder builder) {
|
|
//0xcd 0x00 0x09 0x12 0x01 0x04 0x00 0x04 0xaf 0x59 0x09 0xe1 FitPro
|
|
LOG.debug("FitPro set user data");
|
|
|
|
ActivityUser activityUser = new ActivityUser();
|
|
|
|
int age = activityUser.getAge();
|
|
|
|
int gender = activityUser.getGender();
|
|
byte genderUnit = GENDER_FEMALE;
|
|
if (gender == ActivityUser.GENDER_MALE) {
|
|
genderUnit = GENDER_MALE;
|
|
}
|
|
|
|
int heightCm = activityUser.getHeightCm();
|
|
int weightKg = activityUser.getWeightKg();
|
|
|
|
byte distanceUnit = UNIT_METRIC;
|
|
String units = GBApplication.getPrefs().getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, GBApplication.getContext().getString(R.string.p_unit_metric));
|
|
if (units.equals(GBApplication.getContext().getString(R.string.p_unit_imperial))) {
|
|
distanceUnit = UNIT_IMPERIAL;
|
|
}
|
|
|
|
int userData = genderUnit << 31 | age << 24 | heightCm << 15 | weightKg << 5 | distanceUnit;
|
|
byte[] data = craftData(CMD_GROUP_GENERAL, CMD_SET_USER_DATA, ByteBuffer.allocate(4).putInt(userData).array());
|
|
builder.write(writeCharacteristic, data);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public void onFetchRecordedData(int dataTypes) {
|
|
indicateFinishedFetchingOperation();
|
|
TransactionBuilder builder = new TransactionBuilder("fetch data1");
|
|
builder.add(new SetDeviceBusyAction(getDevice(), getContext().getString(R.string.busy_task_fetch_activity_data), getContext()));
|
|
builder.write(writeCharacteristic, craftData(CMD_GROUP_RECEIVE_SPORTS_DATA, CMD_REQUEST_STEPS_DATA1, VALUE_ON));
|
|
builder.queue(getQueue());
|
|
}
|
|
|
|
|
|
public void handleDayTotalsData(byte[] value) {
|
|
LOG.debug("FitPro handle day data length: " + value.length);
|
|
debugPrintArray(value, "value");
|
|
if (value.length < 10) {
|
|
LOG.debug("FitPro payload too short");
|
|
return;
|
|
}
|
|
debugPrintArray(value, "processing");
|
|
int steps = ByteBuffer.wrap(value, 10, 4).getInt();
|
|
int distance = ByteBuffer.wrap(value, 14, 4).getInt();
|
|
|
|
byte[] caloriesBytes = new byte[3];
|
|
System.arraycopy(value, 18, caloriesBytes, 0, 2);
|
|
int calories = ByteBuffer.wrap(caloriesBytes, 0, 3).getShort();
|
|
|
|
LOG.debug("processing day data summary, steps: " + steps + " distance: " + distance + " calories: " + calories);
|
|
sendAck(value[3], value[1], value[2], value[5]);
|
|
//handleDayTotalsData(steps, distance, calories);
|
|
}
|
|
|
|
public void handleBatteryInfo(nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfo info) {
|
|
LOG.debug("FitPro battery info: " + info);
|
|
batteryCmd.level = (short) info.getPercentCharged();
|
|
handleGBDeviceEvent(batteryCmd);
|
|
}
|
|
|
|
/* public void handleDeviceInfo(nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo info) {
|
|
LOG.debug("FitPro device info: " + info);
|
|
versionCmd.hwVersion = "+FitPro";
|
|
versionCmd.fwVersion = info.getFirmwareRevision();
|
|
handleGBDeviceEvent(versionCmd);
|
|
}
|
|
|
|
*/
|
|
public void handleCamera(byte command) {
|
|
GB.toast(getContext(), "Camera buttons are detected but not further handled.", Toast.LENGTH_SHORT, GB.INFO);
|
|
}
|
|
|
|
public void handleFindPhone() {
|
|
LOG.info("FitPro find phone");
|
|
GBDeviceEventFindPhone deviceEventFindPhone = new GBDeviceEventFindPhone();
|
|
deviceEventFindPhone.event = GBDeviceEventFindPhone.Event.START;
|
|
evaluateGBDeviceEvent(deviceEventFindPhone);
|
|
}
|
|
|
|
public void handleMediaButton(byte command) {
|
|
GBDeviceEventMusicControl deviceEventMusicControl = new GBDeviceEventMusicControl();
|
|
if (command == RX_MEDIA_PLAY_PAUSE) {
|
|
deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PLAYPAUSE;
|
|
evaluateGBDeviceEvent(deviceEventMusicControl);
|
|
} else if (command == RX_MEDIA_FORW) {
|
|
deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.NEXT;
|
|
evaluateGBDeviceEvent(deviceEventMusicControl);
|
|
} else if (command == RX_MEDIA_BACK) {
|
|
deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PREVIOUS;
|
|
evaluateGBDeviceEvent(deviceEventMusicControl);
|
|
}
|
|
}
|
|
|
|
public FitProDeviceSupport setVibrations(TransactionBuilder builder) {
|
|
LOG.debug("FitPro set enable vibrations");
|
|
boolean vibrations = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREF_VIBRATION_ENABLE, false);
|
|
byte[] enable = VALUE_SET_DEVICE_VIBRATIONS_ENABLE;
|
|
if (!vibrations) {
|
|
enable = VALUE_SET_DEVICE_VIBRATIONS_DISABLE;
|
|
}
|
|
builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_SET_DEVICE_VIBRATIONS, enable));
|
|
return this;
|
|
}
|
|
|
|
public void debugPrintArray(byte[] bytes, String label) {
|
|
if (!debugEnabled) return;
|
|
String arrayString = nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils.arrayToString(bytes);
|
|
LOG.debug("FitPro debug print " + label + ": " + arrayString);
|
|
}
|
|
|
|
public FitProDeviceSupport setNotifications(TransactionBuilder builder) {
|
|
LOG.debug("FitPro set enable notifications");
|
|
boolean notifications = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREF_NOTIFICATION_ENABLE, false);
|
|
byte[] enable = VALUE_SET_NOTIFICATIONS_ENABLE_ON;
|
|
if (!notifications) {
|
|
enable = VALUE_SET_NOTIFICATIONS_ENABLE_OFF;
|
|
}
|
|
builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_NOTIFICATIONS_ENABLE, enable));
|
|
return this;
|
|
}
|
|
|
|
public byte[] craftData(byte command_group, byte command, byte value) {
|
|
return craftData(command_group, command, new byte[]{value});
|
|
}
|
|
|
|
public byte[] craftData(byte command_group, byte command) {
|
|
return craftData(command_group, command, new byte[]{});
|
|
}
|
|
|
|
@Override
|
|
public void onSetTime() {
|
|
LOG.debug("FitPro set date and time");
|
|
TransactionBuilder builder = new TransactionBuilder("Set date and time");
|
|
setTime(builder);
|
|
builder.queue(getQueue());
|
|
}
|
|
|
|
|
|
public FitProDeviceSupport setTime(TransactionBuilder builder) {
|
|
LOG.debug("FitPro set time");
|
|
Calendar calendar = Calendar.getInstance();
|
|
|
|
int datetime = calendar.get(Calendar.SECOND) | (
|
|
(calendar.get(Calendar.YEAR) - 2000) << 26 | calendar.get(Calendar.MONTH) + 1 << 22 |
|
|
calendar.get(Calendar.DAY_OF_MONTH) << 17 |
|
|
calendar.get(Calendar.HOUR_OF_DAY) << 12 | calendar.get(Calendar.MINUTE) << 6);
|
|
|
|
//this is how the values can be re-stored
|
|
// result is this
|
|
//byte[] array = new byte[]{(byte) (datetime >> 24), (byte) (datetime >> 16), (byte) (datetime >> 8), (byte) (datetime >> 0)};
|
|
// int datetime2 = ByteBuffer.wrap(array).getInt();
|
|
|
|
//byte[] time = craftData(LT716Constants.CMD_SET_DATE_TIME, new byte[]{(byte) (datetime >> 24), (byte) (datetime >> 16), (byte) (datetime >> 8), (byte) (datetime >> 0)});
|
|
byte[] time = craftData(CMD_GROUP_GENERAL, CMD_SET_DATE_TIME, (ByteBuffer.allocate(4).putInt(datetime).array()));
|
|
builder.write(writeCharacteristic, time);
|
|
return this;
|
|
}
|
|
|
|
|
|
@Override
|
|
public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
|
|
LOG.debug("FitPro set alarms");
|
|
|
|
// handle one-shot alarm from the widget:
|
|
// this device doesn't have concept of on-off alarm, so use the last slot for this and store
|
|
// this alarm in the database so the user knows what is going on and can disable it
|
|
|
|
if (alarms.toArray().length == 1 && alarms.get(0).getRepetition() == 0) {
|
|
Alarm oneshot = alarms.get(0);
|
|
DBHandler db = null;
|
|
try {
|
|
db = GBApplication.acquireDB();
|
|
DaoSession daoSession = db.getDaoSession();
|
|
Device device = DBHelper.getDevice(gbDevice, daoSession);
|
|
User user = DBHelper.getUser(daoSession);
|
|
nodomain.freeyourgadget.gadgetbridge.entities.Alarm tmpAlarm =
|
|
new nodomain.freeyourgadget.gadgetbridge.entities.Alarm(
|
|
device.getId(),
|
|
user.getId(),
|
|
7,
|
|
true,
|
|
false,
|
|
false,
|
|
0,
|
|
oneshot.getHour(),
|
|
oneshot.getMinute(),
|
|
true, //kind of indicate the specialty of this alarm
|
|
"",
|
|
"");
|
|
daoSession.insertOrReplace(tmpAlarm);
|
|
GBApplication.releaseDB();
|
|
} catch (GBException e) {
|
|
LOG.error("error storing one shot quick alarm");
|
|
}
|
|
}
|
|
|
|
try {
|
|
TransactionBuilder builder = performInitialized("Set alarm");
|
|
boolean anyAlarmEnabled = false;
|
|
byte[] all_alarms = new byte[]{};
|
|
|
|
for (Alarm alarm : alarms) {
|
|
Calendar calendar = AlarmUtils.toCalendar(alarm);
|
|
anyAlarmEnabled |= alarm.getEnabled();
|
|
LOG.debug("alarms: " + alarm.getPosition());
|
|
int maxAlarms = 8;
|
|
if (alarm.getPosition() >= maxAlarms) { //we should never encounter this, but just in case
|
|
if (alarm.getEnabled()) {
|
|
GB.toast(getContext(), "Only 8 alarms are supported.", Toast.LENGTH_LONG, GB.WARN);
|
|
}
|
|
return;
|
|
}
|
|
if (alarm.getEnabled()) {
|
|
long datetime = (long) alarm.getRepetition() | (
|
|
(long) (calendar.get(Calendar.YEAR) - 2000) << 34 |
|
|
(long) (calendar.get(Calendar.MONTH) + 1) << 30 |
|
|
(long) (calendar.get(Calendar.DAY_OF_MONTH)) << 25 |
|
|
(long) (calendar.get(Calendar.HOUR_OF_DAY)) << 20 |
|
|
(long) (calendar.get(Calendar.MINUTE)) << 14 |
|
|
1L << 11);
|
|
byte[] single_alarm = new byte[]{(byte) (datetime >> 32), (byte) (datetime >> 24), (byte) (datetime >> 16), (byte) (datetime >> 8), (byte) (datetime)};
|
|
all_alarms = ArrayUtils.addAll(all_alarms, single_alarm);
|
|
}
|
|
}
|
|
|
|
builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_ALARM, all_alarms));
|
|
builder.queue(getQueue());
|
|
if (anyAlarmEnabled) {
|
|
GB.toast(getContext(), getContext().getString(R.string.user_feedback_miband_set_alarms_ok), Toast.LENGTH_SHORT, GB.INFO);
|
|
} else {
|
|
GB.toast(getContext(), getContext().getString(R.string.user_feedback_all_alarms_disabled), Toast.LENGTH_SHORT, GB.INFO);
|
|
}
|
|
} catch (IOException ex) {
|
|
GB.toast(getContext(), getContext().getString(R.string.user_feedback_miband_set_alarms_failed), Toast.LENGTH_LONG, GB.ERROR, ex);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onReset(int flags) {
|
|
LOG.debug("FitPro reset flags: " + flags);
|
|
byte[] command = craftData(CMD_GROUP_RESET, CMD_RESET);
|
|
switch (flags) {
|
|
case 1:
|
|
command = craftData(CMD_GROUP_RESET, CMD_RESET);
|
|
break;
|
|
case 2:
|
|
command = craftData(CMD_GROUP_BIND, CMD_UNBIND);
|
|
break;
|
|
}
|
|
|
|
getQueue().clear();
|
|
TransactionBuilder builder = new TransactionBuilder("resetting");
|
|
builder.write(writeCharacteristic, command);
|
|
builder.queue(getQueue());
|
|
}
|
|
|
|
@Override
|
|
public void onHeartRateTest() {
|
|
TransactionBuilder builder = new TransactionBuilder("notification");
|
|
builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_HEART_RATE_MEASUREMENT, VALUE_ON));
|
|
builder.queue(getQueue());
|
|
}
|
|
|
|
@Override
|
|
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
|
|
|
|
}
|
|
|
|
@Override
|
|
public void onFindDevice(boolean start) {
|
|
getQueue().clear();
|
|
LOG.debug("FitPro find device");
|
|
TransactionBuilder builder = new TransactionBuilder("searching");
|
|
builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_FIND_BAND, start ? VALUE_ON : VALUE_OFF));
|
|
builder.queue(getQueue());
|
|
}
|
|
|
|
@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) {
|
|
|
|
}
|
|
|
|
|
|
public FitProDeviceSupport setAutoHeartRate(TransactionBuilder builder) {
|
|
LOG.debug("FitPro set automatic heartrate measurements");
|
|
boolean prefAutoheartrateSwitch = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean("pref_autoheartrate_switch", false);
|
|
LOG.info("Setting autoheartrate to " + prefAutoheartrateSwitch);
|
|
|
|
boolean sleep = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean("pref_autoheartrate_sleep", false);
|
|
String start = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("pref_autoheartrate_start", "06:00");
|
|
String end = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("pref_autoheartrate_end", "23:00");
|
|
String interval = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("pref_autoheartrate_interval", "2");
|
|
|
|
int intervalInt = Integer.parseInt(interval);
|
|
int sleepInt = sleep ? 1 : 0;
|
|
int autoheartrateInt = prefAutoheartrateSwitch ? 1 : 0;
|
|
|
|
Calendar startCalendar = GregorianCalendar.getInstance();
|
|
Calendar endCalendar = GregorianCalendar.getInstance();
|
|
DateFormat df = new SimpleDateFormat("HH:mm");
|
|
|
|
try {
|
|
startCalendar.setTime(df.parse(start));
|
|
endCalendar.setTime(df.parse(end));
|
|
} catch (ParseException e) {
|
|
LOG.error("settings error: " + e);
|
|
}
|
|
|
|
int startTime = (startCalendar.get(Calendar.HOUR_OF_DAY) * 60) + startCalendar.get(Calendar.MINUTE);
|
|
int endTime = (endCalendar.get(Calendar.HOUR_OF_DAY) * 60) + endCalendar.get(Calendar.MINUTE);
|
|
|
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
|
outputStream.write(autoheartrateInt);
|
|
outputStream.write(sleepInt);
|
|
outputStream.write(intervalInt >> 8);
|
|
outputStream.write(intervalInt);
|
|
outputStream.write(startTime >> 8);
|
|
outputStream.write(startTime);
|
|
outputStream.write(endTime >> 8);
|
|
outputStream.write(endTime);
|
|
//outputStream.write(0x7F);
|
|
|
|
builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_GROUP_HEARTRATE_SETTINGS, outputStream.toByteArray()));
|
|
|
|
return this;
|
|
}
|
|
|
|
public FitProDeviceSupport setLongSitReminder(TransactionBuilder builder) {
|
|
LOG.debug("FitPro set inactivity warning");
|
|
boolean prefLongsitSwitch = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean("pref_longsit_switch", false);
|
|
LOG.info("Setting long sit warning to " + prefLongsitSwitch);
|
|
|
|
if (prefLongsitSwitch) {
|
|
|
|
String inactivity = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("pref_longsit_period", "4");
|
|
String start = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("pref_longsit_start", "08:00");
|
|
String end = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("pref_longsit_end", "16:00");
|
|
Calendar startCalendar = GregorianCalendar.getInstance();
|
|
Calendar endCalendar = GregorianCalendar.getInstance();
|
|
DateFormat df = new SimpleDateFormat("HH:mm");
|
|
|
|
try {
|
|
startCalendar.setTime(df.parse(start));
|
|
endCalendar.setTime(df.parse(end));
|
|
} catch (ParseException e) {
|
|
LOG.debug("settings error: " + e);
|
|
}
|
|
|
|
int inactivityInt = Integer.parseInt(inactivity);
|
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
|
try {
|
|
outputStream.write(VALUE_SET_LONG_SIT_REMINDER_ON);
|
|
outputStream.write(inactivityInt);
|
|
outputStream.write(startCalendar.get(Calendar.HOUR_OF_DAY));
|
|
outputStream.write(endCalendar.get(Calendar.HOUR_OF_DAY));
|
|
outputStream.write(0x7F);
|
|
} catch (IOException e) {
|
|
LOG.error("settings error: " + e);
|
|
}
|
|
|
|
builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_SET_LONG_SIT_REMINDER, outputStream.toByteArray()));
|
|
LOG.info("Setting long sit warning to scheduled");
|
|
|
|
} else {
|
|
builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_SET_LONG_SIT_REMINDER, VALUE_SET_LONG_SIT_REMINDER_OFF));
|
|
LOG.info("Setting long sit warning to OFF");
|
|
}
|
|
return this;
|
|
}
|
|
|
|
public FitProDeviceSupport setDoNotDisturb(TransactionBuilder builder) {
|
|
LOG.debug("FitPro set DND");
|
|
String dnd = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("do_not_disturb_no_auto", "off");
|
|
LOG.info("Setting DND to " + dnd);
|
|
int dndInt = dnd.equals("scheduled") ? 1 : 0;
|
|
|
|
String start = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("do_not_disturb_no_auto_start", "22:00");
|
|
String end = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("do_not_disturb_no_auto_end", "06:00");
|
|
|
|
Calendar startCalendar = GregorianCalendar.getInstance();
|
|
Calendar endCalendar = GregorianCalendar.getInstance();
|
|
DateFormat df = new SimpleDateFormat("HH:mm");
|
|
|
|
try {
|
|
startCalendar.setTime(df.parse(start));
|
|
endCalendar.setTime(df.parse(end));
|
|
} catch (ParseException e) {
|
|
LOG.error("settings error: " + e);
|
|
}
|
|
|
|
int startTime = (startCalendar.get(Calendar.HOUR_OF_DAY) * 60) + startCalendar.get(Calendar.MINUTE);
|
|
int endTime = (endCalendar.get(Calendar.HOUR_OF_DAY) * 60) + endCalendar.get(Calendar.MINUTE);
|
|
|
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
|
|
|
outputStream.write(dndInt);
|
|
outputStream.write(startTime >> 8);
|
|
outputStream.write(startTime);
|
|
outputStream.write(endTime >> 8);
|
|
outputStream.write(endTime);
|
|
|
|
debugPrintArray(craftData(CMD_GROUP_GENERAL, CMD_DND, outputStream.toByteArray()), "enable DND");
|
|
debugPrintArray(outputStream.toByteArray(), "payload");
|
|
LOG.info("Setting DND to scheduled: " + start + " " + end);
|
|
builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_DND, outputStream.toByteArray()));
|
|
LOG.info("Setting DND scheduled");
|
|
|
|
return this;
|
|
}
|
|
|
|
public FitProDeviceSupport setSleepTime(TransactionBuilder builder) {
|
|
LOG.debug("FitPro set sleep times");
|
|
String sleepTime = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("prefs_enable_sleep_time", "off");
|
|
LOG.info("Setting sleep times to " + sleepTime);
|
|
int sleepTimeInt = sleepTime.equals("scheduled") ? 1 : 0;
|
|
|
|
String start = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("prefs_sleep_time_start", "22:00");
|
|
String end = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("prefs_sleep_time_end", "06:00");
|
|
|
|
Calendar startCalendar = GregorianCalendar.getInstance();
|
|
Calendar endCalendar = GregorianCalendar.getInstance();
|
|
DateFormat df = new SimpleDateFormat("HH:mm");
|
|
|
|
try {
|
|
startCalendar.setTime(df.parse(start));
|
|
endCalendar.setTime(df.parse(end));
|
|
} catch (ParseException e) {
|
|
LOG.error("settings error: " + e);
|
|
}
|
|
|
|
int startTime = (startCalendar.get(Calendar.HOUR_OF_DAY) * 60) + startCalendar.get(Calendar.MINUTE);
|
|
int endTime = (endCalendar.get(Calendar.HOUR_OF_DAY) * 60) + endCalendar.get(Calendar.MINUTE);
|
|
|
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
|
outputStream.write(sleepTimeInt);
|
|
outputStream.write(startTime >> 8);
|
|
outputStream.write(startTime);
|
|
outputStream.write(endTime >> 8);
|
|
outputStream.write(endTime);
|
|
debugPrintArray(craftData(CMD_GROUP_GENERAL, CMD_SET_SLEEP_TIMES, outputStream.toByteArray()), "enable sleep time");
|
|
debugPrintArray(outputStream.toByteArray(), "payload");
|
|
LOG.info("Setting sleep times scheduled: " + start + " " + end);
|
|
builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_SET_SLEEP_TIMES, outputStream.toByteArray()));
|
|
LOG.info("Setting sleep times scheduled");
|
|
return this;
|
|
}
|
|
|
|
public FitProDeviceSupport setWearLocation(TransactionBuilder builder) {
|
|
LOG.debug("FitPro set wearing location");
|
|
byte location = VALUE_SET_ARM_LEFT;
|
|
String setLocation = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString(DeviceSettingsPreferenceConst.PREF_WEARLOCATION, "left");
|
|
if ("right".equals(setLocation)) {
|
|
location = VALUE_SET_ARM_RIGHT;
|
|
}
|
|
builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_SET_ARM, location));
|
|
return this;
|
|
}
|
|
|
|
public FitProDeviceSupport setDisplayOnLift(TransactionBuilder builder) {
|
|
LOG.debug("FitPro set display on lift");
|
|
String displayLift = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("activate_display_on_lift_wrist", "off");
|
|
|
|
int displayLiftInt = displayLift.equals("scheduled") ? 1 : 0;
|
|
|
|
LOG.info("Setting activate display on lift wrist to:" + displayLift + ": " + displayLiftInt);
|
|
|
|
String start = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("display_on_lift_start", "08:00");
|
|
String end = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("display_on_lift_end", "16:00");
|
|
|
|
Calendar startCalendar = GregorianCalendar.getInstance();
|
|
Calendar endCalendar = GregorianCalendar.getInstance();
|
|
DateFormat df = new SimpleDateFormat("HH:mm");
|
|
|
|
try {
|
|
startCalendar.setTime(df.parse(start));
|
|
endCalendar.setTime(df.parse(end));
|
|
} catch (ParseException e) {
|
|
LOG.error("settings error: " + e);
|
|
}
|
|
|
|
int startTime = (startCalendar.get(Calendar.HOUR_OF_DAY) * 60) + startCalendar.get(Calendar.MINUTE);
|
|
int endTime = (endCalendar.get(Calendar.HOUR_OF_DAY) * 60) + endCalendar.get(Calendar.MINUTE);
|
|
|
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
|
outputStream.write(displayLiftInt);
|
|
outputStream.write(startTime >> 8);
|
|
outputStream.write(startTime);
|
|
outputStream.write(endTime >> 8);
|
|
outputStream.write(endTime);
|
|
debugPrintArray(craftData(CMD_GROUP_GENERAL, CMD_SET_DISPLAY_ON_LIFT, outputStream.toByteArray()), "enable lift display");
|
|
debugPrintArray(outputStream.toByteArray(), "payload");
|
|
LOG.info("Setting activate display on lift wrist scheduled: " + start + " " + end);
|
|
builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_SET_DISPLAY_ON_LIFT, outputStream.toByteArray()));
|
|
LOG.info("Setting activate display on lift wrist scheduled");
|
|
|
|
return this;
|
|
}
|
|
|
|
public FitProDeviceSupport setStepsGoal(TransactionBuilder builder) {
|
|
LOG.debug("FitPro set step goal");
|
|
//cd 00 09 12 01 03 00 04 00 00 05 dc
|
|
|
|
ActivityUser activityUser = new ActivityUser();
|
|
int stepGoal = activityUser.getStepsGoal();
|
|
byte[] data = craftData(CMD_GROUP_GENERAL, CMD_SET_STEP_GOAL, ByteBuffer.allocate(4).putInt(stepGoal).array());
|
|
|
|
builder.write(writeCharacteristic, data);
|
|
return this;
|
|
}
|
|
|
|
public void handleSleepData(byte[] value) {
|
|
debugPrintArray(value, "sleep data value");
|
|
// sleep packet consists of: date + list of 4bytes of 15minutes intervals
|
|
// these intervals contain seconds offset from the date and type of sleep
|
|
byte[] dateArray = new byte[2];
|
|
System.arraycopy(value, 8, dateArray, 0, 2);
|
|
Calendar date = decodeDateTime(dateArray);
|
|
List<FitProActivitySample> samples = new ArrayList<>();
|
|
for (int i = 12; i < value.length - 3; i = i + 4) {
|
|
byte[] packet = new byte[4];
|
|
System.arraycopy(value, i, packet, 0, 4);
|
|
int data = ByteBuffer.wrap(packet).getInt();
|
|
int activity_kind = (int) (data & 0xff);
|
|
int encodedTime = (int) (data >> 16);
|
|
int seconds = getSleepSecondsOfDay(encodedTime);
|
|
Calendar now = (Calendar) date.clone(); // do not modify the caller's argument
|
|
now.add(Calendar.SECOND, seconds);
|
|
int timestamp = (int) (now.getTimeInMillis() / 1000L);
|
|
debugPrintArray(packet, "processing sleep packet");
|
|
|
|
LOG.debug("FitPro new sleep: " + activity_kind + " seconds: " + seconds + " ts: " + timestamp + " date: " + DateTimeUtils.formatDateTime(new Date(timestamp * 1000L)));
|
|
|
|
FitProActivitySample sample = new FitProActivitySample();
|
|
sample.setTimestamp(timestamp);
|
|
sample.setHeartRate(ActivitySample.NOT_MEASURED);
|
|
sample.setActiveTimeMinutes(15);
|
|
sample.setRawKind(rawSleepKindToUniqueKind(activity_kind));
|
|
samples.add(sample);
|
|
}
|
|
if (addGBActivitySamples(samples)) {
|
|
sendAck(value[3], value[1], value[2], value[5]);
|
|
}
|
|
}
|
|
|
|
public int rawSleepKindToUniqueKind(int kind) {
|
|
//step and sleep are the same kind so we must distinguish them
|
|
return kind + 10;
|
|
}
|
|
|
|
public int rawActivityKindToUniqueKind(int kind) {
|
|
return kind;
|
|
}
|
|
|
|
public void handleStepData(byte[] value) {
|
|
debugPrintArray(value, "step data value");
|
|
// step packet consists of: date + list of 8bytes of (always?) 5minutes intervals
|
|
// these intervals contain seconds offset from the date, type of activity, calories,
|
|
// steps, distance, duration
|
|
|
|
byte[] dateArray = new byte[2];
|
|
System.arraycopy(value, 8, dateArray, 0, 2);
|
|
Calendar date = decodeDateTime(dateArray);
|
|
List<FitProActivitySample> samples = new ArrayList<>();
|
|
for (int i = 12; i < value.length - 7; i = i + 8) {
|
|
byte[] packet = new byte[8];
|
|
System.arraycopy(value, i, packet, 0, 8);
|
|
long data = ByteBuffer.wrap(packet).getLong();
|
|
int steps = (int) Math.abs((data >> 52));
|
|
int calories = (int) (data & 0x7ffff);
|
|
int activity_kind = (int) ((data >> 19) & 0x1);
|
|
int duration = (int) ((data >> 48) & 0xf);
|
|
int distance = (int) ((data >> 32) & 0xffff);
|
|
int encodedTime = (int) ((data >> 21) & 0x7ff);
|
|
int seconds = getSecondsOfDay(encodedTime);
|
|
Calendar now = (Calendar) date.clone(); // do not modify the caller's argument
|
|
now.add(Calendar.SECOND, seconds);
|
|
int timestamp = (int) (now.getTimeInMillis() / 1000L);
|
|
debugPrintArray(packet, "processing steps packet");
|
|
LOG.debug("FitPro adding new steps: " + steps);
|
|
FitProActivitySample sample = new FitProActivitySample();
|
|
sample.setTimestamp(timestamp);
|
|
sample.setHeartRate(ActivitySample.NOT_MEASURED);
|
|
sample.setSteps(steps);
|
|
sample.setDistanceMeters(distance);
|
|
sample.setCaloriesBurnt(calories);
|
|
sample.setActiveTimeMinutes(duration);
|
|
sample.setRawKind(rawActivityKindToUniqueKind(activity_kind));
|
|
samples.add(sample);
|
|
}
|
|
if (addGBActivitySamples(samples)) {
|
|
sendAck(value[3], value[1], value[2], value[5]);
|
|
}
|
|
}
|
|
|
|
public void addGBActivitySample(FitProActivitySample sample) {
|
|
List<FitProActivitySample> samples = new ArrayList<>();
|
|
samples.add(sample);
|
|
addGBActivitySamples(samples);
|
|
}
|
|
|
|
private boolean addGBActivitySamples(List<FitProActivitySample> samples) {
|
|
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
|
|
|
User user = DBHelper.getUser(dbHandler.getDaoSession());
|
|
Device device = DBHelper.getDevice(this.getDevice(), dbHandler.getDaoSession());
|
|
FitProSampleProvider provider = new FitProSampleProvider(this.getDevice(), dbHandler.getDaoSession());
|
|
|
|
for (FitProActivitySample sample : samples) {
|
|
sample.setDevice(device);
|
|
sample.setUser(user);
|
|
sample.setProvider(provider);
|
|
provider.addGBActivitySample(sample);
|
|
}
|
|
|
|
} catch (Exception ex) {
|
|
LOG.error("Error saving samples: " + ex);
|
|
GB.updateTransferNotification(null, "Data transfer failed", false, 0, getContext());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public void broadcastSample(FitProActivitySample sample) {
|
|
Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
|
|
.putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample);
|
|
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
|
|
}
|
|
|
|
public void handleHR(int seconds, int heartRate, int pressureLow, int pressureHigh, int spo2) {
|
|
LOG.debug("FitPro handle heart rate measurement");
|
|
|
|
Calendar date = Calendar.getInstance();
|
|
date.set(Calendar.HOUR_OF_DAY, 0);
|
|
date.set(Calendar.MINUTE, 0);
|
|
date.set(Calendar.SECOND, 0);
|
|
date.add(Calendar.SECOND, seconds);
|
|
LOG.debug("date: " + date);
|
|
|
|
FitProActivitySample sample = new FitProActivitySample();
|
|
|
|
sample.setHeartRate(heartRate);
|
|
sample.setPressureLowMmHg(pressureLow);
|
|
sample.setPressureHighMmHg(pressureHigh);
|
|
sample.setSpo2Percent(spo2);
|
|
|
|
sample.setTimestamp((int) (date.getTimeInMillis() / 1000));
|
|
sample.setRawKind(ActivityKind.TYPE_ACTIVITY);
|
|
|
|
addGBActivitySample(sample);
|
|
broadcastSample(sample);
|
|
GB.signalActivityDataFinish();
|
|
}
|
|
|
|
public void handleDayTotalsData(int steps, int distance, int calories) {
|
|
//this is for day data values, not used in Gb, handleStepData uses the better, 5min data
|
|
LOG.debug("FitPro handle day total steps");
|
|
|
|
LOG.debug("Steps: " + steps);
|
|
LOG.debug("Distance: " + distance);
|
|
LOG.debug("Calories: " + calories);
|
|
|
|
Calendar dateStart = Calendar.getInstance();
|
|
dateStart.set(Calendar.HOUR_OF_DAY, 0);
|
|
dateStart.set(Calendar.MINUTE, 0);
|
|
dateStart.set(Calendar.SECOND, 0);
|
|
|
|
Calendar dateEnd = Calendar.getInstance();
|
|
dateEnd.set(Calendar.HOUR_OF_DAY, 23);
|
|
dateEnd.set(Calendar.MINUTE, 59);
|
|
dateEnd.set(Calendar.SECOND, 59);
|
|
|
|
int dayStepCount = getStepsOnDay(dateStart, dateEnd);
|
|
int newSteps = (steps - dayStepCount);
|
|
LOG.debug("FitPro dayStepCount " + dayStepCount);
|
|
LOG.debug("FitPro new steps " + newSteps);
|
|
|
|
/*
|
|
if (newSteps > 0) {
|
|
LOG.debug("FitPro adding new steps " + newSteps);
|
|
ShenTechActivitySample sample = new ShenTechActivitySample();
|
|
Calendar date = Calendar.getInstance();
|
|
sample.setTimestamp((int) (date.getTimeInMillis() / 1000));
|
|
sample.setSteps(newSteps);
|
|
sample.setDistanceMeters(distance);
|
|
sample.setCaloriesBurnt(calories);
|
|
sample.setRawKind(ActivityKind.TYPE_ACTIVITY);
|
|
sample.setRawIntensity(1);
|
|
addGBActivitySample(sample);
|
|
broadcastSample(sample);
|
|
}
|
|
*/
|
|
}
|
|
|
|
private int getStepsOnDay(Calendar dayStart, Calendar dayEnd) {
|
|
//this is for day data values, not used in Gb, handleStepData uses 5min data which is better
|
|
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
|
|
|
FitProSampleProvider provider = new FitProSampleProvider(this.getDevice(), dbHandler.getDaoSession());
|
|
|
|
List<FitProActivitySample> samples = provider.getActivitySamples(
|
|
(int) (dayStart.getTimeInMillis() / 1000L),
|
|
(int) (dayEnd.getTimeInMillis() / 1000L));
|
|
|
|
int totalSteps = 0;
|
|
|
|
for (FitProActivitySample sample : samples) {
|
|
totalSteps += sample.getSteps();
|
|
}
|
|
|
|
return totalSteps;
|
|
|
|
} catch (Exception ex) {
|
|
LOG.error(ex.getMessage());
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public int getSecondsOfDay(int encodedTime) {
|
|
int hours = (int) Math.floor((encodedTime * 15) / 60);
|
|
int minutes = (encodedTime * 15) % 60;
|
|
int seconds = (hours * 3600) + (minutes * 60);
|
|
return seconds;
|
|
}
|
|
|
|
public int getSleepSecondsOfDay(int encodedTime) {
|
|
int hours = (int) Math.floor(encodedTime / 60);
|
|
int minutes = encodedTime % 60;
|
|
int seconds = (hours * 3600) + (minutes * 60);
|
|
return seconds;
|
|
}
|
|
|
|
public Calendar decodeDateTime(byte[] dateArray) {
|
|
debugPrintArray(dateArray, "array to decode to date time");
|
|
short dateShort = ByteBuffer.wrap(dateArray).getShort();
|
|
|
|
int day = (dateShort & 0x1f);
|
|
int month = ((dateShort >> 5) & 0xf);
|
|
int year = ((dateShort >> 9) + 2000);
|
|
|
|
Calendar date = GregorianCalendar.getInstance();
|
|
date.set(year, month - 1, day, 0, 0, 0);
|
|
return date;
|
|
}
|
|
} |