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 000000000..1d87f181e --- /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 000000000..e170b64c2 --- /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 d543f913d..e55c1065d 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 f17a5eec8..d085de772 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 000000000..36406f6a0 --- /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 7d18e3500..8420cdae4 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 000000000..4a599d7b8 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_device_h30_h10.png differ 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 000000000..f3da742ca Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_device_h30_h10_disabled.png differ 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 000000000..729cbd624 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_device_h30_h10.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_device_h30_h10_disabled.png b/app/src/main/res/drawable-mdpi/ic_device_h30_h10_disabled.png new file mode 100644 index 000000000..72840f433 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_device_h30_h10_disabled.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_device_h30_h10.png b/app/src/main/res/drawable-xhdpi/ic_device_h30_h10.png new file mode 100644 index 000000000..5ce9b58e4 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_device_h30_h10.png differ 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 000000000..a3a55f5c4 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_device_h30_h10_disabled.png differ 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 000000000..08f72ddbe Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_device_h30_h10.png differ 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 000000000..2d1d4eff1 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_device_h30_h10_disabled.png differ diff --git a/app/src/main/res/drawable/level_list_device.xml b/app/src/main/res/drawable/level_list_device.xml index eb68abe6e..5e67a7675 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