1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-27 12:26:48 +01:00

Casio GBX-100: Add step count data and more device settings

Co-Authored-By: andyboeh <andyboeh@noreply.codeberg.org>
Co-Committed-By: andyboeh <andyboeh@noreply.codeberg.org>
This commit is contained in:
andyboeh 2020-12-20 00:22:35 +01:00 committed by Andreas Shimokawa
parent 074bc885c8
commit 02b85f4c11
16 changed files with 888 additions and 82 deletions

View File

@ -43,7 +43,7 @@ public class GBDaoGenerator {
public static void main(String[] args) throws Exception {
Schema schema = new Schema(31, MAIN_PACKAGE + ".entities");
Schema schema = new Schema(32, MAIN_PACKAGE + ".entities");
Entity userAttributes = addUserAttributes(schema);
Entity user = addUserInfo(schema, userAttributes);
@ -79,6 +79,7 @@ public class GBDaoGenerator {
addLefunSleepSample(schema, user, device);
addSonySWR12Sample(schema, user, device);
addBangleJSActivitySample(schema, user, device);
addCasioGBX100Sample(schema, user, device);
addHybridHRActivitySample(schema, user, device);
addCalendarSyncState(schema, device);
@ -420,6 +421,16 @@ public class GBDaoGenerator {
return activitySample;
}
private static Entity addCasioGBX100Sample(Schema schema, Entity user, Entity device) {
Entity activitySample = addEntity(schema, "CasioGBX100ActivitySample");
activitySample.implementsSerializable();
addCommonActivitySampleProperties("AbstractGBX100ActivitySample", activitySample, user, device);
activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
activitySample.addIntProperty("calories").notNull();
return activitySample;
}
private static Entity addLefunActivitySample(Schema schema, Entity user, Entity device) {
Entity activitySample = addEntity(schema, "LefunActivitySample");
activitySample.implementsSerializable();

View File

@ -52,6 +52,11 @@ public class DeviceSettingsPreferenceConst {
public static final String PREF_LONGSIT_SWITCH_NOSHED = "screen_longsit_noshed";
public static final String PREF_DO_NOT_DISTURB_NOAUTO = "do_not_disturb_no_auto";
public static final String PREF_FIND_PHONE_ENABLED = "prefs_find_phone";
public static final String PREF_AUTOLIGHT = "autolight";
public static final String PREF_AUTOREMOVE_MESSAGE = "autoremove_message";
public static final String PREF_OPERATING_SOUNDS = "operating_sounds";
public static final String PREF_KEY_VIBRATION = "key_vibration";
public static final String PREF_FAKE_RING_DURATION = "fake_ring_duration";
public static final String PREF_ANTILOST_ENABLED = "pref_antilost_enabled";
public static final String PREF_HYDRATION_SWITCH = "pref_hydration_switch";

View File

@ -48,6 +48,8 @@ import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.Dev
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_AMPM_ENABLED;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_ANTILOST_ENABLED;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BT_CONNECTED_ADVERTISEMENT;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_AUTOLIGHT;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_AUTOREMOVE_MESSAGE;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BUTTON_1_FUNCTION_DOUBLE;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BUTTON_1_FUNCTION_LONG;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BUTTON_1_FUNCTION_SHORT;
@ -61,17 +63,20 @@ import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.Dev
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DATEFORMAT;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DISCONNECTNOTIF_NOSHED;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_FAKE_RING_DURATION;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_FIND_PHONE_ENABLED;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HYBRID_HR_DRAW_WIDGET_CIRCLES;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HYBRID_HR_FORCE_WHITE_COLOR;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HYBRID_HR_SAVE_RAW_ACTIVITY_FILES;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HYDRATION_PERIOD;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HYDRATION_SWITCH;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_KEY_VIBRATION;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LANGUAGE;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LEFUN_INTERFACE_LANGUAGE;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LIFTWRIST_NOSHED;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LONGSIT_PERIOD;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LONGSIT_SWITCH;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_OPERATING_SOUNDS;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_POWER_MODE;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SCREEN_ORIENTATION;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONYSWR12_LOW_VIBRATION;
@ -383,6 +388,11 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat {
addPreferenceHandlerFor(PREF_LONGSIT_SWITCH);
addPreferenceHandlerFor(PREF_DO_NOT_DISTURB_NOAUTO);
addPreferenceHandlerFor(PREF_FIND_PHONE_ENABLED);
addPreferenceHandlerFor(PREF_AUTOLIGHT);
addPreferenceHandlerFor(PREF_AUTOREMOVE_MESSAGE);
addPreferenceHandlerFor(PREF_KEY_VIBRATION);
addPreferenceHandlerFor(PREF_OPERATING_SOUNDS);
addPreferenceHandlerFor(PREF_FAKE_RING_DURATION);
addPreferenceHandlerFor(PREF_ANTILOST_ENABLED);
addPreferenceHandlerFor(PREF_HYDRATION_SWITCH);
addPreferenceHandlerFor(PREF_HYDRATION_PERIOD);

View File

@ -103,7 +103,7 @@ public final class CasioConstants {
public static final byte CATEGORY_CONDITION = 12;
public static final byte CATEGORY_EMAIL = 6;
public static final byte CATEGORY_ENTERTAINMENT = 11;
public static final byte CATEGORY_HEATH_AND_FITNESS = 8;
public static final byte CATEGORY_HEALTH_AND_FITNESS = 8;
public static final byte CATEGORY_INCOMING_CALL = 1;
public static final byte CATEGORY_LOCATION = 10;
public static final byte CATEGORY_MISSED_CALL = 2;
@ -129,9 +129,21 @@ public final class CasioConstants {
OPTION_STEP_GOAL,
OPTION_DISTANCE_GOAL,
OPTION_ACTIVITY_GOAL,
OPTION_AUTOLIGHT,
OPTION_TIMEFORMAT,
OPTION_KEY_VIBRATION,
OPTION_OPERATING_SOUNDS,
OPTION_ALL
}
public static final int CASIO_CONVOY_DATATYPE_STEPS = 0x04;
public static final int CASIO_CONVOY_DATATYPE_CALORIES = 0x05;
public static final int CASIO_FAKE_RING_SLEEP_DURATION = 3000;
public static final int CASIO_FAKE_RING_RETRIES = 10;
public static final int CASIO_AUTOREMOVE_MESSAGE_DELAY = 10000;
public static Map<String, Byte> characteristicToByte = new HashMap<String, Byte>() {
{
put("CASIO_WATCH_NAME", (byte) 0x23);
@ -150,6 +162,10 @@ public final class CasioConstants {
put("CASIO_SETTING_FOR_USER_PROFILE", (byte) 0x45);
put("CASIO_SETTING_FOR_TARGET_VALUE", (byte) 0x43);
put("ALERT_LEVEL", (byte) 0x0a);
put("CASIO_SETTING_FOR_ALM", (byte) 0x15);
put("CASIO_SETTING_FOR_ALM2", (byte) 0x16);
put("CASIO_SETTING_FOR_BASIC", (byte) 0x13);
put("CASIO_CURRENT_TIME_MANAGER", (byte) 0x39);
}
};
}

View File

@ -0,0 +1,97 @@
/* Copyright (C) 2018-2020 Cre3per, Daniele Gobbetti, Sebastian Kranz
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 <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.casio;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.Property;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.CasioGBX100ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.CasioGBX100ActivitySampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class CasioGBX100SampleProvider extends AbstractSampleProvider<CasioGBX100ActivitySample> {
private static final Logger LOG = LoggerFactory.getLogger(CasioGBX100SampleProvider.class);
public CasioGBX100SampleProvider(GBDevice device, DaoSession session) {
super(device, session);
}
@Override
public int normalizeType(int rawType) {
return rawType;
}
@Override
public int toRawActivityKind(int activityKind) {
return activityKind;
}
@Override
public float normalizeIntensity(int rawIntensity) {
// The magic number 1500 is based on
// https://www.livestrong.com/article/474836-what-sport-burns-the-most-calories-per-hour/
return (rawIntensity / 1500f);
}
@Override
public CasioGBX100ActivitySample createActivitySample() {
return new CasioGBX100ActivitySample();
}
@Override
public AbstractDao<CasioGBX100ActivitySample, ?> getSampleDao() {
return getSession().getCasioGBX100ActivitySampleDao();
}
@Nullable
@Override
protected Property getRawKindSampleProperty() {
return CasioGBX100ActivitySampleDao.Properties.RawKind;
}
@NonNull
@Override
protected Property getTimestampSampleProperty() {
return CasioGBX100ActivitySampleDao.Properties.Timestamp;
}
@NonNull
@Override
protected Property getDeviceIdentifierSampleProperty() {
return CasioGBX100ActivitySampleDao.Properties.DeviceId;
}
@Override
public List<CasioGBX100ActivitySample> getActivitySamples(int timestamp_from, int timestamp_to) {
return super.getActivitySamples(timestamp_from, timestamp_to);
}
@Override
public List<CasioGBX100ActivitySample> getAllActivitySamples(int timestamp_from, int timestamp_to) {
return super.getActivitySamples(timestamp_from, timestamp_to);
}
}

View File

@ -26,12 +26,15 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import androidx.annotation.NonNull;
import de.greenrobot.dao.query.QueryBuilder;
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.devices.casio.CasioGBX100SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.CasioGBX100ActivitySampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
@ -97,17 +100,17 @@ public class CasioGBX100DeviceCoordinator extends AbstractDeviceCoordinator {
@Override
public boolean supportsActivityDataFetching() {
return false;
return true;
}
@Override
public boolean supportsActivityTracking() {
return false;
return true;
}
@Override
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
return null;
return new CasioGBX100SampleProvider(device, session);
}
@Override
@ -147,14 +150,22 @@ public class CasioGBX100DeviceCoordinator extends AbstractDeviceCoordinator {
@Override
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
Long deviceId = device.getId();
QueryBuilder<?> qb = session.getCasioGBX100ActivitySampleDao().queryBuilder();
qb.where(CasioGBX100ActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
}
@Override
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
return new int[]{
R.xml.devicesettings_find_phone,
R.xml.devicesettings_wearlocation
R.xml.devicesettings_wearlocation,
R.xml.devicesettings_timeformat,
R.xml.devicesettings_autolight,
R.xml.devicesettings_key_vibration,
R.xml.devicesettings_operating_sounds,
R.xml.devicesettings_fake_ring_duration,
R.xml.devicesettings_autoremove_message
};
}
}

View File

@ -32,6 +32,7 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@ -39,10 +40,17 @@ import java.util.concurrent.atomic.AtomicInteger;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioConstants;
import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioGBX100SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3.MakibesHR3Constants;
import nodomain.freeyourgadget.gadgetbridge.entities.CasioGBX100ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.User;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
@ -53,12 +61,20 @@ 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.service.devices.casio.operations.FetchStepCountDataOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.operations.GetConfigurationOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.operations.InitOperationGBX100;
import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.operations.SetConfigurationOperation;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_AUTOLIGHT;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_AUTOREMOVE_MESSAGE;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_FAKE_RING_DURATION;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_FIND_PHONE_ENABLED;
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_TIMEFORMAT;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_WEARLOCATION;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_ACTIVETIME_MINUTES;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_DISTANCE_METERS;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_GENDER;
@ -72,9 +88,13 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport implemen
private boolean mFirstConnect = false;
private boolean mGetConfigurationPending = false;
private ArrayList<Integer> mSyncedNotificationIDs = new ArrayList<>();
private int mLastCallId = 0;
private boolean mRingNotificationPending = false;
private final ArrayList<Integer> mSyncedNotificationIDs = new ArrayList<>();
private int mLastCallId = new AtomicInteger((int) (System.currentTimeMillis()/1000)).incrementAndGet();
private int mFakeRingDurationCounter = 0;
private final Handler mFindPhoneHandler = new Handler();
private final Handler mFakeRingDurationHandler = new Handler();
private final Handler mAutoRemoveMessageHandler = new Handler();
public CasioGBX100DeviceSupport() {
super(LOG);
@ -106,12 +126,20 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport implemen
getDevice().setFirmwareVersion("N/A");
getDevice().setFirmwareVersion2("N/A");
SharedPreferences preferences = GBApplication.getDeviceSpecificSharedPrefs(this.getDevice().getAddress());
preferences.registerOnSharedPreferenceChangeListener(this);
//preferences.registerOnSharedPreferenceChangeListener(this);
SharedPreferences prefs = GBApplication.getPrefs().getPreferences();
prefs.registerOnSharedPreferenceChangeListener(this);
if(mFirstConnect) {
SharedPreferences preferences = GBApplication.getDeviceSpecificSharedPrefs(this.getDevice().getAddress());
SharedPreferences.Editor editor = preferences.edit();
editor.putString("charts_tabs", "activity,activitylist,stepsweek");
editor.apply();
}
return builder;
}
@ -128,6 +156,67 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport implemen
return super.onCharacteristicRead(gatt, characteristic, status);
}
public CasioGBX100ActivitySample getSumWithinRange(int timestamp_from, int timestamp_to) {
int steps = 0;
int calories = 0;
try (DBHandler dbHandler = GBApplication.acquireDB()) {
User user = DBHelper.getUser(dbHandler.getDaoSession());
Device device = DBHelper.getDevice(this.getDevice(), dbHandler.getDaoSession());
CasioGBX100SampleProvider provider = new CasioGBX100SampleProvider(this.getDevice(), dbHandler.getDaoSession());
List<CasioGBX100ActivitySample> samples = provider.getActivitySamples(timestamp_from, timestamp_to);
for(CasioGBX100ActivitySample sample : samples) {
if(sample.getDevice().equals(device) &&
sample.getUser().equals(user)) {
steps += sample.getSteps();
calories += sample.getCalories();
}
}
} catch (Exception e) {
LOG.error("Error fetching activity data.");
}
CasioGBX100ActivitySample ret = new CasioGBX100ActivitySample();
ret.setCalories(calories);
ret.setSteps(steps);
LOG.debug("Fetched for today: " + calories + " cals and " + steps + " steps.");
return ret;
}
private void addGBActivitySamples(ArrayList<CasioGBX100ActivitySample> samples) {
try (DBHandler dbHandler = GBApplication.acquireDB()) {
User user = DBHelper.getUser(dbHandler.getDaoSession());
Device device = DBHelper.getDevice(this.getDevice(), dbHandler.getDaoSession());
CasioGBX100SampleProvider provider = new CasioGBX100SampleProvider(this.getDevice(), dbHandler.getDaoSession());
for (CasioGBX100ActivitySample sample : samples) {
sample.setDevice(device);
sample.setUser(user);
sample.setProvider(provider);
provider.addGBActivitySample(sample);
}
} catch (Exception ex) {
// Why is this a toast? The user doesn't care about the error.
GB.toast(getContext(), "Error saving samples: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
GB.updateTransferNotification(null, "Data transfer failed", false, 0, getContext());
LOG.error(ex.getMessage());
}
}
public void stepCountDataFetched(int totalCount, int totalCalories, ArrayList<CasioGBX100ActivitySample> data) {
LOG.info("Got the following step count data: ");
LOG.info("Total Count: " + totalCount);
LOG.info("Total Calories: " + totalCalories);
addGBActivitySamples(data);
}
@Override
public boolean onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
@ -144,7 +233,18 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport implemen
onReverseFindDevice(false);
}
return true;
} else if(data[0] == CasioConstants.characteristicToByte.get("CASIO_CURRENT_TIME_MANAGER")) {
if(data[1] == 0x00) {
try {
TransactionBuilder builder = performInitialized("writeCurrentTime");
writeCurrentTime(builder);
builder.queue(getQueue());
} catch (IOException e) {
LOG.warn("writing current time failed: " + e.getMessage());
}
}
}
}
LOG.info("Unhandled characteristic change: " + characteristicUUID + " code: " + String.format("0x%1x ...", data[0]));
@ -186,21 +286,22 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport implemen
arr[5] = (byte) 0x01; // Set to 0x00 to not vibrate/ring for this notification
arr[6] = icon;
// These bytes contain a timestamp, not yet decoded / implemented
/*arr[7] = (byte) 0x32;
arr[8] = (byte) 0x30;
arr[9] = (byte) 0x32;
arr[10] = (byte) 0x30;
arr[11] = (byte) 0x31;
arr[12] = (byte) 0x31;
arr[13] = (byte) 0x31;
arr[14] = (byte) 0x33;
arr[15] = (byte) 0x54;
arr[16] = (byte) 0x30;
arr[17] = (byte) 0x39;
arr[18] = (byte) 0x33;
arr[19] = (byte) 0x31;
arr[20] = (byte) 0x35;
arr[21] = (byte) 0x33;*/
// ASCII Codes:
/*arr[7] = (byte) 0x32; // 2
arr[8] = (byte) 0x30; // 0
arr[9] = (byte) 0x32; // 2
arr[10] = (byte) 0x30; // 0
arr[11] = (byte) 0x31; // 1
arr[12] = (byte) 0x31; // 1
arr[13] = (byte) 0x31; // 1
arr[14] = (byte) 0x33; // 3
arr[15] = (byte) 0x54; // T
arr[16] = (byte) 0x30; // 0
arr[17] = (byte) 0x39; // 9
arr[18] = (byte) 0x33; // 3
arr[19] = (byte) 0x31; // 1
arr[20] = (byte) 0x35; // 5
arr[21] = (byte) 0x33;*/// 3
byte[] copy = Arrays.copyOf(arr, arr.length + 2);
copy[copy.length-2] = 0;
copy[copy.length-1] = 0;
@ -247,8 +348,9 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport implemen
}
@Override
public void onNotification(NotificationSpec notificationSpec) {
public void onNotification(final NotificationSpec notificationSpec) {
byte icon;
boolean autoremove = false;
switch (notificationSpec.type.getGenericType()) {
case "generic_calendar":
icon = CasioConstants.CATEGORY_SCHEDULE_AND_ALARM;
@ -256,13 +358,26 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport implemen
case "generic_email":
icon = CasioConstants.CATEGORY_EMAIL;
break;
case "generic_sms":
icon = CasioConstants.CATEGORY_SNS;
SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
autoremove = sharedPreferences.getBoolean(PREF_AUTOREMOVE_MESSAGE, false);
break;
default:
icon = CasioConstants.CATEGORY_SNS;
break;
}
LOG.info("onNotification id=" + notificationSpec.getId());
showNotification(icon, notificationSpec.sender, notificationSpec.title, notificationSpec.body, notificationSpec.getId(), false);
mSyncedNotificationIDs.add(new Integer(notificationSpec.getId()));
mSyncedNotificationIDs.add(notificationSpec.getId());
if(autoremove) {
mAutoRemoveMessageHandler.postDelayed(new Runnable() {
@Override
public void run() {
onDeleteNotification(notificationSpec.getId());
}
}, CasioConstants.CASIO_AUTOREMOVE_MESSAGE_DELAY);
}
// The watch only holds up to 10 notifications. However, the user might have deleted
// some notifications in the meantime, so to be sure, we keep the last 100 IDs.
if(mSyncedNotificationIDs.size() > 100) {
@ -273,7 +388,7 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport implemen
@Override
public void onDeleteNotification(int id) {
LOG.info("onDeleteNofication id=" + id);
Integer idInt = new Integer(id);
Integer idInt = id;
if(mSyncedNotificationIDs.contains(idInt)) {
showNotification(CasioConstants.CATEGORY_OTHER, null, null, null, id, true);
mSyncedNotificationIDs.remove(idInt);
@ -300,7 +415,7 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport implemen
int iDuration;
try {
iDuration = Integer.valueOf(duration);
iDuration = Integer.parseInt(duration);
} catch (Exception ex) {
LOG.warn(ex.getMessage());
iDuration = 60;
@ -331,7 +446,7 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport implemen
int year = cal.get(Calendar.YEAR);
arr[0] = CasioConstants.characteristicToByte.get("CASIO_CURRENT_TIME");
arr[1] = (byte)((year >>> 0) & 0xff);
arr[1] = (byte)(year & 0xff);
arr[2] = (byte)((year >>> 8) & 0xff);
arr[3] = (byte)(1 + cal.get(Calendar.MONTH));
arr[4] = (byte)cal.get(Calendar.DAY_OF_MONTH);
@ -358,7 +473,45 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport implemen
@Override
public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
int alarmOffset = 4;
byte[] data1 = new byte[5];
byte[] data2 = new byte[17];
if(!isConnected())
return;
data1[0] = CasioConstants.characteristicToByte.get("CASIO_SETTING_FOR_ALM");
data2[0] = CasioConstants.characteristicToByte.get("CASIO_SETTING_FOR_ALM2");
for(int i=0; i<alarms.size(); i++)
{
byte[] settings = new byte[4];
Alarm alm = alarms.get(i);
if(alm.getEnabled()) {
settings[0] = 0x40;
} else {
settings[0] = 0;
}
if(alm.getRepetition(Alarm.ALARM_ONCE)) {
settings[i * alarmOffset] |= 0x20;
}
settings[1] = 0x40;
settings[2] = (byte)alm.getHour();
settings[3] = (byte)alm.getMinute();
if(i == 0) {
System.arraycopy(settings, 0, data1, 1, settings.length);
} else {
System.arraycopy(settings, 0, data2, 1 + (i-1)*4, settings.length);
}
}
try {
TransactionBuilder builder = performInitialized("setAlarm");
writeAllFeatures(builder, data1);
writeAllFeatures(builder, data2);
builder.queue(getQueue());
} catch(IOException e) {
LOG.error("Error setting alarm: " + e.getMessage());
}
}
@Override
@ -374,18 +527,33 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport implemen
}
@Override
public void onSetCallState(CallSpec callSpec) {
public void onSetCallState(final CallSpec callSpec) {
switch (callSpec.command) {
case CallSpec.CALL_INCOMING:
final AtomicInteger c = new AtomicInteger((int) (System.currentTimeMillis()/1000));
mLastCallId = c.incrementAndGet();
showNotification(CasioConstants.CATEGORY_INCOMING_CALL, "Phone", callSpec.name, callSpec.number, mLastCallId, false);
SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
boolean fakeRingDuration = sharedPreferences.getBoolean(PREF_FAKE_RING_DURATION, false);
if(fakeRingDuration && mFakeRingDurationCounter < CasioConstants.CASIO_FAKE_RING_RETRIES) {
mFakeRingDurationCounter++;
mFakeRingDurationHandler.postDelayed(new Runnable() {
@Override
public void run() {
showNotification(CasioConstants.CATEGORY_INCOMING_CALL, null, null, null, mLastCallId, true);
onSetCallState(callSpec);
}
}, CasioConstants.CASIO_FAKE_RING_SLEEP_DURATION);
} else {
mFakeRingDurationCounter = 0;
}
mRingNotificationPending = true;
break;
case CallSpec.CALL_END:
showNotification(CasioConstants.CATEGORY_INCOMING_CALL, null, null, null, mLastCallId, true);
default:
LOG.info("not sending CallSpec since only CALL_INCOMING is handled");
break;
if(mRingNotificationPending) {
mFakeRingDurationHandler.removeCallbacksAndMessages(null);
mFakeRingDurationCounter = 0;
showNotification(CasioConstants.CATEGORY_INCOMING_CALL, null, null, null, mLastCallId, true);
mLastCallId = new AtomicInteger((int) (System.currentTimeMillis() / 1000)).incrementAndGet();
}
}
}
@ -440,7 +608,11 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport implemen
@Override
public void onFetchRecordedData(int dataTypes) {
try {
new FetchStepCountDataOperation(this).perform();
} catch(IOException e) {
GB.toast(getContext(), "Error fetching data", Toast.LENGTH_SHORT, GB.ERROR, e);
}
}
@Override
@ -496,6 +668,7 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport implemen
@Override
public void onSendConfiguration(String config) {
LOG.info("onSendConfiguration" + config);
onSharedPreferenceChanged(null, config);
}
public void onGetConfigurationFinished() {
@ -519,7 +692,16 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport implemen
@Override
public void onTestNewFunction() {
byte[] data = new byte[2];
data[0] = (byte)0x2e;
data[1] = (byte)0x03;
try {
TransactionBuilder builder = performInitialized("onTestNewFunction");
writeAllFeaturesRequest(builder, data);
builder.queue(getQueue());
} catch(IOException e) {
LOG.error("Error setting alarm: " + e.getMessage());
}
}
@Override
@ -540,27 +722,49 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport implemen
return;
}
try {
if (key.equals(DeviceSettingsPreferenceConst.PREF_WEARLOCATION)) {
new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_WRIST).perform();
} else if(key.equals(PREF_USER_STEPS_GOAL)) {
new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_STEP_GOAL).perform();
} else if(key.equals(PREF_USER_ACTIVETIME_MINUTES)) {
new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_ACTIVITY_GOAL).perform();
} else if(key.equals(PREF_USER_DISTANCE_METERS)) {
new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_DISTANCE_GOAL).perform();
} else if(key.equals(PREF_USER_GENDER)) {
new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_GENDER).perform();
} else if(key.equals(PREF_USER_HEIGHT_CM)) {
new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_HEIGHT).perform();
} else if(key.equals(PREF_USER_WEIGHT_KG)) {
new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_WEIGHT).perform();
} else if(key.equals(PREF_USER_YEAR_OF_BIRTH)) {
new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_BIRTHDAY).perform();
} else if (key.equals(PREF_FIND_PHONE_ENABLED) ||
key.equals(MakibesHR3Constants.PREF_FIND_PHONE_DURATION)) {
// No action, we check the shared preferences when the device tries to ring the phone.
} else {
return;
switch (key) {
case DeviceSettingsPreferenceConst.PREF_WEARLOCATION:
new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_WRIST).perform();
break;
case PREF_USER_STEPS_GOAL:
new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_STEP_GOAL).perform();
break;
case PREF_USER_ACTIVETIME_MINUTES:
new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_ACTIVITY_GOAL).perform();
break;
case PREF_USER_DISTANCE_METERS:
new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_DISTANCE_GOAL).perform();
break;
case PREF_USER_GENDER:
new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_GENDER).perform();
break;
case PREF_USER_HEIGHT_CM:
new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_HEIGHT).perform();
break;
case PREF_USER_WEIGHT_KG:
new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_WEIGHT).perform();
break;
case PREF_USER_YEAR_OF_BIRTH:
new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_BIRTHDAY).perform();
break;
case PREF_TIMEFORMAT:
new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_TIMEFORMAT).perform();
break;
case PREF_KEY_VIBRATION:
new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_KEY_VIBRATION).perform();
break;
case PREF_AUTOLIGHT:
new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_AUTOLIGHT).perform();
break;
case PREF_OPERATING_SOUNDS:
new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_OPERATING_SOUNDS).perform();
break;
case PREF_FAKE_RING_DURATION:
case PREF_FIND_PHONE_ENABLED:
case MakibesHR3Constants.PREF_FIND_PHONE_DURATION:
// No action, we check the shared preferences when the device tries to ring the phone.
break;
default:
}
} catch (IOException e) {
LOG.info("Error sending configuration change to watch");

View File

@ -0,0 +1,295 @@
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.ArrayList;
import java.util.Calendar;
import java.util.TimeZone;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioConstants;
import nodomain.freeyourgadget.gadgetbridge.entities.CasioGBX100ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.CasioGBX100DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class FetchStepCountDataOperation extends AbstractBTLEOperation<CasioGBX100DeviceSupport> {
private static final Logger LOG = LoggerFactory.getLogger(FetchStepCountDataOperation.class);
private final CasioGBX100DeviceSupport support;
public FetchStepCountDataOperation(CasioGBX100DeviceSupport support) {
super(support);
this.support = support;
}
private void enableRequiredNotifications(boolean enable) {
try {
TransactionBuilder builder = performInitialized("enableRequiredNotifications");
builder.setGattCallback(this);
builder.notify(getCharacteristic(CasioConstants.CASIO_DATA_REQUEST_SP_CHARACTERISTIC_UUID), enable);
builder.notify(getCharacteristic(CasioConstants.CASIO_CONVOY_CHARACTERISTIC_UUID), enable);
builder.queue(getQueue());
} catch(IOException e) {
LOG.info("Error enabling required notifications" + e.getMessage());
}
}
private void requestStepCountData() {
byte[] command = {0x00, 0x11, 0x00, 0x00, 0x00};
try {
TransactionBuilder builder = performInitialized("requestStepCountDate");
builder.setGattCallback(this);
builder.write(getCharacteristic(CasioConstants.CASIO_DATA_REQUEST_SP_CHARACTERISTIC_UUID), command);
builder.queue(getQueue());
} catch(IOException e) {
LOG.info("Error requesting step count data: " + e.getMessage());
}
}
private void writeStepCountAck() {
byte[] command = {0x04, 0x11, 0x00, 0x00, 0x00};
try {
TransactionBuilder builder = performInitialized("writeStepCountAck");
builder.setGattCallback(this);
builder.write(getCharacteristic(CasioConstants.CASIO_DATA_REQUEST_SP_CHARACTERISTIC_UUID), command);
builder.queue(getQueue());
} catch(IOException e) {
LOG.info("Error requesting step count data: " + e.getMessage());
}
}
@Override
protected void prePerform() throws IOException {
super.prePerform();
getDevice().setBusyTask("FetchStepCountDataOperation starting..."); // mark as busy quickly to avoid interruptions from the outside
GB.updateTransferNotification(null, getContext().getString(R.string.busy_task_fetch_activity_data), true, 0, getContext());
}
@Override
protected void doPerform() throws IOException {
enableRequiredNotifications(true);
requestStepCountData();
}
@Override
protected void operationFinished() {
LOG.info("SetConfigurationOperation finished");
unsetBusy();
GB.updateTransferNotification(null, getContext().getString(R.string.busy_task_fetch_activity_data), false, 100, getContext());
operationStatus = OperationStatus.FINISHED;
if (getDevice() != null) {
try {
TransactionBuilder builder = performInitialized("finished operation");
builder.setGattCallback(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());
}
}
}
@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_DATA_REQUEST_SP_CHARACTERISTIC_UUID)) {
int length = 0;
if (data.length > 3) {
length = (data[2] & 0xff) | ((data[3] & 0xff) << 8);
}
LOG.debug("Response is going to be " + length + " bytes long");
GB.updateTransferNotification(null, getContext().getString(R.string.busy_task_fetch_activity_data), true, 10, getContext());
return true;
} else if(characteristicUUID.equals(CasioConstants.CASIO_CONVOY_CHARACTERISTIC_UUID)) {
if(data.length < 18) {
LOG.info("Data length too short.");
} else {
for(int i=0; i<data.length; i++)
data[i] = (byte)(~data[i]);
int payloadLength = ((data[0] & 0xff) | ((data[1] & 0xff) << 8));
if(data.length == (payloadLength + 2)) {
LOG.debug("Payload length and data length match.");
} else {
LOG.debug("Payload length and data length do not match: " + payloadLength + " vs. " + data.length);
}
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
ArrayList<CasioGBX100ActivitySample> stepCountData = new ArrayList<>();
int year = data[2];
int month = data[3] - 1; // Zero-based
int day = data[4];
int hour = data[5];
int minute = data[6];
int stepCount = ((data[7] & 0xff) | ((data[8] & 0xff) << 8) | ((data[9] & 0xff) << 16) | ((data[10] & 0xff) << 24));
// it reports 0xfffffffe if no steps have been recorded
if(stepCount == 0xfffffffe)
stepCount = 0;
int calories = ((data[11] & 0xff) | ((data[12] & 0xff) << 8));
if(calories == 0xfffe)
calories = 0;
int yearOfBirth = ((data[13] & 0xff) | ((data[14] & 0xff) << 8));
int monthOfBirth = data[15];
int dayOfBirth = data[16];
LOG.debug("Current step count value: " + stepCount);
LOG.debug("Current calories: " + calories);
// data[17]:
// 0x01 probably means finished.
// 0x00 probably means more data.
// Set timestamps for retrieving recorded data for the current day
cal.set(year + 2000, month, day, hour, 30, 0);
int ts_to = (int)(cal.getTimeInMillis() / 1000);
cal.set(year + 2000, month, day, 0, 0, 0);
int ts_from = (int)(cal.getTimeInMillis() / 1000);
CasioGBX100ActivitySample sum = support.getSumWithinRange(ts_from, ts_to);
int caloriesToday = sum.getCalories();
int stepsToday = sum.getSteps();
// Set timestamp to currently fetched data for fetching historic data
cal.set(year + 2000, month, day, hour, 30, 0);
if(data[17] == 0x00 && data.length > 18) {
LOG.info("We got historic step count data.");
int index = 18;
boolean inPacket = false;
int packetIndex = 0;
int packetLength = 0;
int type = 0;
while(index < data.length) {
if(!inPacket) {
type = data[index];
packetLength = ((data[index + 1] & 0xff) | ((data[index + 2] & 0xff) << 8));
packetIndex = 0;
inPacket = true;
index = index + 3;
LOG.debug("Decoding packet with type: " + type + " and length: " + packetLength);
}
int count = ((data[index] & 0xff) | ((data[index + 1] & 0xff) << 8));
if(count == 0xfffe)
count = 0;
LOG.debug("Got count " + count);
index = index+2;
if(index >= data.length) {
LOG.debug("End of packet.");
}
if(type == CasioConstants.CASIO_CONVOY_DATATYPE_STEPS) {
cal.add(Calendar.HOUR, -1);
int ts = (int)(cal.getTimeInMillis() / 1000);
stepCountData.add(new CasioGBX100ActivitySample());
stepCountData.get(packetIndex/2).setSteps(count);
stepCountData.get(packetIndex/2).setTimestamp(ts);
if(count > 0) {
stepCountData.get(packetIndex / 2).setRawKind(ActivityKind.TYPE_ACTIVITY);
} else {
stepCountData.get(packetIndex / 2).setRawKind(ActivityKind.TYPE_NOT_MEASURED);
}
if(ts > ts_from && ts < ts_to) {
stepsToday += count;
}
} else if(type == CasioConstants.CASIO_CONVOY_DATATYPE_CALORIES) {
if(stepCountData.get(packetIndex/2).getSteps() > 0) {
// The last packet might contain an invalid calory count
// of 255, but only if the steps are also invalid.
stepCountData.get(packetIndex / 2).setCalories(count);
int ts = stepCountData.get(packetIndex / 2).getTimestamp();
if (ts > ts_from && ts < ts_to) {
caloriesToday += count;
}
}
}
packetIndex = packetIndex + 2;
if(packetIndex >= packetLength)
inPacket = false;
}
}
// This generates an artificial "now" timestamp for the current
// activity based on the existing data. This timestamp will be overwritten
// by the next fetch operation with the actual value.
int steps = stepCount - stepsToday;
int cals = calories - caloriesToday;
// For a yet unknown reason, the sum calculated by the watch is sometimes lower than
// the sum calculated by us. I suspect it is just refreshed at a later time!?
if(steps > 0 && cals > 0) {
cal.set(year + 2000, month, day, hour, 30, 0);
int ts = (int) (cal.getTimeInMillis() / 1000);
LOG.debug("Artificial timestamp: " + cals + " calories and " + steps + " steps");
CasioGBX100ActivitySample sample = new CasioGBX100ActivitySample();
sample.setSteps(steps);
sample.setCalories(cals);
sample.setTimestamp(ts);
if (steps > 0)
sample.setRawKind(ActivityKind.TYPE_ACTIVITY);
else
sample.setRawKind(ActivityKind.TYPE_NOT_MEASURED);
stepCountData.add(0, sample);
}
support.stepCountDataFetched(stepCount, calories, stepCountData);
}
GB.updateTransferNotification(null, getContext().getString(R.string.busy_task_fetch_activity_data), true, 80, getContext());
writeStepCountAck();
return true;
} else {
LOG.info("Unhandled characteristic changed: " + characteristicUUID);
return super.onCharacteristicChanged(gatt, characteristic);
}
}
@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_DATA_REQUEST_SP_CHARACTERISTIC_UUID)) {
if(data[0] == 0x00) {
LOG.debug("Request sent successfully");
} else if(data[0] == 0x04) {
LOG.debug("Read step count operation finished");
enableRequiredNotifications(false);
operationFinished();
}
return true;
}
return super.onCharacteristicWrite(gatt, characteristic, status);
}
}

View File

@ -4,25 +4,23 @@ import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.SharedPreferences;
import org.apache.commons.lang3.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.UUID;
import java.util.prefs.Preferences;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsFragment;
import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioConstants;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.PlainAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.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;
public class GetConfigurationOperation extends AbstractBTLEOperation<CasioGBX100DeviceSupport> {
@ -36,6 +34,12 @@ public class GetConfigurationOperation extends AbstractBTLEOperation<CasioGBX100
this.mFirstConnect = firstconnect;
}
@Override
protected void prePerform() throws IOException {
super.prePerform();
getDevice().setBusyTask("GetConfigurationOperation starting..."); // mark as busy quickly to avoid interruptions from the outside
}
@Override
protected void doPerform() throws IOException {
byte[] command = new byte[1];
@ -49,6 +53,7 @@ public class GetConfigurationOperation extends AbstractBTLEOperation<CasioGBX100
@Override
protected void operationFinished() {
operationStatus = OperationStatus.FINISHED;
unsetBusy();
if (getDevice() != null) {
try {
TransactionBuilder builder = performInitialized("finished operation");
@ -62,6 +67,19 @@ public class GetConfigurationOperation extends AbstractBTLEOperation<CasioGBX100
support.onGetConfigurationFinished();
}
private void requestBasicSettings() {
byte[] command = new byte[1];
command[0] = CasioConstants.characteristicToByte.get("CASIO_SETTING_FOR_BASIC");
try {
TransactionBuilder builder = performInitialized("getConfiguration");
builder.setGattCallback(this);
support.writeAllFeaturesRequest(builder, command);
builder.queue(getQueue());
} catch(IOException e) {
LOG.info("Error requesting Casio configuration");
}
}
@Override
public boolean onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
@ -72,18 +90,18 @@ public class GetConfigurationOperation extends AbstractBTLEOperation<CasioGBX100
return true;
if (characteristicUUID.equals(CasioConstants.CASIO_ALL_FEATURES_CHARACTERISTIC_UUID)) {
if(data[0] == CasioConstants.characteristicToByte.get("CASIO_SETTING_FOR_USER_PROFILE")) {
boolean female = ((data[1] & 0x01) == 0x01) ;
if (data[0] == CasioConstants.characteristicToByte.get("CASIO_SETTING_FOR_USER_PROFILE")) {
boolean female = ((data[1] & 0x01) == 0x01);
boolean right = ((data[1] & 0x02) == 0x02);
byte[] compData = new byte[data.length];
for(int i=0; i<data.length; i++) {
compData[i] = (byte)(~data[i]);
for (int i = 0; i < data.length; i++) {
compData[i] = (byte) (~data[i]);
}
int height = BcdUtil.fromBcd8(compData[2]) + BcdUtil.fromBcd8(compData[3]) * 100;
int weight = BcdUtil.fromBcd8(compData[4]) + BcdUtil.fromBcd8(compData[5]) * 100;
int year = BcdUtil.fromBcd8(compData[6]) + BcdUtil.fromBcd8(compData[7]) * 100;
int month = BcdUtil.fromBcd8(compData[8]);
int day = BcdUtil.fromBcd8(compData[9]) - 1;
int day = BcdUtil.fromBcd8(compData[9]);
// Store only the device-specific settings on first-connect
SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
@ -92,22 +110,42 @@ public class GetConfigurationOperation extends AbstractBTLEOperation<CasioGBX100
editor.putString(PREF_WEARLOCATION, right ? "right" : "left");
editor.apply();
requestBasicSettings();
return true;
} else if (data[0] == CasioConstants.characteristicToByte.get("CASIO_SETTING_FOR_BASIC")) {
boolean timeformat = ((data[1] & 0x01) == 0x01);
boolean autolight = ((data[1] & 0x04) == 0x00);
boolean key_vibration = (data[10] == 0x01);
boolean operating_sounds = ((data[1] & 0x02) == 0x00);
// Store only the device-specific settings on first-connect
SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(PREF_AUTOLIGHT, autolight);
editor.putBoolean(PREF_KEY_VIBRATION, key_vibration);
editor.putBoolean(PREF_OPERATING_SOUNDS, operating_sounds);
editor.apply();
LOG.info("GetConfigurationOperation finished");
operationFinished();
// Retrieve all settings from the watch, this overwrites the profile
// on first connect, overwrite the watch settings
if(!mFirstConnect) {
if (!mFirstConnect) {
} else {
support.syncProfile();
}
return true;
}
return true;
} else {
LOG.info("Unhandled characteristic changed: " + characteristicUUID);
return super.onCharacteristicChanged(gatt, characteristic);
}
LOG.info("Unhandled characteristic changed: " + characteristicUUID);
return super.onCharacteristicChanged(gatt, characteristic);
}
@Override

View File

@ -3,18 +3,16 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.casio.operations;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.SharedPreferences;
import android.widget.Toast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
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;
@ -22,8 +20,12 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.CasioGBX100DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
import nodomain.freeyourgadget.gadgetbridge.util.BcdUtil;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
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;
@ -38,6 +40,12 @@ public class SetConfigurationOperation extends AbstractBTLEOperation<CasioGBX10
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];
@ -103,7 +111,7 @@ public class SetConfigurationOperation extends AbstractBTLEOperation<CasioGBX10
data[6] = BcdUtil.toBcd8(year % 100);
data[7] = BcdUtil.toBcd8((year - (year % 100)) / 100);
data[8] = BcdUtil.toBcd8(month);
data[9] = BcdUtil.toBcd8(day + 1);
data[9] = BcdUtil.toBcd8(day);
}
for(int i=2; i<data.length; i++) {
@ -160,6 +168,54 @@ public class SetConfigurationOperation extends AbstractBTLEOperation<CasioGBX10
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.setGattCallback(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] == CasioConstants.characteristicToByte.get("CASIO_SETTING_FOR_BASIC")) {
SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
GBPrefs gbPrefs = new GBPrefs(new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress())));
String timeformat = gbPrefs.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();
@ -186,6 +242,7 @@ public class SetConfigurationOperation extends AbstractBTLEOperation<CasioGBX10
protected void operationFinished() {
LOG.info("SetConfigurationOperation finished");
unsetBusy();
operationStatus = OperationStatus.FINISHED;
if (getDevice() != null) {
try {
@ -199,6 +256,19 @@ public class SetConfigurationOperation extends AbstractBTLEOperation<CasioGBX10
}
}
private void requestBasicSettings() {
byte[] command = new byte[1];
command[0] = CasioConstants.characteristicToByte.get("CASIO_SETTING_FOR_BASIC");
try {
TransactionBuilder builder = performInitialized("getConfiguration");
builder.setGattCallback(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] = CasioConstants.characteristicToByte.get("CASIO_SETTING_FOR_TARGET_VALUE");
@ -227,6 +297,10 @@ public class SetConfigurationOperation extends AbstractBTLEOperation<CasioGBX10
return true;
}
if(data[0] == CasioConstants.characteristicToByte.get("CASIO_SETTING_FOR_TARGET_VALUE")) {
requestBasicSettings();
return true;
}
if(data[0] == CasioConstants.characteristicToByte.get("CASIO_SETTING_FOR_BASIC")) {
operationFinished();
return true;
}

View File

@ -1098,4 +1098,9 @@
<item quantity="many">%d hours</item>
<item quantity="other">%d hours</item>
</plurals>
<string name="prefs_autolight">Automatic light</string>
<string name="prefs_key_vibration">Key Vibration</string>
<string name="prefs_operating_sounds">Operating Sounds</string>
<string name="prefs_fake_ring_duration">Fake continuous ringing</string>
<string name="prefs_autoremove_message">Automatically remove SMS notifications</string>
</resources>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<SwitchPreference
android:defaultValue="false"
android:icon="@drawable/ic_wb_sunny"
android:key="autolight"
android:title="@string/prefs_autolight" />
</androidx.preference.PreferenceScreen>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<SwitchPreference
android:defaultValue="false"
android:icon="@drawable/ic_block"
android:key="autoremove_message"
android:title="@string/prefs_autoremove_message" />
</androidx.preference.PreferenceScreen>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<SwitchPreference
android:defaultValue="false"
android:icon="@drawable/ic_rotate_left"
android:key="fake_ring_duration"
android:title="@string/prefs_fake_ring_duration" />
</androidx.preference.PreferenceScreen>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<SwitchPreference
android:defaultValue="false"
android:icon="@drawable/ic_vibration"
android:key="key_vibration"
android:title="@string/prefs_key_vibration" />
</androidx.preference.PreferenceScreen>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<SwitchPreference
android:defaultValue="false"
android:icon="@drawable/ic_radio"
android:key="operating_sounds"
android:title="@string/prefs_operating_sounds" />
</androidx.preference.PreferenceScreen>