diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casiogb6900/CasioGB6900Constants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casiogb6900/CasioGB6900Constants.java new file mode 100644 index 000000000..65b7e5e2b --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casiogb6900/CasioGB6900Constants.java @@ -0,0 +1,70 @@ +/* Copyright (C) 2018 Andreas Böhler + 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.casiogb6900; + +import java.util.UUID; + +public final class CasioGB6900Constants { + public static final UUID CASIO_VIRTUAL_SERVER_SERVICE = UUID.fromString("26eb0007-b012-49a8-b1f8-394fb2032b0f"); + + public static final UUID CASIO_VIRTUAL_SERVER_FEATURES = UUID.fromString("26eb0008-b012-49a8-b1f8-394fb2032b0f"); + + public static final UUID CASIO_A_NOT_W_REQ_NOT = UUID.fromString( "26eb0009-b012-49a8-b1f8-394fb2032b0f"); + public static final UUID CASIO_A_NOT_COM_SET_NOT = UUID.fromString( "26eb000a-b012-49a8-b1f8-394fb2032b0f"); + + public static final UUID CCC_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); + + // Alert + + public static final UUID ALERT_SERVICE_UUID = UUID.fromString("26eb0000-b012-49a8-b1f8-394fb2032b0f"); + public static final UUID ALERT_CHARACTERISTIC_UUID = UUID.fromString("00002a46-0000-1000-8000-00805f9b34fb"); + public static final UUID ALERT_NOTIFICATION_CONTROL_POINT = UUID.fromString("00002a44-0000-1000-8000-00805f9b34fb"); + + // Phone Alert + public static final UUID CASIO_PHONE_ALERT_STATUS_SERVICE = UUID.fromString("26eb0001-b012-49a8-b1f8-394fb2032b0f"); + public static final UUID RINGER_CONTROL_POINT = UUID.fromString("00002a40-0000-1000-8000-00805f9b34fb"); + + // Phone Finder + + public static final UUID CASIO_IMMEDIATE_ALERT_SERVICE_UUID = UUID.fromString("26eb0005-b012-49a8-b1f8-394fb2032b0f"); + public static final UUID ALERT_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002a06-0000-1000-8000-00805f9b34fb"); + + // Current Time + + public static final UUID CURRENT_TIME_SERVICE_UUID = UUID.fromString("26eb0002-b012-49a8-b1f8-394fb2032b0f"); + public static final UUID CURRENT_TIME_CHARACTERISTIC_UUID = UUID.fromString("00002a2b-0000-1000-8000-00805f9b34fb"); + public static final UUID LOCAL_TIME_CHARACTERISTIC_UUID = UUID.fromString("00002a0f-0000-1000-8000-00805f9b34fb"); + + // Control Mode + public static final UUID WATCH_FEATURES_SERVICE_UUID = UUID.fromString("26eb000d-b012-49a8-b1f8-394fb2032b0f"); + public static final UUID WATCH_CTRL_SERVICE_UUID = UUID.fromString("26eb0018-b012-49a8-b1f8-394fb2032b0f"); + public static final UUID KEY_CONTAINER_CHARACTERISTIC_UUID = UUID.fromString("26eb0019-b012-49a8-b1f8-394fb2032b0f"); + public static final UUID NAME_OF_APP_CHARACTERISTIC_UUID = UUID.fromString("26eb001d-b012-49a8-b1f8-394fb2032b0f"); + public static final UUID FUNCTION_SWITCH_CHARACTERISTIC = UUID.fromString("26eb001e-b012-49a8-b1f8-394fb2032b0f"); + public static final String MUSIC_MESSAGE = "Music"; + + // Notification Types + + public static final byte CALL_NOTIFICATION_ID = 3; + public static final byte MAIL_NOTIFICATION_ID = 1; + public static final byte CALENDAR_NOTIFICATION_ID = 7; + public static final byte SNS_NOTIFICATION_ID = 13; + public static final byte SMS_NOTIFICATION_ID = 5; + + +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casiogb6900/CasioGB6900DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casiogb6900/CasioGB6900DeviceCoordinator.java new file mode 100644 index 000000000..f5d5cec04 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casiogb6900/CasioGB6900DeviceCoordinator.java @@ -0,0 +1,158 @@ +/* Copyright (C) 2018 Andreas Böhler + 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.casiogb6900; + +import android.app.Activity; +import android.content.Context; +import android.net.Uri; +import android.support.annotation.NonNull; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import nodomain.freeyourgadget.gadgetbridge.GBException; +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.impl.GBDeviceCandidate; +import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; +import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; + +public class CasioGB6900DeviceCoordinator extends AbstractDeviceCoordinator { + protected static final Logger LOG = LoggerFactory.getLogger(CasioGB6900DeviceCoordinator.class); + + @NonNull + @Override + public DeviceType getSupportedType(GBDeviceCandidate candidate) { + if (candidate.supportsService(CasioGB6900Constants.CASIO_VIRTUAL_SERVER_SERVICE)) { + return DeviceType.CASIOGB6900; + } + + String name = candidate.getDevice().getName(); + if (name != null) { + if (name.startsWith("CASIO")) { + return DeviceType.CASIOGB6900; + } + } + + return DeviceType.UNKNOWN; + } + + @Override + public int getBondingStyle(GBDevice deviceCandidate){ + return BONDING_STYLE_BOND; + } + + @Override + public boolean supportsCalendarEvents() { + return false; + } + + @Override + public boolean supportsRealtimeData() { + return false; + } + + @Override + public boolean supportsWeather() { + return false; + } + + @Override + public boolean supportsFindDevice() { + return true; + } + + @Override + public DeviceType getDeviceType() { + return DeviceType.CASIOGB6900; + } + + @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 boolean supportsAlarmConfiguration() { + return false; + } + + @Override + public int getAlarmSlotCount() { + return 5; // 4 regular and one snooze but not yet implemented + } + + @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 de073474b..0ca18cfde 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java @@ -51,6 +51,7 @@ public enum DeviceType { WATCH9(100, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_watch9), 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), TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_test); 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 10962047a..82c16790c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java @@ -29,6 +29,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBException; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; +import nodomain.freeyourgadget.gadgetbridge.service.devices.casiogb6900.CasioGB6900DeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitcor.AmazfitCorSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband3.MiBand3Support; @@ -171,6 +172,9 @@ public class DeviceSupportFactory { case ROIDMI3: deviceSupport = new ServiceDeviceSupport(new RoidmiSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); break; + case CASIOGB6900: + deviceSupport = new ServiceDeviceSupport(new CasioGB6900DeviceSupport(), 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/casiogb6900/CasioGATTServer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGATTServer.java new file mode 100644 index 000000000..d76402b8a --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGATTServer.java @@ -0,0 +1,163 @@ +/* Copyright (C) 2018 Andreas Böhler + 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.casiogb6900; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; +import android.bluetooth.BluetoothGattServer; +import android.bluetooth.BluetoothGattServerCallback; +import android.bluetooth.BluetoothGattService; +import android.bluetooth.BluetoothManager; +import android.content.Context; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.CountDownLatch; + +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl; +import nodomain.freeyourgadget.gadgetbridge.devices.casiogb6900.CasioGB6900Constants; + +class CasioGATTServer extends BluetoothGattServerCallback { + private static final Logger LOG = LoggerFactory.getLogger(CasioGATTServer.class); + + private Context mContext; + private BluetoothGattServer mBluetoothGattServer; + private CasioGB6900DeviceSupport mDeviceSupport = null; + private final GBDeviceEventMusicControl musicCmd = new GBDeviceEventMusicControl(); + + CasioGATTServer(Context context, CasioGB6900DeviceSupport deviceSupport) { + mContext = context; + mDeviceSupport = deviceSupport; + } + + public void setContext(Context ctx) + { + mContext = ctx; + } + + boolean initialize() { + BluetoothManager bluetoothManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE); + if (bluetoothManager == null) { + return false; + } + mBluetoothGattServer = bluetoothManager.openGattServer(mContext, this); + if (mBluetoothGattServer == null) { + return false; + } + + BluetoothGattService casioGATTService = new BluetoothGattService(CasioGB6900Constants.WATCH_CTRL_SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY); + BluetoothGattCharacteristic bluetoothgGATTCharacteristic = new BluetoothGattCharacteristic(CasioGB6900Constants.KEY_CONTAINER_CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE, BluetoothGattCharacteristic.PERMISSION_WRITE); + bluetoothgGATTCharacteristic.setValue(new byte[0]); + + BluetoothGattCharacteristic bluetoothgGATTCharacteristic2 = new BluetoothGattCharacteristic(CasioGB6900Constants.NAME_OF_APP_CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ | BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED); + bluetoothgGATTCharacteristic2.setValue(CasioGB6900Constants.MUSIC_MESSAGE.getBytes()); + + BluetoothGattDescriptor bluetoothGattDescriptor = new BluetoothGattDescriptor(CasioGB6900Constants.CCC_DESCRIPTOR_UUID, BluetoothGattDescriptor.PERMISSION_READ | BluetoothGattDescriptor.PERMISSION_WRITE); + bluetoothGattDescriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); + + bluetoothgGATTCharacteristic2.addDescriptor(bluetoothGattDescriptor); + + casioGATTService.addCharacteristic(bluetoothgGATTCharacteristic); + casioGATTService.addCharacteristic(bluetoothgGATTCharacteristic2); + mBluetoothGattServer.addService(casioGATTService); + + return true; + } + + public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) { + + if (!characteristic.getUuid().equals(CasioGB6900Constants.NAME_OF_APP_CHARACTERISTIC_UUID)) { + LOG.warn("unexpected read request"); + return; + } + + LOG.info("will send response to read request from device: " + device.getAddress()); + + if (!this.mBluetoothGattServer.sendResponse(device, requestId, 0, offset, CasioGB6900Constants.MUSIC_MESSAGE.getBytes())) { + LOG.warn("error sending response"); + } + } + + + public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, + boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) { + + if (!characteristic.getUuid().equals(CasioGB6900Constants.KEY_CONTAINER_CHARACTERISTIC_UUID)) { + LOG.warn("unexpected write request"); + return; + } + if((value[0] & 0x03) == 0) + { + int button = value[1] & 0x0f; + LOG.info("Button pressed: " + button); + switch(button) + { + case 2: + musicCmd.event = GBDeviceEventMusicControl.Event.PREVIOUS; + break; + case 1: + musicCmd.event = GBDeviceEventMusicControl.Event.PLAYPAUSE; + break; + case 0: + musicCmd.event = GBDeviceEventMusicControl.Event.NEXT; + break; + } + mDeviceSupport.evaluateGBDeviceEvent(musicCmd); + } + else + { + LOG.info("received from device: " + value.toString()); + } + } + + public void onConnectionStateChange(BluetoothDevice device, int status, int newState) { + + LOG.info("Connection state change for device: " + device.getAddress() + " status = " + status + " newState = " + newState); + if (newState == BluetoothGattServer.STATE_DISCONNECTED) { + + } + } + + public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, + boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) { + + LOG.info("onDescriptorWriteRequest() notifications enabled = " + (value[0] == 1)); + if (!this.mBluetoothGattServer.sendResponse(device, requestId, 0, offset, value)) { + LOG.warn("onDescriptorWriteRequest() error sending response!"); + } + } + + + public void onServiceAdded(int status, BluetoothGattService service) { + LOG.info("onServiceAdded() status = " + status + " service = " + service.getUuid()); + } + + public void onNotificationSent(BluetoothDevice bluetoothDevice, int status) { + LOG.info("onNotificationSent() status = " + status + " to device " + bluetoothDevice.getAddress()); + } + + void close() { + if (mBluetoothGattServer != null) { + mBluetoothGattServer.clearServices(); + mBluetoothGattServer.close(); + } + } + +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGATTThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGATTThread.java new file mode 100644 index 000000000..6ed5426ca --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGATTThread.java @@ -0,0 +1,48 @@ +/* Copyright (C) 2018 Andreas Böhler + 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.casiogb6900; +import android.content.Context; + +public class CasioGATTThread extends Thread { + CasioGATTServer mServer = null; + + public CasioGATTThread(Context context, CasioGB6900DeviceSupport deviceSupport) + { + mServer = new CasioGATTServer(context, deviceSupport); + } + + public void setContext(Context ctx) + { + mServer.setContext(ctx); + } + + @Override + public void run() + { + mServer.initialize(); + while(true) + { + try { + wait(100); + } catch(Exception e) + { + + } + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGB6900DeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGB6900DeviceSupport.java new file mode 100644 index 000000000..2f050a766 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGB6900DeviceSupport.java @@ -0,0 +1,484 @@ +/* Copyright (C) 2018 Andreas Böhler + 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.casiogb6900; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; +import android.content.Context; +import android.net.Uri; + +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.GBDeviceEventFindPhone; +import nodomain.freeyourgadget.gadgetbridge.devices.casiogb6900.CasioGB6900Constants; +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.StringUtils; + +public class CasioGB6900DeviceSupport extends AbstractBTLEDeviceSupport { + private static final Logger LOG = LoggerFactory.getLogger(CasioGB6900DeviceSupport.class); + + public BluetoothGattCharacteristic mCasioCharact1 = null; + public BluetoothGattCharacteristic mCasioCharact2 = null; + public BluetoothGattCharacteristic mCasioCharact3 = null; + public BluetoothGattCharacteristic mCasioCharact4 = null; + public BluetoothGattCharacteristic mCasioCharact5 = null; + private CasioGATTThread mThread = null; + + public CasioGB6900DeviceSupport() { + super(LOG); + addSupportedService(CasioGB6900Constants.CASIO_VIRTUAL_SERVER_SERVICE); + addSupportedService(CasioGB6900Constants.ALERT_SERVICE_UUID); + addSupportedService(CasioGB6900Constants.CASIO_IMMEDIATE_ALERT_SERVICE_UUID); + addSupportedService(CasioGB6900Constants.CURRENT_TIME_SERVICE_UUID); + addSupportedService(CasioGB6900Constants.WATCH_CTRL_SERVICE_UUID); + addSupportedService(CasioGB6900Constants.WATCH_FEATURES_SERVICE_UUID); + addSupportedService(CasioGB6900Constants.CASIO_PHONE_ALERT_STATUS_SERVICE); + mThread = new CasioGATTThread(getContext(), this); + } + + @Override + public void setContext(GBDevice gbDevice, BluetoothAdapter btAdapter, Context context) { + super.setContext(gbDevice, btAdapter, context); + mThread.setContext(context); + mThread.start(); + } + + @Override + protected TransactionBuilder initializeDevice(TransactionBuilder builder) { + LOG.info("Initializing"); + + gbDevice.setState(GBDevice.State.INITIALIZING); + gbDevice.sendDeviceUpdateIntent(getContext()); + + mCasioCharact1 = getCharacteristic(CasioGB6900Constants.CASIO_A_NOT_COM_SET_NOT); + mCasioCharact2 = getCharacteristic(CasioGB6900Constants.CASIO_A_NOT_W_REQ_NOT); + mCasioCharact3 = getCharacteristic(CasioGB6900Constants.FUNCTION_SWITCH_CHARACTERISTIC); + mCasioCharact4 = getCharacteristic(CasioGB6900Constants.ALERT_LEVEL_CHARACTERISTIC_UUID); + mCasioCharact5 = getCharacteristic(CasioGB6900Constants.RINGER_CONTROL_POINT); + + builder.setGattCallback(this); + builder.notify(mCasioCharact1, true); + builder.notify(mCasioCharact2, true); + builder.notify(mCasioCharact3, true); + builder.notify(mCasioCharact4, true); + builder.notify(mCasioCharact5, true); + + LOG.info("Initialization Done"); + + return builder; + } + + private void writeCasioCurrentTime(TransactionBuilder builder) + { + byte[] arr = new byte[10]; + Calendar cal = Calendar.getInstance(); + + int year = cal.get(Calendar.YEAR); + arr[0] = (byte)((year >>> 0) & 0xff); + arr[1] = (byte)((year >>> 8) & 0xff); + arr[2] = (byte)(1 + cal.get(Calendar.MONTH)); + arr[3] = (byte)cal.get(Calendar.DAY_OF_MONTH); + arr[4] = (byte)cal.get(Calendar.HOUR_OF_DAY); + arr[5] = (byte)cal.get(Calendar.MINUTE); + arr[6] = (byte)(1 + cal.get(Calendar.SECOND)); + byte dayOfWk = (byte)(cal.get(Calendar.DAY_OF_WEEK) - 1); + if(dayOfWk == 0) + dayOfWk = 7; + arr[7] = dayOfWk; + arr[8] = (byte)(int) TimeUnit.MILLISECONDS.toSeconds(256 * cal.get(Calendar.MILLISECOND)); + arr[9] = 1; // or 0? + + BluetoothGattCharacteristic charact = getCharacteristic(CasioGB6900Constants.CURRENT_TIME_CHARACTERISTIC_UUID); + if(charact != null) { + charact.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT); + builder.write(charact, arr); + } + else { + LOG.warn("Characteristic not found: CURRENT_TIME_CHARACTERISTIC_UUID"); + } + } + + private void writeCasioLocalTimeInformation(TransactionBuilder builder) + { + Calendar cal = Calendar.getInstance(); + int zoneOffset = (int)TimeUnit.MILLISECONDS.toMinutes(cal.get(Calendar.ZONE_OFFSET)); + int dstOffset = (int)TimeUnit.MILLISECONDS.toMinutes(cal.get(Calendar.DST_OFFSET)); + byte byte0 = (byte)(zoneOffset / 15); + byte byte1 = (byte)(dstOffset / 15); + BluetoothGattCharacteristic charact = getCharacteristic(CasioGB6900Constants.LOCAL_TIME_CHARACTERISTIC_UUID); + if(charact != null) { + builder.write(charact, new byte[]{byte0, byte1}); + } + else { + LOG.warn("Characteristic not found: LOCAL_TIME_CHARACTERISTIC_UUID"); + } + + } + + private void writeCasioVirtualServerFeature(TransactionBuilder builder) + { + byte byte0 = (byte)0; + byte0 |= 1; // Casio Current Time Service + byte0 |= 2; // Casio Alert Notification Service + byte0 |= 4; // Casio Phone Alert Status Service + byte0 |= 8; // Casio Immediate Alert Service + + BluetoothGattCharacteristic charact = getCharacteristic(CasioGB6900Constants.CASIO_VIRTUAL_SERVER_FEATURES); + if(charact != null) { + builder.write(charact, new byte[]{byte0, 0x00}); + } + else { + LOG.warn("Characteristic not found: CASIO_VIRTUAL_SERVER_FEATURES"); + } + } + + private boolean handleCasioCom(byte[] data) + { + boolean handled = false; + switch(data[0]) // ServiceID - actually an int + { + case 0: + switch(data[2]) + { + case (byte) 1: + LOG.info("Initialization done, setting state to INITIALIZED"); + gbDevice.setState(GBDevice.State.INITIALIZED); + gbDevice.sendDeviceUpdateIntent(getContext()); + break; + } + break; + case 2: + switch(data[2]) // Request Type + { + case (byte) 1: + try + { + TransactionBuilder builder = createTransactionBuilder("writeCasioCurrentTime"); + writeCasioCurrentTime(builder); + performImmediately(builder); + handled = true; + } catch (IOException e) { + LOG.warn(e.getMessage()); + } + break; + case (byte) 2: + try + { + TransactionBuilder builder = createTransactionBuilder("writeCasioLocalTimeInformation"); + writeCasioLocalTimeInformation(builder); + performImmediately(builder); + handled = true; + } catch (IOException e) { + LOG.warn(e.getMessage()); + } + break; + } + break; + case 7: + try + { + TransactionBuilder builder = createTransactionBuilder("writeCasioVirtualServerFeature"); + writeCasioVirtualServerFeature(builder); + performImmediately(builder); + handled = true; + } catch (IOException e) { + LOG.warn(e.getMessage()); + } + break; + } + return handled; + } + + @Override + public boolean onCharacteristicChanged(BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic) { + boolean handled = false; + + if (super.onCharacteristicChanged(gatt, characteristic)) { + return true; + } + + UUID characteristicUUID = characteristic.getUuid(); + byte[] data = characteristic.getValue(); + if (data.length == 0) + return true; + + if(characteristicUUID.equals(CasioGB6900Constants.CASIO_A_NOT_W_REQ_NOT)) + { + handled = handleCasioCom(data); + } + + if(characteristicUUID.equals(CasioGB6900Constants.CASIO_A_NOT_COM_SET_NOT)) + { + handled = handleCasioCom(data); + } + + if(characteristicUUID.equals(CasioGB6900Constants.ALERT_LEVEL_CHARACTERISTIC_UUID)) + { + GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone(); + if(data[0] == 0x02) { + findPhoneEvent.event = GBDeviceEventFindPhone.Event.START; + } + else + { + findPhoneEvent.event = GBDeviceEventFindPhone.Event.STOP; + } + evaluateGBDeviceEvent(findPhoneEvent); + handled = true; + } + + if(characteristicUUID.equals(CasioGB6900Constants.RINGER_CONTROL_POINT)) + { + if(data[0] == 0x02) + { + LOG.info("Mute/ignore call event not yet supported by GB"); + } + handled = true; + } + + if(!handled) + { + LOG.info("Unhandled characteristic change: " + characteristicUUID + " code: " + String.format("0x%1x ...", data[0])); + } + return true; + } + + private void showNotification(byte icon, String title, String message) { + try { + TransactionBuilder builder = performInitialized("showNotification"); + int len; + + byte[] titleBytes = title.getBytes(StandardCharsets.US_ASCII); + len = titleBytes.length > 18 ? 18 : titleBytes.length; + byte[] msg = new byte[2 + len]; + msg[0] = icon; + msg[1] = 1; + for(int i=0; i alarms) { + + } + + @Override + public void onSetTime() { + try { + TransactionBuilder builder = performInitialized("SetTime"); + writeCasioLocalTimeInformation(builder); + writeCasioCurrentTime(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(CasioGB6900Constants.CALL_NOTIFICATION_ID, 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) { + } + + @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) { + try { + + } catch(Exception e) { + LOG.warn(e.getMessage()); + } + } + + @Override + public void onHeartRateTest() { + + } + + @Override + public void onEnableRealtimeHeartRateMeasurement(boolean enable) { + + } + + @Override + public void onFindDevice(boolean start) { + if(start) { + showNotification(CasioGB6900Constants.SNS_NOTIFICATION_ID, "You found it!", ""); + } + } + + @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 onTestNewFunction() { + + } + + @Override + public void onSendWeather(WeatherSpec weatherSpec) { + + } +} 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 2f483af39..4ecbb6ed4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java @@ -41,6 +41,7 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.UnknownDeviceCoordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.casiogb6900.CasioGB6900DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.hplus.EXRIZUK8Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.hplus.MakibesF68Coordinator; @@ -216,6 +217,7 @@ public class DeviceHelper { result.add(new Watch9DeviceCoordinator()); result.add(new Roidmi1Coordinator()); result.add(new Roidmi3Coordinator()); + result.add(new CasioGB6900DeviceCoordinator()); return result; } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7731a390f..c2185e8e0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -628,6 +628,7 @@ Watch 9 Roidmi Roidmi 3 + Casio GB-6900 Choose export location Gadgetbridge notifications