1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-07-24 07:38:45 +02:00
Gadgetbridge/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfPreferences.java

393 lines
16 KiB
Java
Raw Normal View History

2024-01-28 19:46:13 +01:00
/* Copyright (C) 2024 José Rebelo
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 <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.cmfwatchpro;
import android.content.Context;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class CmfPreferences {
private static final Logger LOG = LoggerFactory.getLogger(CmfPreferences.class);
private final CmfWatchProSupport mSupport;
protected CmfPreferences(final CmfWatchProSupport support) {
this.mSupport = support;
}
protected void onSetHeartRateMeasurementInterval(final int seconds) {
final boolean enabled = seconds == -1;
LOG.debug("Set HR smart monitoring = {}", enabled);
final byte[] cmd = new byte[]{0x01, (byte) (enabled ? 0x01 : 0x00)};
mSupport.sendCommand("set hr monitoring", CmfCommand.HEART_MONITORING_ENABLED_SET, cmd);
}
protected void onSendConfiguration(final String config) {
switch (config) {
case ActivityUser.PREF_USER_STEPS_GOAL:
case ActivityUser.PREF_USER_DISTANCE_METERS:
case ActivityUser.PREF_USER_CALORIES_BURNT:
setGoals();
return;
case SettingsActivity.PREF_MEASUREMENT_SYSTEM:
setMeasurementSystem();
return;
case DeviceSettingsPreferenceConst.PREF_LANGUAGE:
setLanguage();
return;
case DeviceSettingsPreferenceConst.PREF_TIMEFORMAT:
setTimeFormat();
return;
case DeviceSettingsPreferenceConst.PREF_LIFTWRIST_NOSHED:
setDisplayOnLift();
return;
case DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_LOW_THRESHOLD:
case DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_HIGH_THRESHOLD:
case DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_ACTIVE_HIGH_THRESHOLD:
case DeviceSettingsPreferenceConst.PREF_SPO2_LOW_ALERT_THRESHOLD:
setHeartAlerts();
return;
case DeviceSettingsPreferenceConst.PREF_SPO2_ALL_DAY_MONITORING:
setSpo2MonitoringInterval();
return;
case DeviceSettingsPreferenceConst.PREF_HEARTRATE_STRESS_MONITORING:
setStressMonitoringInterval();
return;
case DeviceSettingsPreferenceConst.PREF_INACTIVITY_ENABLE:
case DeviceSettingsPreferenceConst.PREF_INACTIVITY_THRESHOLD:
case DeviceSettingsPreferenceConst.PREF_INACTIVITY_START:
case DeviceSettingsPreferenceConst.PREF_INACTIVITY_END:
case DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND:
case DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND_START:
case DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND_END:
setStandingReminder();
case DeviceSettingsPreferenceConst.PREF_HYDRATION_SWITCH:
case DeviceSettingsPreferenceConst.PREF_HYDRATION_PERIOD:
case DeviceSettingsPreferenceConst.PREF_HYDRATION_DND:
case DeviceSettingsPreferenceConst.PREF_HYDRATION_DND_START:
case DeviceSettingsPreferenceConst.PREF_HYDRATION_DND_END:
setHydrationReminder();
return;
case HuamiConst.PREF_WORKOUT_ACTIVITY_TYPES_SORTABLE:
setActivityTypes();
return;
// TODO call reminders
}
LOG.warn("Unknown config changed: {}", config);
}
private void setGoals() {
final ActivityUser activityUser = new ActivityUser();
if (activityUser.getStepsGoal() <= 0) {
LOG.warn("Invalid steps goal {}", activityUser.getStepsGoal());
return;
}
if (activityUser.getDistanceGoalMeters() <= 0) {
LOG.warn("Invalid distance goal {}", activityUser.getDistanceGoalMeters());
return;
}
if (activityUser.getCaloriesBurntGoal() <= 0) {
LOG.warn("Invalid calories goal {}", activityUser.getCaloriesBurntGoal());
return;
}
LOG.debug(
"Setting goals, steps={}, distance={}, calories={}",
activityUser.getStepsGoal(),
activityUser.getDistanceGoalMeters(),
activityUser.getCaloriesBurntGoal()
);
final ByteBuffer buf = ByteBuffer.allocate(10).order(ByteOrder.BIG_ENDIAN);
buf.put((byte) 0); // ?
buf.put((byte) 0); // ?
buf.putShort((short) activityUser.getStepsGoal());
buf.put((byte) 0); // ?
buf.put((byte) 0); // ?
buf.putShort((short) activityUser.getDistanceGoalMeters());
buf.putShort((short) activityUser.getCaloriesBurntGoal());
mSupport.sendCommand("set goals", CmfCommand.GOALS_SET, buf.array());
}
private void setMeasurementSystem() {
final Prefs prefs = mSupport.getDevicePrefs();
final String measurementSystem = prefs.getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, "metric");
LOG.debug("Setting measurement system to {}", measurementSystem);
final byte unitByte = (byte) ("metric".equals(measurementSystem) ? 0x00 : 0x01);
final byte[] cmd = new byte[]{0x01, unitByte};
final TransactionBuilder builder = mSupport.createTransactionBuilder("set measurement system");
mSupport.sendCommand(builder, CmfCommand.UNIT_LENGTH, cmd);
mSupport.sendCommand(builder, CmfCommand.UNIT_TEMPERATURE, cmd);
builder.queue(mSupport.getQueue());
}
private void setLanguage() {
String localeString = mSupport.getDevicePrefs().getString(
DeviceSettingsPreferenceConst.PREF_LANGUAGE, DeviceSettingsPreferenceConst.PREF_LANGUAGE_AUTO
);
if (DeviceSettingsPreferenceConst.PREF_LANGUAGE_AUTO.equals(localeString)) {
String language = Locale.getDefault().getLanguage();
String country = Locale.getDefault().getCountry();
if (nodomain.freeyourgadget.gadgetbridge.util.StringUtils.isNullOrEmpty(country)) {
// sometimes country is null, no idea why, guess it.
country = language;
}
localeString = (language + "_" + country).toLowerCase(Locale.ROOT);
}
String languageCommand = null;
if (LANGUAGES.containsKey(localeString)) {
languageCommand = localeString;
} else {
// Break down the language code and attempt to find it
final String[] languageParts = localeString.split("_");
for (int i = 0; i < languageParts.length; i++) {
if (LANGUAGES.containsKey(languageParts[0])) {
languageCommand = languageParts[0];
break;
}
}
}
if (languageCommand == null) {
LOG.warn("Unknown language {}", localeString);
return;
}
LOG.info("Set language: {} -> {}", localeString, languageCommand);
// FIXME watch ignores language?
mSupport.sendCommand("set language", CmfCommand.LANGUAGE_SET, languageCommand.getBytes());
}
private void setTimeFormat() {
final GBPrefs gbPrefs = new GBPrefs(mSupport.getDevicePrefs());
final String timeFormat = gbPrefs.getTimeFormat();
LOG.info("Setting time format to {}", timeFormat);
final byte timeFormatByte = (byte) (timeFormat.equals("24h") ? 0x00 : 0x01);
mSupport.sendCommand("set time format", CmfCommand.TIME_FORMAT, timeFormatByte);
}
private void setDisplayOnLift() {
final Prefs prefs = mSupport.getDevicePrefs();
boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_LIFTWRIST_NOSHED, false);
mSupport.sendCommand("set display on lift", CmfCommand.WAKE_ON_WRIST_RAISE, (byte) (enabled ? 0x01 : 0x00));
}
private void setHeartAlerts() {
final Prefs prefs = mSupport.getDevicePrefs();
final int hrAlertActiveHigh = prefs.getInt(DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_ACTIVE_HIGH_THRESHOLD, 0);
final int hrAlertHigh = prefs.getInt(DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_HIGH_THRESHOLD, 0);
final int hrAlertLow = prefs.getInt(DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_LOW_THRESHOLD, 0);
final int spo2alert = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SPO2_LOW_ALERT_THRESHOLD, 0);
final ByteBuffer buf;
if (hrAlertActiveHigh == 0 && hrAlertHigh == 0 && hrAlertLow == 0 && spo2alert == 0) {
buf = ByteBuffer.allocate(1).order(ByteOrder.BIG_ENDIAN);
buf.put((byte) 0x00);
} else {
buf = ByteBuffer.allocate(9).order(ByteOrder.BIG_ENDIAN);
buf.put((byte) 0x01);
buf.put((byte) hrAlertLow);
buf.put((byte) (hrAlertHigh != 0 ? hrAlertHigh : 255));
buf.put((byte) (hrAlertActiveHigh != 0 ? hrAlertActiveHigh : 255));
buf.put((byte) spo2alert);
buf.put((byte) 0x00); // ?
buf.put((byte) 0x00); // ?
buf.put((byte) 0x00); // ?
buf.put((byte) 0x00); // ?
}
mSupport.sendCommand("set heart monitoring alerts", CmfCommand.HEART_MONITORING_ALERTS, buf.array());
}
private void setSpo2MonitoringInterval() {
final Prefs prefs = mSupport.getDevicePrefs();
final boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SPO2_ALL_DAY_MONITORING, false);
LOG.debug("Set SpO2 monitoring = {}", enabled);
final byte[] cmd = new byte[]{0x02, (byte) (enabled ? 0x01 : 0x00)};
mSupport.sendCommand("set spo2 monitoring", CmfCommand.HEART_MONITORING_ENABLED_SET, cmd);
}
private void setStressMonitoringInterval() {
final Prefs prefs = mSupport.getDevicePrefs();
final boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HEARTRATE_STRESS_MONITORING, false);
LOG.debug("Set stress monitoring = {}", enabled);
final byte[] cmd = new byte[]{0x04, (byte) (enabled ? 0x01 : 0x00)};
mSupport.sendCommand("set stress monitoring", CmfCommand.HEART_MONITORING_ENABLED_SET, cmd);
}
private void setStandingReminder() {
final Prefs prefs = mSupport.getDevicePrefs();
final boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_ENABLE, false);
final int threshold = prefs.getInt(DeviceSettingsPreferenceConst.PREF_INACTIVITY_THRESHOLD, 60);
final boolean dnd = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND, false);
final Date dndStart = prefs.getTimePreference(DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND_START, "12:00");
final Date dndEnd = prefs.getTimePreference(DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND_END, "14:00");
final Calendar calendar = GregorianCalendar.getInstance();
if (threshold < 0 || threshold > 180) {
LOG.error("Invalid inactivity threshold: {}", threshold);
return;
}
final ByteBuffer buf = ByteBuffer.allocate(11).order(ByteOrder.BIG_ENDIAN);
buf.put((byte) (enabled ? 0x01 : 0x00));
buf.putShort((short) threshold);
if (enabled && dnd) {
calendar.setTime(dndStart);
buf.putInt((calendar.get(Calendar.HOUR_OF_DAY) * 3600 + calendar.get(Calendar.MINUTE) * 60));
calendar.setTime(dndEnd);
buf.putInt((calendar.get(Calendar.HOUR_OF_DAY) * 3600 + calendar.get(Calendar.MINUTE) * 60));
} else {
buf.putInt(0);
buf.putInt(0);
}
mSupport.sendCommand("set standing reminders", CmfCommand.STANDING_REMINDER_SET, buf.array());
}
private void setHydrationReminder() {
final Prefs prefs = mSupport.getDevicePrefs();
final boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HYDRATION_SWITCH, false);
final int threshold = prefs.getInt(DeviceSettingsPreferenceConst.PREF_HYDRATION_PERIOD, 60);
final boolean dnd = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HYDRATION_DND, false);
final Date dndStart = prefs.getTimePreference(DeviceSettingsPreferenceConst.PREF_HYDRATION_DND_START, "12:00");
final Date dndEnd = prefs.getTimePreference(DeviceSettingsPreferenceConst.PREF_HYDRATION_DND_END, "14:00");
final Calendar calendar = GregorianCalendar.getInstance();
if (threshold < 0 || threshold > 180) {
LOG.error("Invalid hydration threshold: {}", threshold);
return;
}
final ByteBuffer buf = ByteBuffer.allocate(11).order(ByteOrder.BIG_ENDIAN);
buf.put((byte) (enabled ? 0x01 : 0x00));
buf.putShort((short) threshold);
if (enabled && dnd) {
calendar.setTime(dndStart);
buf.putInt((calendar.get(Calendar.HOUR_OF_DAY) * 3600 + calendar.get(Calendar.MINUTE) * 60));
calendar.setTime(dndEnd);
buf.putInt((calendar.get(Calendar.HOUR_OF_DAY) * 3600 + calendar.get(Calendar.MINUTE) * 60));
} else {
buf.putInt(0);
buf.putInt(0);
}
mSupport.sendCommand("set hydration reminders", CmfCommand.WATER_REMINDER_SET, buf.array());
}
private void setActivityTypes() {
final Prefs prefs = mSupport.getDevicePrefs();
List<String> activityTypes = new ArrayList<>(prefs.getList(HuamiConst.PREF_WORKOUT_ACTIVITY_TYPES_SORTABLE, Collections.emptyList()));
if (activityTypes.isEmpty()) {
activityTypes.add(CmfActivityType.OUTDOOR_RUNNING.name().toLowerCase(Locale.ROOT));
activityTypes.add(CmfActivityType.INDOOR_RUNNING.name().toLowerCase(Locale.ROOT));
}
if (activityTypes.size() > 36) {
LOG.warn("Truncating activity types list to 36");
activityTypes = activityTypes.subList(0, 36);
}
final ByteBuffer buf = ByteBuffer.allocate(activityTypes.size() + 1);
buf.put((byte) activityTypes.size());
for (final String activityType : activityTypes) {
buf.put(CmfActivityType.valueOf(activityType.toUpperCase(Locale.ROOT)).getCode());
}
mSupport.sendCommand("set activity types", CmfCommand.SPORTS_SET, buf.array());
}
protected boolean onCommand(final CmfCommand cmd, final byte[] payload) {
// TODO handle preference replies from watch
return false;
}
private Context getContext() {
return mSupport.getContext();
}
private GBDevice getDevice() {
return mSupport.getDevice();
}
private static final Map<String, String> LANGUAGES = new HashMap<String, String>() {{
put("ar", "ar_SA");
put("de", "de_DE");
put("en", "en_US");
put("es", "es_ES");
put("fr", "fr_FR");
put("hi", "hi_IN");
put("in", "id_ID");
put("it", "it_IT");
put("ja", "ja_JP");
put("ko", "ko_KO");
put("zh_cn", "zh_CN");
put("zh_hk", "zh_HK");
}};
}