/* Copyright (C) 2023-2024 Johannes Krude 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.gbx100; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; import android.content.SharedPreferences; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.time.LocalDate; import java.util.Arrays; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioConstants; import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.Casio2C2DSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.gbx100.CasioGBX100DeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus; import nodomain.freeyourgadget.gadgetbridge.util.BcdUtil; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_AUTOLIGHT; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_KEY_VIBRATION; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_OPERATING_SOUNDS; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_WEARLOCATION; import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.GENDER_MALE; public class SetConfigurationOperation extends AbstractBTLEOperation { private static final Logger LOG = LoggerFactory.getLogger(GetConfigurationOperation.class); private final CasioGBX100DeviceSupport support; private final CasioConstants.ConfigurationOption option; public SetConfigurationOperation(CasioGBX100DeviceSupport support, CasioConstants.ConfigurationOption option) { super(support); this.support = support; this.option = option; } @Override protected void prePerform() throws IOException { super.prePerform(); getDevice().setBusyTask("SetConfigurationOperation starting..."); // mark as busy quickly to avoid interruptions from the outside } @Override protected void doPerform() throws IOException { byte[] command = new byte[1]; command[0] = Casio2C2DSupport.FEATURE_SETTING_FOR_USER_PROFILE; TransactionBuilder builder = performInitialized("getConfiguration"); builder.setCallback(this); support.writeAllFeaturesRequest(builder, command); builder.queue(getQueue()); } @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)) { byte[] oldData = new byte[data.length]; System.arraycopy(data, 0, oldData, 0, data.length); if (data[0] == Casio2C2DSupport.FEATURE_SETTING_FOR_USER_PROFILE) { ActivityUser user = new ActivityUser(); boolean all = (option == CasioConstants.ConfigurationOption.OPTION_ALL); if (option == CasioConstants.ConfigurationOption.OPTION_GENDER || all) { if (user.getGender() == GENDER_MALE) { data[1] = (byte) (data[1] & ~0x01); } else { data[1] = (byte) (data[1] | 0x01); } } for(int i=2; i> 8) & 0xff); } if(option == CasioConstants.ConfigurationOption.OPTION_DISTANCE_GOAL || all) { // The watch requires a monthly goal, so we multiply that with 30 // and divide it by 100 because the value is set in 100m units int distance = user.getDistanceGoalMeters() * 30; distance = distance / 100; data[6] = (byte)(distance & 0xff); data[7] = (byte)((distance >> 8) & 0xff); } if(option == CasioConstants.ConfigurationOption.OPTION_ACTIVITY_GOAL || all) { // The watch requires a monthly goal, so we multiply that with 30 int time = user.getActiveTimeGoalMinutes() * 30; data[9] = (byte)(time & 0xff); data[10] = (byte)((time >> 8) & 0xff); } if(Arrays.equals(oldData, data)) { LOG.info("No configuration update required"); requestBasicSettings(); } else { // Basic settings will be requested in Gatt callback try { TransactionBuilder builder = performInitialized("setConfiguration"); builder.setCallback(this); support.writeAllFeatures(builder, data); builder.queue(getQueue()); } catch (IOException e) { LOG.info("Error writing configuration to Casio watch"); } } return true; } else if(data[0] == Casio2C2DSupport.FEATURE_SETTING_FOR_BASIC) { SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()); String timeformat = GBApplication.getDevicePrefs(getDevice()).getTimeFormat(); if(timeformat.equals(getContext().getString(R.string.p_timeformat_24h))) { data[1] |= 0x01; } else { data[1] &= ~0x01; } boolean autolight = sharedPreferences.getBoolean(PREF_AUTOLIGHT, false); if(autolight) { data[1] &= ~0x04; } else { data[1] |= 0x04; } boolean key_vibration = sharedPreferences.getBoolean(PREF_KEY_VIBRATION, true); if (key_vibration) { data[10] = 1; } else { data[10] = 0; } boolean operating_sounds = sharedPreferences.getBoolean(PREF_OPERATING_SOUNDS, false); if(operating_sounds) { data[1] &= ~0x02; } else { data[1] |= 0x02; } if(Arrays.equals(oldData, data)) { LOG.info("No configuration update required"); operationFinished(); } else { // Operation will be finished in Gatt callback try { TransactionBuilder builder = performInitialized("setConfiguration"); builder.setCallback(this); support.writeAllFeatures(builder, data); builder.queue(getQueue()); } catch (IOException e) { LOG.info("Error writing configuration to Casio watch"); } } return true; } } LOG.info("Unhandled characteristic changed: " + characteristicUUID); return super.onCharacteristicChanged(gatt, characteristic); } @Override protected void operationFinished() { LOG.info("SetConfigurationOperation finished"); unsetBusy(); operationStatus = OperationStatus.FINISHED; if (getDevice() != null) { try { TransactionBuilder builder = performInitialized("finished operation"); builder.setCallback(null); // unset ourselves from being the queue's gatt callback builder.wait(0); builder.queue(getQueue()); } catch (IOException ex) { LOG.info("Error resetting Gatt callback: " + ex.getMessage()); } } } private void requestBasicSettings() { byte[] command = new byte[1]; command[0] = Casio2C2DSupport.FEATURE_SETTING_FOR_BASIC; try { TransactionBuilder builder = performInitialized("getConfiguration"); builder.setCallback(this); support.writeAllFeaturesRequest(builder, command); builder.queue(getQueue()); } catch(IOException e) { LOG.info("Error requesting Casio configuration"); } } private void requestTargetSettings() { byte[] command = new byte[1]; command[0] = Casio2C2DSupport.FEATURE_SETTING_FOR_TARGET_VALUE; try { TransactionBuilder builder = performInitialized("getConfiguration"); builder.setCallback(this); support.writeAllFeaturesRequest(builder, command); builder.queue(getQueue()); } catch(IOException e) { LOG.info("Error requesting Casio configuration"); } } @Override public boolean onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { 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] == Casio2C2DSupport.FEATURE_SETTING_FOR_USER_PROFILE) { requestTargetSettings(); return true; } if(data[0] == Casio2C2DSupport.FEATURE_SETTING_FOR_TARGET_VALUE) { requestBasicSettings(); return true; } if(data[0] == Casio2C2DSupport.FEATURE_SETTING_FOR_BASIC) { operationFinished(); return true; } } return super.onCharacteristicWrite(gatt, characteristic, status); } }