Add initial support for Teclast H30
Scan and connection, battery level, firmware version, date and time sync (along with some other currently hardcoded settings), notification support, alarm support, and some more.
@ -0,0 +1,41 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.jyou;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public final class JYouConstants {
|
||||
public static final UUID UUID_CHARACTERISTIC_CONTROL = UUID.fromString("000033f3-0000-1000-8000-00805f9b34fb");
|
||||
public static final UUID UUID_CHARACTERISTIC_MEASURE = UUID.fromString("000033f4-0000-1000-8000-00805f9b34fb");
|
||||
public static final UUID UUID_SERVICE_JYOU = UUID.fromString("000056ff-0000-1000-8000-00805f9b34fb");
|
||||
|
||||
public static final byte CMD_SET_DATE_AND_TIME = 0x08;
|
||||
public static final byte CMD_SET_HEARTRATE_AUTO = 0x38;
|
||||
public static final byte CMD_SET_HEARTRATE_WARNING_VALUE = 0x01;
|
||||
public static final byte CMD_SET_TARGET_STEPS = 0x03;
|
||||
public static final byte CMD_SET_ALARM_1 = 0x09;
|
||||
public static final byte CMD_SET_ALARM_2 = 0x22;
|
||||
public static final byte CMD_SET_ALARM_3 = 0x23;
|
||||
public static final byte CMD_GET_STEP_COUNT = 0x1D;
|
||||
public static final byte CMD_GET_SLEEP_TIME = 0x32;
|
||||
public static final byte CMD_SET_NOON_TIME = 0x26;
|
||||
public static final byte CMD_SET_SLEEP_TIME = 0x27;
|
||||
public static final byte CMD_SET_DND_SETTINGS = 0x39;
|
||||
public static final byte CMD_SET_INACTIVITY_WARNING_TIME = 0x24;
|
||||
public static final byte CMD_ACTION_HEARTRATE_SWITCH = 0x0D;
|
||||
public static final byte CMD_ACTION_SHOW_NOTIFICATION = 0x2C;
|
||||
public static final byte CMD_ACTION_REBOOT_DEVICE = 0x0E;
|
||||
|
||||
public static final byte RECEIVE_BATTERY_LEVEL = (byte)0xF7;
|
||||
public static final byte RECEIVE_DEVICE_INFO = (byte)0xF6;
|
||||
public static final byte RECEIVE_STEPS_DATA = (byte)0xF9;
|
||||
public static final byte RECEIVE_HEARTRATE = (byte)0xFC;
|
||||
|
||||
public static final byte ICON_CALL = 0;
|
||||
public static final byte ICON_SMS = 1;
|
||||
public static final byte ICON_WECHAT = 2;
|
||||
public static final byte ICON_QQ = 3;
|
||||
public static final byte ICON_FACEBOOK = 4;
|
||||
public static final byte ICON_SKYPE = 5;
|
||||
public static final byte ICON_TWITTER = 6;
|
||||
public static final byte ICON_WHATSAPP = 7;
|
||||
public static final byte ICON_LINE = 8;
|
||||
}
|
@ -0,0 +1,144 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.jyou;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.bluetooth.le.ScanFilter;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.ParcelUuid;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import de.greenrobot.dao.query.QueryBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
public class TeclastH30Coordinator extends AbstractDeviceCoordinator {
|
||||
|
||||
protected static final Logger LOG = LoggerFactory.getLogger(TeclastH30Coordinator.class);
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public Collection<? extends ScanFilter> createBLEScanFilters() {
|
||||
ParcelUuid uuid = new ParcelUuid(JYouConstants.UUID_SERVICE_JYOU);
|
||||
ScanFilter filter = new ScanFilter.Builder().setServiceUuid(uuid).build();
|
||||
return Collections.singletonList(filter);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
|
||||
String name = candidate.getDevice().getName();
|
||||
if (name != null && name.startsWith("TECLAST_H30")) {
|
||||
return DeviceType.TECLASTH30;
|
||||
}
|
||||
return DeviceType.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBondingStyle(GBDevice deviceCandidate){
|
||||
return BONDING_STYLE_NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCalendarEvents() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRealtimeData() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceType getDeviceType() {
|
||||
return DeviceType.TECLASTH30;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Activity> getPairingActivity() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Activity> getPrimaryActivity() {
|
||||
return ChartsActivity.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstallHandler findInstallHandler(Uri uri, Context context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityDataFetching() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityTracking() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsScreenshots() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAlarmConfiguration() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsSmartWakeup(GBDevice device) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturer() {
|
||||
return "Teclast";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAppsManagement() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Activity> getAppsManagementActivity() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
|
||||
|
||||
}
|
||||
}
|
@ -38,6 +38,7 @@ public enum DeviceType {
|
||||
HPLUS(40, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled),
|
||||
MAKIBESF68(41, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled),
|
||||
NO1F1(50, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled),
|
||||
TECLASTH30(60, R.drawable.ic_device_h30_h10, R.drawable.ic_device_h30_h10_disabled),
|
||||
TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled);
|
||||
|
||||
private final int key;
|
||||
|
@ -36,6 +36,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.no1f1.No1F1Support;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.vibratissimo.VibratissimoSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.hplus.HPlusSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.TeclastH30Support;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class DeviceSupportFactory {
|
||||
@ -129,6 +130,9 @@ public class DeviceSupportFactory {
|
||||
case NO1F1:
|
||||
deviceSupport = new ServiceDeviceSupport(new No1F1Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||
break;
|
||||
case TECLASTH30:
|
||||
deviceSupport = new ServiceDeviceSupport(new TeclastH30Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||
break;
|
||||
}
|
||||
if (deviceSupport != null) {
|
||||
deviceSupport.setContext(gbDevice, mBtAdapter, mContext);
|
||||
|
@ -0,0 +1,484 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.jyou;
|
||||
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.net.Uri;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.jyou.JYouConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||
|
||||
public class TeclastH30Support extends AbstractBTLEDeviceSupport {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TeclastH30Support.class);
|
||||
|
||||
public BluetoothGattCharacteristic ctrlCharacteristic = null;
|
||||
public BluetoothGattCharacteristic measureCharacteristic = null;
|
||||
|
||||
private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
|
||||
private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo();
|
||||
|
||||
public TeclastH30Support() {
|
||||
super(LOG);
|
||||
addSupportedService(JYouConstants.UUID_SERVICE_JYOU);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
|
||||
LOG.info("Initializing");
|
||||
|
||||
gbDevice.setState(GBDevice.State.INITIALIZING);
|
||||
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||
|
||||
measureCharacteristic = getCharacteristic(JYouConstants.UUID_CHARACTERISTIC_MEASURE);
|
||||
ctrlCharacteristic = getCharacteristic(JYouConstants.UUID_CHARACTERISTIC_CONTROL);
|
||||
|
||||
builder.setGattCallback(this);
|
||||
builder.notify(measureCharacteristic, true);
|
||||
|
||||
syncSettings(builder);
|
||||
|
||||
gbDevice.setState(GBDevice.State.INITIALIZED);
|
||||
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||
|
||||
LOG.info("Initialization Done");
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCharacteristicChanged(BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic) {
|
||||
if (super.onCharacteristicChanged(gatt, characteristic)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
UUID characteristicUUID = characteristic.getUuid();
|
||||
byte[] data = characteristic.getValue();
|
||||
if (data.length == 0)
|
||||
return true;
|
||||
|
||||
switch (data[0]) {
|
||||
case JYouConstants.RECEIVE_DEVICE_INFO:
|
||||
int fwVerNum = data[4] & 0xFF;
|
||||
versionCmd.fwVersion = (fwVerNum / 100) + "." + ((fwVerNum % 100) / 10) + "." + ((fwVerNum % 100) % 10);
|
||||
handleGBDeviceEvent(versionCmd);
|
||||
LOG.info("Firmware version is: " + versionCmd.fwVersion);
|
||||
return true;
|
||||
case JYouConstants.RECEIVE_BATTERY_LEVEL:
|
||||
batteryCmd.level = data[8];
|
||||
handleGBDeviceEvent(batteryCmd);
|
||||
LOG.info("Battery level is: " + batteryCmd.level);
|
||||
return true;
|
||||
case JYouConstants.RECEIVE_STEPS_DATA:
|
||||
int steps = ByteBuffer.wrap(data, 5, 4).getInt();
|
||||
LOG.info("Number of walked steps: " + steps);
|
||||
return true;
|
||||
case JYouConstants.RECEIVE_HEARTRATE:
|
||||
LOG.info("Current heart rate: " + data[8]);
|
||||
return true;
|
||||
default:
|
||||
LOG.info("Unhandled characteristic change: " + characteristicUUID + " code: " + String.format("0x%1x ...", data[0]));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void syncDateAndTime(TransactionBuilder builder) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
String strYear = String.valueOf(cal.get(Calendar.YEAR));
|
||||
byte year1 = (byte)Integer.parseInt(strYear.substring(0, 2));
|
||||
byte year2 = (byte)Integer.parseInt(strYear.substring(2, 4));
|
||||
byte month = (byte)cal.get(Calendar.MONTH);
|
||||
byte day = (byte)cal.get(Calendar.DAY_OF_MONTH);
|
||||
byte hour = (byte)cal.get(Calendar.HOUR_OF_DAY);
|
||||
byte minute = (byte)cal.get(Calendar.MINUTE);
|
||||
byte second = (byte)cal.get(Calendar.SECOND);
|
||||
byte weekDay = (byte)cal.get(Calendar.DAY_OF_WEEK);
|
||||
|
||||
builder.write(ctrlCharacteristic, commandWithChecksum(
|
||||
JYouConstants.CMD_SET_DATE_AND_TIME,
|
||||
(year1 << 24) | (year2 << 16) | (month << 8) | day,
|
||||
(hour << 24) | (minute << 16) | (second << 8) | weekDay
|
||||
));
|
||||
}
|
||||
|
||||
private void syncSettings(TransactionBuilder builder) {
|
||||
syncDateAndTime(builder);
|
||||
|
||||
// TODO: unhardcode and separate stuff
|
||||
builder.write(ctrlCharacteristic, commandWithChecksum(
|
||||
JYouConstants.CMD_SET_HEARTRATE_WARNING_VALUE, 0, 152
|
||||
));
|
||||
builder.write(ctrlCharacteristic, commandWithChecksum(
|
||||
JYouConstants.CMD_SET_TARGET_STEPS, 0, 10000
|
||||
));
|
||||
builder.write(ctrlCharacteristic, commandWithChecksum(
|
||||
JYouConstants.CMD_GET_STEP_COUNT, 0, 0
|
||||
));
|
||||
builder.write(ctrlCharacteristic, commandWithChecksum(
|
||||
JYouConstants.CMD_GET_SLEEP_TIME, 0, 0
|
||||
));
|
||||
builder.write(ctrlCharacteristic, commandWithChecksum(
|
||||
JYouConstants.CMD_SET_NOON_TIME, 12 * 60 * 60, 14 * 60 * 60 // 12:00 - 14:00
|
||||
));
|
||||
builder.write(ctrlCharacteristic, commandWithChecksum(
|
||||
JYouConstants.CMD_SET_SLEEP_TIME, 21 * 60 * 60, 8 * 60 * 60 // 21:00 - 08:00
|
||||
));
|
||||
builder.write(ctrlCharacteristic, commandWithChecksum(
|
||||
JYouConstants.CMD_SET_INACTIVITY_WARNING_TIME, 0, 0
|
||||
));
|
||||
|
||||
// do not disturb and a couple more features
|
||||
byte dndStartHour = 22;
|
||||
byte dndStartMin = 0;
|
||||
byte dndEndHour = 8;
|
||||
byte dndEndMin = 0;
|
||||
boolean dndToggle = false;
|
||||
boolean vibrationToggle = true;
|
||||
boolean wakeOnRaiseToggle = true;
|
||||
builder.write(ctrlCharacteristic, commandWithChecksum(
|
||||
JYouConstants.CMD_SET_DND_SETTINGS,
|
||||
(dndStartHour << 24) | (dndStartMin << 16) | (dndEndHour << 8) | dndEndMin,
|
||||
((dndToggle ? 0 : 1) << 2) | ((vibrationToggle ? 1 : 0) << 1) | (wakeOnRaiseToggle ? 1 : 0)
|
||||
));
|
||||
}
|
||||
|
||||
private void showNotification(byte icon, String title, String message) {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("ShowNotification");
|
||||
|
||||
byte[] titleBytes = stringToUTF8Bytes(title, 16);
|
||||
byte[] messageBytes = stringToUTF8Bytes(message, 80);
|
||||
|
||||
for (int i = 1; i <= 7; i++)
|
||||
{
|
||||
byte[] currentPacket = new byte[20];
|
||||
currentPacket[0] = JYouConstants.CMD_ACTION_SHOW_NOTIFICATION;
|
||||
currentPacket[1] = 7;
|
||||
currentPacket[2] = (byte)i;
|
||||
switch(i) {
|
||||
case 1:
|
||||
currentPacket[4] = icon;
|
||||
break;
|
||||
case 2:
|
||||
if (titleBytes != null) {
|
||||
System.arraycopy(titleBytes, 0, currentPacket, 3, 6);
|
||||
System.arraycopy(titleBytes, 6, currentPacket, 10, 10);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (messageBytes != null) {
|
||||
System.arraycopy(messageBytes, 16 * (i - 3), currentPacket, 3, 6);
|
||||
System.arraycopy(messageBytes, 6 + 16 * (i - 3), currentPacket, 10, 10);
|
||||
}
|
||||
break;
|
||||
}
|
||||
builder.write(ctrlCharacteristic, currentPacket);
|
||||
}
|
||||
performConnected(builder.getTransaction());
|
||||
} catch (IOException e) {
|
||||
LOG.warn(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useAutoConnect() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotification(NotificationSpec notificationSpec) {
|
||||
String notificationTitle = StringUtils.getFirstOf(notificationSpec.sender, notificationSpec.title);
|
||||
byte icon;
|
||||
switch (notificationSpec.type) {
|
||||
case GENERIC_SMS:
|
||||
icon = JYouConstants.ICON_SMS;
|
||||
break;
|
||||
case FACEBOOK:
|
||||
case FACEBOOK_MESSENGER:
|
||||
icon = JYouConstants.ICON_FACEBOOK;
|
||||
break;
|
||||
case TWITTER:
|
||||
icon = JYouConstants.ICON_TWITTER;
|
||||
break;
|
||||
case WHATSAPP:
|
||||
icon = JYouConstants.ICON_WHATSAPP;
|
||||
break;
|
||||
default:
|
||||
icon = JYouConstants.ICON_LINE;
|
||||
break;
|
||||
}
|
||||
showNotification(icon, notificationTitle, notificationSpec.body);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeleteNotification(int id) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("SetAlarms");
|
||||
|
||||
for (int i = 0; i < alarms.size(); i++)
|
||||
{
|
||||
byte cmd;
|
||||
switch (i) {
|
||||
case 0:
|
||||
cmd = JYouConstants.CMD_SET_ALARM_1;
|
||||
break;
|
||||
case 1:
|
||||
cmd = JYouConstants.CMD_SET_ALARM_2;
|
||||
break;
|
||||
case 2:
|
||||
cmd = JYouConstants.CMD_SET_ALARM_3;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
Calendar cal = alarms.get(i).getAlarmCal();
|
||||
builder.write(ctrlCharacteristic, commandWithChecksum(
|
||||
cmd,
|
||||
alarms.get(i).isEnabled() ? cal.get(Calendar.HOUR_OF_DAY) : -1,
|
||||
alarms.get(i).isEnabled() ? cal.get(Calendar.MINUTE) : -1
|
||||
));
|
||||
}
|
||||
performConnected(builder.getTransaction());
|
||||
GB.toast(getContext(), "Alarm settings applied - do note that the current device does not support day specification", Toast.LENGTH_LONG, GB.INFO);
|
||||
} catch(IOException e) {
|
||||
LOG.warn(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetTime() {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("SetTime");
|
||||
syncDateAndTime(builder);
|
||||
performConnected(builder.getTransaction());
|
||||
} catch(IOException e) {
|
||||
LOG.warn(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetCallState(CallSpec callSpec) {
|
||||
switch (callSpec.command) {
|
||||
case CallSpec.CALL_INCOMING:
|
||||
showNotification(JYouConstants.ICON_CALL, callSpec.name, callSpec.number);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetMusicState(MusicStateSpec stateSpec) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetMusicInfo(MusicSpec musicSpec) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnableRealtimeSteps(boolean enable) {
|
||||
onEnableRealtimeHeartRateMeasurement(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) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppReorder(UUID[] uuids) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchActivityData() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReboot() {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("Reboot");
|
||||
builder.write(ctrlCharacteristic, commandWithChecksum(
|
||||
JYouConstants.CMD_ACTION_REBOOT_DEVICE, 0, 0
|
||||
));
|
||||
performConnected(builder.getTransaction());
|
||||
} catch(Exception e) {
|
||||
LOG.warn(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeartRateTest() {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("HeartRateTest");
|
||||
builder.write(ctrlCharacteristic, commandWithChecksum(
|
||||
JYouConstants.CMD_ACTION_HEARTRATE_SWITCH, 0, 1
|
||||
));
|
||||
performConnected(builder.getTransaction());
|
||||
} catch(Exception e) {
|
||||
LOG.warn(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
|
||||
// TODO: test
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("RealTimeHeartMeasurement");
|
||||
builder.write(ctrlCharacteristic, commandWithChecksum(
|
||||
JYouConstants.CMD_SET_HEARTRATE_AUTO, 0, enable ? 1 : 0
|
||||
));
|
||||
performConnected(builder.getTransaction());
|
||||
} catch(Exception e) {
|
||||
LOG.warn(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFindDevice(boolean start) {
|
||||
if (start) {
|
||||
showNotification(JYouConstants.ICON_QQ, "Gadgetbridge", "Bzzt! Bzzt!");
|
||||
GB.toast(getContext(), "As your device doesn't have sound, it will only vibrate 3 times consecutively", Toast.LENGTH_LONG, GB.INFO);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetConstantVibration(int integer) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScreenshotReq() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnableHeartRateSleepSupport(boolean enable) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeleteCalendarEvent(byte type, long id) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendConfiguration(String config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTestNewFunction() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendWeather(WeatherSpec weatherSpec) {
|
||||
|
||||
}
|
||||
|
||||
private byte[] commandWithChecksum(byte cmd, int argSlot1, int argSlot2)
|
||||
{
|
||||
ByteBuffer buf = ByteBuffer.allocate(10);
|
||||
buf.put(cmd);
|
||||
buf.putInt(argSlot1);
|
||||
buf.putInt(argSlot2);
|
||||
|
||||
byte[] bytesToWrite = buf.array();
|
||||
|
||||
byte checksum = 0;
|
||||
for (byte b : bytesToWrite) {
|
||||
checksum += b;
|
||||
}
|
||||
|
||||
bytesToWrite[9] = checksum;
|
||||
|
||||
return bytesToWrite;
|
||||
}
|
||||
|
||||
private byte[] stringToUTF8Bytes(String src, int byteCount) {
|
||||
try {
|
||||
if (src == null)
|
||||
return null;
|
||||
|
||||
for (int i = src.length(); i > 0; i--) {
|
||||
String sub = src.substring(0, i);
|
||||
byte[] subUTF8 = sub.getBytes("UTF-8");
|
||||
|
||||
if (subUTF8.length == byteCount) {
|
||||
return subUTF8;
|
||||
}
|
||||
|
||||
if (subUTF8.length < byteCount) {
|
||||
byte[] largerSubUTF8 = new byte[byteCount];
|
||||
System.arraycopy(subUTF8, 0, largerSubUTF8, 0, subUTF8.length);
|
||||
return largerSubUTF8;
|
||||
}
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
LOG.warn(e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -42,6 +42,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.UnknownDeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.amazfitbip.AmazfitBipCooordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.MakibesF68Coordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.jyou.TeclastH30Coordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.liveview.LiveviewCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Coordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
|
||||
@ -195,6 +196,7 @@ public class DeviceHelper {
|
||||
result.add(new HPlusCoordinator());
|
||||
result.add(new No1F1Coordinator());
|
||||
result.add(new MakibesF68Coordinator());
|
||||
result.add(new TeclastH30Coordinator());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
BIN
app/src/main/res/drawable-hdpi/ic_device_h30_h10.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_device_h30_h10_disabled.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_device_h30_h10.png
Normal file
After Width: | Height: | Size: 980 B |
BIN
app/src/main/res/drawable-mdpi/ic_device_h30_h10_disabled.png
Normal file
After Width: | Height: | Size: 992 B |
BIN
app/src/main/res/drawable-xhdpi/ic_device_h30_h10.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_device_h30_h10_disabled.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
app/src/main/res/drawable-xxhdpi/ic_device_h30_h10.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
app/src/main/res/drawable-xxhdpi/ic_device_h30_h10_disabled.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
@ -10,6 +10,7 @@
|
||||
<item android:maxLevel="140" android:drawable="@drawable/ic_device_hplus_disabled" />
|
||||
<item android:maxLevel="141" android:drawable="@drawable/ic_device_hplus_disabled" />
|
||||
<item android:maxLevel="150" android:drawable="@drawable/ic_device_hplus_disabled" />
|
||||
<item android:maxLevel="160" android:drawable="@drawable/ic_device_h30_h10_disabled" />
|
||||
|
||||
<item android:maxLevel="199" android:drawable="@drawable/ic_launcher" />
|
||||
<item android:maxLevel="201" android:drawable="@drawable/ic_device_pebble" />
|
||||
@ -21,5 +22,6 @@
|
||||
<item android:maxLevel="240" android:drawable="@drawable/ic_device_hplus" />
|
||||
<item android:maxLevel="241" android:drawable="@drawable/ic_device_hplus" />
|
||||
<item android:maxLevel="250" android:drawable="@drawable/ic_device_hplus" />
|
||||
<item android:maxLevel="260" android:drawable="@drawable/ic_device_h30_h10" />
|
||||
|
||||
</level-list>
|