diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/CasioConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/CasioConstants.java index 1a2301baf..cc3428a66 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/CasioConstants.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/CasioConstants.java @@ -17,6 +17,8 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.devices.casio; +import java.util.HashMap; +import java.util.Map; import java.util.UUID; public final class CasioConstants { @@ -68,6 +70,13 @@ public final class CasioConstants { public static final UUID FUNCTION_SWITCH_CHARACTERISTIC = UUID.fromString("26eb001e-b012-49a8-b1f8-394fb2032b0f"); public static final String MUSIC_MESSAGE = "Music"; + // Modern Watches - All Features + public static final UUID CASIO_READ_REQUEST_FOR_ALL_FEATURES_CHARACTERISTIC_UUID = UUID.fromString("26eb002c-b012-49a8-b1f8-394fb2032b0f"); + public static final UUID CASIO_ALL_FEATURES_CHARACTERISTIC_UUID = UUID.fromString("26eb002d-b012-49a8-b1f8-394fb2032b0f"); + public static final UUID CASIO_DATA_REQUEST_SP_CHARACTERISTIC_UUID = UUID.fromString("26eb0023-b012-49a8-b1f8-394fb2032b0f"); + public static final UUID CASIO_CONVOY_CHARACTERISTIC_UUID = UUID.fromString("26eb0024-b012-49a8-b1f8-394fb2032b0f"); + public static final UUID CASIO_NOTIFICATION_CHARACTERISTIC_UUID = UUID.fromString("26eb0030-b012-49a8-b1f8-394fb2032b0f"); + // Link Loss public static final UUID LINK_LOSS_SERVICE = UUID.fromString("00001803-0000-1000-8000-00805f9b34fb"); @@ -91,6 +100,25 @@ public final class CasioConstants { public enum Model { MODEL_CASIO_GENERIC, MODEL_CASIO_6900B, - MODEL_CASIO_5600B + MODEL_CASIO_5600B, + MODEL_CASIO_GBX100 } + + public static Map characteristicToByte = new HashMap() { + { + put("CASIO_WATCH_NAME", (byte) 0x23); + put("CASIO_APP_INFORMATION", (byte) 0x22); + put("CASIO_BLE_FEATURES", (byte) 0x10); + put("CASIO_SETTING_FOR_BLE", (byte) 0x11); + put("CASIO_ADVERTISE_PARAMETER_MANAGER", (byte) 0x3b); + put("CASIO_CONNECTION_PARAMETER_MANAGER", (byte) 0x3a); + put("CASIO_MODULE_ID", (byte) 0x26); + put("CASIO_WATCH_CONDITION", (byte) 0x28); + put("CASIO_VERSION_INFORMATION", (byte) 0x20); + put("CASIO_DST_WATCH_STATE", (byte) 0x1d); + put("CASIO_DST_SETTING", (byte) 0x1e); + put("CASIO_SERVICE_DISCOVERY_MANAGER", (byte) 0x47); + put("CASIO_CURRENT_TIME", (byte) 0x09); + } + }; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gb6900/CasioGB6900DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gb6900/CasioGB6900DeviceCoordinator.java index d3177f671..fb609c829 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gb6900/CasioGB6900DeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gb6900/CasioGB6900DeviceCoordinator.java @@ -45,10 +45,6 @@ public class CasioGB6900DeviceCoordinator extends AbstractDeviceCoordinator { @NonNull @Override public DeviceType getSupportedType(GBDeviceCandidate candidate) { - if (candidate.supportsService(CasioConstants.CASIO_VIRTUAL_SERVER_SERVICE)) { - return DeviceType.CASIOGB6900; - } - String name = candidate.getDevice().getName(); if (name != null) { if (name.startsWith("CASIO") && (name.endsWith("6900B") || name.endsWith("5600B"))) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gbx100/CasioGBX100DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gbx100/CasioGBX100DeviceCoordinator.java new file mode 100644 index 000000000..dcaf5df92 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gbx100/CasioGBX100DeviceCoordinator.java @@ -0,0 +1,152 @@ +/* Copyright (C) 2016-2020 Andreas Böhler, Andreas Shimokawa, Carsten + Pfeiffer, Daniele Gobbetti, José Rebelo + based on code from BlueWatcher, https://github.com/masterjc/bluewatcher + + 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 . */ +package nodomain.freeyourgadget.gadgetbridge.devices.casio.gbx100; + +import android.app.Activity; +import android.content.Context; +import android.net.Uri; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import androidx.annotation.NonNull; +import nodomain.freeyourgadget.gadgetbridge.GBException; +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; +import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; +import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioConstants; +import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; +import nodomain.freeyourgadget.gadgetbridge.entities.Device; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; +import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; +import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; + +public class CasioGBX100DeviceCoordinator extends AbstractDeviceCoordinator { + protected static final Logger LOG = LoggerFactory.getLogger(CasioGBX100DeviceCoordinator.class); + + @NonNull + @Override + public DeviceType getSupportedType(GBDeviceCandidate candidate) { + String name = candidate.getDevice().getName(); + if (name != null) { + if (name.startsWith("CASIO") && name.endsWith("GBX-100")) { + return DeviceType.CASIOGBX100; + } + } + + return DeviceType.UNKNOWN; + } + + @Override + public int getBondingStyle(){ + return BONDING_STYLE_NONE; + } + + @Override + public boolean supportsCalendarEvents() { + return false; + } + + @Override + public boolean supportsRealtimeData() { + return false; + } + + @Override + public boolean supportsWeather() { + return false; + } + + @Override + public boolean supportsFindDevice() { + return false; + } + + @Override + public DeviceType getDeviceType() { + return DeviceType.CASIOGBX100; + } + + @Override + public Class getPairingActivity() { + return null; + } + + @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 int getAlarmSlotCount() { + return 0; // 4 regular and one snooze + } + + @Override + public boolean supportsSmartWakeup(GBDevice device) { + return false; + } + + @Override + public boolean supportsHeartRateMeasurement(GBDevice device) { + return false; + } + + @Override + public String getManufacturer() { + return "Casio"; + } + + @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 5ccb94eda..4fa9851c4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java @@ -70,6 +70,7 @@ public enum DeviceType { ROIDMI(110, R.drawable.ic_device_roidmi, R.drawable.ic_device_roidmi_disabled, R.string.devicetype_roidmi), ROIDMI3(112, R.drawable.ic_device_roidmi, R.drawable.ic_device_roidmi_disabled, R.string.devicetype_roidmi3), CASIOGB6900(120, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_casiogb6900), + CASIOGBX100(121, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_casiogbx100), MISCALE2(131, R.drawable.ic_device_miscale2, R.drawable.ic_device_miscale2_disabled, R.string.devicetype_miscale2), BFH16(140, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_bfh16), MAKIBESHR3(150, R.drawable.ic_device_default, R.drawable.ic_device_hplus_disabled, R.string.devicetype_makibes_hr3), 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 bc9bed08d..9d9010101 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java @@ -29,10 +29,12 @@ import java.util.EnumSet; import nodomain.freeyourgadget.gadgetbridge.GBException; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.devices.casio.gbx100.CasioGBX100DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; import nodomain.freeyourgadget.gadgetbridge.service.devices.banglejs.BangleJSDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.CasioGB6900DeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.CasioGBX100DeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.hplus.HPlusSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitband5.AmazfitBand5Support; @@ -252,6 +254,9 @@ public class DeviceSupportFactory { case CASIOGB6900: deviceSupport = new ServiceDeviceSupport(new CasioGB6900DeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); break; + case CASIOGBX100: + deviceSupport = new ServiceDeviceSupport(new CasioGBX100DeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); + break; case MISCALE2: deviceSupport = new ServiceDeviceSupport(new MiScale2DeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); break; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/CasioGBX100DeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/CasioGBX100DeviceSupport.java new file mode 100644 index 000000000..818a9b89b --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/CasioGBX100DeviceSupport.java @@ -0,0 +1,309 @@ +/* Copyright (C) 2018-2020 Andreas Böhler, Sebastian Kranz + based on code from BlueWatcher, https://github.com/masterjc/bluewatcher + + 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 . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.casio; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; +import android.bluetooth.BluetoothGattService; +import android.net.Uri; +import android.widget.Toast; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl; +import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioConstants; +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.GattCharacteristic; +import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService; +import nodomain.freeyourgadget.gadgetbridge.service.btle.ServerTransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction; +import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.operations.InitOperationGBX100; +import nodomain.freeyourgadget.gadgetbridge.util.GB; +import nodomain.freeyourgadget.gadgetbridge.util.StringUtils; + +public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport { + private static final Logger LOG = LoggerFactory.getLogger(CasioGBX100DeviceSupport.class); + + private final ArrayList mCasioCharacteristics = new ArrayList(); + private MusicSpec mBufferMusicSpec = null; + private MusicStateSpec mBufferMusicStateSpec = null; + private BluetoothGatt mBtGatt = null; + private boolean mFirstConnect = false; + + public CasioGBX100DeviceSupport() { + super(LOG); + + addSupportedService(CasioConstants.WATCH_FEATURES_SERVICE_UUID); + } + + /* + @Override + public boolean connectFirstTime() { + GB.toast(getContext(), "After first connect, disable and enable bluetooth on your Casio watch to really connect", Toast.LENGTH_SHORT, GB.INFO); + mFirstConnect = true; + return super.connect(); + } + */ + + public void setInitialized() { + gbDevice.setState(GBDevice.State.INITIALIZED); + gbDevice.sendDeviceUpdateIntent(getContext()); + } + + @Override + public void onServicesDiscovered(BluetoothGatt gatt) { + mBtGatt = gatt; + super.onServicesDiscovered(gatt); + } + + @Override + protected TransactionBuilder initializeDevice(TransactionBuilder builder) { + LOG.info("Initializing"); + + try { + new InitOperationGBX100(this, builder).perform(); + } catch (IOException e) { + GB.toast(getContext(), "Initializing Casio watch failed", Toast.LENGTH_SHORT, GB.ERROR, e); + } + + getDevice().setFirmwareVersion("N/A"); + getDevice().setFirmwareVersion2("N/A"); + + LOG.info("Initialization Done"); + + return builder; + } + + @Override + public boolean onCharacteristicRead(BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic, int status) { + + UUID characteristicUUID = characteristic.getUuid(); + byte[] data = characteristic.getValue(); + + if(data.length == 0) + return true; + + return super.onCharacteristicRead(gatt, characteristic, status); + } + + @Override + public boolean onCharacteristicChanged(BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic) { + boolean handled = false; + + UUID characteristicUUID = characteristic.getUuid(); + byte[] data = characteristic.getValue(); + if (data.length == 0) + return true; + + LOG.info("Unhandled characteristic change: " + characteristicUUID + " code: " + String.format("0x%1x ...", data[0])); + return super.onCharacteristicChanged(gatt, characteristic); + } + + @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 = CasioConstants.SMS_NOTIFICATION_ID; + break; + case GENERIC_CALENDAR: + icon = CasioConstants.CALENDAR_NOTIFICATION_ID; + break; + case GENERIC_EMAIL: + icon = CasioConstants.MAIL_NOTIFICATION_ID; + break; + default: + icon = CasioConstants.SNS_NOTIFICATION_ID; + break; + } + } + + @Override + public void onDeleteNotification(int id) { + + } + + @Override + public void onSetAlarms(ArrayList alarms) { + + } + + @Override + public void onSetTime() { + + } + + @Override + public void onSetCallState(CallSpec callSpec) { + + } + + @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 onFetchRecordedData(int dataTypes) { + + } + + @Override + public void onReset(int flags) { + + } + + @Override + public void onHeartRateTest() { + + } + + @Override + public void onEnableRealtimeHeartRateMeasurement(boolean enable) { + + } + + @Override + public void onFindDevice(boolean start) { + + } + + @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) { + + } + + @Override + public void onSendConfiguration(String config) { + + } + + @Override + public void onReadConfiguration(String config) { + + } + + @Override + public void onTestNewFunction() { + + } + + @Override + public void onSendWeather(WeatherSpec weatherSpec) { + + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/operations/InitOperationGBX100.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/operations/InitOperationGBX100.java new file mode 100644 index 000000000..7a2cb37f7 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/operations/InitOperationGBX100.java @@ -0,0 +1,368 @@ +/* Copyright (C) 2020 Andreas Böhler + + 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 . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.casio.operations; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Calendar; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioConstants; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction; +import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.CasioGBX100DeviceSupport; + +public class InitOperationGBX100 extends AbstractBTLEOperation { + private static final Logger LOG = LoggerFactory.getLogger(InitOperationGBX100.class); + + private final TransactionBuilder builder; + private final CasioGBX100DeviceSupport support; + + public InitOperationGBX100(CasioGBX100DeviceSupport support, TransactionBuilder builder) { + super(support); + this.builder = builder; + this.support = support; + builder.setGattCallback(this); + } + + private void writeAllFeaturesRequest(TransactionBuilder builder, byte[] arr) { + builder.write(getCharacteristic(CasioConstants.CASIO_READ_REQUEST_FOR_ALL_FEATURES_CHARACTERISTIC_UUID), arr); + } + + private void writeAllFeaturesRequest(byte[] arr) { + try { + TransactionBuilder builder = createTransactionBuilder("writeAllFeaturesRequest"); + builder.setGattCallback(this); + writeAllFeaturesRequest(builder, arr); + support.performImmediately(builder); + } catch(IOException e) { + LOG.error("Error writing all features: " + e.getMessage()); + } + } + + private void writeAllFeatures(TransactionBuilder builder, byte[] arr) { + builder.write(getCharacteristic(CasioConstants.CASIO_ALL_FEATURES_CHARACTERISTIC_UUID), arr); + } + + private void writeAllFeatures(byte[] arr) { + try { + TransactionBuilder builder = createTransactionBuilder("writeAllFeatures"); + builder.setGattCallback(this); + writeAllFeatures(builder, arr); + support.performImmediately(builder); + } catch(IOException e) { + LOG.error("Error writing all features: " + e.getMessage()); + } + } + + private void writeAllFeaturesInit() { + byte[] arr = new byte[2]; + arr[0] = 0x00; + arr[1] = 0x01; + + writeAllFeatures(arr); + } + + private void requestWatchName(TransactionBuilder builder) { + writeAllFeaturesRequest(builder, new byte[]{CasioConstants.characteristicToByte.get("CASIO_WATCH_NAME")}); + } + + private void requestWatchName() { + writeAllFeaturesRequest(new byte[]{CasioConstants.characteristicToByte.get("CASIO_WATCH_NAME")}); + } + + private void requestBleConfiguration() { + writeAllFeaturesRequest(new byte[]{CasioConstants.characteristicToByte.get("CASIO_BLE_FEATURES")}); + } + + private void requestBleSettings() { + writeAllFeaturesRequest(new byte[]{CasioConstants.characteristicToByte.get("CASIO_SETTING_FOR_BLE")}); + } + + private void requestAdvertisingParameters() { + writeAllFeaturesRequest(new byte[]{CasioConstants.characteristicToByte.get("CASIO_ADVERTISE_PARAMETER_MANAGER")}); + } + + private void requestConnectionParameters() { + writeAllFeaturesRequest(new byte[]{CasioConstants.characteristicToByte.get("CASIO_CONNECTION_PARAMETER_MANAGER")}); + } + + private void requestModuleId() { + writeAllFeaturesRequest(new byte[]{CasioConstants.characteristicToByte.get("CASIO_MODULE_ID")}); + } + + private void requestWatchCondition() { + writeAllFeaturesRequest(new byte[]{CasioConstants.characteristicToByte.get("CASIO_WATCH_CONDITION")}); + } + + private void requestVersionInformation() { + writeAllFeaturesRequest(new byte[]{CasioConstants.characteristicToByte.get("CASIO_VERSION_INFORMATION")}); + } + + private void requestDstWatchState() { + writeAllFeaturesRequest(new byte[]{CasioConstants.characteristicToByte.get("CASIO_DST_WATCH_STATE")}); + } + + private void requestDstSetting() { + writeAllFeaturesRequest(new byte[]{CasioConstants.characteristicToByte.get("CASIO_DST_SETTING")}); + } + + private void writeWatchName() { + // FIXME: The App ID should be auto-generated and stored in the + // preferences instead of hard-coding it here + // CASIO_APP_INFORMATION: + byte arr[] = new byte[12]; + arr[0] = CasioConstants.characteristicToByte.get("CASIO_APP_INFORMATION"); + for(int i=0; i<10; i++) + arr[i+1] = (byte) (i & 0xff); + arr[11] = 2; + writeAllFeatures(arr); + } + + private void writeBleSettings(byte[] data) { + if(data.length < 14) + return; + + // FIXME: We use hard-coded values here, should they be changeable? + + // CASIO_SETTING_FOR_BLE: + // Don't forget first byte, we write to ALL_FEATURES! + // 0000000600000000000100801e00 + // rest: mirror + // mBleSettings[5] = (byte)(connInterval & 0xff); + // mBleSettings[6] = (byte)((connInterval >> 8) & 0xff); + // mBleSettings[7] = (byte)(slaveLatency & 0xff); + // mBleSettings[8] = (byte)((slaveLatency >> 8) & 0xff); + // mBleSettings[11] = Auto Reconnect Hour + // mBleSettings[12] = Auto Reconnect Minute + + // Shift-by-one b/c of ALL_FEATURES! + data[12] = (byte)0x80; + data[13] = (byte)0x1e; + writeAllFeatures(data); + } + + private void writeAdvertisingParameters() { + // FIXME: This is hardcoded for now, based on the HCI log of my watch + // CASIO_ADVERTISE_PARAMETER_MANAGER + // Don't forget first byte, we write to ALL_FEATURES! + // 50000a4001050540060a02a000054001051c00 + // byte arr[] = new byte[19]; + // arr[0] = advertiseParamAfterSleepResetOrKeyFunctionAdvertiseInterval & 0xff; + // arr[1] = advertiseParamAfterSleepResetOrKeyFunctionAdvertiseInterval >> 8 & 0xff; + // arr[2] = getAdvertiseParamAfterSleepResetOrKeyFunctionAdvertisingTime; + // arr[3] = advertiseParamHighIntervalAdvertisementAfterLinkLossAdvertiseInterval & 0xff; + // arr[4] = advertiseParamHighIntervalAdvertisementAfterLinkLossAdvertiseInterval >> 8 & 0xff; + // arr[5] = getAdvertiseParamHighIntervalAdvertisementAfterLinkLossAdvertisingTime; + // arr[6] = getAdvertiseParamHighIntervalAdvertisementAfterLinkLossFrequencyToAdvertise; + // arr[7] = advertiseParamLowIntervalAdvertisementAfterLinkLossAdvertiseInterval & 0xff; + // arr[8] = advertiseParamLowIntervalAdvertisementAfterLinkLossAdvertiseInterval >> 8 & 0xff; + // arr[9] = getAdvertiseParamLowIntervalAdvertisementAfterLinkLossAdvertisingTime & 0xff; + // arr[10] = getAdvertiseParamLowIntervalAdvertisementAfterLinkLossFrequencyToAdvertise & 0xff; + // arr[11] = advertiseParamRegularBeaconAdvertisementAdvertiseInterval & 0xff; + // arr[12] = advertiseParamRegularBeaconAdvertisementAdvertiseInterval >> 8 & 0xff; + // arr[13] = getAdvertiseParamRegularBeaconAdvertisementAdvertisingTime; + // arr[14] = advertiseParamAdvertiseImmediatelyAfterLinkLossAdvertiseInterval & 0xff; + // arr[15] = advertiseParamAdvertiseImmediatelyAfterLinkLossAdvertiseInterval >> 8 & 0xff; + // arr[16] = getAdvertiseParamAdvertiseImmediatelyAfterLinkLossAdvertisingTime + // arr[17] = advertiseParamPeriodUntilSuspensionOfAutoAdvertise & 0xff; + // arr[18] = advertiseParamPeriodUntilSuspensionOfAutoAdvertise >> 8 & 0xff; + + byte arr[] = new byte[20]; + arr[0] = CasioConstants.characteristicToByte.get("CASIO_ADVERTISE_PARAMETER_MANAGER"); + arr[1] = (byte)0x50; + arr[2] = (byte)0x00; + arr[3] = (byte)0x0a; + arr[4] = (byte)0x40; + arr[5] = (byte)0x01; + arr[6] = (byte)0x05; + arr[7] = (byte)0x05; + arr[8] = (byte)0x40; + arr[9] = (byte)0x06; + arr[10] = (byte)0x0a; + arr[11] = (byte)0x02; + arr[12] = (byte)0xa0; + arr[13] = (byte)0x00; + arr[14] = (byte)0x05; + arr[15] = (byte)0x40; + arr[16] = (byte)0x01; + arr[17] = (byte)0x05; + arr[18] = (byte)0x1c; + arr[19] = (byte)0x00; + writeAllFeatures(arr); + } + + private void writeConnectionParameters() { + // FIXME: This is hardcoded for now, based on the HCI log of my watch + // CASIO_CONNECTION_PARAMETER_MANAGER + // Don't forget first byte, we write to ALL_FEATURES! + // 00340140010400dc05 + // byte[] arr = new byte[9]; + // arr[0] = getConnectionParamCommand; + // arr[1] = connectionParamMinimumConnectionInterval & 0xff; + // arr[2] = connectionParamMinimumConnectionInterval >> 8 & 0xff; + // arr[3] = connectionParamMaxConnectionInterval & 0xff; + // arr[4] = connectionParamMaxConnectionInterval >> 8 & 0xff; + // arr[5] = connectionParamConnLatency & 0xff; + // arr[6] = connectionParamConnLatency >> 8 & 0xff; + // arr[7] = connectionParamSupervisionTimeout & 0xff; + // arr[8] = connectionParamSupervisionTimeout >> 8 & 0xff; + + byte[] arr = new byte[10]; + arr[0] = CasioConstants.characteristicToByte.get("CASIO_CONNECTION_PARAMETER_MANAGER"); + arr[1] = (byte)0x00; + arr[2] = (byte)0x34; + arr[3] = (byte)0x01; + arr[4] = (byte)0x40; + arr[5] = (byte)0x01; + arr[6] = (byte)0x04; + arr[7] = (byte)0x00; + arr[8] = (byte)0xdc; + arr[9] = (byte)0x05; + writeAllFeatures(arr); + } + + private void writeCurrentTime() { + byte[] arr = new byte[11]; + Calendar cal = Calendar.getInstance(); + + int year = cal.get(Calendar.YEAR); + arr[0] = CasioConstants.characteristicToByte.get("CASIO_CURRENT_TIME"); + arr[1] = (byte)((year >>> 0) & 0xff); + arr[2] = (byte)((year >>> 8) & 0xff); + arr[3] = (byte)(1 + cal.get(Calendar.MONTH)); + arr[4] = (byte)cal.get(Calendar.DAY_OF_MONTH); + arr[5] = (byte)cal.get(Calendar.HOUR_OF_DAY); + arr[6] = (byte)cal.get(Calendar.MINUTE); + arr[7] = (byte)(1 + cal.get(Calendar.SECOND)); + byte dayOfWk = (byte)(cal.get(Calendar.DAY_OF_WEEK) - 1); + if(dayOfWk == 0) + dayOfWk = 7; + arr[8] = dayOfWk; + arr[9] = (byte)(int) TimeUnit.MILLISECONDS.toSeconds(256 * cal.get(Calendar.MILLISECOND)); + arr[10] = 1; // or 0? + + writeAllFeatures(arr); + } + + private void enableAllFeatures(boolean enable) { + try { + TransactionBuilder builder = createTransactionBuilder("notifyAllFeatures"); + builder.setGattCallback(this); + enableAllFeatures(builder, enable); + support.performImmediately(builder); + } catch(IOException e) { + LOG.error("Error setting notification value on all features: " + e.getMessage()); + } + } + + private void enableAllFeatures(TransactionBuilder builder, boolean enable) { + builder.notify(getCharacteristic(CasioConstants.CASIO_ALL_FEATURES_CHARACTERISTIC_UUID), enable); + } + + @Override + protected void doPerform() throws IOException { + + builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext())); + enableAllFeatures(builder, true); + requestWatchName(builder); + //writeAllFeaturesInit(); + } + + @Override + public TransactionBuilder performInitialized(String taskName) throws IOException { + throw new UnsupportedOperationException("This IS the initialization class, you cannot call this method"); + } + + @Override + public boolean onCharacteristicChanged(BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic) { + UUID characteristicUUID = characteristic.getUuid(); + byte[] data = characteristic.getValue(); + + if(data.length == 0) + return true; + + if(characteristicUUID.equals(CasioConstants.CASIO_ALL_FEATURES_CHARACTERISTIC_UUID)) { + if(data[0] == CasioConstants.characteristicToByte.get("CASIO_WATCH_NAME")) { + LOG.info("Got watch name, requesting BLE features; should write CASIO_APP_INFORMATION"); + writeWatchName(); + requestBleConfiguration(); + } else if(data[0] == CasioConstants.characteristicToByte.get("CASIO_BLE_FEATURES")) { + LOG.info("Got BLE features, requesting BLE settings"); + requestBleSettings(); + } else if(data[0] == CasioConstants.characteristicToByte.get("CASIO_SETTING_FOR_BLE")) { + LOG.info("Got BLE settings, requesting advertising parameters; should write BLE settings"); + writeBleSettings(data); + requestAdvertisingParameters(); + } else if(data[0] == CasioConstants.characteristicToByte.get("CASIO_ADVERTISE_PARAMETER_MANAGER")) { + LOG.info("Got advertising parameters, requesting connection parameters; should write advertising parameters"); + writeAdvertisingParameters(); + requestConnectionParameters(); + } else if(data[0] == CasioConstants.characteristicToByte.get("CASIO_CONNECTION_PARAMETER_MANAGER")) { + LOG.info("Got connection parameters, requesting module ID; should write connection parameters"); + writeConnectionParameters(); + requestModuleId(); + } else if(data[0] == CasioConstants.characteristicToByte.get("CASIO_MODULE_ID")) { + LOG.info("Got module ID, requesting watch condition"); + requestWatchCondition(); + } else if(data[0] == CasioConstants.characteristicToByte.get("CASIO_WATCH_CONDITION")) { + LOG.info("Got watch condition, requesting version information"); + requestVersionInformation(); + } else if(data[0] == CasioConstants.characteristicToByte.get("CASIO_VERSION_INFORMATION")) { + LOG.info("Got version information, requesting DST watch state"); + requestDstWatchState(); + } else if(data[0] == CasioConstants.characteristicToByte.get("CASIO_DST_WATCH_STATE")) { + LOG.info("Got DST watch state, requesting DST setting; should write DST watch state"); + requestDstSetting(); + } else if(data[0] == CasioConstants.characteristicToByte.get("CASIO_DST_SETTING")) { + LOG.info("Got DST setting, waiting...; should write DST setting and location and radio information"); + } else if(data[0] == CasioConstants.characteristicToByte.get("CASIO_SERVICE_DISCOVERY_MANAGER")) { + if(data[1] == 0x02) { + LOG.info("We need to bond here. This is actually the request for the current time."); + writeCurrentTime(); + writeAllFeaturesInit(); + } else if(data[1] == 0x01) { + LOG.info("We need to encrypt here."); + writeAllFeaturesInit(); + } + } else if(data[0] == 0x3d) { + LOG.info("Init operation done."); + support.setInitialized(); + } + return true; + } else { + LOG.info("Unhandled characteristic changed: " + characteristicUUID); + return super.onCharacteristicChanged(gatt, characteristic); + } + } + + @Override + public boolean onCharacteristicRead(BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic, int status) { + + return super.onCharacteristicRead(gatt, characteristic, status); + } +} \ No newline at end of file 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 769f1b1d1..14398ce03 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java @@ -44,6 +44,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.UnknownDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.casio.gb6900.CasioGB6900DeviceCoordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.casio.gbx100.CasioGBX100DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.hplus.EXRIZUK8Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.hplus.MakibesF68Coordinator; @@ -255,6 +256,7 @@ public class DeviceHelper { result.add(new Roidmi3Coordinator()); result.add(new Y5Coordinator()); result.add(new CasioGB6900DeviceCoordinator()); + result.add(new CasioGBX100DeviceCoordinator()); result.add(new BFH16DeviceCoordinator()); result.add(new MijiaLywsd02Coordinator()); result.add(new ITagCoordinator()); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0208298f8..8a9ef9f7b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -804,6 +804,7 @@ Roidmi 3 Y5 Casio GB-6900 + Casio GBX-100 Mi Scale 2 iTag BFH-16 @@ -953,7 +954,7 @@ Contributors Andreas Shimokawa\nCarsten Pfeiffer\nDaniele Gobbetti Additional device support - João Paulo Barraca (HPlus)\nVitaly Svyastyn (NO.1 F1)\nSami Alaoui (Teclast H30)\n“ladbsoft” (XWatch)\nSebastian Kranz (ZeTime)\nVadim Kaushan (ID115)\n“maxirnilian” (Lenovo Watch 9)\n“ksiwczynski”, “mkusnierz”, “mamutcho” (Lenovo Watch X Plus)\nAndreas Böhler (Casio GB-6900B)\nJean-François Greffier (Mi Scale 2)\nJohannes Schmitt (BFH-16)\nLukas Schwichtenberg (Makibes HR3)\nDaniel Dakhno (Fossil Q Hybrid, Fossil Hybrid HR)\nGordon Williams (Bangle.js)\nPavel Elagin (JYou Y5)\nTaavi Eomäe (iTag) + João Paulo Barraca (HPlus)\nVitaly Svyastyn (NO.1 F1)\nSami Alaoui (Teclast H30)\n“ladbsoft” (XWatch)\nSebastian Kranz (ZeTime)\nVadim Kaushan (ID115)\n“maxirnilian” (Lenovo Watch 9)\n“ksiwczynski”, “mkusnierz”, “mamutcho” (Lenovo Watch X Plus)\nAndreas Böhler (Casio GB-6900B, Casio GB-5600B, Casio GBX-100)\nJean-François Greffier (Mi Scale 2)\nJohannes Schmitt (BFH-16)\nLukas Schwichtenberg (Makibes HR3)\nDaniel Dakhno (Fossil Q Hybrid, Fossil Hybrid HR)\nGordon Williams (Bangle.js)\nPavel Elagin (JYou Y5)\nTaavi Eomäe (iTag) Many thanks to all unlisted contributors for contributing code, translations, support, ideas, motivation, bug reports, money… ✊ Links All these permissions are required and instability might occur if not granted