1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-26 20:06:52 +01:00

Da Fit: Add device settings

This commit is contained in:
krzys-h 2019-12-28 14:11:42 +01:00 committed by Arjan Schrijver
parent 5b0c2a9b69
commit ea58fd3f0d
30 changed files with 1477 additions and 3 deletions

View File

@ -369,4 +369,15 @@ public class DaFitConstants {
return DaFitConstants.WEATHER_HAZE;
}
}
public static final String PREF_WATCH_FACE = "dafit_watch_face";
public static final String PREF_LANGUAGE = "dafit_language";
public static final String PREF_LANGUAGE_SUPPORT = "dafit_language_supported";
public static final String PREF_DEVICE_VERSION = "dafit_device_version";
public static final String PREF_SEDENTARY_REMINDER = "sedentary_reminder";
public static final String PREF_SEDENTARY_REMINDER_PERIOD = "sedentary_reminder_period";
public static final String PREF_SEDENTARY_REMINDER_STEPS = "sedentary_reminder_steps";
public static final String PREF_SEDENTARY_REMINDER_START = "sedentary_reminder_start";
public static final String PREF_SEDENTARY_REMINDER_END = "sedentary_reminder_end";
}

View File

@ -31,9 +31,22 @@ import java.util.Collection;
import java.util.Collections;
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.dafit.settings.DaFitEnumDeviceVersion;
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.settings.DaFitEnumMetricSystem;
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.settings.DaFitEnumTimeSystem;
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.settings.DaFitSetting;
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.settings.DaFitSettingBool;
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.settings.DaFitSettingByte;
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.settings.DaFitSettingEnum;
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.settings.DaFitSettingInt;
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.settings.DaFitSettingLanguage;
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.settings.DaFitSettingRemindersToMove;
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.settings.DaFitSettingTimeRange;
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.settings.DaFitSettingUserInfo;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
@ -187,8 +200,52 @@ public class DaFitDeviceCoordinator extends AbstractDeviceCoordinator {
@Override
public boolean supportsUnicodeEmojis() { return false; }
private static final DaFitSetting[] DAFIT_SETTINGS = new DaFitSetting[] {
new DaFitSettingUserInfo("USER_INFO", DaFitConstants.CMD_SET_USER_INFO),
new DaFitSettingByte("STEP_LENGTH", (byte)-1, DaFitConstants.CMD_SET_STEP_LENGTH),
// (*) new DaFitSettingEnum<>("DOMINANT_HAND", DaFitConstants.CMD_QUERY_DOMINANT_HAND, DaFitConstants.CMD_SET_DOMINANT_HAND, DaFitEnumDominantHand.class),
new DaFitSettingInt("GOAL_STEP", DaFitConstants.CMD_QUERY_GOAL_STEP, DaFitConstants.CMD_SET_GOAL_STEP),
new DaFitSettingEnum<>("DEVICE_VERSION", DaFitConstants.CMD_QUERY_DEVICE_VERSION, DaFitConstants.CMD_SET_DEVICE_VERSION, DaFitEnumDeviceVersion.class),
new DaFitSettingLanguage("DEVICE_LANGUAGE", DaFitConstants.CMD_QUERY_DEVICE_LANGUAGE, DaFitConstants.CMD_SET_DEVICE_LANGUAGE),
new DaFitSettingEnum<>("TIME_SYSTEM", DaFitConstants.CMD_QUERY_TIME_SYSTEM, DaFitConstants.CMD_SET_TIME_SYSTEM, DaFitEnumTimeSystem.class),
new DaFitSettingEnum<>("METRIC_SYSTEM", DaFitConstants.CMD_QUERY_METRIC_SYSTEM, DaFitConstants.CMD_SET_METRIC_SYSTEM, DaFitEnumMetricSystem.class),
// (*) new DaFitSetting("DISPLAY_DEVICE_FUNCTION", DaFitConstants.CMD_QUERY_DISPLAY_DEVICE_FUNCTION, DaFitConstants.CMD_SET_DISPLAY_DEVICE_FUNCTION),
// (*) new DaFitSetting("SUPPORT_WATCH_FACE", DaFitConstants.CMD_QUERY_SUPPORT_WATCH_FACE, (byte)-1),
// (*) new DaFitSetting("WATCH_FACE_LAYOUT", DaFitConstants.CMD_QUERY_WATCH_FACE_LAYOUT, DaFitConstants.CMD_SET_WATCH_FACE_LAYOUT),
new DaFitSettingByte("DISPLAY_WATCH_FACE", DaFitConstants.CMD_QUERY_DISPLAY_WATCH_FACE, DaFitConstants.CMD_SET_DISPLAY_WATCH_FACE),
new DaFitSettingBool("OTHER_MESSAGE_STATE", DaFitConstants.CMD_QUERY_OTHER_MESSAGE_STATE, DaFitConstants.CMD_SET_OTHER_MESSAGE_STATE),
new DaFitSettingBool("QUICK_VIEW", DaFitConstants.CMD_QUERY_QUICK_VIEW, DaFitConstants.CMD_SET_QUICK_VIEW),
new DaFitSettingTimeRange("QUICK_VIEW_TIME", DaFitConstants.CMD_QUERY_QUICK_VIEW_TIME, DaFitConstants.CMD_SET_QUICK_VIEW_TIME),
new DaFitSettingBool("SEDENTARY_REMINDER", DaFitConstants.CMD_QUERY_SEDENTARY_REMINDER, DaFitConstants.CMD_SET_SEDENTARY_REMINDER),
new DaFitSettingRemindersToMove("REMINDERS_TO_MOVE_PERIOD", DaFitConstants.CMD_QUERY_REMINDERS_TO_MOVE_PERIOD, DaFitConstants.CMD_SET_REMINDERS_TO_MOVE_PERIOD),
new DaFitSettingTimeRange("DO_NOT_DISTURB_TIME", DaFitConstants.CMD_QUERY_DO_NOT_DISTURB_TIME, DaFitConstants.CMD_SET_DO_NOT_DISTURB_TIME),
// (*) new DaFitSetting("PSYCHOLOGICAL_PERIOD", DaFitConstants.CMD_QUERY_PSYCHOLOGICAL_PERIOD, DaFitConstants.CMD_SET_PSYCHOLOGICAL_PERIOD),
new DaFitSettingBool("BREATHING_LIGHT", DaFitConstants.CMD_QUERY_BREATHING_LIGHT, DaFitConstants.CMD_SET_BREATHING_LIGHT)
};
@Override
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
return null;
return new int[]{
R.xml.devicesettings_personalinfo,
//R.xml.devicesettings_steplength, // TODO is this needed? does it work? write-only so hard to tell
R.xml.devicesettings_dafit_device_version,
R.xml.devicesettings_dafit_language,
R.xml.devicesettings_timeformat,
R.xml.devicesettings_measurementsystem,
R.xml.devicesettings_dafit_watchface,
//R.xml.devicesettings_dafit_othermessage, // not implemented because this doesn't really do anything on the watch side, only enables/disables sending of "other" notifications in the app (no idea why they store the setting on the watch)
R.xml.devicesettings_liftwrist_display,
R.xml.devicesettings_dafit_sedentary_reminder,
R.xml.devicesettings_donotdisturb_no_auto_v2,
//R.xml.devicesettings_dafit_breathinglight, // No idea what this does but it doesn't seem to change anything
};
}
public DaFitSetting[] getSupportedSettings() {
return DAFIT_SETTINGS;
}
}

View File

@ -0,0 +1,21 @@
/* Copyright (C) 2019 krzys_h
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.dafit.settings;
public interface DaFitEnum {
byte value();
}

View File

@ -0,0 +1,33 @@
/* Copyright (C) 2019 krzys_h
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.dafit.settings;
public enum DaFitEnumDeviceVersion implements DaFitEnum {
CHINESE_EDITION((byte)0),
INTERNATIONAL_EDITION((byte)1);
public final byte value;
DaFitEnumDeviceVersion(byte value) {
this.value = value;
}
@Override
public byte value() {
return value;
}
}

View File

@ -0,0 +1,33 @@
/* Copyright (C) 2019 krzys_h
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.dafit.settings;
public enum DaFitEnumDominantHand implements DaFitEnum {
LEFT_HAND((byte)0),
RIGHT_HAND((byte)1);
public final byte value;
DaFitEnumDominantHand(byte value) {
this.value = value;
}
@Override
public byte value() {
return value;
}
}

View File

@ -0,0 +1,56 @@
/* Copyright (C) 2019 krzys_h
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.dafit.settings;
public enum DaFitEnumLanguage implements DaFitEnum {
LANGUAGE_ENGLISH((byte)0),
LANGUAGE_CHINESE((byte)1),
LANGUAGE_JAPANESE((byte)2),
LANGUAGE_KOREAN((byte)3),
LANGUAGE_GERMAN((byte)4),
LANGUAGE_FRENCH((byte)5),
LANGUAGE_SPANISH((byte)6),
LANGUAGE_ARABIC((byte)7),
LANGUAGE_RUSSIAN((byte)8),
LANGUAGE_TRADITIONAL((byte)9),
LANGUAGE_UKRAINIAN((byte)10),
LANGUAGE_ITALIAN((byte)11),
LANGUAGE_PORTUGUESE((byte)12),
LANGUAGE_DUTCH((byte)13),
LANGUAGE_POLISH((byte)14),
LANGUAGE_SWEDISH((byte)15),
LANGUAGE_FINNISH((byte)16),
LANGUAGE_DANISH((byte)17),
LANGUAGE_NORWEGIAN((byte)18),
LANGUAGE_HUNGARIAN((byte)19),
LANGUAGE_CZECH((byte)20),
LANGUAGE_BULGARIAN((byte)21),
LANGUAGE_ROMANIAN((byte)22),
LANGUAGE_SLOVAK_LANGUAGE((byte)23),
LANGUAGE_LATVIAN((byte)24);
public final byte value;
DaFitEnumLanguage(byte value) {
this.value = value;
}
@Override
public byte value() {
return value;
}
}

View File

@ -0,0 +1,33 @@
/* Copyright (C) 2019 krzys_h
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.dafit.settings;
public enum DaFitEnumMetricSystem implements DaFitEnum {
METRIC_SYSTEM((byte)0),
IMPERIAL_SYSTEM((byte)1);
public final byte value;
DaFitEnumMetricSystem(byte value) {
this.value = value;
}
@Override
public byte value() {
return value;
}
}

View File

@ -0,0 +1,33 @@
/* Copyright (C) 2019 krzys_h
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.dafit.settings;
public enum DaFitEnumTimeSystem implements DaFitEnum {
TIME_SYSTEM_12((byte)0),
TIME_SYSTEM_24((byte)1);
public final byte value;
DaFitEnumTimeSystem(byte value) {
this.value = value;
}
@Override
public byte value() {
return value;
}
}

View File

@ -0,0 +1,32 @@
/* Copyright (C) 2019 krzys_h
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.dafit.settings;
public abstract class DaFitSetting<T> {
public final String name;
public final byte cmdQuery;
public final byte cmdSet;
public DaFitSetting(String name, byte cmdQuery, byte cmdSet) {
this.name = name;
this.cmdQuery = cmdQuery;
this.cmdSet = cmdSet;
}
public abstract byte[] encode(T value);
public abstract T decode(byte[] data);
}

View File

@ -0,0 +1,37 @@
/* Copyright (C) 2019 krzys_h
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.dafit.settings;
public class DaFitSettingBool extends DaFitSetting<Boolean> {
public DaFitSettingBool(String name, byte cmdQuery, byte cmdSet) {
super(name, cmdQuery, cmdSet);
}
@Override
public byte[] encode(Boolean value) {
return new byte[] { value ? (byte)1 : (byte)0 };
}
@Override
public Boolean decode(byte[] data) {
if (data.length != 1)
throw new IllegalArgumentException("Wrong data length, should be 1, was " + data.length);
if (data[0] != 0 && data[0] != 1)
throw new IllegalArgumentException("Expected a boolean, got " + data[0]);
return data[0] != 0;
}
}

View File

@ -0,0 +1,35 @@
/* Copyright (C) 2019 krzys_h
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.dafit.settings;
public class DaFitSettingByte extends DaFitSetting<Byte> {
public DaFitSettingByte(String name, byte cmdQuery, byte cmdSet) {
super(name, cmdQuery, cmdSet);
}
@Override
public byte[] encode(Byte value) {
return new byte[] { value };
}
@Override
public Byte decode(byte[] data) {
if (data.length != 1)
throw new IllegalArgumentException("Wrong data length, should be 1, was " + data.length);
return data[0];
}
}

View File

@ -0,0 +1,54 @@
/* Copyright (C) 2019 krzys_h
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.dafit.settings;
public class DaFitSettingEnum<T extends Enum <?> & DaFitEnum> extends DaFitSetting<T> {
protected final Class<T> clazz;
public DaFitSettingEnum(String name, byte cmdQuery, byte cmdSet, Class<T> clazz) {
super(name, cmdQuery, cmdSet);
this.clazz = clazz;
}
public T findByValue(byte value)
{
for (T e : clazz.getEnumConstants()) {
if (e.value() == value) {
return e;
}
}
throw new IllegalArgumentException("No enum value for " + value);
}
@Override
public byte[] encode(T value) {
return new byte[] { value.value() };
}
@Override
public T decode(byte[] data) {
if (data.length != 1)
throw new IllegalArgumentException("Wrong data length, should be 1, was " + data.length);
return findByValue(data[0]);
}
public T[] decodeSupportedValues(byte[] data) {
return clazz.getEnumConstants();
}
}

View File

@ -0,0 +1,43 @@
/* Copyright (C) 2019 krzys_h
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.dafit.settings;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class DaFitSettingInt extends DaFitSetting<Integer> {
public DaFitSettingInt(String name, byte cmdQuery, byte cmdSet) {
super(name, cmdQuery, cmdSet);
}
@Override
public byte[] encode(Integer value) {
ByteBuffer buffer = ByteBuffer.allocate(4);
buffer.order(ByteOrder.BIG_ENDIAN); // <- this is what happens when somebody in China designs a communication protocol
buffer.putInt(value);
return buffer.array();
}
@Override
public Integer decode(byte[] data) {
if (data.length != 4)
throw new IllegalArgumentException("Wrong data length, should be 4, was " + data.length);
ByteBuffer buffer = ByteBuffer.wrap(data);
buffer.order(ByteOrder.LITTLE_ENDIAN); // <- yes, it's different here
return buffer.getInt();
}
}

View File

@ -0,0 +1,63 @@
/* Copyright (C) 2019 krzys_h
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.dafit.settings;
import android.util.Pair;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
public class DaFitSettingLanguage extends DaFitSettingEnum<DaFitEnumLanguage> {
public DaFitSettingLanguage(String name, byte cmdQuery, byte cmdSet) {
super(name, cmdQuery, cmdSet, DaFitEnumLanguage.class);
}
private Pair<DaFitEnumLanguage, DaFitEnumLanguage[]> decodeData(byte[] data) {
if (data.length != 5)
throw new IllegalArgumentException("Wrong data length, should be 5, was " + data.length);
byte[] current = new byte[] { data[0] };
byte[] supported = new byte[] { data[1], data[2], data[3], data[4] };
ByteBuffer buffer = ByteBuffer.wrap(supported);
int supportedNum = buffer.getInt();
String supportedStr = new StringBuffer(Integer.toBinaryString(supportedNum)).reverse().toString();
DaFitEnumLanguage currentLanguage = super.decode(current);
List<DaFitEnumLanguage> supportedLanguages = new ArrayList<>();
for (DaFitEnumLanguage e : clazz.getEnumConstants()) {
if (e.value() >= supportedStr.length())
continue;
if (Integer.parseInt(supportedStr.substring(e.value(), e.value() + 1)) != 0)
supportedLanguages.add(e);
}
DaFitEnumLanguage[] supportedLanguagesArr = new DaFitEnumLanguage[supportedLanguages.size()];
return Pair.create(currentLanguage, supportedLanguages.toArray(supportedLanguagesArr));
}
@Override
public DaFitEnumLanguage decode(byte[] data) {
return decodeData(data).first;
}
@Override
public DaFitEnumLanguage[] decodeSupportedValues(byte[] data) {
return decodeData(data).second;
}
}

View File

@ -0,0 +1,74 @@
/* Copyright (C) 2019 krzys_h
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.dafit.settings;
import java.nio.ByteBuffer;
public class DaFitSettingRemindersToMove extends DaFitSetting<DaFitSettingRemindersToMove.RemindersToMove> {
public static class RemindersToMove {
public byte period;
public byte steps;
public byte start_h;
public byte end_h;
public RemindersToMove() {
}
public RemindersToMove(byte period, byte steps, byte start_h, byte end_h) {
this.period = period;
this.steps = steps;
this.start_h = start_h;
this.end_h = end_h;
}
@Override
public String toString() {
return "RemindersToMove{" +
"period=" + period +
", steps=" + steps +
", start_h=" + start_h +
", end_h=" + end_h +
'}';
}
}
public DaFitSettingRemindersToMove(String name, byte cmdQuery, byte cmdSet) {
super(name, cmdQuery, cmdSet);
}
@Override
public byte[] encode(RemindersToMove value) {
ByteBuffer buffer = ByteBuffer.allocate(4);
buffer.put(value.period);
buffer.put(value.steps);
buffer.put(value.start_h);
buffer.put(value.end_h);
return buffer.array();
}
@Override
public RemindersToMove decode(byte[] data) {
if (data.length != 4)
throw new IllegalArgumentException("Wrong data length, should be 4, was " + data.length);
ByteBuffer buffer = ByteBuffer.wrap(data);
byte period = buffer.get();
byte steps = buffer.get();
byte start_h = buffer.get();
byte end_h = buffer.get();
return new RemindersToMove(period, steps, start_h, end_h);
}
}

View File

@ -0,0 +1,76 @@
/* Copyright (C) 2019 krzys_h
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.dafit.settings;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class DaFitSettingTimeRange extends DaFitSetting<DaFitSettingTimeRange.TimeRange> {
public static class TimeRange {
public byte start_h;
public byte start_m;
public byte end_h;
public byte end_m;
public TimeRange() {
}
public TimeRange(byte start_h, byte start_m, byte end_h, byte end_m) {
this.start_h = start_h;
this.start_m = start_m;
this.end_h = end_h;
this.end_m = end_m;
}
@Override
public String toString() {
return "TimeRange{" +
"start_h=" + start_h +
", start_m=" + start_m +
", end_h=" + end_h +
", end_m=" + end_m +
'}';
}
}
public DaFitSettingTimeRange(String name, byte cmdQuery, byte cmdSet) {
super(name, cmdQuery, cmdSet);
}
// Yes, these are different. Was somebody drunk when designing this?
@Override
public byte[] encode(TimeRange value) {
ByteBuffer buffer = ByteBuffer.allocate(4);
buffer.put(value.start_h);
buffer.put(value.start_m);
buffer.put(value.end_h);
buffer.put(value.end_m);
return buffer.array();
}
@Override
public TimeRange decode(byte[] data) {
if (data.length != 4)
throw new IllegalArgumentException("Wrong data length, should be 4, was " + data.length);
ByteBuffer buffer = ByteBuffer.wrap(data);
buffer.order(ByteOrder.LITTLE_ENDIAN);
short start = buffer.getShort();
short end = buffer.getShort();
return new TimeRange((byte)(start / 60), (byte)(start % 60), (byte)(end / 60), (byte)(start % 60));
}
}

View File

@ -0,0 +1,44 @@
/* Copyright (C) 2019 krzys_h
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.dafit.settings;
import org.apache.commons.lang3.NotImplementedException;
import java.nio.ByteBuffer;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
public class DaFitSettingUserInfo extends DaFitSetting<ActivityUser> {
public DaFitSettingUserInfo(String name, byte cmdSet) {
super(name, (byte)-1, cmdSet);
}
@Override
public byte[] encode(ActivityUser value) {
ByteBuffer buffer = ByteBuffer.allocate(4);
buffer.put((byte)value.getHeightCm());
buffer.put((byte)value.getWeightKg());
buffer.put((byte)value.getAge());
buffer.put((byte)value.getGender());
return buffer.array();
}
@Override
public ActivityUser decode(byte[] data) {
throw new NotImplementedException("decode");
}
}

View File

@ -19,8 +19,10 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.dafit;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Handler;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
import android.widget.Toast;
@ -32,29 +34,51 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.Logging;
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.GBDeviceEventBatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventConfigurationRead;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.DaFitConstants;
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.DaFitWeatherForecast;
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.DaFitWeatherToday;
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.DaFitDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.DaFitSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.settings.DaFitEnumDeviceVersion;
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.settings.DaFitEnumLanguage;
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.settings.DaFitEnumMetricSystem;
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.settings.DaFitEnumTimeSystem;
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.settings.DaFitSetting;
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.settings.DaFitSettingEnum;
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.settings.DaFitSettingLanguage;
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.settings.DaFitSettingRemindersToMove;
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.settings.DaFitSettingTimeRange;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.DaFitActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.User;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
@ -75,8 +99,10 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.Batter
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.heartrate.HeartRateProfile;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.NotificationUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
// TODO: figure out the training data
@ -139,6 +165,8 @@ public class DaFitDeviceSupport extends AbstractBTLEDeviceSupport {
builder.notify(getCharacteristic(DaFitConstants.UUID_CHARACTERISTIC_DATA_IN), true);
deviceInfoProfile.requestDeviceInfo(builder);
setTime(builder);
sendSetting(builder, getSetting("USER_INFO"), new ActivityUser()); // these settings are write-only, so write them just in case because there is no way to know if they desynced somehow
sendSetting(builder, getSetting("GOAL_STEP"), new ActivityUser().getStepsGoal());
batteryInfoProfile.requestBatteryInfo(builder);
batteryInfoProfile.enableNotify(builder);
heartRateProfile.enableNotify(builder);
@ -331,6 +359,17 @@ public class DaFitDeviceSupport extends AbstractBTLEDeviceSupport {
return true;
}
for (DaFitSetting setting : queriedSettings)
{
if (setting.cmdQuery == packetType)
{
Object value = setting.decode(payload);
onReadConfigurationDone(setting, value, payload);
queriedSettings.remove(setting);
return true;
}
}
LOG.warn("Unhandled packet " + packetType + ": " + Logging.formatBytes(payload));
return false;
}
@ -938,18 +977,377 @@ public class DaFitDeviceSupport extends AbstractBTLEDeviceSupport {
throw new UnsupportedOperationException();
}
@SuppressWarnings("unchecked")
private <T extends DaFitSetting> T getSetting(String id) {
DaFitDeviceCoordinator coordinator = (DaFitDeviceCoordinator) DeviceHelper.getInstance().getCoordinator(getDevice());
for(DaFitSetting setting : coordinator.getSupportedSettings())
{
if (setting.name.equals(id))
return (T) setting;
}
throw new IllegalArgumentException("No such setting: " + id);
}
private static Calendar getTimePref(Prefs prefs, String key, String defaultValue) {
String timePref = prefs.getString(key, defaultValue);
Date time = null;
try {
time = new SimpleDateFormat("HH:mm").parse(timePref);
} catch (ParseException e) {
e.printStackTrace();
}
Calendar cal = Calendar.getInstance();
cal.setTime(time);
return cal;
}
private <T> void sendSetting(TransactionBuilder builder, DaFitSetting<T> setting, T newValue)
{
sendPacket(builder, DaFitPacketOut.buildPacket(setting.cmdSet, setting.encode(newValue)));
}
private <T> void sendSetting(DaFitSetting<T> setting, T newValue)
{
try {
TransactionBuilder builder = performInitialized("sendSetting");
sendSetting(builder, setting, newValue);
builder.queue(getQueue());
} catch (IOException e) {
e.printStackTrace();
}
}
private Set<DaFitSetting> queriedSettings = new HashSet<>();
private void querySetting(DaFitSetting setting)
{
if (queriedSettings.contains(setting))
return;
try {
TransactionBuilder builder = performInitialized("querySetting");
sendPacket(builder, DaFitPacketOut.buildPacket(setting.cmdQuery, new byte[0]));
builder.queue(getQueue());
queriedSettings.add(setting);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onSendConfiguration(String config) {
// TODO
Log.i("OOOOOOOOOOOOOOOOsend", config);
Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
switch (config) {
case ActivityUser.PREF_USER_HEIGHT_CM:
case ActivityUser.PREF_USER_WEIGHT_KG:
case ActivityUser.PREF_USER_YEAR_OF_BIRTH:
case ActivityUser.PREF_USER_GENDER:
sendSetting(getSetting("USER_INFO"), new ActivityUser());
break;
case ActivityUser.PREF_USER_STEPS_GOAL:
sendSetting(getSetting("GOAL_STEP"), new ActivityUser().getStepsGoal());
break;
case DeviceSettingsPreferenceConst.PREF_TIMEFORMAT:
String timeSystemPref = prefs.getString(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT, getContext().getString(R.string.p_timeformat_24h));
DaFitEnumTimeSystem timeSystem;
if (timeSystemPref.equals(getContext().getString(R.string.p_timeformat_24h)))
timeSystem = DaFitEnumTimeSystem.TIME_SYSTEM_24;
else if (timeSystemPref.equals(getContext().getString(R.string.p_timeformat_am_pm)))
timeSystem = DaFitEnumTimeSystem.TIME_SYSTEM_12;
else
throw new IllegalArgumentException();
sendSetting(getSetting("TIME_SYSTEM"), timeSystem);
break;
case DeviceSettingsPreferenceConst.PREF_MEASUREMENTSYSTEM:
String metricSystemPref = prefs.getString(DeviceSettingsPreferenceConst.PREF_MEASUREMENTSYSTEM, getContext().getString(R.string.p_unit_metric));
DaFitEnumMetricSystem metricSystem;
if (metricSystemPref.equals(getContext().getString(R.string.p_unit_metric)))
metricSystem = DaFitEnumMetricSystem.METRIC_SYSTEM;
else if (metricSystemPref.equals(getContext().getString(R.string.p_unit_imperial)))
metricSystem = DaFitEnumMetricSystem.IMPERIAL_SYSTEM;
else
throw new IllegalArgumentException();
sendSetting(getSetting("METRIC_SYSTEM"), metricSystem);
break;
case DaFitConstants.PREF_WATCH_FACE:
String watchFacePref = prefs.getString(DaFitConstants.PREF_WATCH_FACE, String.valueOf(1));
byte watchFace = Byte.valueOf(watchFacePref);
sendSetting(getSetting("DISPLAY_WATCH_FACE"), watchFace);
break;
case DaFitConstants.PREF_LANGUAGE:
String languagePref = prefs.getString(DaFitConstants.PREF_LANGUAGE,
String.valueOf(DaFitEnumLanguage.LANGUAGE_ENGLISH.value()));
byte languageNum = Byte.valueOf(languagePref);
DaFitSettingEnum<DaFitEnumLanguage> languageSetting = getSetting("DEVICE_LANGUAGE");
sendSetting(languageSetting, languageSetting.findByValue(languageNum));
break;
case DaFitConstants.PREF_DEVICE_VERSION:
String versionPref = prefs.getString(DaFitConstants.PREF_DEVICE_VERSION,
String.valueOf(DaFitEnumDeviceVersion.INTERNATIONAL_EDITION.value()));
byte versionNum = Byte.valueOf(versionPref);
DaFitSettingEnum<DaFitEnumDeviceVersion> versionSetting = getSetting("DEVICE_VERSION");
sendSetting(versionSetting, versionSetting.findByValue(versionNum));
break;
case MiBandConst.PREF_DO_NOT_DISTURB:
case MiBandConst.PREF_DO_NOT_DISTURB_START:
case MiBandConst.PREF_DO_NOT_DISTURB_END:
String doNotDisturbPref = prefs.getString(MiBandConst.PREF_DO_NOT_DISTURB, MiBandConst.PREF_DO_NOT_DISTURB_OFF);
boolean doNotDisturbEnabled = !MiBandConst.PREF_DO_NOT_DISTURB_OFF.equals(doNotDisturbPref);
Calendar doNotDisturbStart = getTimePref(prefs, MiBandConst.PREF_DO_NOT_DISTURB_START, "01:00");
Calendar doNotDisturbEnd = getTimePref(prefs, MiBandConst.PREF_DO_NOT_DISTURB_END, "06:00");
DaFitSettingTimeRange.TimeRange doNotDisturb;
if (doNotDisturbEnabled)
doNotDisturb = new DaFitSettingTimeRange.TimeRange(
(byte) doNotDisturbStart.get(Calendar.HOUR_OF_DAY), (byte) doNotDisturbStart.get(Calendar.MINUTE),
(byte) doNotDisturbEnd.get(Calendar.HOUR_OF_DAY), (byte) doNotDisturbEnd.get(Calendar.MINUTE));
else
doNotDisturb = new DaFitSettingTimeRange.TimeRange((byte)0, (byte)0, (byte)0, (byte)0);
sendSetting(getSetting("DO_NOT_DISTURB_TIME"), doNotDisturb);
break;
case HuamiConst.PREF_ACTIVATE_DISPLAY_ON_LIFT:
case HuamiConst.PREF_DISPLAY_ON_LIFT_START:
case HuamiConst.PREF_DISPLAY_ON_LIFT_END:
String quickViewPref = prefs.getString(HuamiConst.PREF_ACTIVATE_DISPLAY_ON_LIFT, MiBandConst.PREF_DO_NOT_DISTURB_OFF);
boolean quickViewEnabled = !quickViewPref.equals(getContext().getString(R.string.p_off));
boolean quickViewScheduled = quickViewPref.equals(getContext().getString(R.string.p_scheduled));
Calendar quickViewStart = getTimePref(prefs, HuamiConst.PREF_DISPLAY_ON_LIFT_START, "00:00");
Calendar quickViewEnd = getTimePref(prefs, HuamiConst.PREF_DISPLAY_ON_LIFT_END, "00:00");
DaFitSettingTimeRange.TimeRange quickViewTime;
if (quickViewEnabled && quickViewScheduled)
quickViewTime = new DaFitSettingTimeRange.TimeRange(
(byte) quickViewStart.get(Calendar.HOUR_OF_DAY), (byte) quickViewStart.get(Calendar.MINUTE),
(byte) quickViewEnd.get(Calendar.HOUR_OF_DAY), (byte) quickViewEnd.get(Calendar.MINUTE));
else
quickViewTime = new DaFitSettingTimeRange.TimeRange((byte)0, (byte)0, (byte)0, (byte)0);
sendSetting(getSetting("QUICK_VIEW"), quickViewEnabled);
sendSetting(getSetting("QUICK_VIEW_TIME"), quickViewTime);
break;
case DaFitConstants.PREF_SEDENTARY_REMINDER:
String sedentaryReminderPref = prefs.getString(DaFitConstants.PREF_SEDENTARY_REMINDER, "off");
boolean sedentaryReminderEnabled = !sedentaryReminderPref.equals("off");
sendSetting(getSetting("SEDENTARY_REMINDER"), sedentaryReminderEnabled);
break;
case DaFitConstants.PREF_SEDENTARY_REMINDER_PERIOD:
case DaFitConstants.PREF_SEDENTARY_REMINDER_STEPS:
case DaFitConstants.PREF_SEDENTARY_REMINDER_START:
case DaFitConstants.PREF_SEDENTARY_REMINDER_END:
byte sedentaryPeriod = (byte) prefs.getInt(DaFitConstants.PREF_SEDENTARY_REMINDER_PERIOD, 30);
byte sedentarySteps = (byte) prefs.getInt(DaFitConstants.PREF_SEDENTARY_REMINDER_STEPS, 100);
byte sedentaryStart = (byte) prefs.getInt(DaFitConstants.PREF_SEDENTARY_REMINDER_START, 10);
byte sedentaryEnd = (byte) prefs.getInt(DaFitConstants.PREF_SEDENTARY_REMINDER_END, 22);
sendSetting(getSetting("REMINDERS_TO_MOVE_PERIOD"),
new DaFitSettingRemindersToMove.RemindersToMove(sedentaryPeriod, sedentarySteps, sedentaryStart, sedentaryEnd));
break;
}
// Query the setting to make sure the configuration got actually applied
// TODO: breaks sedentary
//onReadConfiguration(config);
}
@Override
public void onReadConfiguration(String config) {
// TODO
Log.i("OOOOOOOOOOOOOOOOread", config);
switch (config) {
/* These use the global Gadgetbridge configuration and are always forced on device upon connection
case ActivityUser.PREF_USER_HEIGHT_CM:
case ActivityUser.PREF_USER_WEIGHT_KG:
case ActivityUser.PREF_USER_YEAR_OF_BIRTH:
case ActivityUser.PREF_USER_GENDER:
querySetting(getSetting("USER_INFO"));
break;
case ActivityUser.PREF_USER_STEPS_GOAL:
querySetting(getSetting("GOAL_STEP"));
break;*/
case DeviceSettingsPreferenceConst.PREF_TIMEFORMAT:
querySetting(getSetting("TIME_SYSTEM"));
break;
case DeviceSettingsPreferenceConst.PREF_MEASUREMENTSYSTEM:
querySetting(getSetting("METRIC_SYSTEM"));
break;
case DaFitConstants.PREF_WATCH_FACE:
querySetting(getSetting("DISPLAY_WATCH_FACE"));
break;
case DaFitConstants.PREF_LANGUAGE:
querySetting(getSetting("DEVICE_LANGUAGE"));
break;
case DaFitConstants.PREF_DEVICE_VERSION:
querySetting(getSetting("DEVICE_VERSION"));
break;
case MiBandConst.PREF_DO_NOT_DISTURB:
case MiBandConst.PREF_DO_NOT_DISTURB_START:
case MiBandConst.PREF_DO_NOT_DISTURB_END:
querySetting(getSetting("DO_NOT_DISTURB_TIME"));
break;
case HuamiConst.PREF_ACTIVATE_DISPLAY_ON_LIFT:
case HuamiConst.PREF_DISPLAY_ON_LIFT_START:
case HuamiConst.PREF_DISPLAY_ON_LIFT_END:
querySetting(getSetting("QUICK_VIEW"));
querySetting(getSetting("QUICK_VIEW_TIME"));
break;
case DaFitConstants.PREF_SEDENTARY_REMINDER:
querySetting(getSetting("SEDENTARY_REMINDER"));
break;
case DaFitConstants.PREF_SEDENTARY_REMINDER_PERIOD:
case DaFitConstants.PREF_SEDENTARY_REMINDER_STEPS:
case DaFitConstants.PREF_SEDENTARY_REMINDER_START:
case DaFitConstants.PREF_SEDENTARY_REMINDER_END:
querySetting(getSetting("REMINDERS_TO_MOVE_PERIOD"));
break;
default:
return;
}
GBDeviceEventConfigurationRead configReadEvent = new GBDeviceEventConfigurationRead();
configReadEvent.config = config;
configReadEvent.event = GBDeviceEventConfigurationRead.Event.IN_PROGRESS;
evaluateGBDeviceEvent(configReadEvent);
}
public void onReadConfigurationDone(DaFitSetting setting, Object value, byte[] data)
{
Log.i("CONFIG", setting.name + " = " + value);
Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
Map<String, String> changedProperties = new ArrayMap<>();
SharedPreferences.Editor prefsEditor = prefs.getPreferences().edit();
switch (setting.name) {
case "TIME_SYSTEM":
DaFitEnumTimeSystem timeSystem = (DaFitEnumTimeSystem) value;
if (timeSystem == DaFitEnumTimeSystem.TIME_SYSTEM_24)
changedProperties.put(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT, getContext().getString(R.string.p_timeformat_24h));
else if (timeSystem == DaFitEnumTimeSystem.TIME_SYSTEM_12)
changedProperties.put(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT, getContext().getString(R.string.p_timeformat_am_pm));
else
throw new IllegalArgumentException("Invalid value");
break;
case "METRIC_SYSTEM":
DaFitEnumMetricSystem metricSystem = (DaFitEnumMetricSystem) value;
if (metricSystem == DaFitEnumMetricSystem.METRIC_SYSTEM)
changedProperties.put(DeviceSettingsPreferenceConst.PREF_MEASUREMENTSYSTEM, getContext().getString(R.string.p_unit_metric));
else if (metricSystem == DaFitEnumMetricSystem.IMPERIAL_SYSTEM)
changedProperties.put(DeviceSettingsPreferenceConst.PREF_MEASUREMENTSYSTEM, getContext().getString(R.string.p_unit_imperial));
else
throw new IllegalArgumentException("Invalid value");
break;
case "DISPLAY_WATCH_FACE":
byte watchFace = (Byte) value;
changedProperties.put(DaFitConstants.PREF_WATCH_FACE, String.valueOf(watchFace));
break;
case "DEVICE_LANGUAGE":
DaFitEnumLanguage language = (DaFitEnumLanguage) value;
changedProperties.put(DaFitConstants.PREF_LANGUAGE, String.valueOf(language.value()));
DaFitEnumLanguage[] supportedLanguages = ((DaFitSettingLanguage) setting).decodeSupportedValues(data);
Set<String> supportedLanguagesList = new HashSet<>();
for(DaFitEnumLanguage supportedLanguage : supportedLanguages)
supportedLanguagesList.add(String.valueOf(supportedLanguage.value()));
prefsEditor.putStringSet(DaFitConstants.PREF_LANGUAGE_SUPPORT, supportedLanguagesList);
break;
case "DEVICE_VERSION":
DaFitEnumDeviceVersion deviceVersion = (DaFitEnumDeviceVersion) value;
changedProperties.put(DaFitConstants.PREF_DEVICE_VERSION, String.valueOf(deviceVersion.value()));
break;
case "DO_NOT_DISTURB_TIME":
DaFitSettingTimeRange.TimeRange doNotDisturb = (DaFitSettingTimeRange.TimeRange) value;
if (doNotDisturb.start_h == 0 && doNotDisturb.start_m == 0 &&
doNotDisturb.end_h == 0 && doNotDisturb.end_m == 0)
changedProperties.put(MiBandConst.PREF_DO_NOT_DISTURB, MiBandConst.PREF_DO_NOT_DISTURB_OFF);
else
changedProperties.put(MiBandConst.PREF_DO_NOT_DISTURB, MiBandConst.PREF_DO_NOT_DISTURB_SCHEDULED);
changedProperties.put(MiBandConst.PREF_DO_NOT_DISTURB_START, String.format(Locale.ROOT, "%02d:%02d", doNotDisturb.start_h, doNotDisturb.start_m));
changedProperties.put(MiBandConst.PREF_DO_NOT_DISTURB_END, String.format(Locale.ROOT, "%02d:%02d", doNotDisturb.end_h, doNotDisturb.end_m));
break;
case "QUICK_VIEW":
boolean quickViewEnabled = (Boolean) value;
boolean quickViewScheduled = prefs.getString(HuamiConst.PREF_ACTIVATE_DISPLAY_ON_LIFT, getContext().getString(R.string.p_off)).equals(getContext().getString(R.string.p_scheduled));
changedProperties.put(HuamiConst.PREF_ACTIVATE_DISPLAY_ON_LIFT, quickViewEnabled ? (quickViewScheduled ? getContext().getString(R.string.p_scheduled) : getContext().getString(R.string.p_on)) : getContext().getString(R.string.p_off));
break;
case "QUICK_VIEW_TIME":
boolean quickViewEnabled2 = !prefs.getString(HuamiConst.PREF_ACTIVATE_DISPLAY_ON_LIFT, getContext().getString(R.string.p_off)).equals(getContext().getString(R.string.p_off));
DaFitSettingTimeRange.TimeRange quickViewTime = (DaFitSettingTimeRange.TimeRange) value;
if (quickViewEnabled2)
{
if (quickViewTime.start_h == 0 && quickViewTime.start_m == 0 &&
quickViewTime.end_h == 0 && quickViewTime.end_m == 0)
changedProperties.put(HuamiConst.PREF_ACTIVATE_DISPLAY_ON_LIFT, getContext().getString(R.string.p_on));
else
changedProperties.put(HuamiConst.PREF_ACTIVATE_DISPLAY_ON_LIFT, getContext().getString(R.string.p_scheduled));
}
changedProperties.put(HuamiConst.PREF_DISPLAY_ON_LIFT_START, String.format(Locale.ROOT, "%02d:%02d", quickViewTime.start_h, quickViewTime.start_m));
changedProperties.put(HuamiConst.PREF_DISPLAY_ON_LIFT_END, String.format(Locale.ROOT, "%02d:%02d", quickViewTime.end_h, quickViewTime.end_m));
break;
case "SEDENTARY_REMINDER":
boolean sedentaryReminderEnabled = (Boolean) value;
changedProperties.put(DaFitConstants.PREF_SEDENTARY_REMINDER, sedentaryReminderEnabled ? "on": "off");
break;
case "REMINDERS_TO_MOVE_PERIOD":
DaFitSettingRemindersToMove.RemindersToMove remindersToMove = (DaFitSettingRemindersToMove.RemindersToMove) value;
changedProperties.put(DaFitConstants.PREF_SEDENTARY_REMINDER_PERIOD, String.valueOf(remindersToMove.period));
changedProperties.put(DaFitConstants.PREF_SEDENTARY_REMINDER_STEPS, String.valueOf(remindersToMove.steps));
changedProperties.put(DaFitConstants.PREF_SEDENTARY_REMINDER_START, String.valueOf(remindersToMove.start_h));
changedProperties.put(DaFitConstants.PREF_SEDENTARY_REMINDER_END, String.valueOf(remindersToMove.end_h));
break;
}
for (Map.Entry<String, String> property : changedProperties.entrySet())
prefsEditor.putString(property.getKey(), property.getValue());
prefsEditor.apply();
for (Map.Entry<String, String> property : changedProperties.entrySet())
{
GBDeviceEventConfigurationRead configReadEvent = new GBDeviceEventConfigurationRead();
configReadEvent.config = property.getKey();
configReadEvent.event = GBDeviceEventConfigurationRead.Event.SUCCESS;
evaluateGBDeviceEvent(configReadEvent);
}
}
@Override
public void onTestNewFunction() {
try {
new QuerySettingsOperation(this).perform();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override

View File

@ -0,0 +1,137 @@
/* Copyright (C) 2019 krzys_h
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.service.devices.dafit;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.util.Log;
import android.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.DaFitConstants;
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.DaFitDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.dafit.settings.DaFitSetting;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
public class QuerySettingsOperation extends AbstractBTLEOperation<DaFitDeviceSupport> {
private static final Logger LOG = LoggerFactory.getLogger(QuerySettingsOperation.class);
private final DaFitSetting[] settingsToQuery;
private boolean[] received;
private DaFitPacketIn packetIn = new DaFitPacketIn();
public QuerySettingsOperation(DaFitDeviceSupport support, DaFitSetting[] settingsToQuery) {
super(support);
this.settingsToQuery = settingsToQuery;
}
public QuerySettingsOperation(DaFitDeviceSupport support) {
super(support);
DaFitDeviceCoordinator coordinator = (DaFitDeviceCoordinator) DeviceHelper.getInstance().getCoordinator(getDevice());
this.settingsToQuery = coordinator.getSupportedSettings();
}
@Override
protected void prePerform() {
getDevice().setBusyTask("Querying settings"); // mark as busy quickly to avoid interruptions from the outside
getDevice().sendDeviceUpdateIntent(getContext());
}
@Override
protected void doPerform() throws IOException {
received = new boolean[settingsToQuery.length];
TransactionBuilder builder = performInitialized("QuerySettingsOperation");
for (DaFitSetting setting : settingsToQuery)
{
if (setting.cmdQuery == -1)
continue;
getSupport().sendPacket(builder, DaFitPacketOut.buildPacket(setting.cmdQuery, new byte[0]));
}
builder.queue(getQueue());
}
@Override
public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
if (!isOperationRunning())
{
LOG.error("onCharacteristicChanged but operation is not running!");
}
else
{
UUID charUuid = characteristic.getUuid();
if (charUuid.equals(DaFitConstants.UUID_CHARACTERISTIC_DATA_IN))
{
if (packetIn.putFragment(characteristic.getValue())) {
Pair<Byte, byte[]> packet = DaFitPacketIn.parsePacket(packetIn.getPacket());
packetIn = new DaFitPacketIn();
if (packet != null) {
byte packetType = packet.first;
byte[] payload = packet.second;
if (handlePacket(packetType, payload))
return true;
}
}
}
}
return super.onCharacteristicChanged(gatt, characteristic);
}
private boolean handlePacket(byte packetType, byte[] payload) {
boolean handled = false;
boolean receivedEverything = true;
for(int i = 0; i < settingsToQuery.length; i++)
{
DaFitSetting setting = settingsToQuery[i];
if (setting.cmdQuery == -1)
continue;
if (setting.cmdQuery == packetType)
{
Object value = setting.decode(payload);
Log.i("SETTING QUERY", setting.name + " = " + value.toString());
received[i] = true;
handled = true;
}
else if (!received[i])
receivedEverything = false;
}
if (receivedEverything)
operationFinished();
return handled;
}
@Override
protected void operationFinished() {
operationStatus = OperationStatus.FINISHED;
if (getDevice() != null && getDevice().isConnected()) {
unsetBusy();
}
}
}

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#7E7E7E"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M8,19h3v4h2v-4h3l-4,-4 -4,4zM16,5h-3L13,1h-2v4L8,5l4,4 4,-4zM4,11v2h16v-2L4,11z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#7E7E7E"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M4,18v3h3v-3h10v3h3v-6L4,15zM19,10h3v3h-3zM2,10h3v3L2,13zM17,13L7,13L7,5c0,-1.1 0.9,-2 2,-2h6c1.1,0 2,0.9 2,2v8z"/>
</vector>

View File

@ -366,6 +366,16 @@
<item>@string/p_scheduled</item>
</string-array>
<string-array name="sedentary_reminder">
<item>@string/off</item>
<item>@string/on</item>
</string-array>
<string-array name="sedentary_reminder_values">
<item>@string/p_off</item>
<item>@string/p_on</item>
</string-array>
<string-array name="mi2_do_not_disturb">
<item>@string/mi2_dnd_off</item>
<item>@string/mi2_dnd_automatic</item>
@ -2624,6 +2634,28 @@
<item>ru_RU</item>
</string-array>
<string-array name="pref_dafit_watch_face">
<item>Watch face 1</item>
<item>Watch face 2</item>
<item>Watch face 3</item>
</string-array>
<string-array name="pref_dafit_watch_face_values">
<item>1</item>
<item>2</item>
<item>3</item>
</string-array>
<string-array name="pref_dafit_device_version">
<item>Chinese edition</item>
<item>International edition</item>
</string-array>
<string-array name="pref_dafit_device_version_values">
<item>0</item>
<item>1</item>
</string-array>
<string-array name="prefs_heartrate_measurement_interval">
<!-- This will be filled dynamically by HeartRateCapability -->
<item name="0">@string/off</item>

View File

@ -436,6 +436,13 @@
<string name="pref_display_add_device_fab_off">Visible only if no device is added</string>
<string name="pref_title_huami_force_new_protocol">New Auth Protocol</string>
<string name="pref_summary_huami_force_new_protocol">Enable if your device no longer connects after a firmware upgrade</string>
<string name="pref_watch_face">Watch face</string>
<string name="pref_device_version">Device version</string>
<string name="pref_sedentary_reminder">Sedentary reminder</string>
<string name="pref_sedentary_reminder_period">Time period (minutes)</string>
<string name="pref_sedentary_reminder_steps">Minimum steps</string>
<string name="pref_sedentary_reminder_start">Start time (hour)</string>
<string name="pref_sedentary_reminder_end">End time (hour)</string>
<!-- HPlus Preferences -->
<string name="pref_title_unit_system">Units</string>
<string name="pref_title_timeformat">Time format</string>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<ListPreference
android:defaultValue="1"
android:entries="@array/pref_dafit_device_version"
android:entryValues="@array/pref_dafit_device_version_values"
android:key="dafit_device_version"
android:summary="%s"
android:title="@string/pref_device_version" />
</androidx.preference.PreferenceScreen>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<ListPreference
android:icon="@drawable/ic_language"
android:defaultValue="0"
android:key="dafit_language"
android:summary="%s"
android:title="@string/pref_title_language" />
</androidx.preference.PreferenceScreen>

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceScreen
android:icon="@drawable/ic_sitting"
android:key="screen_sedentary_reminder"
android:persistent="false"
android:title="@string/pref_sedentary_reminder">
<!-- workaround for missing toolbar -->
<PreferenceCategory android:title="@string/pref_sedentary_reminder" />
<nodomain.freeyourgadget.gadgetbridge.util.XListPreference
android:defaultValue="@string/p_off"
android:entries="@array/sedentary_reminder"
android:entryValues="@array/sedentary_reminder_values"
android:key="sedentary_reminder"
android:summary="%s"
app:dependentValue="@string/p_on"
android:title="@string/pref_sedentary_reminder" />
<androidx.preference.EditTextPreference
android:defaultValue="30"
android:inputType="number"
android:key="sedentary_reminder_period"
android:maxLength="3"
app:useSimpleSummaryProvider="true"
android:dependency="sedentary_reminder"
android:title="@string/pref_sedentary_reminder_period" />
<androidx.preference.EditTextPreference
android:defaultValue="100"
android:inputType="number"
android:key="sedentary_reminder_steps"
android:maxLength="3"
app:useSimpleSummaryProvider="true"
android:dependency="sedentary_reminder"
android:title="@string/pref_sedentary_reminder_steps" />
<androidx.preference.EditTextPreference
android:defaultValue="10"
android:inputType="number"
android:key="sedentary_reminder_start"
android:maxLength="2"
app:useSimpleSummaryProvider="true"
android:dependency="sedentary_reminder"
android:title="@string/pref_sedentary_reminder_start" />
<androidx.preference.EditTextPreference
android:defaultValue="22"
android:inputType="number"
android:key="sedentary_reminder_end"
android:maxLength="2"
app:useSimpleSummaryProvider="true"
android:dependency="sedentary_reminder"
android:title="@string/pref_sedentary_reminder_end" />
</PreferenceScreen>
</androidx.preference.PreferenceScreen>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<ListPreference
android:icon="@drawable/ic_widgets"
android:defaultValue="1"
android:entries="@array/pref_dafit_watch_face"
android:entryValues="@array/pref_dafit_watch_face_values"
android:key="dafit_watch_face"
android:summary="%s"
android:title="@string/pref_watch_face" />
</androidx.preference.PreferenceScreen>

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- FIXME: A better version of _no_auto because the other one changes the IDs so the change notification does not work... -->
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceScreen
android:icon="@drawable/ic_block"
android:key="screen_do_not_disturb"
android:persistent="false"
android:summary="@string/mi2_prefs_do_not_disturb_summary"
android:title="@string/mi2_prefs_do_not_disturb">
<!-- workaround for missing toolbar -->
<PreferenceCategory android:title="@string/mi2_prefs_do_not_disturb" />
<nodomain.freeyourgadget.gadgetbridge.util.XListPreference
android:defaultValue="@string/p_off"
android:entries="@array/do_not_disturb_no_auto"
android:entryValues="@array/do_not_disturb_no_auto_values"
android:key="do_not_disturb"
android:summary="%s"
app:dependentValue="@string/p_scheduled"
android:title="@string/mi2_prefs_do_not_disturb" />
<nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
android:defaultValue="01:00"
android:key="do_not_disturb_start"
android:dependency="do_not_disturb"
android:title="@string/mi2_prefs_do_not_disturb_start" />
<nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
android:defaultValue="06:00"
android:key="do_not_disturb_end"
android:dependency="do_not_disturb"
android:title="@string/mi2_prefs_do_not_disturb_end" />
</PreferenceScreen>
</androidx.preference.PreferenceScreen>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<ListPreference
android:icon="@drawable/ic_measurement_system"
android:defaultValue="metric"
android:entries="@array/pref_entries_unit_system"
android:entryValues="@array/pref_values_unit_system"
android:key="measurement_system"
android:summary="%s"
android:title="@string/pref_title_unit_system" />
</androidx.preference.PreferenceScreen>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<Preference
android:icon="@drawable/ic_person"
android:key="personal_info"
android:title="@string/activity_prefs_about_you">
<intent
android:targetPackage="nodomain.freeyourgadget.gadgetbridge"
android:targetClass="nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity" />
<!-- TODO: link this to proper subscreen -->
</Preference>
</androidx.preference.PreferenceScreen>