From f6ce0c1a0ec1f832a718da149384c4ad935d766a Mon Sep 17 00:00:00 2001 From: Sami Alaoui <4ndroidgeek@gmail.com> Date: Mon, 4 Sep 2017 07:43:34 +0100 Subject: [PATCH] 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. --- .../devices/jyou/JYouConstants.java | 41 ++ .../devices/jyou/TeclastH30Coordinator.java | 144 ++++++ .../gadgetbridge/model/DeviceType.java | 1 + .../service/DeviceSupportFactory.java | 4 + .../devices/jyou/TeclastH30Support.java | 484 ++++++++++++++++++ .../gadgetbridge/util/DeviceHelper.java | 2 + .../res/drawable-hdpi/ic_device_h30_h10.png | Bin 0 -> 1595 bytes .../ic_device_h30_h10_disabled.png | Bin 0 -> 1526 bytes .../res/drawable-mdpi/ic_device_h30_h10.png | Bin 0 -> 980 bytes .../ic_device_h30_h10_disabled.png | Bin 0 -> 992 bytes .../res/drawable-xhdpi/ic_device_h30_h10.png | Bin 0 -> 2203 bytes .../ic_device_h30_h10_disabled.png | Bin 0 -> 2028 bytes .../res/drawable-xxhdpi/ic_device_h30_h10.png | Bin 0 -> 3136 bytes .../ic_device_h30_h10_disabled.png | Bin 0 -> 3272 bytes .../main/res/drawable/level_list_device.xml | 2 + 15 files changed, 678 insertions(+) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/JYouConstants.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/TeclastH30Coordinator.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/jyou/TeclastH30Support.java create mode 100644 app/src/main/res/drawable-hdpi/ic_device_h30_h10.png create mode 100644 app/src/main/res/drawable-hdpi/ic_device_h30_h10_disabled.png create mode 100644 app/src/main/res/drawable-mdpi/ic_device_h30_h10.png create mode 100644 app/src/main/res/drawable-mdpi/ic_device_h30_h10_disabled.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_device_h30_h10.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_device_h30_h10_disabled.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_device_h30_h10.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_device_h30_h10_disabled.png diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/JYouConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/JYouConstants.java new file mode 100644 index 00000000..1d87f181 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/JYouConstants.java @@ -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; +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/TeclastH30Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/TeclastH30Coordinator.java new file mode 100644 index 00000000..e170b64c --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/TeclastH30Coordinator.java @@ -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 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 getPairingActivity() { + return null; + } + + @Override + public Class 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 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 getAppsManagementActivity() { + return null; + } + + @Override + protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException { + + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java index d543f913..e55c1065 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java @@ -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; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java index f17a5eec..d085de77 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java @@ -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); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/jyou/TeclastH30Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/jyou/TeclastH30Support.java new file mode 100644 index 00000000..36406f6a --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/jyou/TeclastH30Support.java @@ -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 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; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java index 7d18e350..8420cdae 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java @@ -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; } diff --git a/app/src/main/res/drawable-hdpi/ic_device_h30_h10.png b/app/src/main/res/drawable-hdpi/ic_device_h30_h10.png new file mode 100644 index 0000000000000000000000000000000000000000..4a599d7b839f0c8839d8988c14eb9b81539056ff GIT binary patch literal 1595 zcmZ`(X;f2Z5Pc#dg2bd4gGwKiH8;z$k1$=xk$P;oIFgUSmNQsb^FOVo=I7R>gQeg~Vn)GU<1HqXK zxTioUgE;IM81VGOQ(ze492UbI0iWP2;Cl*qJP=}X7+gY73A#wfG;8nAU281VS;=SC?Xw}5%^4kh(U58ZX|j3*c?FQEEeQ~1ktbrX$1%w z96n6sgD@VU5Vvxj6MX?-i5cCC5-dHW&wt?;0a|KI7mL-R2;s2FoBYD(51w>P2MXeQ z*ZyiCU0;>-BW|>lMUJCg-@)=P*0rT)a?^d|GPaWTo=wN~NA1nP8I31DB1eY34K>c_ z?rlw#eAWdwww=L6u{8G50tDW=Y<+W6_HPx%o>liA;I(gc3;`f%C zN(^o&l0AOfrbuvAJG>)oxw{qQSH*mt)J69(*6L&7~o$(W+o>d!qP_fqc!} zdffV~lDGoe{w2wG&nnBt1r+C`N2IDKqqU~<%d{Cz#ekJ=A%}@EwoSwlUqc-=x zGq~ce>KMANHl_L>IiNb^lOH}cTY>)cvQa22^pK3Z&W$a-*1S0_f2D1vEVEeSr+F6< z+12frs46QvnKOU6wnA8v>Y@C4+7zPl7es6-6i){ByLJ9}bhc|IJj-k>WU;&k8o1`9 zm{|7TJ^asl`s(rel-IFd{;H2x{}@dKPbtRU){pI(&F$t7eQ@-JfuJ@hniWDYmy;a%nQa zQ{L^JcD3W@&Q`mH zoU?teeZxL8>KOg7bl10PcxqPOnu224X;KZ+BX@Lb{cx58o66z6XH7Eb{4cVm!L=Fb zt(AkaxRu3=E;3j}mKK~qPTpVl^bR}8eT3iTpG)DWsm5S8T~5#KOK+u7@5mE`Bu?X= z@_9R(JMP~E8{~ai#q;!yY*I5WnO*AX;#Ws}fE#$15oq zaaw4u&f_+93f#%I>zWpajZA8f>V2n)uB=10w&%xnnxWU`H!Xfph925_8G}nkO<;LJ iooSXBn^h@~CRU`ijZkxLnWK=;6QI+4y{f2Dihls;o7bWM literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_device_h30_h10_disabled.png b/app/src/main/res/drawable-hdpi/ic_device_h30_h10_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..f3da742caf3f68f0abde4f27a54bbae55cd6e11b GIT binary patch literal 1526 zcmZ`(dpOg37@x~rCNw04V@XaJlS>K3jHk^lv}zS&<~Lhro9#EZbXIfeCRCU*U8KmF zOi75jB&5qpEyqcRSkD@gTo!`?TA<`j=pcXc~7pn`^sQ> zX12X{*|o~bwgwV8peNZHA zD_0hLWv|$6Kz_q*df!r!-Z{!?Bs`lQY_BDZX>CVl?yx5N3dPG8wAqrSWuT$PYx)>U{&SJ>R#-0kA(dfM&K*>Kl`$26WiFhrUZ78frZ zeTps2yhOsx&CTWZAG|_{3p^wA@LiesoMxxesz9kl6u|88M@(#d5k@GuZh=q}k5xzY z#@3bac)VT5HGh9;56|}Vd6K`%)bv77U|Q^787NnW?+g)U<{P_u7nu$cBK9!0tZJV9t-6=b~GzO1~jMTieKh*)A6e|!!?~9BiE-WlS zT)dg&rxa;pHLH$QC=`7I1I6j6p}|3&2A@Uj8C_2=uU+a#eWlB)&AMN%=H&g`ivQ`* ze{g#%9I1;52un(;8z&3Nozbrr=jXo=^6DfKbW?e`wXC{2Fk_Y4;wlcC!r^>$faWay zi#bBqI&5on>f}ilfA4y&Eo6?aY08aM!n|gU5vF+c6wb=Q!C@pj^4J){uj`p-MmC@X zJuR0vh;`QxYv5S7I4B77vRJAWT1}vZg#EFb$r0PI?glZ+$a6f!d&-r-36<(|&r~wc zcv3CDCv!3^+V97Bd(B(v@$RDWr%#^(`zsNSTcV(aCDIwAE`L9F-*mV5zV%w&{Ra-T z)z{a193Q=Y-PUT9&171z>GYDS+S;EOafg{&_t5LtOEon$PlkqvKa{gGY8uv!?CYBk z+i>mrby`SJ2`%$sTxVjvR7=s-DqmV!ievT?cUsisT^bH)4MH7F z?-%7N_HZ(ilNWkgT3U93CZBKD*SlGx>;#1(k)ZL>i;0Gg0UBDeK8??xKalt?|CEU{ z*>k$3gK&PkY&Hjd^0JMMHmQxz=Ob~w=Ma@|wTq0`2Ag;uCzAAC;=I%S0}g9XM#P55 Txf^n!e-B0o2=%||7bE-!7ecc~ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_device_h30_h10.png b/app/src/main/res/drawable-mdpi/ic_device_h30_h10.png new file mode 100644 index 0000000000000000000000000000000000000000..729cbd624e35f0bb6d5df6fbbb017698777383b7 GIT binary patch literal 980 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 b=H=~!C?a|eP8e$D9%o>K##sGnflK%syo}{l literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_device_h30_h10_disabled.png b/app/src/main/res/drawable-xhdpi/ic_device_h30_h10_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..a3a55f5c4613b2ebc95754b38dbdb015057611c7 GIT binary patch literal 2028 zcmah~eK^y58~<)vro0tL<0Qi=F2x8HQQpQ#m2u zCGxHmjj~ddm!9LTqEzypm3QgPd7i(X>w2!|dtLYUzV6Tc`F!p_zV~%~lYPA0RMzRN z0|2PF<6L~@IcznMYvuR*tdo1>X${5kpd$d~=hrV25b|0n8W+R@z-wSNV1%5PVRBHL zjSXb`L2Mcs_?;xvBGB$c1|983j*6xc*kqZ*P@0^iyh?IB&ZaV$fD=una}ox}Tje-$ z6?bMtbD0o@$_CEPXh$}i356eLLkzm97lZE1h>9XJi4cKidWcG3k{xN#F*2IUW=Gp% zFhnMqz=lqciE#DHs|=B-2@B7ABl)N++7K2pB6fbBuGB!BP1*)atlHj0pBg zxfO^eKy!ETCL0zm zzN9`4$@-%9C4ln|;flq0S_%8|krzk>PGK4bO0>ap?W%oiO?=>NBXka&&HqtI;S|3@4cDK3!nB^iaXPuFdsI*VS{=nZ*!pa4nxDC;i{^t`kOaz zF6C!rXu9q2m{D^|=wN))LqFBneyZ$r-U1cW+cH@!wFoj<_gdbikzvyLC#*e(Pp=f* zG0Pm=wDX6g<&x1{$I^9x?APlq4L_ky?A4FUirRst)wLFCl_5^-9)=9`bfI0)z*k+V=CVJd2(_^L-8JKSoN63oBG%QQNY7X zl)pTn1M06b%aR_;ebpLOpY|E+C?ycnm*1MWU%9jGXy0_qpdhgE<4jDqk97e~X{Td9 ze2}uwqqM)4VF{Lgb;b%uMa!%;4rOW}6yDs_)bw3h(lGeb&c53H&V#tkM9;2W89pb6%>T02L1j0FRyV32N(XM z!mCKeW>N$Kn8mJLTK+NfUkYeP%66SKDFN-tjg5{L78d&3jEtJ+=kMM8GO zefzeMntEPu|NDepS0~0tM>lqLb-kY+dJ-NP>0uZ1?ddM`s`~V4#PrAKn|jNOinjJ@ zSAU=Aua$kJp6k97Di=*nZ7MD2xnU&KJq+25}W!5?v!*nw$C#jLkbx)T@z3 zo!8;{aL6NSz!R^6?sKlTM1f-|eV)n^SKi_`_XjaEk&lFa6C^-5cf&g`Wp?&N?-rB_ z%EWK4wnJ7pm#dxN^r8Eck#{cYh_*wI!q&2v!C0w~yTUic>Y~2^^)XYkg(O8V=*FEcf!&n`Jy;|Zi?tWHR?_F0n6?wYVzB79^|7^b+Nr^ydQzDB+rUO!!3F_O=iuo0xN_gy660I2$4UR^ zz5nMT^c@CwqqZs_53X|v4i0`h2h68_eg>R09^5J^naQ?2ZV~|76FWQJAeBnnr?SUR z+*cF%qW;;q`uw^&+oe{qCDh#9e71%?eDrlcnA;2oSF? zXw7`7DsgEAJlKsn&*Af>dz^y$!Lp@s9f>FbG-5hxKgYR=ct!_-8*_thDtc zy^NM0;7gkb;Fy}AspLp4ihmFWI#plrvd)hhWmJZqR)XCdTbvn7ZB&)^8OtKmU&Py$ z=_^s<`3p5Lv?sEytu3Ldre>`@dTf7VVR68nl?czf<{?EnqBgfbF%vckE4-Pa1DO%Y zrBn#ou$%GrTx=t4>oRXhs*?31;LxE<>vOezx16Chgbq&ghF|1t&(Bc{xf}g1FLe_p zh0}d`dw?hYOx8o*0&{oHpS`5a5?{+#B16H4Ig-kaF0sJvc>5C1t|n<4O>Kmu{5&cC zqT*yl#m)dOm#gD(&@ZLnHoI1*r+i=$6qD>Kl+Z{gt=ugxf`oA2&w7jQAUlv0G{!TW zDz4J_T~sKk+b6;0ca!)Mq2zd>hLP9xYrmD7yqYJ?Ug)~vt+$wGyZ-^eRajx;Qj;{) z&*Hy^x9EON9QlkXoyvdxcf@Cos~ez_EPCzr2xgM(9yN&a>q* zxK9BZr+#K*hlr!T7&%fa(+~et?4wxKBy_{R=BTQwrbi_Q-(Nu#zVaz|@iP5O{@Vd} Ltd~ovQ&{5P&=R8O literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_device_h30_h10.png b/app/src/main/res/drawable-xxhdpi/ic_device_h30_h10.png new file mode 100644 index 0000000000000000000000000000000000000000..08f72ddbecc4def744208b3156bef2b4da56f52f GIT binary patch literal 3136 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_device_h30_h10_disabled.png b/app/src/main/res/drawable-xxhdpi/ic_device_h30_h10_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..2d1d4eff1b3d2e155170b4b1ae70f3efe1ce0f4d GIT binary patch literal 3272 zcmb_ec{Cf^)=!NQ)J#xBlA^>s6m3Z)G_EO171a`{A)yILv{hqBt1(ZPYrIjd>b<2X zN-3ANq-eFO#k)i_ttvhO^Jx65<^1i05lqcqSL9wz$iMALe_VskkOQj7lWt-B0gN-BNR^!LWL8aA1Bo z4@xloGS3wt0#78<4ah-s2oGXl6>b{u3IK=~J7Q3tSI&Pcd+z2rshDzqb3ws=5r<=T z3kWflM4%RI5mD>NY3x4e1Ov z2@{HT$UN?FNTRBtp}{j={q69ur^v0i#){fnvP9&Qmv^2Ub2Hju`$J14^kjVy=@bFS3NRZ2FhjwyI|2C}_T#(UpU&Nhw zt%6cFo}2DUe&SYsymfw{$aM2%*|E*-#qkH~%K7a%D(_FkZLbxVmzT>98$i7HNkD=n zWbmhk?xy_7rVkv8-)*_PIA&}DL|w$ehJPY$OjVxDs=}euG_LcRZ3FkkjlUz+i{jb^ zMGszSyS%I=257Q-0dY5y8;^mO-20mX=0ej?HyWO7I zt>h@GgX?A>p>7$N)7E9RX0jDMsG1ju3qpOlI!-DjkcMClu<|cfDw}?{1m$&RknD+x ziAIF~5iGL^*kqrwBpcZQ)vRk!gslY>!jSc)GHw1H!LGdjF&%2t%zvJ{1FD zO4|ZOI*r-*Jp6VY@%+J*NtqKcDY1phSy>I*cz9sU>WM@Lc%lfVCP=>*| zM{|#3w}y7OpPaHgmLvD~ev}fH-?hrc`tCC7ulHt^j;(&1Ji7e$_cI@w!bYi?FH$h# zYkNO-AZp{QyoWi~t4IjAH?s=U_6_ZwN8c1q1-Y=N^t)%XWPvg^78*LZ9EU5r@>oq> z9ckfIdr$c`qADD-$d=$5p?GlX<4LCuN^H#KNa*j^*9e6hu6so@~k zs&%{=Mw4UQvDm#`E6ar#rtKXh$ld*(0Dc^l)t{J_eCeN>K{@q8hQN9<-=@x)xcTVS z#_DYEboH`~&2@nz82fq<^AD5YK%0m-Mus(-$+xpZ!YxGLJCqMneC>|CEcq-1!0?R@){9GXIbRQ~!8r1Hma zlMnauN@ob)HD3$NO>t1Ee^zPPotKN^sgHtmVCh6`mx6BdJnK2uS zMWd6GCA4rGxGy^v)sWp7DCq~IDnR|?^%&Th|5z9QUM2s76D<4HnuidHMAFS0U6rr4 ztP5sWy?pu7I@%T@@WY;wnula7R=5Xiz;K@u#q(Y7=&?Y~*uM^QYXyAI&&n-`=&-?r?Tk z(AVB)*{P|i-N50I5ii~Q1XWd4ZLFxxwPToE`#PqJT;z~<2j=E;ZhX>WO3y&aQK=>a zpT4R*(w?ja9R2gE( z^M=`{?33{B@lnp!_s>#Nq$NXt268-;sT!Wd0bU5!#CrPp_~`n(@|qjzhL32!;>HOm zPI;r1>znlE^6+ue{_HVXa-A47MA1Z+mvY|Up3T*geYCxs9aN(0=oRzs{e{cemad*u z1Z%*#@#19jsP7#tb*v+9XWwWR9EpL47sve{kegU4qNbmup#3`AYWd66czP5JdD?Huom&kO> z;BmXh&xB8_vn^EeIk}rRCR`jTbkJKaoF z=r<9*#eg&OL{38R^{?ots3^iM!X7$=>wE6(*^*k`sDZun#)TVyRrhC-j4#T?HQkEp1D-Q?L*pxui+Cy;{&o7Fr zi(NP00We3%I8{mGC+B+_mL_IrOtEl5m8@P{NUlWlsR=E7A|Ojj`Z9buuio?d&QCW8 zWH+R`CXLcx9w*|*J`PSg*hLzX?7#iy@SkP_g{X`ke!utD^og4s&?4n<(&|_v9w4rW(0LZHGMdQ9VI0`9FVCUOscrS9Ce{cy zgFhxG#Ml|>o&2?8_*nA9nAGlFBZN$q(+%d1-e2Ot3o48zOYos15Y+j&Yzoa^uNoaZ zUtdsb6TVS?&;Z)T)xtxFtAPb832M&O- zTkNMwl(<$PTAd*bft~W!;?j)Gjm*UsJdB2W1OhLY?)o-vTUP#a0A@b5zX*q`2PN!k Z7zB16BYjv8;Z5=YM|)RHtzAIUKL7<6&Hn%Z literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/level_list_device.xml b/app/src/main/res/drawable/level_list_device.xml index eb68abe6..5e67a767 100644 --- a/app/src/main/res/drawable/level_list_device.xml +++ b/app/src/main/res/drawable/level_list_device.xml @@ -10,6 +10,7 @@ + @@ -21,5 +22,6 @@ + \ No newline at end of file