mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
ultrahuman: redesign device support
This commit is contained in:
committed by
José Rebelo
parent
f5e015ff57
commit
42033aad18
+86
-38
@@ -23,9 +23,10 @@ import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -33,12 +34,15 @@ import org.slf4j.LoggerFactory;
|
||||
import java.util.Calendar;
|
||||
import java.util.Locale;
|
||||
|
||||
import lineageos.weather.util.WeatherUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
|
||||
|
||||
// TODO: verify GUI lifecycle
|
||||
// TODO: updates when multiple devices are active
|
||||
// TODO: polish and localize the GUI
|
||||
public class UltrahumanBreathingActivity extends AbstractGBActivity {
|
||||
@@ -47,25 +51,44 @@ public class UltrahumanBreathingActivity extends AbstractGBActivity {
|
||||
private TextView UiStatus;
|
||||
private TextView UiHR;
|
||||
private TextView UiHRV;
|
||||
//private TextView UiMystery;
|
||||
private TextView UiTemp;
|
||||
private TextView UiTime;
|
||||
|
||||
private TextView UiBatGadget;
|
||||
private boolean MetricUnits;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_ultrahuman_breathing);
|
||||
|
||||
findViewById(R.id.ultrahuman_breathing_start).setOnClickListener(new StartListener());
|
||||
findViewById(R.id.ultrahuman_breathing_stop).setOnClickListener(new StopListener());
|
||||
UiStatus = findViewById(R.id.ultrahuman_breathing_status);
|
||||
UiHR = findViewById(R.id.ultrahuman_breathing_HR);
|
||||
UiTemp = findViewById(R.id.ultrahuman_breathing_temp);
|
||||
UiHRV = findViewById(R.id.ultrahuman_breathing_HRV);
|
||||
UiTime = findViewById(R.id.ultrahuman_breathing_time);
|
||||
findViewById(R.id.ultrahuman_exercise_start1).setOnClickListener(new ExerciseClick(UltrahumanExercise.BREATHING_START));
|
||||
findViewById(R.id.ultrahuman_exercise_stop1).setOnClickListener(new ExerciseClick(UltrahumanExercise.BREATHING_STOP));
|
||||
|
||||
UiStatus = findViewById(R.id.ultrahuman_exercise_type);
|
||||
UiTime = findViewById(R.id.ultrahuman_exercise_time);
|
||||
UiHR = findViewById(R.id.ultrahuman_exercise_hr_current);
|
||||
UiTemp = findViewById(R.id.ultrahuman_exercise_temperature_current);
|
||||
UiBatGadget = findViewById(R.id.ultrahuman_exercise_batGadget);
|
||||
|
||||
|
||||
|
||||
UiHRV = findViewById(R.id.ultrahuman_exercise_hrv_current);
|
||||
//UiMystery = findViewById(R.id.ultrahuman_breathing_mystery);
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(DeviceService.ACTION_REALTIME_SAMPLES);
|
||||
filter.addAction(UltrahumanConstants.ACTION_EXERCISE_UPDATE);
|
||||
ContextCompat.registerReceiver(getApplicationContext(), UpdateReceiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED);
|
||||
LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(UpdateReceiver, filter);
|
||||
|
||||
final GBPrefs prefs = GBApplication.getPrefs();
|
||||
final String metric = getString(R.string.p_unit_metric);
|
||||
final String unitSystem = prefs.getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, metric);
|
||||
MetricUnits = unitSystem.equals(metric);
|
||||
|
||||
Button temperatureUom = findViewById(R.id.ultrahuman_exercise_temperature_uom);
|
||||
temperatureUom.setText(MetricUnits ? R.string.unit_celsius : R.string.unit_fahrenheit);
|
||||
|
||||
changeExercise(UltrahumanExercise.CHECK);
|
||||
}
|
||||
@@ -80,55 +103,80 @@ public class UltrahumanBreathingActivity extends AbstractGBActivity {
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
getApplicationContext().unregisterReceiver(UpdateReceiver);
|
||||
LocalBroadcastManager.getInstance(getApplicationContext()).unregisterReceiver(UpdateReceiver);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
private class StartListener implements View.OnClickListener {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
changeExercise(UltrahumanExercise.BREATHING_START);
|
||||
private class ExerciseClick implements View.OnClickListener {
|
||||
final UltrahumanExercise Exercise;
|
||||
ExerciseClick(UltrahumanExercise exercise){
|
||||
Exercise = exercise;
|
||||
}
|
||||
}
|
||||
|
||||
private class StopListener implements View.OnClickListener {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
changeExercise(UltrahumanExercise.BREATHING_STOP);
|
||||
changeExercise(Exercise);
|
||||
}
|
||||
}
|
||||
|
||||
private class ExerciseUpdateReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
UltrahumanExerciseData data = (UltrahumanExerciseData) intent.getSerializableExtra(UltrahumanConstants.EXTRA_EXERCISE);
|
||||
UltrahumanExerciseData data = (UltrahumanExerciseData) intent.getSerializableExtra(DeviceService.EXTRA_REALTIME_SAMPLE);
|
||||
|
||||
if (data != null) {
|
||||
String update = String.format(Locale.US, "b:%s%% t:%x", data.BatteryLevel, 0xFF & data.Exercise);
|
||||
UiStatus.setText(update);
|
||||
String exercise = String.format(Locale.ROOT, "[%02x]", 0xFF & data.Exercise);
|
||||
UiStatus.setText(exercise);
|
||||
|
||||
|
||||
if(data.BatteryLevel > -1) {
|
||||
String batGadget = getString(R.string.ultrahuman_exercise_charge, data.BatteryLevel);
|
||||
UiBatGadget.setText(batGadget);
|
||||
}
|
||||
|
||||
if (data.Timestamp > -1) {
|
||||
// todo - display local time
|
||||
final Calendar calendar = DateTimeUtils.getCalendarUTC();
|
||||
final Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTimeInMillis(data.Timestamp * 1000L);
|
||||
int hour = calendar.get(Calendar.HOUR_OF_DAY);
|
||||
int minute = calendar.get(Calendar.MINUTE);
|
||||
int second = calendar.get(Calendar.SECOND);
|
||||
String time = String.format(Locale.US, "%02d:%02d:%02d", hour, minute, second);
|
||||
|
||||
final String time = getString(R.string.ultrahuman_exercise_time_format, hour, minute, second);
|
||||
UiTime.setText(time);
|
||||
|
||||
if (data.HR > -1) {
|
||||
String hr = getString(R.string.ultrahuman_exercise_hr_format, data.HR);
|
||||
UiHR.setText(hr);
|
||||
} else {
|
||||
UiHR.setText("");
|
||||
}
|
||||
|
||||
if (data.Temperature > -1) {
|
||||
double degree;
|
||||
if (MetricUnits) {
|
||||
degree = data.Temperature;
|
||||
} else {
|
||||
degree = WeatherUtils.celsiusToFahrenheit(data.Temperature);
|
||||
}
|
||||
|
||||
String temp = getString(R.string.ultrahuman_exercise_temperature_format, degree);
|
||||
UiTemp.setText(temp);
|
||||
} else {
|
||||
UiTemp.setText("");
|
||||
}
|
||||
|
||||
if (data.HRV > 0) {
|
||||
String hrv = getString(R.string.ultrahuman_exercise_hrv_format, data.HRV);
|
||||
UiHRV.setText(hrv);
|
||||
} else if (data.MeasurementType != 0x72) {
|
||||
UiHRV.setText("");
|
||||
}
|
||||
}
|
||||
|
||||
if (data.HR > -1) {
|
||||
String hr = String.format(Locale.US, "%s bpm", data.HR);
|
||||
UiHR.setText(hr);
|
||||
}
|
||||
if (data.HRV > 0) {
|
||||
String hrv = String.format(Locale.US, "%s ms", data.HRV);
|
||||
UiHRV.setText(hrv);
|
||||
}
|
||||
if (data.Temperature > -1) {
|
||||
String temp = String.format(Locale.US, "%.3f °C", data.Temperature);
|
||||
UiTemp.setText(temp);
|
||||
}
|
||||
/*if (data.Mystery != null) {
|
||||
UiMystery.setText(data.Mystery + " ?");
|
||||
} else {
|
||||
UiMystery.setText("");
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-7
@@ -33,8 +33,10 @@ public final class UltrahumanConstants {
|
||||
public static final byte OPERATION_ACTIVATE_AIRPLANE_MODE = 0x70;
|
||||
public static final byte OPERATION_BREATHING_START = 0x72;
|
||||
public static final byte OPERATION_BREATHING_STOP = 0x74;
|
||||
public static final byte OPERATION_DISABLE_POWERSAVE = (byte) 0xD1;
|
||||
public static final byte OPERATION_ENABLE_POWERSAVE = (byte) 0xD2;
|
||||
public static final byte OPERATION_ENABLE_SPO2 = (byte) 0xD1;
|
||||
public static final byte OPERATION_DISABLE_SPO2 = (byte) 0xD2;
|
||||
public static final byte OPERATION_EXERCISE_START = 0x62;
|
||||
public static final byte OPERATION_EXERCISE_STOP = 0x64;
|
||||
public static final byte OPERATION_GET_FIRST_RECORDING_NR = 0x07;
|
||||
public static final byte OPERATION_GET_LAST_RECORDING_NR = 0x08;
|
||||
public static final byte OPERATION_GET_RECORDINGS = 0x04;
|
||||
@@ -43,15 +45,15 @@ public final class UltrahumanConstants {
|
||||
public static final byte OPERATION_SETTIME = 0x02;
|
||||
|
||||
public static final UUID UUID_SERVICE_DATA = UUID.fromString("86f66000-f706-58a0-95b2-1fb9261e4dc7");
|
||||
public static final UUID UUID_CHARACTERISTIC_DATA = UUID.fromString("86f66001-f706-58a0-95b2-1fb9261e4dc7");
|
||||
public static final UUID UUID_DATA = UUID.fromString("86f66001-f706-58a0-95b2-1fb9261e4dc7");
|
||||
|
||||
public static final UUID UUID_SERVICE_REQUEST = UUID.fromString("86f65000-f706-58a0-95b2-1fb9261e4dc7");
|
||||
public static final UUID UUID_CHARACTERISTIC_COMMAND = UUID.fromString("86f65001-f706-58a0-95b2-1fb9261e4dc7");
|
||||
public static final UUID UUID_CHARACTERISTIC_RESPONSE = UUID.fromString("86f65002-f706-58a0-95b2-1fb9261e4dc7");
|
||||
public static final UUID UUID_COMMAND = UUID.fromString("86f65001-f706-58a0-95b2-1fb9261e4dc7");
|
||||
public static final UUID UUID_RESPONSE = UUID.fromString("86f65002-f706-58a0-95b2-1fb9261e4dc7");
|
||||
|
||||
public static final UUID UUID_SERVICE_STATE = UUID.fromString("86f61000-f706-58a0-95b2-1fb9261e4dc7");
|
||||
public static final UUID UUID_CHARACTERISTIC_STATE = UUID.fromString("86f61001-f706-58a0-95b2-1fb9261e4dc7");
|
||||
public static final UUID UUID_STATE = UUID.fromString("86f61001-f706-58a0-95b2-1fb9261e4dc7");
|
||||
|
||||
public static final UUID UUID_SERVICE_TODO = UUID.fromString("86f63000-f706-58a0-95b2-1fb9261e4dc7");
|
||||
public static final UUID UUID_CHARACTERISTIC_TODO = UUID.fromString("86f63001-f706-58a0-95b2-1fb9261e4dc7");
|
||||
public static final UUID UUID_TODO = UUID.fromString("86f63001-f706-58a0-95b2-1fb9261e4dc7");
|
||||
}
|
||||
|
||||
+9
-5
@@ -20,6 +20,8 @@ package nodomain.freeyourgadget.gadgetbridge.devices.ultrahuman;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
|
||||
@@ -33,10 +35,12 @@ public class UltrahumanDeviceCardAction implements DeviceCardAction {
|
||||
private final int Description;
|
||||
private final int Question;
|
||||
|
||||
@Nullable
|
||||
private final String IntentAction;
|
||||
@Nullable
|
||||
private final Class<?> IntentClass;
|
||||
|
||||
public UltrahumanDeviceCardAction(int icon, int description, int question, String action) {
|
||||
UltrahumanDeviceCardAction(int icon, int description, int question, String action) {
|
||||
Icon = icon;
|
||||
Description = description;
|
||||
Question = question;
|
||||
@@ -44,7 +48,7 @@ public class UltrahumanDeviceCardAction implements DeviceCardAction {
|
||||
IntentClass = null;
|
||||
}
|
||||
|
||||
public UltrahumanDeviceCardAction(int icon, int description, Class<?> cls) {
|
||||
UltrahumanDeviceCardAction(int icon, int description, Class<?> cls) {
|
||||
Icon = icon;
|
||||
Description = description;
|
||||
Question = 0;
|
||||
@@ -64,7 +68,9 @@ public class UltrahumanDeviceCardAction implements DeviceCardAction {
|
||||
|
||||
@Override
|
||||
public void onClick(GBDevice device, Context context) {
|
||||
if (Question != 0) {
|
||||
if (Question == 0) {
|
||||
sendIntent(device, context);
|
||||
} else {
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(Description)
|
||||
.setMessage(Question)
|
||||
@@ -73,8 +79,6 @@ public class UltrahumanDeviceCardAction implements DeviceCardAction {
|
||||
-> sendIntent(device, context))
|
||||
.setNegativeButton(android.R.string.no, null)
|
||||
.show();
|
||||
} else {
|
||||
sendIntent(device, context);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+21
-13
@@ -34,6 +34,7 @@ import de.greenrobot.dao.Property;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCardAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.GenericHeartRateSampleProvider;
|
||||
@@ -80,7 +81,7 @@ public class UltrahumanDeviceCoordinator extends AbstractBLEDeviceCoordinator {
|
||||
editor.putBoolean(GBPrefs.DEVICE_CONNECT_BACK, true);
|
||||
editor.putBoolean(GBPrefs.DEVICE_AUTO_RECONNECT, true);
|
||||
|
||||
// when the battery is low the gadget loses it's clock
|
||||
// the gadget loses it's clock when the battery is low
|
||||
editor.putBoolean(DeviceSettingsPreferenceConst.PREF_TIME_SYNC, true);
|
||||
|
||||
// O2 measurement with smart rings is still work in progress
|
||||
@@ -132,6 +133,13 @@ public class UltrahumanDeviceCoordinator extends AbstractBLEDeviceCoordinator {
|
||||
return UltrahumanDeviceSupport.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceSpecificSettings getDeviceSpecificSettings(GBDevice device) {
|
||||
final DeviceSpecificSettings settings = new DeviceSpecificSettings();
|
||||
settings.addRootScreen(R.xml.devicesettings_ultrahuman_air);
|
||||
return settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturer() {
|
||||
return "Ultrahuman Healthcare Pvt Ltd";
|
||||
@@ -167,11 +175,6 @@ public class UltrahumanDeviceCoordinator extends AbstractBLEDeviceCoordinator {
|
||||
return Pattern.compile("^UH_[0-9A-F]{12}$");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||
return new int[]{R.xml.devicesettings_ultrahuman_air};
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimeSampleProvider<? extends TemperatureSample> getTemperatureSampleProvider(GBDevice device, DaoSession session) {
|
||||
return new GenericTemperatureSampleProvider(device, session);
|
||||
@@ -188,7 +191,7 @@ public class UltrahumanDeviceCoordinator extends AbstractBLEDeviceCoordinator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityDataFetching(final GBDevice device) {
|
||||
public boolean supportsActivityDataFetching(@NonNull final GBDevice device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -198,32 +201,37 @@ public class UltrahumanDeviceCoordinator extends AbstractBLEDeviceCoordinator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsContinuousTemperature(final GBDevice device) {
|
||||
public boolean supportsContinuousTemperature(@NonNull final GBDevice device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||
public boolean supportsHeartRateMeasurement(@NonNull GBDevice device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsHrvMeasurement(final GBDevice device) {
|
||||
public boolean supportsHrvMeasurement(@NonNull final GBDevice device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsManualHeartRateMeasurement(final GBDevice device) {
|
||||
public boolean supportsManualHeartRateMeasurement(@NonNull final GBDevice device) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRealtimeData(@NonNull GBDevice device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsSleepMeasurement(@NonNull GBDevice device) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsSpo2(GBDevice device) {
|
||||
public boolean supportsSpo2(@NonNull GBDevice device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -233,7 +241,7 @@ public class UltrahumanDeviceCoordinator extends AbstractBLEDeviceCoordinator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsTemperatureMeasurement(final GBDevice device) {
|
||||
public boolean supportsTemperatureMeasurement(@NonNull final GBDevice device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -18,9 +18,11 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.ultrahuman;
|
||||
|
||||
public enum UltrahumanExercise {
|
||||
CHECK((byte) 0),
|
||||
CHECK(UltrahumanConstants.OPERATION_CHECK_DATA),
|
||||
BREATHING_START(UltrahumanConstants.OPERATION_BREATHING_START),
|
||||
BREATHING_STOP(UltrahumanConstants.OPERATION_BREATHING_STOP);
|
||||
BREATHING_STOP(UltrahumanConstants.OPERATION_BREATHING_STOP),
|
||||
EXERCISE_START(UltrahumanConstants.OPERATION_EXERCISE_START),
|
||||
EXERCISE_STOP(UltrahumanConstants.OPERATION_EXERCISE_STOP);
|
||||
|
||||
public final byte Code;
|
||||
|
||||
|
||||
+74
-3
@@ -19,20 +19,31 @@ package nodomain.freeyourgadget.gadgetbridge.devices.ultrahuman;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class UltrahumanExerciseData implements Serializable {
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
|
||||
/**
|
||||
* Helper for DeviceService#ACTION_REALTIME_SAMPLES and UltrahumanConstants.ACTION_EXERCISE_UPDATE intents.
|
||||
*/
|
||||
public class UltrahumanExerciseData implements ActivitySample, Serializable{
|
||||
public int BatteryLevel;
|
||||
public int Exercise;
|
||||
|
||||
public int Timestamp;
|
||||
public int MeasurementType;
|
||||
public int HR;
|
||||
public int HRV;
|
||||
public float Temperature;
|
||||
public int Timestamp;
|
||||
|
||||
public String Mystery;
|
||||
|
||||
public UltrahumanExerciseData() {
|
||||
BatteryLevel = -1;
|
||||
Exercise = -1;
|
||||
HR = -1;
|
||||
HRV = -1;
|
||||
Temperature = -1;
|
||||
Temperature = -1.0f;
|
||||
Timestamp = -1;
|
||||
}
|
||||
|
||||
@@ -41,4 +52,64 @@ public class UltrahumanExerciseData implements Serializable {
|
||||
BatteryLevel = batteryLevel;
|
||||
Exercise = exercise;
|
||||
}
|
||||
|
||||
public UltrahumanExerciseData(int batteryLevel, int exercise, byte type) {
|
||||
this(batteryLevel,exercise);
|
||||
MeasurementType = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SampleProvider<?> getProvider() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRawKind() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActivityKind getKind() {
|
||||
return ActivityKind.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRawIntensity() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getIntensity() {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSteps() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDistanceCm() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getActiveCalories() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeartRate() {
|
||||
return HR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeartRate(int value) {
|
||||
HR = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTimestamp() {
|
||||
return Timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
+13
-21
@@ -35,18 +35,15 @@ public class UltrahumanActivitySampleProvider extends AbstractSampleProvider<Ult
|
||||
}
|
||||
|
||||
public static ActivityKind normalizeTypeStatic(int rawType) {
|
||||
switch (rawType) {
|
||||
case 5:
|
||||
return ActivityKind.BREATHWORK;
|
||||
case 6:
|
||||
return ActivityKind.EXERCISE;
|
||||
default:
|
||||
return (rawType >= 100) ? ActivityKind.NOT_WORN : ActivityKind.UNKNOWN;
|
||||
}
|
||||
return switch (rawType) {
|
||||
case 5 -> ActivityKind.BREATHWORK;
|
||||
case 6 -> ActivityKind.EXERCISE;
|
||||
default -> (rawType >= 100) ? ActivityKind.NOT_WORN : ActivityKind.UNKNOWN;
|
||||
};
|
||||
}
|
||||
|
||||
public static float normalizeIntensityStatic(int rawIntensity) {
|
||||
return rawIntensity / 150f;
|
||||
return rawIntensity / 150.0f;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -80,18 +77,13 @@ public class UltrahumanActivitySampleProvider extends AbstractSampleProvider<Ult
|
||||
|
||||
@Override
|
||||
public int toRawActivityKind(ActivityKind activityKind) {
|
||||
switch (activityKind) {
|
||||
case UNKNOWN:
|
||||
return 1;
|
||||
case BREATHWORK:
|
||||
return 5;
|
||||
case EXERCISE:
|
||||
return 6;
|
||||
case NOT_WORN:
|
||||
return 100;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
return switch (activityKind) {
|
||||
case UNKNOWN -> 1;
|
||||
case BREATHWORK -> 5;
|
||||
case EXERCISE -> 6;
|
||||
case NOT_WORN -> 100;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+5
-5
@@ -22,11 +22,11 @@ import java.util.UUID;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.ultrahuman.UltrahumanConstants;
|
||||
|
||||
public enum UltrahumanCharacteristic {
|
||||
COMMAND(UltrahumanConstants.UUID_CHARACTERISTIC_COMMAND),
|
||||
RESPONSE(UltrahumanConstants.UUID_CHARACTERISTIC_RESPONSE),
|
||||
STATE(UltrahumanConstants.UUID_CHARACTERISTIC_STATE),
|
||||
DATA(UltrahumanConstants.UUID_CHARACTERISTIC_DATA),
|
||||
TODO(UltrahumanConstants.UUID_CHARACTERISTIC_TODO);
|
||||
COMMAND(UltrahumanConstants.UUID_COMMAND),
|
||||
RESPONSE(UltrahumanConstants.UUID_RESPONSE),
|
||||
STATE(UltrahumanConstants.UUID_STATE),
|
||||
DATA(UltrahumanConstants.UUID_DATA),
|
||||
TODO(UltrahumanConstants.UUID_TODO);
|
||||
|
||||
public final UUID uuid;
|
||||
|
||||
|
||||
+531
-423
File diff suppressed because it is too large
Load Diff
+39
@@ -0,0 +1,39 @@
|
||||
/* Copyright (C) 2025 Thomas Kuehne
|
||||
|
||||
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.ultrahuman
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.ultrahuman.UltrahumanConstants
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.ConditionalWriteAction
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils
|
||||
|
||||
/** calculate date/time on the fly to avoid setting an outdated value */
|
||||
class UltrahumanWriteTime(characteristic: BluetoothGattCharacteristic) :
|
||||
ConditionalWriteAction(characteristic) {
|
||||
override fun checkCondition(): ByteArray {
|
||||
val epoc: Long = DateTimeUtils.getEpochSeconds()
|
||||
|
||||
val value = byteArrayOf(
|
||||
UltrahumanConstants.OPERATION_SETTIME,
|
||||
(epoc and 0xff).toByte(),
|
||||
((epoc shr 8) and 0xff).toByte(),
|
||||
((epoc shr 16) and 0xff).toByte(),
|
||||
((epoc shr 24) and 0xff).toByte()
|
||||
)
|
||||
return value
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import java.text.FieldPosition;
|
||||
import java.text.ParseException;
|
||||
import java.text.ParsePosition;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
@@ -334,4 +335,17 @@ public class DateTimeUtils {
|
||||
SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss", Locale.ROOT);
|
||||
return format.format(date);
|
||||
}
|
||||
|
||||
/// number of seconds since UTC epoch of 1970-01-01T00:00:00Z
|
||||
public static long getEpochSeconds() {
|
||||
final long epoc;
|
||||
if (GBApplication.isRunningOreoOrLater()) {
|
||||
epoc = Instant.now().getEpochSecond();
|
||||
} else {
|
||||
Calendar calendar = getCalendarUTC();
|
||||
long millis = calendar.getTimeInMillis();
|
||||
epoc = Math.round(millis / 1000.0d);
|
||||
}
|
||||
return epoc;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M190,840Q144,840 112,808Q80,776 80,730L80,556Q80,551 81,545.5Q82,540 84,535L188,266Q200,235 228,217.5Q256,200 289,200Q331,200 360.5,229.5Q390,259 390,301L390,362L312,440Q303,449 303.5,461Q304,473 313,482Q322,491 334.5,491Q347,491 356,482L450,388L450,110Q450,97 458.5,88.5Q467,80 480,80Q493,80 501.5,88.5Q510,97 510,110L510,388L605,483Q614,492 626.5,491.5Q639,491 648,482Q657,473 657,460.5Q657,448 648,439L570,362L570,301Q570,259 599.5,229.5Q629,200 671,200Q704,200 732,217.5Q760,235 772,266L876,535Q878,540 879,545.5Q880,551 880,556L880,730Q880,776 848,808Q816,840 770,840L630,840Q584,840 552,808Q520,776 520,730L520,664Q520,662 520.5,660Q521,658 521,656L541,504L480,442L418,504L439,656Q439,658 439.5,660Q440,662 440,664L440,730Q440,776 408,808Q376,840 330,840L190,840Z"/>
|
||||
android:pathData="M200,840Q149,840 114.5,805.5Q80,771 80,720L80,553L185,272Q197,239 227,219.5Q257,200 292,200Q337,200 368.5,232.5Q400,265 400,311L400,360L320,360L320,311Q320,298 311,289Q302,280 290,280Q280,280 271.5,285.5Q263,291 260,300L160,568L160,720Q160,737 171.5,748.5Q183,760 200,760L320,760Q337,760 348.5,748.5Q360,737 360,720L360,640L440,640L440,720Q440,771 405,805.5Q370,840 320,840L200,840ZM759,840L639,840Q589,840 554,805.5Q519,771 519,720L519,640L599,640L599,720Q599,737 610.5,748.5Q622,760 639,760L759,760Q776,760 787.5,748.5Q799,737 799,720L799,568L699,300Q695,291 687,285.5Q679,280 669,280Q656,280 647.5,289Q639,298 639,311L639,360L559,360L559,311Q559,265 590.5,232.5Q622,200 667,200Q702,200 731.5,219.5Q761,239 774,272L879,553L879,720Q879,771 844,805.5Q809,840 759,840ZM320,504L320,504L320,504L320,504Q320,504 320,504Q320,504 320,504L320,504Q320,504 320,504Q320,504 320,504L320,504L320,504Q320,504 320,504Q320,504 320,504Q320,504 320,504Q320,504 320,504L320,504L320,504L320,504L320,504ZM639,504L639,504L639,504L639,504L639,504L639,504Q639,504 639,504Q639,504 639,504Q639,504 639,504Q639,504 639,504L639,504L639,504Q639,504 639,504Q639,504 639,504L639,504Q639,504 639,504Q639,504 639,504L639,504L639,504ZM480,457L376,560L320,504L440,384L440,80L520,80L520,384L640,504L583,560L480,457Z"/>
|
||||
</vector>
|
||||
|
||||
@@ -1,75 +1,184 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/ultrahuman_breathing_status"
|
||||
<GridLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:text="b:000% t:00"
|
||||
android:textAlignment="center" />
|
||||
android:columnCount="2">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/ultrahuman_breathing_time"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:text="00:00:00"
|
||||
android:textAlignment="center" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/ultrahuman_breathing_HR"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:text="000 bpm"
|
||||
android:textAlignment="center" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/ultrahuman_breathing_temp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:text="00.000 °C"
|
||||
android:textAlignment="center" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/ultrahuman_breathing_HRV"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:text="000 ms"
|
||||
android:textAlignment="center" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="2"
|
||||
android:orientation="horizontal">
|
||||
<TextView
|
||||
android:id="@+id/ultrahuman_exercise_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_rowSpan="3"
|
||||
android:layout_gravity="fill"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:gravity="center"
|
||||
android:text="00:00:00" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/ultrahuman_breathing_start"
|
||||
android:id="@+id/ultrahuman_exercise_batGadget"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:text="@string/ultrahuman_breathing_start" />
|
||||
android:enabled="false"
|
||||
android:text="100 %" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/ultrahuman_breathing_stop"
|
||||
android:id="@+id/ultrahuman_exercise_type"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:text="@string/ultrahuman_breathing_stop" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
android:clickable="false"
|
||||
android:enabled="true"
|
||||
android:text="DT" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/ultrahuman_exercise_batAndroid"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:clickable="false"
|
||||
android:enabled="false"
|
||||
android:text="" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/ultrahuman_exercise_hr_current"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_rowSpan="3"
|
||||
android:layout_gravity="fill"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:gravity="center"
|
||||
android:text="80" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/ultrahuman_exercise_hr_max"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:enabled="false"
|
||||
android:text="" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/ultrahuman_exercise_hr_uom"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:clickable="false"
|
||||
android:text="BPM" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/ultrahuman_exercise_hr_min"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:enabled="false"
|
||||
android:text="" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/ultrahuman_exercise_temperature_current"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_rowSpan="3"
|
||||
android:layout_gravity="fill"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:clickable="false"
|
||||
android:gravity="center"
|
||||
android:text="36.000" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/ultrahuman_exercise_temperature_max"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:enabled="false"
|
||||
android:text="" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/ultrahuman_exercise_temperature_uom"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:clickable="false"
|
||||
android:text="@string/unit_celsius" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/ultrahuman_exercise_temperature_min"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:enabled="false"
|
||||
android:text="" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/ultrahuman_exercise_hrv_current"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_rowSpan="3"
|
||||
android:layout_gravity="fill"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:clickable="false"
|
||||
android:gravity="center"
|
||||
android:text="120" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/ultrahuman_exercise_hrv_max"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:enabled="false"
|
||||
android:text="" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/ultrahuman_exercise_hrv_uom"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:clickable="false"
|
||||
android:text="ms" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/ultrahuman_exercise_hrv_min"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:enabled="false"
|
||||
android:text="" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/ultrahuman_exercise_start1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_rowSpan="3"
|
||||
android:layout_gravity="fill"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:text="@string/start"
|
||||
app:icon="@drawable/ic_play" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/ultrahuman_exercise_connection"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:enabled="false"
|
||||
android:text="" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/ultrahuman_exercise_stop1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:text="@string/stop" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/ultrahuman_exercise_stop2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:enabled="false" />
|
||||
</GridLayout>
|
||||
</ScrollView>
|
||||
@@ -4112,6 +4112,13 @@
|
||||
<string name="ultrahuman_breathing_stop">Stop</string>
|
||||
<string name="ultrahuman_unhandled_error_response">unhandled response %2$#02x %3$#02x for operation %1$#02x</string>
|
||||
<string name="ultrahuman_unhandled_operation_response">unhandled operation response %s</string>
|
||||
<string name="ultrahuman_exercise_time_format" translatable="false">%1$02d:%2$02d:%3$02d</string>
|
||||
<string name="ultrahuman_exercise_hr_format" translatable="false">%d</string>
|
||||
<string name="ultrahuman_exercise_temperature_format" translatable="false">%.3f</string>
|
||||
<string name="ultrahuman_exercise_hrv_format" translatable="false">%d</string>
|
||||
<string name="ultrahuman_exercise_charge" translatable="false">%d%%</string>
|
||||
<string name="unit_celsius">℃</string>
|
||||
<string name="unit_fahrenheit">℉</string>
|
||||
<string name="weight_scale_jin">jin - 市斤</string>
|
||||
<string name="weight_scale_jin_format">%1$.2f 市斤</string>
|
||||
<string name="weight_scale_kilogram">kilogram - kg</string>
|
||||
|
||||
Reference in New Issue
Block a user