mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-28 04:46:51 +01:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
2335a7a337
10
.github/ISSUE_TEMPLATE.md
vendored
10
.github/ISSUE_TEMPLATE.md
vendored
@ -17,6 +17,16 @@ If you got it from Google Play, please note [that version](https://github.com/Ta
|
||||
|
||||
#### Your issue is:
|
||||
*If possible, please attach [logs](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Log-Files)! that might help identifying the problem.*
|
||||
*Long logs can be also included but make sure to tuck them into the `details` tag:*
|
||||
|
||||
<details>
|
||||
<summary>Click to see my log, which is a bit longer</summary>
|
||||
|
||||
```
|
||||
Here go lines of that log.
|
||||
```
|
||||
</details>
|
||||
|
||||
|
||||
#### Your wearable device is:
|
||||
|
||||
|
@ -1,5 +1,13 @@
|
||||
### Changelog
|
||||
|
||||
#### 0.48.0 (WIP)
|
||||
* Initial support for Sony SWR12
|
||||
* Initial support for Lefun Smart Bands
|
||||
* InfiniTime: Improved music support for latest firmware
|
||||
* Add sport activity list tab in charts
|
||||
* Weather: Fix wind speed and direction not being passed properly
|
||||
* Fix find your phone feature on Android 10 (need companion device pairing)
|
||||
|
||||
#### 0.47.2
|
||||
* Amazfit Bip S: Send sunrise and sunset on latest firmware if enabled
|
||||
* Huami: Support new firmware update protocol (fixes firmware flashing with firmware 2.1.1.50/4.1.5.55 on Amazfit Bip S)
|
||||
|
@ -43,7 +43,7 @@ public class GBDaoGenerator {
|
||||
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Schema schema = new Schema(30, MAIN_PACKAGE + ".entities");
|
||||
Schema schema = new Schema(31, MAIN_PACKAGE + ".entities");
|
||||
|
||||
Entity userAttributes = addUserAttributes(schema);
|
||||
Entity user = addUserInfo(schema, userAttributes);
|
||||
@ -74,6 +74,10 @@ public class GBDaoGenerator {
|
||||
addWatchXPlusHealthActivitySample(schema, user, device);
|
||||
addWatchXPlusHealthActivityKindOverlay(schema, user, device);
|
||||
addTLW64ActivitySample(schema, user, device);
|
||||
addLefunActivitySample(schema, user, device);
|
||||
addLefunBiometricSample(schema,user,device);
|
||||
addLefunSleepSample(schema, user, device);
|
||||
addSonySWR12Sample(schema, user, device);
|
||||
|
||||
addHybridHRActivitySample(schema, user, device);
|
||||
addCalendarSyncState(schema, device);
|
||||
@ -404,6 +408,59 @@ public class GBDaoGenerator {
|
||||
return activitySample;
|
||||
}
|
||||
|
||||
private static Entity addSonySWR12Sample(Schema schema, Entity user, Entity device) {
|
||||
Entity activitySample = addEntity(schema, "SonySWR12Sample");
|
||||
activitySample.implementsSerializable();
|
||||
addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
|
||||
addHeartRateProperties(activitySample);
|
||||
activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
||||
activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
||||
activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
||||
return activitySample;
|
||||
}
|
||||
|
||||
private static Entity addLefunActivitySample(Schema schema, Entity user, Entity device) {
|
||||
Entity activitySample = addEntity(schema, "LefunActivitySample");
|
||||
activitySample.implementsSerializable();
|
||||
addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
|
||||
activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
||||
activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
||||
activitySample.addIntProperty("distance").notNull();
|
||||
activitySample.addIntProperty("calories").notNull();
|
||||
addHeartRateProperties(activitySample);
|
||||
return activitySample;
|
||||
}
|
||||
|
||||
private static Entity addLefunBiometricSample(Schema schema, Entity user, Entity device) {
|
||||
Entity biometricSample = addEntity(schema, "LefunBiometricSample");
|
||||
biometricSample.implementsSerializable();
|
||||
|
||||
biometricSample.addIntProperty("timestamp").notNull().primaryKey();
|
||||
Property deviceId = biometricSample.addLongProperty("deviceId").primaryKey().notNull().getProperty();
|
||||
biometricSample.addToOne(device, deviceId);
|
||||
Property userId = biometricSample.addLongProperty("userId").notNull().getProperty();
|
||||
biometricSample.addToOne(user, userId);
|
||||
|
||||
biometricSample.addIntProperty("type").notNull();
|
||||
biometricSample.addIntProperty("value1").notNull();
|
||||
biometricSample.addIntProperty("value2");
|
||||
return biometricSample;
|
||||
}
|
||||
|
||||
private static Entity addLefunSleepSample(Schema schema, Entity user, Entity device) {
|
||||
Entity sleepSample = addEntity(schema, "LefunSleepSample");
|
||||
sleepSample.implementsSerializable();
|
||||
|
||||
sleepSample.addIntProperty("timestamp").notNull().primaryKey();
|
||||
Property deviceId = sleepSample.addLongProperty("deviceId").primaryKey().notNull().getProperty();
|
||||
sleepSample.addToOne(device, deviceId);
|
||||
Property userId = sleepSample.addLongProperty("userId").notNull().getProperty();
|
||||
sleepSample.addToOne(user, userId);
|
||||
|
||||
sleepSample.addIntProperty("type").notNull();
|
||||
return sleepSample;
|
||||
}
|
||||
|
||||
private static void addCommonActivitySampleProperties(String superClass, Entity activitySample, Entity user, Entity device) {
|
||||
activitySample.setSuperclass(superClass);
|
||||
activitySample.addImport(MAIN_PACKAGE + ".devices.SampleProvider");
|
||||
|
@ -41,9 +41,11 @@ vendor's servers.
|
||||
* Fossil Hybrid HR (WARNING: NEEDS FOSSIL APP WITH ACCOUNT ONCE AND COMPLICATED PROCEDURE) [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Fossil-Hybrid-HR)
|
||||
* Fossil Q Hybrid
|
||||
* HPlus Devices (e.g. ZeBand) [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/HPlus)
|
||||
* iTag
|
||||
* iTag [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/iTag)
|
||||
* ID115
|
||||
* Nut Mini 3, Nut 2 and possibly others [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Nut)
|
||||
* JYou Y5
|
||||
* Lefun
|
||||
* Lenovo Watch 9
|
||||
* Lenovo Watch X (Plus) [Wiki](https://codeberg.org/mamutcho/Gadgetbridge/wiki)
|
||||
* Liveview
|
||||
@ -94,6 +96,7 @@ Please see [FEATURES.md](https://codeberg.org/Freeyourgadget/Gadgetbridge/src/ma
|
||||
* Pavel Elagin (JYou Y5)
|
||||
* Taavi Eomäe (iTag)
|
||||
* Erik Bloß (TLW64)
|
||||
* Yukai Li (Lefun)
|
||||
|
||||
## Contribute
|
||||
|
||||
|
@ -25,13 +25,13 @@ android {
|
||||
targetSdkVersion 29
|
||||
|
||||
// Note: always bump BOTH versionCode and versionName!
|
||||
versionName "0.47.2"
|
||||
versionCode 181
|
||||
versionName "0.48.0"
|
||||
versionCode 182
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
||||
}
|
||||
}
|
||||
@ -60,21 +60,22 @@ pmd {
|
||||
dependencies {
|
||||
// testImplementation "ch.qos.logback:logback-classic:1.1.3"
|
||||
// testImplementation "ch.qos.logback:logback-core:1.1.3"
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
||||
implementation 'com.android.support.constraint:constraint-layout:2.0.2'
|
||||
testImplementation "junit:junit:4.12"
|
||||
testImplementation "org.mockito:mockito-core:1.10.19"
|
||||
testImplementation "org.robolectric:robolectric:4.2.1"
|
||||
testImplementation "com.google.code.gson:gson:2.8.5"
|
||||
testImplementation "com.google.code.gson:gson:2.8.6"
|
||||
|
||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
implementation "androidx.appcompat:appcompat:1.1.0"
|
||||
implementation "androidx.appcompat:appcompat:1.2.0"
|
||||
implementation "androidx.preference:preference:1.1.1"
|
||||
implementation "androidx.cardview:cardview:1.0.0"
|
||||
implementation "androidx.recyclerview:recyclerview:1.1.0"
|
||||
implementation "androidx.legacy:legacy-support-v4:1.0.0"
|
||||
implementation "androidx.gridlayout:gridlayout:1.0.0"
|
||||
implementation "com.google.android.material:material:1.1.0"
|
||||
implementation "com.google.android.material:material:1.2.1"
|
||||
implementation "androidx.palette:palette:1.0.0"
|
||||
implementation "no.nordicsemi.android:dfu:1.11.0"
|
||||
implementation("com.github.tony19:logback-android-classic:1.1.1-6") {
|
||||
exclude group: "com.google.android", module: "android"
|
||||
}
|
||||
|
6
app/proguard-rules.pro
vendored
6
app/proguard-rules.pro
vendored
@ -9,6 +9,8 @@
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
-dontobfuscate
|
||||
|
||||
# Pebble BG-JS
|
||||
-keepclassmembers class * {
|
||||
@android.webkit.JavascriptInterface <methods>;
|
||||
@ -33,8 +35,12 @@
|
||||
|
||||
-keep class **$Properties { *; }
|
||||
|
||||
# Keep database migration classes accessed trough reflection
|
||||
-keep class **.gadgetbridge.database.schema.* { *; }
|
||||
|
||||
# Keep Nordic DFU library
|
||||
-keep class no.nordicsemi.android.dfu.** { *; }
|
||||
|
||||
# Keep dependency android-emojify (io.wax911.emojify) uses
|
||||
-keep class org.hamcrest.** { *; }
|
||||
|
||||
|
@ -346,6 +346,9 @@
|
||||
</service>
|
||||
<service android:name=".service.NotificationCollectorMonitorService" />
|
||||
<service android:name=".service.DeviceCommunicationService" />
|
||||
<service
|
||||
android:name=".devices.pinetime.PineTimeDFUService"
|
||||
android:label="PineTime Nordic DFU service" />
|
||||
|
||||
<receiver
|
||||
android:name=".externalevents.WeatherNotificationReceiver"
|
||||
|
@ -19,21 +19,26 @@ package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
@ -43,12 +48,16 @@ import android.widget.TableRow;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.content.FileProvider;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.Date;
|
||||
@ -65,28 +74,49 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryItems;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryJsonSummary;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.SwipeEvents;
|
||||
//import nodomain.freeyourgadget.gadgetbridge.util.OnSwipeTouchListener;
|
||||
|
||||
public class ActivitySummaryDetail extends AbstractGBActivity {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ActivitySummaryDetail.class);
|
||||
private GBDevice gbDevice;
|
||||
|
||||
private boolean show_raw_data = false;
|
||||
BaseActivitySummary currentItem = null;
|
||||
private GBDevice gbDevice;
|
||||
private boolean show_raw_data = false;
|
||||
private int alternateColor;
|
||||
//private Object BottomSheetBehavior;
|
||||
private Menu mOptionsMenu;
|
||||
|
||||
public static int getAlternateColor(Context context) {
|
||||
TypedValue typedValue = new TypedValue();
|
||||
Resources.Theme theme = context.getTheme();
|
||||
theme.resolveAttribute(R.attr.alternate_row_background, typedValue, true);
|
||||
return typedValue.data;
|
||||
}
|
||||
|
||||
public static Bitmap getScreenShot(View view, int height, int width, Context context) {
|
||||
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
if (GBApplication.isDarkThemeEnabled()) {
|
||||
canvas.drawColor(GBApplication.getBackgroundColor(context));
|
||||
} else {
|
||||
canvas.drawColor(Color.WHITE);
|
||||
}
|
||||
view.draw(canvas);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
final Context appContext = this.getApplicationContext();
|
||||
if (appContext instanceof GBApplication) {
|
||||
setContentView(R.layout.activity_summary_details);
|
||||
}
|
||||
|
||||
|
||||
Intent intent = getIntent();
|
||||
|
||||
Bundle bundle = intent.getExtras();
|
||||
@ -137,9 +167,9 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
|
||||
currentItem = newItem;
|
||||
makeSummaryHeader(newItem);
|
||||
makeSummaryContent(newItem);
|
||||
activitySummariesChartFragment.setDateAndGetData(gbDevice, currentItem.getStartTime().getTime()/1000, currentItem.getEndTime().getTime()/1000);
|
||||
activitySummariesChartFragment.setDateAndGetData(gbDevice, currentItem.getStartTime().getTime() / 1000, currentItem.getEndTime().getTime() / 1000);
|
||||
layout.startAnimation(animFadeRight);
|
||||
|
||||
show_hide_gpx_menu();
|
||||
} else {
|
||||
layout.startAnimation(animBounceRight);
|
||||
}
|
||||
@ -152,8 +182,9 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
|
||||
currentItem = newItem;
|
||||
makeSummaryHeader(newItem);
|
||||
makeSummaryContent(newItem);
|
||||
activitySummariesChartFragment.setDateAndGetData(gbDevice, currentItem.getStartTime().getTime()/1000, currentItem.getEndTime().getTime()/1000);
|
||||
activitySummariesChartFragment.setDateAndGetData(gbDevice, currentItem.getStartTime().getTime() / 1000, currentItem.getEndTime().getTime() / 1000);
|
||||
layout.startAnimation(animFadeLeft);
|
||||
show_hide_gpx_menu();
|
||||
} else {
|
||||
layout.startAnimation(animBounceLeft);
|
||||
}
|
||||
@ -164,7 +195,7 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
|
||||
if (currentItem != null) {
|
||||
makeSummaryHeader(currentItem);
|
||||
makeSummaryContent(currentItem);
|
||||
activitySummariesChartFragment.setDateAndGetData(gbDevice, currentItem.getStartTime().getTime()/1000, currentItem.getEndTime().getTime()/1000);
|
||||
activitySummariesChartFragment.setDateAndGetData(gbDevice, currentItem.getStartTime().getTime() / 1000, currentItem.getEndTime().getTime() / 1000);
|
||||
}
|
||||
|
||||
|
||||
@ -190,7 +221,7 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
|
||||
String name = currentItem.getName();
|
||||
input.setText((name != null) ? name : "");
|
||||
FrameLayout container = new FrameLayout(ActivitySummaryDetail.this);
|
||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
params.leftMargin = getResources().getDimensionPixelSize(R.dimen.dialog_margin);
|
||||
params.rightMargin = getResources().getDimensionPixelSize(R.dimen.dialog_margin);
|
||||
input.setLayoutParams(params);
|
||||
@ -224,22 +255,6 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
|
||||
|
||||
private void makeSummaryHeader(BaseActivitySummary item) {
|
||||
//make view of data from main part of item
|
||||
final String gpxTrack = item.getGpxTrack();
|
||||
Button show_track_btn = findViewById(R.id.showTrack);
|
||||
show_track_btn.setVisibility(View.GONE);
|
||||
|
||||
if (gpxTrack != null) {
|
||||
show_track_btn.setVisibility(View.VISIBLE);
|
||||
show_track_btn.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
try {
|
||||
AndroidUtils.viewFile(gpxTrack, Intent.ACTION_VIEW, ActivitySummaryDetail.this);
|
||||
} catch (IOException e) {
|
||||
GB.toast(getApplicationContext(), "Unable to display GPX track: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
String activitykindname = ActivityKind.asString(item.getActivityKind(), getApplicationContext());
|
||||
String activityname = item.getName();
|
||||
Date starttime = item.getStartTime();
|
||||
@ -292,7 +307,7 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
|
||||
TableRow label_row = new TableRow(ActivitySummaryDetail.this);
|
||||
TextView label_field = new TextView(ActivitySummaryDetail.this);
|
||||
label_field.setTextSize(16);
|
||||
label_field.setPadding(0,10,0,0);
|
||||
label_field.setPadding(0, 10, 0, 0);
|
||||
label_field.setTypeface(null, Typeface.BOLD);
|
||||
label_field.setText(String.format("%s", getStringResourceByName(key)));
|
||||
label_row.addView(label_field);
|
||||
@ -361,14 +376,6 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static int getAlternateColor(Context context) {
|
||||
TypedValue typedValue = new TypedValue();
|
||||
Resources.Theme theme = context.getTheme();
|
||||
theme.resolveAttribute(R.attr.alternate_row_background, typedValue, true);
|
||||
return typedValue.data;
|
||||
}
|
||||
|
||||
private String getStringResourceByName(String aString) {
|
||||
String packageName = getPackageName();
|
||||
int resId = getResources().getIdentifier(aString, "string", packageName);
|
||||
@ -387,9 +394,82 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
|
||||
// back button
|
||||
finish();
|
||||
return true;
|
||||
case R.id.activity_action_take_screenshot:
|
||||
take_share_screenshot(ActivitySummaryDetail.this);
|
||||
return true;
|
||||
case R.id.activity_action_share_gpx:
|
||||
share_gpx_track(ActivitySummaryDetail.this);
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void take_share_screenshot(Context context) {
|
||||
final ScrollView layout = findViewById(R.id.activity_summary_detail_scroll_layout);
|
||||
int width = layout.getChildAt(0).getHeight();
|
||||
int height = layout.getChildAt(0).getWidth();
|
||||
Bitmap screenShot = getScreenShot(layout, width, height, context);
|
||||
|
||||
String fileName = FileUtils.makeValidFileName("Screenshot-" + ActivityKind.asString(currentItem.getActivityKind(), context).toLowerCase() + "-" + DateTimeUtils.formatIso8601(currentItem.getStartTime()) + ".png");
|
||||
try {
|
||||
File targetFile = new File(FileUtils.getExternalFilesDir(), fileName);
|
||||
FileOutputStream fOut = new FileOutputStream(targetFile);
|
||||
screenShot.compress(Bitmap.CompressFormat.PNG, 85, fOut);
|
||||
fOut.flush();
|
||||
fOut.close();
|
||||
shareScreenshot(targetFile, context);
|
||||
GB.toast(getApplicationContext(), "Screenshot saved", Toast.LENGTH_LONG, GB.INFO);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void shareScreenshot(File targetFile, Context context) {
|
||||
Uri contentUri = FileProvider.getUriForFile(context,
|
||||
context.getApplicationContext().getPackageName() + ".screenshot_provider", targetFile);
|
||||
Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
|
||||
sharingIntent.setType("image/*");
|
||||
String shareBody = "Sports Activity";
|
||||
sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "Sports Activity");
|
||||
sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, shareBody);
|
||||
sharingIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
|
||||
|
||||
try {
|
||||
startActivity(Intent.createChooser(sharingIntent, "Share via"));
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(context, R.string.activity_error_no_app_for_png, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void share_gpx_track(Context context) {
|
||||
final String gpxTrack = currentItem.getGpxTrack();
|
||||
|
||||
if (gpxTrack != null) {
|
||||
try {
|
||||
AndroidUtils.viewFile(gpxTrack, Intent.ACTION_VIEW, context);
|
||||
} catch (IOException e) {
|
||||
GB.toast(getApplicationContext(), "Unable to display GPX track: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
}
|
||||
} else {
|
||||
GB.toast(getApplicationContext(), "No GPX track in this activity", Toast.LENGTH_LONG, GB.INFO);
|
||||
}
|
||||
}
|
||||
|
||||
private void show_hide_gpx_menu() {
|
||||
final String gpxTrack = currentItem.getGpxTrack();
|
||||
if (gpxTrack == null) {
|
||||
mOptionsMenu.findItem(R.id.activity_detail_overflowMenu).getSubMenu().findItem(R.id.activity_action_share_gpx).setVisible(false);
|
||||
} else {
|
||||
mOptionsMenu.findItem(R.id.activity_detail_overflowMenu).getSubMenu().findItem(R.id.activity_action_share_gpx).setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
mOptionsMenu = menu;
|
||||
getMenuInflater().inflate(R.menu.activity_take_screenshot_menu, menu);
|
||||
show_hide_gpx_menu();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2015-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, Lem Dulfo
|
||||
Gobbetti, Lem Dulfo, Taavi Eomäe
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -31,14 +31,15 @@ import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.app.NavUtils;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.core.app.NavUtils;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.ItemWithDetailsAdapter;
|
||||
@ -64,16 +65,17 @@ public class FwAppInstallerActivity extends AbstractGBActivity implements Instal
|
||||
private InstallHandler installHandler;
|
||||
private boolean mayConnect;
|
||||
|
||||
private ProgressBar mProgressBar;
|
||||
private ProgressBar progressBar;
|
||||
private TextView progressText;
|
||||
private ListView itemListView;
|
||||
private final List<ItemWithDetails> mItems = new ArrayList<>();
|
||||
private ItemWithDetailsAdapter mItemAdapter;
|
||||
private final List<ItemWithDetails> items = new ArrayList<>();
|
||||
private ItemWithDetailsAdapter itemAdapter;
|
||||
|
||||
private ListView detailsListView;
|
||||
private ItemWithDetailsAdapter mDetailsItemAdapter;
|
||||
private ArrayList<ItemWithDetails> mDetails = new ArrayList<>();
|
||||
private ItemWithDetailsAdapter detailsAdapter;
|
||||
private ArrayList<ItemWithDetails> details = new ArrayList<>();
|
||||
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
private final BroadcastReceiver receiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
@ -93,6 +95,23 @@ public class FwAppInstallerActivity extends AbstractGBActivity implements Instal
|
||||
validateInstallation();
|
||||
}
|
||||
}
|
||||
} else if (GB.ACTION_SET_PROGRESS_BAR.equals(action)) {
|
||||
if (intent.hasExtra(GB.PROGRESS_BAR_INDETERMINATE)) {
|
||||
setProgressIndeterminate(intent.getBooleanExtra(GB.PROGRESS_BAR_INDETERMINATE, false));
|
||||
}
|
||||
|
||||
if (intent.hasExtra(GB.PROGRESS_BAR_PROGRESS)) {
|
||||
setProgressIndeterminate(false);
|
||||
setProgressBar(intent.getIntExtra(GB.PROGRESS_BAR_PROGRESS, 0));
|
||||
}
|
||||
} else if (GB.ACTION_SET_PROGRESS_TEXT.equals(action)) {
|
||||
if (intent.hasExtra(GB.DISPLAY_MESSAGE_MESSAGE)) {
|
||||
setProgressText(intent.getStringExtra(GB.DISPLAY_MESSAGE_MESSAGE));
|
||||
}
|
||||
} else if (GB.ACTION_SET_INFO_TEXT.equals(action)) {
|
||||
if (intent.hasExtra(GB.DISPLAY_MESSAGE_MESSAGE)) {
|
||||
setInfoText(intent.getStringExtra(GB.DISPLAY_MESSAGE_MESSAGE));
|
||||
}
|
||||
} else if (GB.ACTION_DISPLAY_MESSAGE.equals(action)) {
|
||||
String message = intent.getStringExtra(GB.DISPLAY_MESSAGE_MESSAGE);
|
||||
int severity = intent.getIntExtra(GB.DISPLAY_MESSAGE_SEVERITY, GB.INFO);
|
||||
@ -103,16 +122,30 @@ public class FwAppInstallerActivity extends AbstractGBActivity implements Instal
|
||||
|
||||
private void refreshBusyState(GBDevice dev) {
|
||||
if (dev.isConnecting() || dev.isBusy()) {
|
||||
mProgressBar.setVisibility(View.VISIBLE);
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
boolean wasBusy = mProgressBar.getVisibility() != View.GONE;
|
||||
boolean wasBusy = progressBar.getVisibility() != View.GONE;
|
||||
if (wasBusy) {
|
||||
mProgressBar.setVisibility(View.GONE);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
// done!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setProgressIndeterminate(boolean indeterminate) {
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
progressBar.setIndeterminate(indeterminate);
|
||||
}
|
||||
|
||||
public void setProgressBar(int progress) {
|
||||
progressBar.setProgress(progress);
|
||||
}
|
||||
|
||||
public void setProgressText(String text) {
|
||||
progressText.setVisibility(View.VISIBLE);
|
||||
progressText.setText(text);
|
||||
}
|
||||
|
||||
private void connect() {
|
||||
mayConnect = false; // only do that once per #onCreate
|
||||
GBApplication.deviceService().connect(device);
|
||||
@ -134,28 +167,33 @@ public class FwAppInstallerActivity extends AbstractGBActivity implements Instal
|
||||
device = dev;
|
||||
}
|
||||
if (savedInstanceState != null) {
|
||||
mDetails = savedInstanceState.getParcelableArrayList(ITEM_DETAILS);
|
||||
if (mDetails == null) {
|
||||
mDetails = new ArrayList<>();
|
||||
details = savedInstanceState.getParcelableArrayList(ITEM_DETAILS);
|
||||
if (details == null) {
|
||||
details = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
mayConnect = true;
|
||||
itemListView = (ListView) findViewById(R.id.itemListView);
|
||||
mItemAdapter = new ItemWithDetailsAdapter(this, mItems);
|
||||
itemListView.setAdapter(mItemAdapter);
|
||||
fwAppInstallTextView = (TextView) findViewById(R.id.infoTextView);
|
||||
installButton = (Button) findViewById(R.id.installButton);
|
||||
mProgressBar = (ProgressBar) findViewById(R.id.installProgressBar);
|
||||
detailsListView = (ListView) findViewById(R.id.detailsListView);
|
||||
mDetailsItemAdapter = new ItemWithDetailsAdapter(this, mDetails);
|
||||
mDetailsItemAdapter.setSize(ItemWithDetailsAdapter.SIZE_SMALL);
|
||||
detailsListView.setAdapter(mDetailsItemAdapter);
|
||||
itemListView = findViewById(R.id.itemListView);
|
||||
itemAdapter = new ItemWithDetailsAdapter(this, items);
|
||||
itemListView.setAdapter(itemAdapter);
|
||||
fwAppInstallTextView = findViewById(R.id.infoTextView);
|
||||
installButton = findViewById(R.id.installButton);
|
||||
progressBar = findViewById(R.id.installProgressBar);
|
||||
progressText = findViewById(R.id.installProgressText);
|
||||
detailsListView = findViewById(R.id.detailsListView);
|
||||
detailsAdapter = new ItemWithDetailsAdapter(this, details);
|
||||
detailsAdapter.setSize(ItemWithDetailsAdapter.SIZE_SMALL);
|
||||
detailsListView.setAdapter(detailsAdapter);
|
||||
|
||||
setInstallEnabled(false);
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(GBDevice.ACTION_DEVICE_CHANGED);
|
||||
filter.addAction(GB.ACTION_DISPLAY_MESSAGE);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
|
||||
filter.addAction(GB.ACTION_SET_PROGRESS_BAR);
|
||||
filter.addAction(GB.ACTION_SET_PROGRESS_TEXT);
|
||||
filter.addAction(GB.ACTION_SET_INFO_TEXT);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(receiver, filter);
|
||||
|
||||
installButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
@ -167,7 +205,7 @@ public class FwAppInstallerActivity extends AbstractGBActivity implements Instal
|
||||
});
|
||||
|
||||
uri = getIntent().getData();
|
||||
if (uri == null) { //for "share" intent
|
||||
if (uri == null) { // For "share" intent
|
||||
uri = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
|
||||
}
|
||||
installHandler = findInstallHandlerFor(uri);
|
||||
@ -188,7 +226,7 @@ public class FwAppInstallerActivity extends AbstractGBActivity implements Instal
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putParcelableArrayList(ITEM_DETAILS, mDetails);
|
||||
outState.putParcelableArrayList(ITEM_DETAILS, details);
|
||||
}
|
||||
|
||||
private InstallHandler findInstallHandlerFor(Uri uri) {
|
||||
@ -236,7 +274,7 @@ public class FwAppInstallerActivity extends AbstractGBActivity implements Instal
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@ -259,19 +297,19 @@ public class FwAppInstallerActivity extends AbstractGBActivity implements Instal
|
||||
|
||||
@Override
|
||||
public void clearInstallItems() {
|
||||
mItems.clear();
|
||||
mItemAdapter.notifyDataSetChanged();
|
||||
items.clear();
|
||||
itemAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInstallItem(ItemWithDetails item) {
|
||||
mItems.clear();
|
||||
mItems.add(item);
|
||||
mItemAdapter.notifyDataSetChanged();
|
||||
items.clear();
|
||||
items.add(item);
|
||||
itemAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void addMessage(String message, int severity) {
|
||||
mDetails.add(new GenericItem(message));
|
||||
mDetailsItemAdapter.notifyDataSetChanged();
|
||||
details.add(new GenericItem(message));
|
||||
detailsAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,55 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.AbstractItemAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
|
||||
public class ActivityListingAdapter extends AbstractItemAdapter<StepAnalysis.StepSession> {
|
||||
public ActivityListingAdapter(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getName(StepAnalysis.StepSession item) {
|
||||
int activityKind = item.getActivityKind();
|
||||
String activityKindLabel = ActivityKind.asString(activityKind, getContext());
|
||||
Date startTime = item.getStepStart();
|
||||
Date endTime = item.getStepEnd();
|
||||
|
||||
String fromTime = DateTimeUtils.formatTime(startTime.getHours(), startTime.getMinutes());
|
||||
String toTime = DateTimeUtils.formatTime(endTime.getHours(), endTime.getMinutes());
|
||||
String duration = DateTimeUtils.formatDurationHoursMinutes(endTime.getTime() - startTime.getTime(), TimeUnit.MILLISECONDS);
|
||||
|
||||
if (activityKind == ActivityKind.TYPE_UNKNOWN) {
|
||||
return getContext().getString(R.string.chart_no_active_data);
|
||||
}
|
||||
return activityKindLabel + " " + duration + " (" + fromTime + " - " + toTime + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDetails(StepAnalysis.StepSession item) {
|
||||
String heartRate = "";
|
||||
if (item.getActivityKind() == ActivityKind.TYPE_UNKNOWN) {
|
||||
return getContext().getString(R.string.chart_get_active_and_synchronize);
|
||||
}
|
||||
if (item.getHeartRateAverage() > 50) {
|
||||
heartRate = " ❤️ " + item.getHeartRateAverage();
|
||||
}
|
||||
|
||||
return "👣 " + item.getSteps() + heartRate;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getIcon(StepAnalysis.StepSession item) {
|
||||
int activityKind = item.getActivityKind();
|
||||
return ActivityKind.getIconId(activityKind);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,143 @@
|
||||
/* Copyright (C) 2015-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, Dikay900, Pavel Elagin
|
||||
|
||||
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.activities.charts;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.github.mikephil.charting.charts.Chart;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
|
||||
|
||||
public class ActivityListingChartFragment extends AbstractChartFragment {
|
||||
protected static final Logger LOG = LoggerFactory.getLogger(ActivityListingChartFragment.class);
|
||||
int tsDataFrom;
|
||||
private View rootView;
|
||||
private List<? extends ActivitySample> activitySamples;
|
||||
private ActivityListingAdapter stepListAdapter;
|
||||
private TextView stepsDateView;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
rootView = inflater.inflate(R.layout.fragment_steps_list, container, false);
|
||||
|
||||
ListView stepsList = rootView.findViewById(R.id.itemListView);
|
||||
stepListAdapter = new ActivityListingAdapter(getContext());
|
||||
stepsList.setAdapter(stepListAdapter);
|
||||
stepsDateView = rootView.findViewById(R.id.stepsDateView);
|
||||
//refresh();
|
||||
return rootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return "Steps list";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (action.equals(ChartsHost.REFRESH)) {
|
||||
// TODO: use LimitLines to visualize smart alarms?
|
||||
//refresh();
|
||||
} else {
|
||||
super.onReceive(context, intent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
|
||||
//trying to fit found peg into square hole of the Gb Charts fragment system
|
||||
//get the data
|
||||
activitySamples = getSamples(db, device);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateChartsnUIThread(ChartsData chartsData) {
|
||||
//top displays selected date
|
||||
stepsDateView.setText(DateTimeUtils.formatDate(new Date(tsDataFrom * 1000L)));
|
||||
//calculate active sessions
|
||||
StepAnalysis stepAnalysis = new StepAnalysis();
|
||||
if (activitySamples != null) {
|
||||
List<StepAnalysis.StepSession> stepSessions = stepAnalysis.calculateStepSessions(activitySamples);
|
||||
if (stepSessions.toArray().length == 0) {
|
||||
stepSessions = create_empty_record();
|
||||
}
|
||||
//push to the adapter
|
||||
stepListAdapter.setItems(stepSessions, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderCharts() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupLegend(Chart chart) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
|
||||
Calendar day = Calendar.getInstance();
|
||||
day.setTimeInMillis(tsTo * 1000L); //we need today initially, which is the end of the time range
|
||||
day.set(Calendar.HOUR_OF_DAY, 0); //and we set time for the start and end of the same day
|
||||
day.set(Calendar.MINUTE, 0);
|
||||
day.set(Calendar.SECOND, 0);
|
||||
|
||||
tsFrom = (int) (day.getTimeInMillis() / 1000);
|
||||
tsTo = tsFrom + 24 * 60 * 60 - 1;
|
||||
tsDataFrom = tsFrom;
|
||||
return getAllSamples(db, device, tsFrom, tsTo);
|
||||
}
|
||||
|
||||
private List<StepAnalysis.StepSession> create_empty_record() {
|
||||
//have an "Unknown Activity" in the list in case there are no active sessions
|
||||
List<StepAnalysis.StepSession> result = new ArrayList<>();
|
||||
int tsTo = tsDataFrom + 24 * 60 * 60 - 1;
|
||||
result.add(new StepAnalysis.StepSession(new Date(tsDataFrom * 1000L), new Date(tsTo * 1000L), 0, 0, ActivityKind.TYPE_UNKNOWN));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -355,14 +355,16 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
|
||||
case 0:
|
||||
return new ActivitySleepChartFragment();
|
||||
case 1:
|
||||
return new SleepChartFragment();
|
||||
return new ActivityListingChartFragment();
|
||||
case 2:
|
||||
return new WeekSleepChartFragment();
|
||||
return new SleepChartFragment();
|
||||
case 3:
|
||||
return new WeekStepsChartFragment();
|
||||
return new WeekSleepChartFragment();
|
||||
case 4:
|
||||
return new SpeedZonesFragment();
|
||||
return new WeekStepsChartFragment();
|
||||
case 5:
|
||||
return new SpeedZonesFragment();
|
||||
case 6:
|
||||
return new LiveActivityFragment();
|
||||
}
|
||||
return null;
|
||||
@ -373,9 +375,9 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
|
||||
// Show 5 or 6 total pages.
|
||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(mGBDevice);
|
||||
if (coordinator.supportsRealtimeData()) {
|
||||
return 6;
|
||||
return 7;
|
||||
}
|
||||
return 5;
|
||||
return 6;
|
||||
}
|
||||
|
||||
private String getSleepTitle() {
|
||||
@ -402,14 +404,16 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
|
||||
case 0:
|
||||
return getString(R.string.activity_sleepchart_activity_and_sleep);
|
||||
case 1:
|
||||
return getString(R.string.sleepchart_your_sleep);
|
||||
return getString(R.string.charts_activity_list);
|
||||
case 2:
|
||||
return getSleepTitle();
|
||||
return getString(R.string.sleepchart_your_sleep);
|
||||
case 3:
|
||||
return getStepsTitle();
|
||||
return getSleepTitle();
|
||||
case 4:
|
||||
return getString(R.string.stats_title);
|
||||
return getStepsTitle();
|
||||
case 5:
|
||||
return getString(R.string.stats_title);
|
||||
case 6:
|
||||
return getString(R.string.liveactivity_live_activity);
|
||||
}
|
||||
return super.getPageTitle(position);
|
||||
|
@ -0,0 +1,183 @@
|
||||
/* Copyright (C) 2019-2020 Q-er
|
||||
|
||||
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.activities.charts;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
|
||||
public class StepAnalysis {
|
||||
protected static final Logger LOG = LoggerFactory.getLogger(StepAnalysis.class);
|
||||
private final int MIN_SESSION_STEPS = 100;
|
||||
|
||||
public List<StepSession> calculateStepSessions(List<? extends ActivitySample> samples) {
|
||||
List<StepSession> result = new ArrayList<>();
|
||||
final int MIN_SESSION_LENGTH = 60 * GBApplication.getPrefs().getInt("chart_list_min_session_length", 5);
|
||||
final int MAX_IDLE_PHASE_LENGTH = 60 * GBApplication.getPrefs().getInt("chart_list_max_idle_phase_length", 5);
|
||||
final int MIN_STEPS_PER_MINUTE = GBApplication.getPrefs().getInt("chart_list_min_steps_per_minute", 40);
|
||||
|
||||
ActivitySample previousSample = null;
|
||||
Date stepStart = null;
|
||||
Date stepEnd = null;
|
||||
int activeSteps = 0;
|
||||
int heartRateForAverage = 0;
|
||||
int heartRateToAdd = 0;
|
||||
int activeSamplesForAverage = 0;
|
||||
int activeSamplesToAdd = 0;
|
||||
int stepsBetweenActivities = 0;
|
||||
int heartRateBetweenActivities = 0;
|
||||
int durationSinceLastActiveStep = 0;
|
||||
int activityKind;
|
||||
|
||||
for (ActivitySample sample : samples) {
|
||||
if (isStep(sample)) { //TODO we could improve/extend this to other activities as well, if in database
|
||||
|
||||
if (sample.getHeartRate() != 255 && sample.getHeartRate() != -1) {
|
||||
heartRateToAdd = sample.getHeartRate();
|
||||
activeSamplesToAdd = 1;
|
||||
} else {
|
||||
heartRateToAdd = 0;
|
||||
activeSamplesToAdd = 0;
|
||||
}
|
||||
|
||||
if (stepStart == null) {
|
||||
stepStart = getDateFromSample(sample);
|
||||
activeSteps = sample.getSteps();
|
||||
heartRateForAverage = heartRateToAdd;
|
||||
activeSamplesForAverage = activeSamplesToAdd;
|
||||
durationSinceLastActiveStep = 0;
|
||||
stepsBetweenActivities = 0;
|
||||
heartRateBetweenActivities = 0;
|
||||
previousSample = null;
|
||||
}
|
||||
if (previousSample != null) {
|
||||
int durationSinceLastSample = sample.getTimestamp() - previousSample.getTimestamp();
|
||||
activeSamplesForAverage += activeSamplesToAdd;
|
||||
if (sample.getSteps() > MIN_STEPS_PER_MINUTE) {
|
||||
activeSteps += sample.getSteps() + stepsBetweenActivities;
|
||||
heartRateForAverage += heartRateToAdd + heartRateBetweenActivities;
|
||||
stepsBetweenActivities = 0;
|
||||
heartRateBetweenActivities = 0;
|
||||
durationSinceLastActiveStep = 0;
|
||||
} else {
|
||||
stepsBetweenActivities += sample.getSteps();
|
||||
heartRateBetweenActivities += heartRateToAdd;
|
||||
durationSinceLastActiveStep += durationSinceLastSample;
|
||||
}
|
||||
if (durationSinceLastActiveStep >= MAX_IDLE_PHASE_LENGTH) {
|
||||
|
||||
int current = sample.getTimestamp();
|
||||
int starting = (int) (stepStart.getTime() / 1000);
|
||||
int session_length = current - starting - durationSinceLastActiveStep;
|
||||
int heartRateAverage = activeSamplesForAverage > 0 ? heartRateForAverage / activeSamplesForAverage : 0;
|
||||
|
||||
if (session_length >= MIN_SESSION_LENGTH) {
|
||||
stepEnd = new Date((sample.getTimestamp() - durationSinceLastActiveStep) * 1000L);
|
||||
activityKind = detect_activity(session_length, activeSteps, heartRateAverage);
|
||||
result.add(new StepSession(stepStart, stepEnd, activeSteps, heartRateAverage, activityKind));
|
||||
}
|
||||
stepStart = null;
|
||||
}
|
||||
}
|
||||
previousSample = sample;
|
||||
}
|
||||
}
|
||||
//make sure we show the last portion of the data as well in case no further activity is recorded yet
|
||||
if (stepStart != null && previousSample != null) {
|
||||
int current = previousSample.getTimestamp();
|
||||
int starting = (int) (stepStart.getTime() / 1000);
|
||||
int session_length = current - starting - durationSinceLastActiveStep;
|
||||
int heartRateAverage = activeSamplesForAverage > 0 ? heartRateForAverage / activeSamplesForAverage : 0;
|
||||
|
||||
if (session_length > MIN_SESSION_LENGTH && activeSteps > MIN_SESSION_STEPS) {
|
||||
stepEnd = getDateFromSample(previousSample);
|
||||
activityKind = detect_activity(session_length, activeSteps, heartRateAverage);
|
||||
result.add(new StepSession(stepStart, stepEnd, activeSteps, heartRateAverage, activityKind));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private int detect_activity(int session_length, int activeSteps, int heartRateAverage) {
|
||||
final int MIN_STEPS_PER_MINUTE_FOR_RUN = GBApplication.getPrefs().getInt("chart_list_min_steps_per_minute_for_run", 120);
|
||||
int spm = (int) (activeSteps / (session_length / 60));
|
||||
if (spm > MIN_STEPS_PER_MINUTE_FOR_RUN) {
|
||||
return ActivityKind.TYPE_RUNNING;
|
||||
}
|
||||
if (activeSteps > 200) {
|
||||
return ActivityKind.TYPE_WALKING;
|
||||
}
|
||||
if (heartRateAverage > 90) {
|
||||
return ActivityKind.TYPE_EXERCISE;
|
||||
}
|
||||
return ActivityKind.TYPE_ACTIVITY;
|
||||
}
|
||||
|
||||
private boolean isStep(ActivitySample sample) {
|
||||
return sample.getKind() == ActivityKind.TYPE_WALKING || sample.getKind() == ActivityKind.TYPE_RUNNING || sample.getKind() == ActivityKind.TYPE_ACTIVITY;
|
||||
}
|
||||
|
||||
private Date getDateFromSample(ActivitySample sample) {
|
||||
return new Date(sample.getTimestamp() * 1000L);
|
||||
}
|
||||
|
||||
public static class StepSession {
|
||||
private final Date stepStart;
|
||||
private final Date stepEnd;
|
||||
private final int steps;
|
||||
private final int heartRateAverage;
|
||||
private final int activityKind;
|
||||
|
||||
StepSession(Date stepStart,
|
||||
Date stepEnd,
|
||||
int steps, int heartRateAverage, int activityKind) {
|
||||
this.stepStart = stepStart;
|
||||
this.stepEnd = stepEnd;
|
||||
this.steps = steps;
|
||||
this.heartRateAverage = heartRateAverage;
|
||||
this.activityKind = activityKind;
|
||||
}
|
||||
|
||||
public Date getStepStart() {
|
||||
return stepStart;
|
||||
}
|
||||
|
||||
public Date getStepEnd() {
|
||||
return stepEnd;
|
||||
}
|
||||
|
||||
public int getSteps() {
|
||||
return steps;
|
||||
}
|
||||
|
||||
public int getHeartRateAverage() {
|
||||
return heartRateAverage;
|
||||
}
|
||||
|
||||
public int getActivityKind() {
|
||||
return activityKind;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -45,4 +45,15 @@ public class DeviceSettingsPreferenceConst {
|
||||
public static final String PREF_LONGSIT_SWITCH = "pref_longsit_switch";
|
||||
public static final String PREF_LONGSIT_SWITCH_NOSHED = "screen_longsit_noshed";
|
||||
public static final String PREF_DO_NOT_DISTURB_NOAUTO = "do_not_disturb_no_auto";
|
||||
public static final String PREF_FIND_PHONE_ENABLED = "prefs_find_phone";
|
||||
|
||||
public static final String PREF_ANTILOST_ENABLED = "pref_antilost_enabled";
|
||||
public static final String PREF_HYDRATION_SWITCH = "pref_hydration_switch";
|
||||
public static final String PREF_HYDRATION_PERIOD = "pref_hydration_period";
|
||||
public static final String PREF_AMPM_ENABLED = "pref_ampm_enabled";
|
||||
public static final String PREF_LEFUN_INTERFACE_LANGUAGE = "pref_lefun_interface_language";
|
||||
|
||||
public static final String PREF_SONYSWR12_LOW_VIBRATION = "vibration_preference";
|
||||
public static final String PREF_SONYSWR12_STAMINA = "stamina_preference";
|
||||
public static final String PREF_SONYSWR12_SMART_INTERVAL = "smart_alarm_interval_preference";
|
||||
}
|
@ -41,6 +41,8 @@ import nodomain.freeyourgadget.gadgetbridge.util.XTimePreference;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.XTimePreferenceFragment;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_ALTITUDE_CALIBRATE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_AMPM_ENABLED;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_ANTILOST_ENABLED;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BUTTON_1_FUNCTION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BUTTON_2_FUNCTION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BUTTON_3_FUNCTION;
|
||||
@ -48,15 +50,22 @@ import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.Dev
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DATEFORMAT;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DISCONNECTNOTIF_NOSHED;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_FIND_PHONE_ENABLED;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HYBRID_HR_DRAW_WIDGET_CIRCLES;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HYBRID_HR_FORCE_WHITE_COLOR;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HYBRID_HR_SAVE_RAW_ACTIVITY_FILES;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HYDRATION_PERIOD;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HYDRATION_SWITCH;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LANGUAGE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LEFUN_INTERFACE_LANGUAGE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LIFTWRIST_NOSHED;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LONGSIT_PERIOD;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LONGSIT_SWITCH;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_POWER_MODE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SCREEN_ORIENTATION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONYSWR12_LOW_VIBRATION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONYSWR12_SMART_INTERVAL;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONYSWR12_STAMINA;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TIMEFORMAT;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_VIBRATION_STRENGH_PERCENTAGE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_WEARLOCATION;
|
||||
@ -351,11 +360,20 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat {
|
||||
addPreferenceHandlerFor(PREF_LONGSIT_PERIOD);
|
||||
addPreferenceHandlerFor(PREF_LONGSIT_SWITCH);
|
||||
addPreferenceHandlerFor(PREF_DO_NOT_DISTURB_NOAUTO);
|
||||
addPreferenceHandlerFor(PREF_FIND_PHONE_ENABLED);
|
||||
addPreferenceHandlerFor(PREF_ANTILOST_ENABLED);
|
||||
addPreferenceHandlerFor(PREF_HYDRATION_SWITCH);
|
||||
addPreferenceHandlerFor(PREF_HYDRATION_PERIOD);
|
||||
addPreferenceHandlerFor(PREF_AMPM_ENABLED);
|
||||
addPreferenceHandlerFor(PREF_LEFUN_INTERFACE_LANGUAGE);
|
||||
|
||||
addPreferenceHandlerFor(PREF_HYBRID_HR_DRAW_WIDGET_CIRCLES);
|
||||
addPreferenceHandlerFor(PREF_HYBRID_HR_FORCE_WHITE_COLOR);
|
||||
addPreferenceHandlerFor(PREF_HYBRID_HR_SAVE_RAW_ACTIVITY_FILES);
|
||||
|
||||
addPreferenceHandlerFor(PREF_SONYSWR12_STAMINA);
|
||||
addPreferenceHandlerFor(PREF_SONYSWR12_LOW_VIBRATION);
|
||||
addPreferenceHandlerFor(PREF_SONYSWR12_SMART_INTERVAL);
|
||||
|
||||
String displayOnLiftState = prefs.getString(PREF_ACTIVATE_DISPLAY_ON_LIFT, PREF_DO_NOT_DISTURB_OFF);
|
||||
boolean displayOnLiftScheduled = displayOnLiftState.equals(PREF_DO_NOT_DISTURB_SCHEDULED);
|
||||
|
@ -19,6 +19,8 @@ package nodomain.freeyourgadget.gadgetbridge.devices.itag;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class ITagConstants {
|
||||
public static final UUID UUID_SERVICE_BUTTON = UUID.fromString("0000ffe1-0000-1000-8000-00805f9b34fb"); // Contains information about the button state
|
||||
public static final UUID UUID_LINK_LOSS_ALERT_LEVEL = UUID.fromString("00002a06-0000-1000-8000-00805f9b34fb"); // Contains information about the button state
|
||||
/** Contains information about the button state */
|
||||
public static final UUID UUID_SERVICE_BUTTON = UUID.fromString("0000ffe1-0000-1000-8000-00805f9b34fb");
|
||||
/** Contains information about the button state */
|
||||
public static final UUID UUID_LINK_LOSS_ALERT_LEVEL = UUID.fromString("00002a06-0000-1000-8000-00805f9b34fb");
|
||||
}
|
||||
|
@ -0,0 +1,88 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport.BASE_UUID;
|
||||
|
||||
/**
|
||||
* Constants used with Lefun device support
|
||||
*/
|
||||
public class LefunConstants {
|
||||
// BLE UUIDs
|
||||
public static final UUID UUID_SERVICE_LEFUN = UUID.fromString(String.format(BASE_UUID, "18D0"));
|
||||
public static final UUID UUID_CHARACTERISTIC_LEFUN_WRITE = UUID.fromString(String.format(BASE_UUID, "2D01"));
|
||||
public static final UUID UUID_CHARACTERISTIC_LEFUN_NOTIFY = UUID.fromString(String.format(BASE_UUID, "2D00"));
|
||||
|
||||
// Coordinator constants
|
||||
public static final String ADVERTISEMENT_NAME = "Lefun";
|
||||
public static final String MANUFACTURER_NAME = "Teng Jin Da";
|
||||
// Commands
|
||||
public static final byte CMD_REQUEST_ID = (byte) 0xab;
|
||||
public static final byte CMD_RESPONSE_ID = 0x5a;
|
||||
public static final int CMD_MAX_LENGTH = 20;
|
||||
// 3 header bytes plus checksum
|
||||
public static final int CMD_HEADER_LENGTH = 4;
|
||||
public static final byte CMD_FIRMWARE_INFO = 0x00;
|
||||
public static final byte CMD_BONDING_REQUEST = 0x01;
|
||||
public static final byte CMD_SETTINGS = 0x02;
|
||||
public static final byte CMD_BATTERY_LEVEL = 0x03;
|
||||
public static final byte CMD_TIME = 0x04;
|
||||
public static final byte CMD_ALARM = 0x05;
|
||||
public static final byte CMD_PROFILE = 0x06;
|
||||
public static final byte CMD_UI_PAGES = 0x07;
|
||||
public static final byte CMD_FEATURES = 0x08;
|
||||
public static final byte CMD_FIND_DEVICE = 0x09;
|
||||
public static final byte CMD_FIND_PHONE = 0x0a;
|
||||
public static final byte CMD_SEDENTARY_REMINDER_INTERVAL = 0x0b;
|
||||
public static final byte CMD_HYDRATION_REMINDER_INTERVAL = 0x0c;
|
||||
public static final byte CMD_REMOTE_CAMERA = 0x0d;
|
||||
public static final byte CMD_REMOTE_CAMERA_TRIGGERED = 0x0e;
|
||||
public static final byte CMD_PPG_START = 0x0f;
|
||||
public static final byte CMD_PPG_RESULT = 0x10;
|
||||
public static final byte CMD_PPG_DATA = 0x11;
|
||||
public static final byte CMD_STEPS_DATA = 0x12;
|
||||
public static final byte CMD_ACTIVITY_DATA = 0x13;
|
||||
public static final byte CMD_SLEEP_TIME_DATA = 0x14;
|
||||
public static final byte CMD_SLEEP_DATA = 0x15;
|
||||
public static final byte CMD_NOTIFICATION = 0x17;
|
||||
public static final byte CMD_LANGUAGE = 0x21;
|
||||
public static final byte CMD_UNKNOWN_22 = 0x22;
|
||||
public static final byte CMD_UNKNOWN_25 = 0x25;
|
||||
public static final byte CMD_UNKNOWN_80 = (byte) 0x80;
|
||||
public static final int PPG_TYPE_INVALID = -1;
|
||||
public static final int PPG_TYPE_HEART_RATE = 0;
|
||||
public static final int PPG_TYPE_BLOOD_PRESSURE = 1;
|
||||
public static final int PPG_TYPE_BLOOD_OXYGEN = 2;
|
||||
public static final int PPG_TYPE_COUNT = 3;
|
||||
// DB activity kinds
|
||||
public static final int DB_ACTIVITY_KIND_UNKNOWN = 0;
|
||||
public static final int DB_ACTIVITY_KIND_ACTIVITY = 1;
|
||||
public static final int DB_ACTIVITY_KIND_HEART_RATE = 2;
|
||||
public static final int DB_ACTIVITY_KIND_LIGHT_SLEEP = 3;
|
||||
public static final int DB_ACTIVITY_KIND_DEEP_SLEEP = 4;
|
||||
// Pseudo-intensity
|
||||
public static final int INTENSITY_MIN = 0;
|
||||
public static final int INTENSITY_DEEP_SLEEP = 1;
|
||||
public static final int INTENSITY_LIGHT_SLEEP = 2;
|
||||
public static final int INTENSITY_AWAKE = 3;
|
||||
public static final int INTENSITY_MAX = 4;
|
||||
public static int NUM_ALARM_SLOTS = 5;
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
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.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants.ADVERTISEMENT_NAME;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants.MANUFACTURER_NAME;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants.NUM_ALARM_SLOTS;
|
||||
|
||||
/**
|
||||
* Device coordinator for Lefun band
|
||||
*/
|
||||
public class LefunDeviceCoordinator extends AbstractDeviceCoordinator {
|
||||
@Override
|
||||
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBondingStyle() {
|
||||
return BONDING_STYLE_NONE;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
|
||||
// There's a bunch of other names other than "Lefun", but let's just focus on one for now.
|
||||
if (ADVERTISEMENT_NAME.equals(candidate.getName())) {
|
||||
// The device does not advertise service UUIDs, so can't check whether it supports
|
||||
// the proper service. We can check that it doesn't advertise any services, though.
|
||||
// We're actually supposed to check for presence of the string "TJDR" within the
|
||||
// manufacturer specific data, which consists of the device's MAC address and said
|
||||
// string. But we're not being given it, so *shrug*.
|
||||
if (candidate.getServiceUuids().length == 0) {
|
||||
return DeviceType.LEFUN;
|
||||
}
|
||||
}
|
||||
|
||||
return DeviceType.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceType getDeviceType() {
|
||||
return DeviceType.LEFUN;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Class<? extends Activity> getPairingActivity() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityDataFetching() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityTracking() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
|
||||
return new LefunSampleProvider(device, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstallHandler findInstallHandler(Uri uri, Context context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsScreenshots() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAlarmSlotCount() {
|
||||
return NUM_ALARM_SLOTS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsSmartWakeup(GBDevice device) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturer() {
|
||||
return MANUFACTURER_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAppsManagement() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Activity> getAppsManagementActivity() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCalendarEvents() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRealtimeData() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsWeather() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsFindDevice() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||
return new int[]{
|
||||
R.xml.devicesettings_liftwrist_display_noshed,
|
||||
R.xml.devicesettings_timeformat,
|
||||
R.xml.devicesettings_antilost,
|
||||
R.xml.devicesettings_longsit,
|
||||
R.xml.devicesettings_hydration_reminder,
|
||||
R.xml.devicesettings_lefun_interface_language,
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun;
|
||||
|
||||
/**
|
||||
* Feature support utilities for Lefun devices
|
||||
*/
|
||||
public class LefunFeatureSupport {
|
||||
public static final int SUPPORT_HEART_RATE = 1 << 2;
|
||||
public static final int SUPPORT_BLOOD_PRESSURE = 1 << 3;
|
||||
public static final int SUPPORT_FAKE_ECG = 1 << 10;
|
||||
public static final int SUPPORT_ECG = 1 << 11;
|
||||
public static final int SUPPORT_WALLPAPER_UPLOAD = 1 << 12;
|
||||
|
||||
public static final int RESERVE_BLOOD_OXYGEN = 1 << 0;
|
||||
public static final int RESERVE_CLOCK_FACE_UPLOAD = 1 << 3;
|
||||
public static final int RESERVE_CONTACTS = 1 << 5;
|
||||
public static final int RESERVE_WALLPAPER = 1 << 6;
|
||||
public static final int RESERVE_REMOTE_CAMERA = 1 << 7;
|
||||
|
||||
/**
|
||||
* Checks whether a feature is supported
|
||||
*
|
||||
* @param deviceSupport the feature flags from the device
|
||||
* @param featureSupport the feature you want to check
|
||||
* @return whether feature is supported
|
||||
*/
|
||||
public static boolean checkSupported(short deviceSupport, int featureSupport) {
|
||||
return (deviceSupport & featureSupport) == featureSupport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a feature is not reserved
|
||||
* <p>
|
||||
* Reserve flags indicate a feature is not available if set. This function takes care of the
|
||||
* inverting for you, so if you get true, the feature is available.
|
||||
*
|
||||
* @param deviceReserve the reserve flags from the device
|
||||
* @param featureReserve the reserve flag you want to check
|
||||
* @return whether feature is supported
|
||||
*/
|
||||
public static boolean checkNotReserved(short deviceReserve, int featureReserve) {
|
||||
return !((deviceReserve & featureReserve) == featureReserve);
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import de.greenrobot.dao.AbstractDao;
|
||||
import de.greenrobot.dao.Property;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.LefunActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.LefunActivitySampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
|
||||
/**
|
||||
* Sample provider for Lefun devices
|
||||
*/
|
||||
public class LefunSampleProvider extends AbstractSampleProvider<LefunActivitySample> {
|
||||
public LefunSampleProvider(GBDevice device, DaoSession session) {
|
||||
super(device, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractDao<LefunActivitySample, ?> getSampleDao() {
|
||||
return getSession().getLefunActivitySampleDao();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Property getRawKindSampleProperty() {
|
||||
return LefunActivitySampleDao.Properties.RawKind;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Property getTimestampSampleProperty() {
|
||||
return LefunActivitySampleDao.Properties.Timestamp;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Property getDeviceIdentifierSampleProperty() {
|
||||
return LefunActivitySampleDao.Properties.DeviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int normalizeType(int rawType) {
|
||||
switch (rawType) {
|
||||
case LefunConstants.DB_ACTIVITY_KIND_ACTIVITY:
|
||||
case LefunConstants.DB_ACTIVITY_KIND_HEART_RATE:
|
||||
return ActivityKind.TYPE_ACTIVITY;
|
||||
case LefunConstants.DB_ACTIVITY_KIND_LIGHT_SLEEP:
|
||||
return ActivityKind.TYPE_LIGHT_SLEEP;
|
||||
case LefunConstants.DB_ACTIVITY_KIND_DEEP_SLEEP:
|
||||
return ActivityKind.TYPE_DEEP_SLEEP;
|
||||
default:
|
||||
return ActivityKind.TYPE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int toRawActivityKind(int activityKind) {
|
||||
switch (activityKind) {
|
||||
case ActivityKind.TYPE_ACTIVITY:
|
||||
return LefunConstants.DB_ACTIVITY_KIND_ACTIVITY;
|
||||
case ActivityKind.TYPE_LIGHT_SLEEP:
|
||||
return LefunConstants.DB_ACTIVITY_KIND_LIGHT_SLEEP;
|
||||
case ActivityKind.TYPE_DEEP_SLEEP:
|
||||
return LefunConstants.DB_ACTIVITY_KIND_DEEP_SLEEP;
|
||||
default:
|
||||
return LefunConstants.DB_ACTIVITY_KIND_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float normalizeIntensity(int rawIntensity) {
|
||||
return rawIntensity / (float) LefunConstants.INTENSITY_MAX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LefunActivitySample createActivitySample() {
|
||||
return new LefunActivitySample();
|
||||
}
|
||||
}
|
@ -0,0 +1,175 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.commands;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
|
||||
public class AlarmCommand extends BaseCommand {
|
||||
public static final int DOW_SUNDAY = 0;
|
||||
public static final int DOW_MONDAY = 1;
|
||||
public static final int DOW_TUESDAY = 2;
|
||||
public static final int DOW_WEDNESDAY = 3;
|
||||
public static final int DOW_THURSDAY = 4;
|
||||
public static final int DOW_FRIDAY = 5;
|
||||
public static final int DOW_SATURDAY = 6;
|
||||
|
||||
private byte op;
|
||||
private byte index;
|
||||
private boolean enabled;
|
||||
// Snooze is not implemented how you think it would be
|
||||
// Number of snoozes is decremented every time the alarm triggers, and the alarm time
|
||||
// is moved forward by number of minutes in snooze time. It never gets reset to the
|
||||
// original time.
|
||||
private byte numOfSnoozes;
|
||||
private byte snoozeTime;
|
||||
private byte dayOfWeek;
|
||||
private byte hour;
|
||||
private byte minute;
|
||||
|
||||
private boolean setSuccess;
|
||||
|
||||
public byte getOp() {
|
||||
return op;
|
||||
}
|
||||
|
||||
public void setOp(byte op) {
|
||||
if (op != OP_GET && op != OP_SET)
|
||||
throw new IllegalArgumentException("Operation must be get or set");
|
||||
this.op = op;
|
||||
}
|
||||
|
||||
public byte getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public void setIndex(byte index) {
|
||||
if (index < 0 || index >= LefunConstants.NUM_ALARM_SLOTS)
|
||||
throw new IllegalArgumentException("Index must be between 0 and "
|
||||
+ (LefunConstants.NUM_ALARM_SLOTS - 1) + " inclusive");
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public byte getNumOfSnoozes() {
|
||||
return numOfSnoozes;
|
||||
}
|
||||
|
||||
public void setNumOfSnoozes(byte numOfSnoozes) {
|
||||
this.numOfSnoozes = numOfSnoozes;
|
||||
}
|
||||
|
||||
public byte getSnoozeTime() {
|
||||
return snoozeTime;
|
||||
}
|
||||
|
||||
public void setSnoozeTime(byte snoozeTime) {
|
||||
this.snoozeTime = snoozeTime;
|
||||
}
|
||||
|
||||
public boolean getDayOfWeek(int day) {
|
||||
if (day < 0 || day > 6)
|
||||
throw new IllegalArgumentException("Invalid day of week");
|
||||
return getBit(dayOfWeek, 1 << day);
|
||||
}
|
||||
|
||||
public void setDayOfWeek(int day, boolean enabled) {
|
||||
if (day < 0 || day > 6)
|
||||
throw new IllegalArgumentException("Invalid day of week");
|
||||
dayOfWeek = setBit(dayOfWeek, 1 << day, enabled);
|
||||
}
|
||||
|
||||
public byte getHour() {
|
||||
return hour;
|
||||
}
|
||||
|
||||
public void setHour(byte hour) {
|
||||
if (hour < 0 || hour > 23)
|
||||
throw new IllegalArgumentException("Hour must be between 0 and 23 inclusive");
|
||||
this.hour = hour;
|
||||
}
|
||||
|
||||
public byte getMinute() {
|
||||
return minute;
|
||||
}
|
||||
|
||||
public void setMinute(byte minute) {
|
||||
if (minute < 0 || minute > 59)
|
||||
throw new IllegalArgumentException("Minute must be between 0 and 59 inclusive");
|
||||
this.minute = minute;
|
||||
}
|
||||
|
||||
public boolean isSetSuccess() {
|
||||
return setSuccess;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeParams(byte id, ByteBuffer params) {
|
||||
validateId(id, LefunConstants.CMD_ALARM);
|
||||
|
||||
int paramsLength = params.limit() - params.position();
|
||||
if (paramsLength < 2)
|
||||
throwUnexpectedLength();
|
||||
|
||||
op = params.get();
|
||||
index = params.get();
|
||||
if (op == OP_GET) {
|
||||
if (paramsLength != 8)
|
||||
throwUnexpectedLength();
|
||||
|
||||
enabled = params.get() == 1;
|
||||
numOfSnoozes = params.get();
|
||||
snoozeTime = params.get();
|
||||
dayOfWeek = params.get();
|
||||
hour = params.get();
|
||||
minute = params.get();
|
||||
} else if (op == OP_SET) {
|
||||
if (paramsLength != 3)
|
||||
throwUnexpectedLength();
|
||||
|
||||
setSuccess = params.get() == 1;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid operation type received");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte serializeParams(ByteBuffer params) {
|
||||
params.put(op);
|
||||
// No alarm ID for get; all of them are returned
|
||||
if (op == OP_SET) {
|
||||
params.put(index);
|
||||
params.put((byte)(enabled ? 1: 0));
|
||||
params.put(numOfSnoozes);
|
||||
params.put(snoozeTime);
|
||||
params.put(dayOfWeek);
|
||||
params.put(hour);
|
||||
params.put(minute);
|
||||
}
|
||||
return LefunConstants.CMD_ALARM;
|
||||
}
|
||||
}
|
@ -0,0 +1,237 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.commands;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
|
||||
/**
|
||||
* Base class for Lefun Bluetooth commands and responses
|
||||
*/
|
||||
public abstract class BaseCommand {
|
||||
// Common constants
|
||||
/**
|
||||
* Common get operation type
|
||||
*/
|
||||
public static final byte OP_GET = 0;
|
||||
/**
|
||||
* Common set operation type
|
||||
*/
|
||||
public static final byte OP_SET = 1;
|
||||
|
||||
/**
|
||||
* Calculates command checksum
|
||||
*
|
||||
* @param data the data to generate checksum from
|
||||
* @param offset the offset in data to start calculating from
|
||||
* @param length the number of bytes to include in calculation
|
||||
* @return the computed checksum
|
||||
*/
|
||||
public static byte calculateChecksum(byte[] data, int offset, int length) {
|
||||
int checksum = 0;
|
||||
for (int i = offset; i < offset + length; ++i) {
|
||||
byte b = data[i];
|
||||
for (int j = 0; j < 8; ++j) {
|
||||
if (((b ^ checksum) & 1) == 0) {
|
||||
checksum >>= 1;
|
||||
} else {
|
||||
checksum = (checksum ^ 0x18) >> 1 | 0x80;
|
||||
}
|
||||
b >>= 1;
|
||||
}
|
||||
}
|
||||
return (byte) checksum;
|
||||
}
|
||||
|
||||
/**
|
||||
* When implemented in a subclass, parses the response from a device
|
||||
*
|
||||
* @param id the command ID
|
||||
* @param params the params buffer
|
||||
*/
|
||||
abstract protected void deserializeParams(byte id, ByteBuffer params);
|
||||
|
||||
/**
|
||||
* When implemented in a subclass, provides the arguments to send in the command
|
||||
*
|
||||
* @param params the params buffer to write to
|
||||
* @return the command ID
|
||||
*/
|
||||
abstract protected byte serializeParams(ByteBuffer params);
|
||||
|
||||
/**
|
||||
* Deserialize a response from the device
|
||||
*
|
||||
* @param response the response data to deserialize
|
||||
*/
|
||||
public void deserialize(byte[] response) {
|
||||
if (response.length < LefunConstants.CMD_HEADER_LENGTH || response.length < response[1])
|
||||
throw new IllegalArgumentException("Response is too short");
|
||||
if (calculateChecksum(response, 0, response[1] - 1) != response[response[1] - 1])
|
||||
throw new IllegalArgumentException("Incorrect message checksum");
|
||||
ByteBuffer buffer = ByteBuffer.wrap(response, LefunConstants.CMD_HEADER_LENGTH - 1,
|
||||
response[1] - LefunConstants.CMD_HEADER_LENGTH);
|
||||
buffer.order(ByteOrder.BIG_ENDIAN);
|
||||
deserializeParams(response[2], buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a command to send to the device
|
||||
*
|
||||
* @return the data to send to the device
|
||||
*/
|
||||
public byte[] serialize() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(LefunConstants.CMD_MAX_LENGTH - LefunConstants.CMD_HEADER_LENGTH);
|
||||
buffer.order(ByteOrder.BIG_ENDIAN);
|
||||
byte id = serializeParams(buffer);
|
||||
return makeCommand(id, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a command given ID and parameters buffer.
|
||||
*
|
||||
* @param id the command ID
|
||||
* @param params the parameters buffer
|
||||
* @return the assembled command buffer
|
||||
*/
|
||||
protected byte[] makeCommand(byte id, ByteBuffer params) {
|
||||
if (params.position() > LefunConstants.CMD_MAX_LENGTH - LefunConstants.CMD_HEADER_LENGTH)
|
||||
throw new IllegalArgumentException("params is too long to fit");
|
||||
|
||||
int paramsLength = params.position();
|
||||
byte[] request = new byte[paramsLength + LefunConstants.CMD_HEADER_LENGTH];
|
||||
request[0] = LefunConstants.CMD_REQUEST_ID;
|
||||
request[1] = (byte) request.length;
|
||||
request[2] = id;
|
||||
params.flip();
|
||||
params.get(request, LefunConstants.CMD_HEADER_LENGTH - 1, paramsLength);
|
||||
request[request.length - 1] = calculateChecksum(request, 0, request.length - 1);
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws a standard parameters length exception
|
||||
*/
|
||||
protected void throwUnexpectedLength() {
|
||||
throw new IllegalArgumentException("Unexpected parameters length");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for valid command ID and throws if wrong ID provided
|
||||
*
|
||||
* @param id command ID from device
|
||||
* @param expectedId expected command ID
|
||||
*/
|
||||
protected void validateId(byte id, byte expectedId) {
|
||||
if (id != expectedId)
|
||||
throw new IllegalArgumentException("Wrong command ID");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for valid command ID and command length
|
||||
*
|
||||
* @param id command ID from device
|
||||
* @param params params buffer from device
|
||||
* @param expectedId expected command ID
|
||||
* @param expectedLength expected params length
|
||||
*/
|
||||
protected void validateIdAndLength(byte id, ByteBuffer params, byte expectedId, int expectedLength) {
|
||||
validateId(id, expectedId);
|
||||
if (params.limit() - params.position() != expectedLength)
|
||||
throwUnexpectedLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether a bit is set
|
||||
*
|
||||
* @param value the value to check against
|
||||
* @param mask the bitmask
|
||||
* @return whether the bits indicated by the bitmask are set
|
||||
*/
|
||||
protected boolean getBit(int value, int mask) {
|
||||
return (value & mask) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a bit in a value
|
||||
*
|
||||
* @param value the value to modify
|
||||
* @param mask the bitmask
|
||||
* @param set whether to set or clear the bits
|
||||
* @return the modified value
|
||||
*/
|
||||
protected int setBit(int value, int mask, boolean set) {
|
||||
if (set) {
|
||||
return value | mask;
|
||||
} else {
|
||||
return value & ~mask;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a bit in a value
|
||||
*
|
||||
* @param value the value to modify
|
||||
* @param mask the bitmask
|
||||
* @param set whether to set or clear the bits
|
||||
* @return the modified value
|
||||
*/
|
||||
protected short setBit(short value, int mask, boolean set) {
|
||||
if (set) {
|
||||
return (short) (value | mask);
|
||||
} else {
|
||||
return (short) (value & ~mask);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a bit in a value
|
||||
*
|
||||
* @param value the value to modify
|
||||
* @param mask the bitmask
|
||||
* @param set whether to set or clear the bits
|
||||
* @return the modified value
|
||||
*/
|
||||
protected byte setBit(byte value, int mask, boolean set) {
|
||||
if (set) {
|
||||
return (byte) (value | mask);
|
||||
} else {
|
||||
return (byte) (value & ~mask);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find index of first bit that is set
|
||||
*
|
||||
* @param value the value to look at
|
||||
* @return the index of the lowest set bit, starting at 0 for least significant bit; -1 if no bits set
|
||||
*/
|
||||
protected int getLowestSetBitIndex(int value) {
|
||||
if (value == 0) return -1;
|
||||
|
||||
int i = 0;
|
||||
while ((value & 1) == 0) {
|
||||
++i;
|
||||
value >>= 1;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.commands;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
|
||||
public class Cmd22Command extends BaseCommand {
|
||||
private byte op;
|
||||
private short unknown;
|
||||
|
||||
private boolean setSuccess;
|
||||
|
||||
public byte getOp() {
|
||||
return op;
|
||||
}
|
||||
|
||||
public void setOp(byte op) {
|
||||
if (op != OP_GET && op != OP_SET)
|
||||
throw new IllegalArgumentException("Operation must be get or set");
|
||||
this.op = op;
|
||||
}
|
||||
|
||||
public short getUnknown() {
|
||||
return unknown;
|
||||
}
|
||||
|
||||
public void setUnknown(short unknown) {
|
||||
this.unknown = unknown;
|
||||
}
|
||||
|
||||
public boolean isSetSuccess() {
|
||||
return setSuccess;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeParams(byte id, ByteBuffer params) {
|
||||
validateId(id, LefunConstants.CMD_UNKNOWN_22);
|
||||
|
||||
int paramsLength = params.limit() - params.position();
|
||||
if (paramsLength < 1)
|
||||
throwUnexpectedLength();
|
||||
|
||||
op = params.get();
|
||||
if (op == OP_GET) {
|
||||
if (paramsLength != 3)
|
||||
throwUnexpectedLength();
|
||||
|
||||
unknown = params.getShort();
|
||||
} else if (op == OP_SET) {
|
||||
if (paramsLength != 2)
|
||||
throwUnexpectedLength();
|
||||
|
||||
setSuccess = params.get() == 1;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid operation type received");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte serializeParams(ByteBuffer params) {
|
||||
params.put(op);
|
||||
if (op == OP_SET) {
|
||||
params.putShort(unknown);
|
||||
}
|
||||
return LefunConstants.CMD_UNKNOWN_22;
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.commands;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
|
||||
public class Cmd25Command extends BaseCommand {
|
||||
private byte op;
|
||||
private int unknown;
|
||||
|
||||
private boolean setSuccess;
|
||||
|
||||
public byte getOp() {
|
||||
return op;
|
||||
}
|
||||
|
||||
public void setOp(byte op) {
|
||||
if (op != OP_GET && op != OP_SET)
|
||||
throw new IllegalArgumentException("Operation must be get or set");
|
||||
this.op = op;
|
||||
}
|
||||
|
||||
public int getUnknown() {
|
||||
return unknown;
|
||||
}
|
||||
|
||||
public void setUnknown(int unknown) {
|
||||
if (unknown < 0 || unknown > 65000)
|
||||
throw new IllegalArgumentException("Value must be between 0 and 65000 inclusive");
|
||||
this.unknown = unknown;
|
||||
}
|
||||
|
||||
public boolean isSetSuccess() {
|
||||
return setSuccess;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeParams(byte id, ByteBuffer params) {
|
||||
validateId(id, LefunConstants.CMD_UNKNOWN_25);
|
||||
|
||||
int paramsLength = params.limit() - params.position();
|
||||
if (paramsLength < 1)
|
||||
throwUnexpectedLength();
|
||||
|
||||
op = params.get();
|
||||
if (op == OP_GET) {
|
||||
if (paramsLength != 5)
|
||||
throwUnexpectedLength();
|
||||
|
||||
unknown = params.getInt();
|
||||
} else if (op == OP_SET) {
|
||||
if (paramsLength != 2)
|
||||
throwUnexpectedLength();
|
||||
|
||||
setSuccess = params.get() == 1;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid operation type received");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte serializeParams(ByteBuffer params) {
|
||||
params.put(op);
|
||||
if (op == OP_SET) {
|
||||
params.putInt(unknown);
|
||||
}
|
||||
return LefunConstants.CMD_UNKNOWN_25;
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.commands;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
|
||||
public class FeaturesCommand extends BaseCommand {
|
||||
public static final int FEATURE_RAISE_TO_WAKE = 0;
|
||||
public static final int FEATURE_SEDENTARY_REMINDER = 1;
|
||||
public static final int FEATURE_HYDRATION_REMINDER = 2;
|
||||
public static final int FEATURE_REMOTE_CAMERA = 3;
|
||||
public static final int FEATURE_UNKNOWN_4 = 4;
|
||||
public static final int FEATURE_ANTI_LOST = 5;
|
||||
|
||||
private byte op;
|
||||
private short features;
|
||||
|
||||
private boolean setSuccess;
|
||||
|
||||
public byte getOp() {
|
||||
return op;
|
||||
}
|
||||
|
||||
public void setOp(byte op) {
|
||||
if (op != OP_GET && op != OP_SET)
|
||||
throw new IllegalArgumentException("Operation must be get or set");
|
||||
this.op = op;
|
||||
}
|
||||
|
||||
public boolean getFeature(int index) {
|
||||
if (index < 0 || index > 5)
|
||||
throw new IllegalArgumentException("Index must be between 0 and 5 inclusive");
|
||||
return getBit(features, 1 << index);
|
||||
}
|
||||
|
||||
public void setFeature(int index, boolean enabled) {
|
||||
if (index < 0 || index > 5)
|
||||
throw new IllegalArgumentException("Index must be between 0 and 5 inclusive");
|
||||
features = setBit(features, 1 << index, enabled);
|
||||
}
|
||||
|
||||
public boolean isSetSuccess() {
|
||||
return setSuccess;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeParams(byte id, ByteBuffer params) {
|
||||
validateId(id, LefunConstants.CMD_FEATURES);
|
||||
|
||||
int paramsLength = params.limit() - params.position();
|
||||
if (paramsLength < 1)
|
||||
throwUnexpectedLength();
|
||||
|
||||
op = params.get();
|
||||
if (op == OP_GET) {
|
||||
if (paramsLength != 3)
|
||||
throwUnexpectedLength();
|
||||
|
||||
features = params.getShort();
|
||||
} else if (op == OP_SET) {
|
||||
if (paramsLength != 2)
|
||||
throwUnexpectedLength();
|
||||
|
||||
setSuccess = params.get() == 1;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid operation type received");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte serializeParams(ByteBuffer params) {
|
||||
params.put(op);
|
||||
if (op == OP_SET) {
|
||||
params.putShort(features);
|
||||
}
|
||||
return LefunConstants.CMD_FEATURES;
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.commands;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
|
||||
public class FindDeviceCommand extends BaseCommand {
|
||||
private boolean success;
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeParams(byte id, ByteBuffer params) {
|
||||
validateIdAndLength(id, params, LefunConstants.CMD_FIND_DEVICE, 1);
|
||||
|
||||
success = params.get() == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte serializeParams(ByteBuffer params) {
|
||||
return LefunConstants.CMD_FIND_DEVICE;
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.commands;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
|
||||
public class FindPhoneCommand extends BaseCommand {
|
||||
@Override
|
||||
protected void deserializeParams(byte id, ByteBuffer params) {
|
||||
validateIdAndLength(id, params, LefunConstants.CMD_FIND_PHONE, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte serializeParams(ByteBuffer params) {
|
||||
return LefunConstants.CMD_FIND_PHONE;
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.commands;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
|
||||
public class GetActivityDataCommand extends BaseCommand {
|
||||
private byte daysAgo;
|
||||
private byte totalRecords;
|
||||
private byte currentRecord;
|
||||
private byte year;
|
||||
private byte month;
|
||||
private byte day;
|
||||
private byte hour;
|
||||
private byte minute;
|
||||
private short steps;
|
||||
private short distance; // m
|
||||
private short calories; // calories
|
||||
|
||||
public byte getDaysAgo() {
|
||||
return daysAgo;
|
||||
}
|
||||
|
||||
public void setDaysAgo(byte daysAgo) {
|
||||
if (daysAgo < 0 || daysAgo > 6)
|
||||
throw new IllegalArgumentException("Days ago must be between 0 and 6 inclusive");
|
||||
this.daysAgo = daysAgo;
|
||||
}
|
||||
|
||||
public byte getTotalRecords() {
|
||||
return totalRecords;
|
||||
}
|
||||
|
||||
public byte getCurrentRecord() {
|
||||
return currentRecord;
|
||||
}
|
||||
|
||||
public byte getYear() {
|
||||
return year;
|
||||
}
|
||||
|
||||
public byte getMonth() {
|
||||
return month;
|
||||
}
|
||||
|
||||
public byte getDay() {
|
||||
return day;
|
||||
}
|
||||
|
||||
public byte getHour() {
|
||||
return hour;
|
||||
}
|
||||
|
||||
public byte getMinute() {
|
||||
return minute;
|
||||
}
|
||||
|
||||
public short getSteps() {
|
||||
return steps;
|
||||
}
|
||||
|
||||
public short getDistance() {
|
||||
return distance;
|
||||
}
|
||||
|
||||
public short getCalories() {
|
||||
return calories;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeParams(byte id, ByteBuffer params) {
|
||||
validateIdAndLength(id, params, LefunConstants.CMD_ACTIVITY_DATA, 14);
|
||||
|
||||
daysAgo = params.get();
|
||||
totalRecords = params.get();
|
||||
currentRecord = params.get();
|
||||
year = params.get();
|
||||
month = params.get();
|
||||
day = params.get();
|
||||
hour = params.get();
|
||||
minute = params.get();
|
||||
steps = params.getShort();
|
||||
distance = params.getShort();
|
||||
calories = params.getShort();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte serializeParams(ByteBuffer params) {
|
||||
params.put(daysAgo);
|
||||
return LefunConstants.CMD_ACTIVITY_DATA;
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.commands;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
|
||||
public class GetBatteryLevelCommand extends BaseCommand {
|
||||
private byte batteryLevel;
|
||||
|
||||
public byte getBatteryLevel() {
|
||||
return batteryLevel;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeParams(byte id, ByteBuffer params) {
|
||||
validateIdAndLength(id, params, LefunConstants.CMD_BATTERY_LEVEL, 1);
|
||||
|
||||
batteryLevel = params.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte serializeParams(ByteBuffer params) {
|
||||
return LefunConstants.CMD_BATTERY_LEVEL;
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.commands;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
|
||||
public class GetFirmwareInfoCommand extends BaseCommand {
|
||||
private short supportCode;
|
||||
private short devTypeReserveCode;
|
||||
private String typeCode;
|
||||
private short hardwareVersion;
|
||||
private short softwareVersion;
|
||||
private String vendorCode;
|
||||
|
||||
public short getSupportCode() {
|
||||
return supportCode;
|
||||
}
|
||||
|
||||
public short getDevTypeReserveCode() {
|
||||
return devTypeReserveCode;
|
||||
}
|
||||
|
||||
public String getTypeCode() {
|
||||
return typeCode;
|
||||
}
|
||||
|
||||
public short getHardwareVersion() {
|
||||
return hardwareVersion;
|
||||
}
|
||||
|
||||
public short getSoftwareVersion() {
|
||||
return softwareVersion;
|
||||
}
|
||||
|
||||
public String getVendorCode() {
|
||||
return vendorCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeParams(byte id, ByteBuffer params) {
|
||||
validateIdAndLength(id, params, LefunConstants.CMD_FIRMWARE_INFO, 16);
|
||||
|
||||
supportCode = (short) (params.get() | (params.get() << 8));
|
||||
devTypeReserveCode = params.getShort();
|
||||
byte[] typeCodeBytes = new byte[4];
|
||||
params.get(typeCodeBytes);
|
||||
typeCode = new String(typeCodeBytes, StandardCharsets.US_ASCII);
|
||||
hardwareVersion = params.getShort();
|
||||
softwareVersion = params.getShort();
|
||||
byte[] vendorCodeBytes = new byte[4];
|
||||
params.get(vendorCodeBytes);
|
||||
vendorCode = new String(vendorCodeBytes, StandardCharsets.US_ASCII);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte serializeParams(ByteBuffer params) {
|
||||
return LefunConstants.CMD_FIRMWARE_INFO;
|
||||
}
|
||||
}
|
@ -0,0 +1,137 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.commands;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
|
||||
public class GetPpgDataCommand extends BaseCommand {
|
||||
private byte ppgType;
|
||||
private short totalRecords;
|
||||
private short currentRecord;
|
||||
private byte year;
|
||||
private byte month;
|
||||
private byte day;
|
||||
private byte hour;
|
||||
private byte minute;
|
||||
private byte second;
|
||||
private byte[] ppgData;
|
||||
|
||||
public int getPpgType() {
|
||||
return getLowestSetBitIndex(ppgType);
|
||||
}
|
||||
|
||||
public void setPpgType(int type) {
|
||||
if (type < 0 || type > 2)
|
||||
throw new IllegalArgumentException("Invalid PPG type");
|
||||
this.ppgType = (byte)(1 << type);
|
||||
}
|
||||
|
||||
public short getTotalRecords() {
|
||||
return totalRecords;
|
||||
}
|
||||
|
||||
public short getCurrentRecord() {
|
||||
return currentRecord;
|
||||
}
|
||||
|
||||
public byte getYear() {
|
||||
return year;
|
||||
}
|
||||
|
||||
public byte getMonth() {
|
||||
return month;
|
||||
}
|
||||
|
||||
public byte getDay() {
|
||||
return day;
|
||||
}
|
||||
|
||||
public byte getHour() {
|
||||
return hour;
|
||||
}
|
||||
|
||||
public byte getMinute() {
|
||||
return minute;
|
||||
}
|
||||
|
||||
public byte getSecond() {
|
||||
return second;
|
||||
}
|
||||
|
||||
public byte[] getPpgData() {
|
||||
return ppgData;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeParams(byte id, ByteBuffer params) {
|
||||
validateId(id, LefunConstants.CMD_PPG_DATA);
|
||||
|
||||
int paramsLength = params.limit() - params.position();
|
||||
if (paramsLength < 9)
|
||||
throwUnexpectedLength();
|
||||
|
||||
ppgType = params.get();
|
||||
totalRecords = params.get();
|
||||
currentRecord = params.get();
|
||||
year = params.get();
|
||||
month = params.get();
|
||||
day = params.get();
|
||||
hour = params.get();
|
||||
minute = params.get();
|
||||
second = params.get();
|
||||
|
||||
int typeIndex = getPpgType();
|
||||
int dataLength;
|
||||
switch (typeIndex) {
|
||||
case LefunConstants.PPG_TYPE_HEART_RATE:
|
||||
case LefunConstants.PPG_TYPE_BLOOD_OXYGEN:
|
||||
dataLength = 1;
|
||||
break;
|
||||
case LefunConstants.PPG_TYPE_BLOOD_PRESSURE:
|
||||
dataLength = 2;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown PPG type");
|
||||
}
|
||||
|
||||
if (paramsLength < dataLength + 9)
|
||||
throwUnexpectedLength();
|
||||
|
||||
ppgData = new byte[dataLength];
|
||||
params.get(ppgData);
|
||||
|
||||
// Extended count/index
|
||||
if (paramsLength == dataLength + 11)
|
||||
{
|
||||
totalRecords |= params.get() << 8;
|
||||
currentRecord |= params.get() << 8;
|
||||
}
|
||||
else if (paramsLength > dataLength + 11) {
|
||||
throwUnexpectedLength();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte serializeParams(ByteBuffer params) {
|
||||
params.put(ppgType);
|
||||
return LefunConstants.CMD_PPG_DATA;
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.commands;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
|
||||
public class GetSleepDataCommand extends BaseCommand {
|
||||
public static final int SLEEP_TYPE_AWAKE = 1;
|
||||
public static final int SLEEP_TYPE_LIGHT_SLEEP = 2;
|
||||
public static final int SLEEP_TYPE_DEEP_SLEEP = 3;
|
||||
|
||||
private byte daysAgo;
|
||||
private short totalRecords;
|
||||
private short currentRecord;
|
||||
private byte year;
|
||||
private byte month;
|
||||
private byte day;
|
||||
private byte hour;
|
||||
private byte minute;
|
||||
private byte sleepType;
|
||||
|
||||
public byte getDaysAgo() {
|
||||
return daysAgo;
|
||||
}
|
||||
|
||||
public void setDaysAgo(byte daysAgo) {
|
||||
if (daysAgo < 0 || daysAgo > 6)
|
||||
throw new IllegalArgumentException("Days ago must be between 0 and 6 inclusive");
|
||||
this.daysAgo = daysAgo;
|
||||
}
|
||||
|
||||
public short getTotalRecords() {
|
||||
return totalRecords;
|
||||
}
|
||||
|
||||
public short getCurrentRecord() {
|
||||
return currentRecord;
|
||||
}
|
||||
|
||||
public byte getYear() {
|
||||
return year;
|
||||
}
|
||||
|
||||
public byte getMonth() {
|
||||
return month;
|
||||
}
|
||||
|
||||
public byte getDay() {
|
||||
return day;
|
||||
}
|
||||
|
||||
public byte getHour() {
|
||||
return hour;
|
||||
}
|
||||
|
||||
public byte getMinute() {
|
||||
return minute;
|
||||
}
|
||||
|
||||
public byte getSleepType() {
|
||||
return sleepType;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeParams(byte id, ByteBuffer params) {
|
||||
validateIdAndLength(id, params, LefunConstants.CMD_SLEEP_DATA, 11);
|
||||
|
||||
daysAgo = params.get();
|
||||
totalRecords = params.getShort();
|
||||
currentRecord = params.getShort();
|
||||
year = params.get();
|
||||
month = params.get();
|
||||
day = params.get();
|
||||
hour = params.get();
|
||||
minute = params.get();
|
||||
sleepType = params.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte serializeParams(ByteBuffer params) {
|
||||
params.put(daysAgo);
|
||||
return LefunConstants.CMD_SLEEP_DATA;
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.commands;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
|
||||
public class GetSleepTimeCommand extends BaseCommand {
|
||||
private byte daysAgo;
|
||||
private byte year;
|
||||
private byte month;
|
||||
private byte day;
|
||||
private short minutes;
|
||||
|
||||
public byte getDaysAgo() {
|
||||
return daysAgo;
|
||||
}
|
||||
|
||||
public void setDaysAgo(byte daysAgo) {
|
||||
if (daysAgo < 0 || daysAgo > 6)
|
||||
throw new IllegalArgumentException("Days ago must be between 0 and 6 inclusive");
|
||||
this.daysAgo = daysAgo;
|
||||
}
|
||||
|
||||
public byte getYear() {
|
||||
return year;
|
||||
}
|
||||
|
||||
public byte getMonth() {
|
||||
return month;
|
||||
}
|
||||
|
||||
public byte getDay() {
|
||||
return day;
|
||||
}
|
||||
|
||||
public short getMinutes() {
|
||||
return minutes;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void deserializeParams(byte id, ByteBuffer params) {
|
||||
validateIdAndLength(id, params, LefunConstants.CMD_SLEEP_TIME_DATA, 6);
|
||||
|
||||
daysAgo = params.get();
|
||||
year = params.get();
|
||||
month = params.get();
|
||||
day = params.get();
|
||||
minutes = params.getShort();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte serializeParams(ByteBuffer params) {
|
||||
params.put(daysAgo);
|
||||
return LefunConstants.CMD_SLEEP_TIME_DATA;
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.commands;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
|
||||
public class GetStepsDataCommand extends BaseCommand {
|
||||
private byte daysAgo;
|
||||
private byte year;
|
||||
private byte month;
|
||||
private byte day;
|
||||
private int steps;
|
||||
private int distance; // m
|
||||
private int calories; // calories
|
||||
|
||||
public byte getDaysAgo() {
|
||||
return daysAgo;
|
||||
}
|
||||
|
||||
public void setDaysAgo(byte daysAgo) {
|
||||
if (daysAgo < 0 || daysAgo > 6)
|
||||
throw new IllegalArgumentException("Days ago must be between 0 and 6 inclusive");
|
||||
this.daysAgo = daysAgo;
|
||||
}
|
||||
|
||||
public byte getYear() {
|
||||
return year;
|
||||
}
|
||||
|
||||
public byte getMonth() {
|
||||
return month;
|
||||
}
|
||||
|
||||
public byte getDay() {
|
||||
return day;
|
||||
}
|
||||
|
||||
public int getSteps() {
|
||||
return steps;
|
||||
}
|
||||
|
||||
public int getDistance() {
|
||||
return distance;
|
||||
}
|
||||
|
||||
public int getCalories() {
|
||||
return calories;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeParams(byte id, ByteBuffer params) {
|
||||
validateIdAndLength(id, params, LefunConstants.CMD_STEPS_DATA, 16);
|
||||
|
||||
daysAgo = params.get();
|
||||
year = params.get();
|
||||
month = params.get();
|
||||
day = params.get();
|
||||
steps = params.getInt();
|
||||
distance = params.getInt();
|
||||
calories = params.getInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte serializeParams(ByteBuffer params) {
|
||||
params.put(daysAgo);
|
||||
return LefunConstants.CMD_STEPS_DATA;
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.commands;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
|
||||
public class HydrationReminderIntervalCommand extends BaseCommand {
|
||||
private byte op;
|
||||
private byte hydrationReminderInterval;
|
||||
|
||||
private boolean setSuccess;
|
||||
|
||||
public byte getOp() {
|
||||
return op;
|
||||
}
|
||||
|
||||
public void setOp(byte op) {
|
||||
if (op != OP_GET && op != OP_SET)
|
||||
throw new IllegalArgumentException("Operation must be get or set");
|
||||
this.op = op;
|
||||
}
|
||||
|
||||
public byte getHydrationReminderInterval() {
|
||||
return hydrationReminderInterval;
|
||||
}
|
||||
|
||||
public void setHydrationReminderInterval(byte hydrationReminderInterval) {
|
||||
if (hydrationReminderInterval == 0)
|
||||
throw new IllegalArgumentException("Interval must be non-zero");
|
||||
this.hydrationReminderInterval = hydrationReminderInterval;
|
||||
}
|
||||
|
||||
public boolean isSetSuccess() {
|
||||
return setSuccess;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeParams(byte id, ByteBuffer params) {
|
||||
validateId(id, LefunConstants.CMD_HYDRATION_REMINDER_INTERVAL);
|
||||
|
||||
int paramsLength = params.limit() - params.position();
|
||||
if (paramsLength < 1)
|
||||
throwUnexpectedLength();
|
||||
|
||||
op = params.get();
|
||||
if (op == OP_GET) {
|
||||
if (paramsLength != 2)
|
||||
throwUnexpectedLength();
|
||||
|
||||
hydrationReminderInterval = params.get();
|
||||
} else if (op == OP_SET) {
|
||||
if (paramsLength != 2)
|
||||
throwUnexpectedLength();
|
||||
|
||||
setSuccess = params.get() == 1;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid operation type received");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte serializeParams(ByteBuffer params) {
|
||||
params.put(op);
|
||||
if (op == OP_SET) {
|
||||
params.put(hydrationReminderInterval);
|
||||
}
|
||||
return LefunConstants.CMD_HYDRATION_REMINDER_INTERVAL;
|
||||
}
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.commands;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
|
||||
public class NotificationCommand extends BaseCommand {
|
||||
public static final byte SERVICE_TYPE_CALL = 0;
|
||||
public static final byte SERVICE_TYPE_TEXT = 1;
|
||||
public static final byte SERVICE_TYPE_QQ = 2;
|
||||
public static final byte SERVICE_TYPE_WECHAT = 3;
|
||||
public static final byte SERVICE_TYPE_EXTENDED = 4;
|
||||
|
||||
public static final byte EXTENDED_SERVICE_TYPE_FACEBOOK = 1;
|
||||
public static final byte EXTENDED_SERVICE_TYPE_TWITTER = 2;
|
||||
public static final byte EXTENDED_SERVICE_TYPE_LINKEDIN = 3;
|
||||
public static final byte EXTENDED_SERVICE_TYPE_WHATSAPP = 4;
|
||||
public static final byte EXTENDED_SERVICE_TYPE_LINE = 5;
|
||||
public static final byte EXTENDED_SERVICE_TYPE_KAKAOTALK = 6;
|
||||
|
||||
public static final int MAX_PAYLOAD_LENGTH = 13;
|
||||
public static final int MAX_MESSAGE_LENGTH = 254;
|
||||
|
||||
private byte serviceType;
|
||||
private byte totalPieces;
|
||||
private byte currentPiece;
|
||||
private byte extendedServiceType;
|
||||
private byte[] payload;
|
||||
|
||||
public int getServiceType() {
|
||||
return getLowestSetBitIndex(serviceType);
|
||||
}
|
||||
|
||||
public void setServiceType(int type) {
|
||||
if (type < 0 || type > 4)
|
||||
throw new IllegalArgumentException("Invalid service type");
|
||||
this.serviceType = (byte) (1 << type);
|
||||
}
|
||||
|
||||
public byte getTotalPieces() {
|
||||
return totalPieces;
|
||||
}
|
||||
|
||||
public void setTotalPieces(byte totalPieces) {
|
||||
// This check isn't on device, but should probably be added
|
||||
if (totalPieces == 0)
|
||||
throw new IllegalArgumentException("Total pieces must not be 0");
|
||||
this.totalPieces = totalPieces;
|
||||
}
|
||||
|
||||
public byte getCurrentPiece() {
|
||||
return currentPiece;
|
||||
}
|
||||
|
||||
public void setCurrentPiece(byte currentPiece) {
|
||||
// This check isn't on device, but should probably be added
|
||||
if (currentPiece == 0)
|
||||
throw new IllegalArgumentException("Current piece must not be 0");
|
||||
this.currentPiece = currentPiece;
|
||||
}
|
||||
|
||||
public byte getExtendedServiceType() {
|
||||
return extendedServiceType;
|
||||
}
|
||||
|
||||
public void setExtendedServiceType(byte extendedServiceType) {
|
||||
this.extendedServiceType = extendedServiceType;
|
||||
}
|
||||
|
||||
public byte[] getPayload() {
|
||||
return payload;
|
||||
}
|
||||
|
||||
public void setPayload(byte[] payload) {
|
||||
if (payload == null)
|
||||
throw new IllegalArgumentException("Payload must not be null");
|
||||
if (payload.length > 13)
|
||||
throw new IllegalArgumentException("Payload is too long");
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeParams(byte id, ByteBuffer params) {
|
||||
// We should not receive a response for this
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte serializeParams(ByteBuffer params) {
|
||||
boolean hasExtendedServiceType = (serviceType & (1 << SERVICE_TYPE_EXTENDED)) != 0
|
||||
&& (extendedServiceType & 0x0f) != 0;
|
||||
int maxPayloadLength = MAX_PAYLOAD_LENGTH;
|
||||
if (hasExtendedServiceType) maxPayloadLength -= 1;
|
||||
|
||||
if (payload.length > maxPayloadLength)
|
||||
throw new IllegalStateException("Payload is too long");
|
||||
|
||||
params.put(serviceType);
|
||||
params.put(totalPieces);
|
||||
params.put(currentPiece);
|
||||
if (hasExtendedServiceType)
|
||||
params.put(extendedServiceType);
|
||||
params.put(payload);
|
||||
|
||||
return LefunConstants.CMD_NOTIFICATION;
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.commands;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
|
||||
public class PpgResultCommand extends BaseCommand {
|
||||
private byte ppgType;
|
||||
private byte[] ppgData;
|
||||
|
||||
public int getPpgType() {
|
||||
return getLowestSetBitIndex(ppgType);
|
||||
}
|
||||
|
||||
public byte[] getPpgData() {
|
||||
return ppgData;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeParams(byte id, ByteBuffer params) {
|
||||
validateId(id, LefunConstants.CMD_PPG_RESULT);
|
||||
|
||||
int paramsLength = params.limit() - params.position();
|
||||
if (paramsLength < 1)
|
||||
throwUnexpectedLength();
|
||||
|
||||
ppgType = params.get();
|
||||
|
||||
int typeIndex = getPpgType();
|
||||
int dataLength;
|
||||
switch (typeIndex) {
|
||||
case LefunConstants.PPG_TYPE_HEART_RATE:
|
||||
case LefunConstants.PPG_TYPE_BLOOD_OXYGEN:
|
||||
dataLength = 1;
|
||||
break;
|
||||
case LefunConstants.PPG_TYPE_BLOOD_PRESSURE:
|
||||
dataLength = 2;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown PPG type");
|
||||
}
|
||||
|
||||
if (paramsLength != dataLength + 1)
|
||||
throwUnexpectedLength();
|
||||
|
||||
ppgData = new byte[dataLength];
|
||||
params.get(ppgData);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte serializeParams(ByteBuffer params) {
|
||||
// No handler on device side
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.commands;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
|
||||
public class ProfileCommand extends BaseCommand {
|
||||
public static final byte GENDER_FEMALE = 0;
|
||||
public static final byte GENDER_MALE = 1;
|
||||
|
||||
private byte op;
|
||||
private byte gender;
|
||||
private byte height; // cm
|
||||
private byte weight; // kg
|
||||
private byte age; // years
|
||||
|
||||
private boolean setSuccess;
|
||||
|
||||
public byte getOp() {
|
||||
return op;
|
||||
}
|
||||
|
||||
public void setOp(byte op) {
|
||||
if (op != OP_GET && op != OP_SET)
|
||||
throw new IllegalArgumentException("Operation must be get or set");
|
||||
this.op = op;
|
||||
}
|
||||
|
||||
public byte getGender() {
|
||||
return gender;
|
||||
}
|
||||
|
||||
public void setGender(byte gender) {
|
||||
if (gender != GENDER_FEMALE && gender != GENDER_MALE)
|
||||
throw new IllegalArgumentException("Invalid gender");
|
||||
this.gender = gender;
|
||||
}
|
||||
|
||||
public byte getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setHeight(byte height) {
|
||||
int intHeight = (int)height & 0xff;
|
||||
if (intHeight < 40 || intHeight > 210)
|
||||
throw new IllegalArgumentException("Height must be between 40 and 210 cm inclusive");
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public byte getWeight() {
|
||||
return weight;
|
||||
}
|
||||
|
||||
public void setWeight(byte weight) {
|
||||
int intWeight = (int)weight & 0xff;
|
||||
if (intWeight < 5 || intWeight > 200)
|
||||
throw new IllegalArgumentException("Weight must be between 5 and 200 kg inclusive");
|
||||
this.weight = weight;
|
||||
}
|
||||
|
||||
public byte getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(byte age) {
|
||||
if (age < 0 || age > 110)
|
||||
throw new IllegalArgumentException("Age must be between 0 and 110 years inclusive");
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public boolean isSetSuccess() {
|
||||
return setSuccess;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeParams(byte id, ByteBuffer params) {
|
||||
validateId(id, LefunConstants.CMD_PROFILE);
|
||||
|
||||
int paramsLength = params.limit() - params.position();
|
||||
if (paramsLength < 1)
|
||||
throwUnexpectedLength();
|
||||
|
||||
op = params.get();
|
||||
if (op == OP_GET) {
|
||||
if (paramsLength != 5)
|
||||
throwUnexpectedLength();
|
||||
|
||||
gender = params.get();
|
||||
height = params.get();
|
||||
weight = params.get();
|
||||
age = params.get();
|
||||
} else if (op == OP_SET) {
|
||||
if (paramsLength != 2)
|
||||
throwUnexpectedLength();
|
||||
|
||||
setSuccess = params.get() == 1;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid operation type received");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte serializeParams(ByteBuffer params) {
|
||||
params.put(op);
|
||||
if (op == OP_SET) {
|
||||
params.put(gender);
|
||||
params.put(height);
|
||||
params.put(weight);
|
||||
params.put(age);
|
||||
}
|
||||
return LefunConstants.CMD_PROFILE;
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.commands;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
|
||||
public class RemoteCameraTriggeredCommand extends BaseCommand {
|
||||
@Override
|
||||
protected void deserializeParams(byte id, ByteBuffer params) {
|
||||
validateIdAndLength(id, params, LefunConstants.CMD_REMOTE_CAMERA_TRIGGERED, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte serializeParams(ByteBuffer params) {
|
||||
// No handler on device side
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.commands;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
|
||||
public class RequestBondingCommand extends BaseCommand {
|
||||
public static final byte STATUS_ALREADY_BONDED = 0;
|
||||
public static final byte STATUS_BONDING_SUCCESSFUL = 1;
|
||||
|
||||
private byte status;
|
||||
|
||||
public byte getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeParams(byte id, ByteBuffer params) {
|
||||
validateIdAndLength(id, params, LefunConstants.CMD_BONDING_REQUEST, 1);
|
||||
|
||||
status = params.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte serializeParams(ByteBuffer params) {
|
||||
return LefunConstants.CMD_BONDING_REQUEST;
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.commands;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
|
||||
public class SedentaryReminderIntervalCommand extends BaseCommand {
|
||||
private byte op;
|
||||
private byte sedentaryReminderInterval;
|
||||
|
||||
private boolean setSuccess;
|
||||
|
||||
public byte getOp() {
|
||||
return op;
|
||||
}
|
||||
|
||||
public void setOp(byte op) {
|
||||
if (op != OP_GET && op != OP_SET)
|
||||
throw new IllegalArgumentException("Operation must be get or set");
|
||||
this.op = op;
|
||||
}
|
||||
|
||||
public byte getSedentaryReminderInterval() {
|
||||
return sedentaryReminderInterval;
|
||||
}
|
||||
|
||||
public void setSedentaryReminderInterval(byte sedentaryReminderInterval) {
|
||||
if (sedentaryReminderInterval == 0)
|
||||
throw new IllegalArgumentException("Interval must be non-zero");
|
||||
this.sedentaryReminderInterval = sedentaryReminderInterval;
|
||||
}
|
||||
|
||||
public boolean isSetSuccess() {
|
||||
return setSuccess;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeParams(byte id, ByteBuffer params) {
|
||||
validateId(id, LefunConstants.CMD_SEDENTARY_REMINDER_INTERVAL);
|
||||
|
||||
int paramsLength = params.limit() - params.position();
|
||||
if (paramsLength < 1)
|
||||
throwUnexpectedLength();
|
||||
|
||||
op = params.get();
|
||||
if (op == OP_GET) {
|
||||
if (paramsLength != 2)
|
||||
throwUnexpectedLength();
|
||||
|
||||
sedentaryReminderInterval = params.get();
|
||||
} else if (op == OP_SET) {
|
||||
if (paramsLength != 2)
|
||||
throwUnexpectedLength();
|
||||
|
||||
setSuccess = params.get() == 1;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid operation type received");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte serializeParams(ByteBuffer params) {
|
||||
params.put(op);
|
||||
if (op == OP_SET) {
|
||||
params.put(sedentaryReminderInterval);
|
||||
}
|
||||
return LefunConstants.CMD_SEDENTARY_REMINDER_INTERVAL;
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.commands;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
|
||||
public class SetLanguageCommand extends BaseCommand {
|
||||
private byte language;
|
||||
|
||||
private boolean setSuccess;
|
||||
|
||||
public byte getLanguage() {
|
||||
return language;
|
||||
}
|
||||
|
||||
public void setLanguage(byte language) {
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
public boolean isSetSuccess() {
|
||||
return setSuccess;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeParams(byte id, ByteBuffer params) {
|
||||
validateIdAndLength(id, params, LefunConstants.CMD_LANGUAGE, 1);
|
||||
|
||||
setSuccess = params.get() == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte serializeParams(ByteBuffer params) {
|
||||
params.put(language);
|
||||
return LefunConstants.CMD_LANGUAGE;
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.commands;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
|
||||
public class SetRemoteCameraCommand extends BaseCommand {
|
||||
private boolean remoteCameraEnabled;
|
||||
|
||||
private boolean setSuccess;
|
||||
|
||||
public boolean getRemoteCameraEnabled() {
|
||||
return remoteCameraEnabled;
|
||||
}
|
||||
|
||||
public void setRemoteCameraEnabled(boolean remoteCameraEnabled) {
|
||||
this.remoteCameraEnabled = remoteCameraEnabled;
|
||||
}
|
||||
|
||||
public boolean isSetSuccess() {
|
||||
return setSuccess;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeParams(byte id, ByteBuffer params) {
|
||||
validateIdAndLength(id, params, LefunConstants.CMD_REMOTE_CAMERA, 1);
|
||||
|
||||
setSuccess = params.get() == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte serializeParams(ByteBuffer params) {
|
||||
params.put((byte)(remoteCameraEnabled ? 1 : 0));
|
||||
return LefunConstants.CMD_REMOTE_CAMERA;
|
||||
}
|
||||
}
|
@ -0,0 +1,118 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.commands;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
|
||||
public class SettingsCommand extends BaseCommand {
|
||||
public static final byte AM_PM_24_HOUR = 0;
|
||||
public static final byte AM_PM_12_HOUR = 1;
|
||||
public static final byte MEASUREMENT_UNIT_METRIC = 0;
|
||||
public static final byte MEASUREMENT_UNIT_IMPERIAL = 1;
|
||||
|
||||
private byte op;
|
||||
private byte option1;
|
||||
private byte amPmIndicator;
|
||||
private byte measurementUnit;
|
||||
|
||||
private boolean setSuccess;
|
||||
|
||||
public byte getOp() {
|
||||
return op;
|
||||
}
|
||||
|
||||
public void setOp(byte op) {
|
||||
if (op != OP_GET && op != OP_SET)
|
||||
throw new IllegalArgumentException("Operation must be get or set");
|
||||
this.op = op;
|
||||
}
|
||||
|
||||
public byte getOption1() {
|
||||
return option1;
|
||||
}
|
||||
|
||||
public void setOption1(byte option1) {
|
||||
if (option1 != (byte)0xff && (option1 < 0 || option1 > 24))
|
||||
throw new IllegalArgumentException("option1 must be between 0 and 24 inclusive");
|
||||
this.option1 = option1;
|
||||
}
|
||||
|
||||
public byte getAmPmIndicator() {
|
||||
return amPmIndicator;
|
||||
}
|
||||
|
||||
public void setAmPmIndicator(byte amPmIndicator) {
|
||||
if (amPmIndicator != (byte)0xff && (amPmIndicator != AM_PM_12_HOUR && amPmIndicator != AM_PM_24_HOUR))
|
||||
throw new IllegalArgumentException("Indicator must be 12 or 24 hours");
|
||||
this.amPmIndicator = amPmIndicator;
|
||||
}
|
||||
|
||||
public byte getMeasurementUnit() {
|
||||
return measurementUnit;
|
||||
}
|
||||
|
||||
public void setMeasurementUnit(byte measurementUnit) {
|
||||
if (measurementUnit != (byte)0xff && (measurementUnit != MEASUREMENT_UNIT_METRIC && measurementUnit != MEASUREMENT_UNIT_IMPERIAL))
|
||||
throw new IllegalArgumentException(("Unit must be metric or imperial"));
|
||||
this.measurementUnit = measurementUnit;
|
||||
}
|
||||
|
||||
public boolean isSetSuccess() {
|
||||
return setSuccess;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeParams(byte id, ByteBuffer params) {
|
||||
validateId(id, LefunConstants.CMD_SETTINGS);
|
||||
|
||||
int paramsLength = params.limit() - params.position();
|
||||
if (paramsLength < 1)
|
||||
throwUnexpectedLength();
|
||||
|
||||
op = params.get();
|
||||
if (op == OP_GET) {
|
||||
if (paramsLength != 4)
|
||||
throwUnexpectedLength();
|
||||
|
||||
option1 = params.get();
|
||||
amPmIndicator = params.get();
|
||||
measurementUnit = params.get();
|
||||
} else if (op == OP_SET) {
|
||||
if (paramsLength != 2)
|
||||
throwUnexpectedLength();
|
||||
|
||||
setSuccess = params.get() == 1;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid operation type received");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte serializeParams(ByteBuffer params) {
|
||||
params.put(op);
|
||||
if (op == OP_SET) {
|
||||
params.put(option1);
|
||||
params.put(amPmIndicator);
|
||||
params.put(measurementUnit);
|
||||
}
|
||||
return LefunConstants.CMD_SETTINGS;
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.commands;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
|
||||
public class StartPpgSensingCommand extends BaseCommand {
|
||||
private byte ppgType;
|
||||
|
||||
private boolean setSuccess;
|
||||
|
||||
public int getPpgType() {
|
||||
return getLowestSetBitIndex(ppgType);
|
||||
}
|
||||
|
||||
public void setPpgType(int type) {
|
||||
if (type < 0 || type > 2)
|
||||
throw new IllegalArgumentException("Invalid PPG type");
|
||||
this.ppgType = (byte)(1 << type);
|
||||
}
|
||||
|
||||
public boolean isSetSuccess() {
|
||||
return setSuccess;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeParams(byte id, ByteBuffer params) {
|
||||
validateIdAndLength(id, params, LefunConstants.CMD_PPG_START, 2);
|
||||
|
||||
ppgType = params.get();
|
||||
setSuccess = params.get() == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte serializeParams(ByteBuffer params) {
|
||||
params.put(ppgType);
|
||||
return LefunConstants.CMD_PPG_START;
|
||||
}
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.commands;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
|
||||
public class TimeCommand extends BaseCommand {
|
||||
private byte op;
|
||||
private byte year;
|
||||
private byte month;
|
||||
private byte day;
|
||||
private byte hour;
|
||||
private byte minute;
|
||||
private byte second;
|
||||
|
||||
private boolean setSuccess;
|
||||
|
||||
public byte getOp() {
|
||||
if (op != OP_GET && op != OP_SET)
|
||||
throw new IllegalArgumentException("Operation must be get or set");
|
||||
return op;
|
||||
}
|
||||
|
||||
public void setOp(byte op) {
|
||||
this.op = op;
|
||||
}
|
||||
|
||||
public byte getYear() {
|
||||
return year;
|
||||
}
|
||||
|
||||
public void setYear(byte year) {
|
||||
this.year = year;
|
||||
}
|
||||
|
||||
public byte getMonth() {
|
||||
return month;
|
||||
}
|
||||
|
||||
public void setMonth(byte month) {
|
||||
if (month < 1 || month > 12)
|
||||
throw new IllegalArgumentException("Month must be between 1 and 12 inclusive");
|
||||
this.month = month;
|
||||
}
|
||||
|
||||
public byte getDay() {
|
||||
return day;
|
||||
}
|
||||
|
||||
public void setDay(byte day) {
|
||||
if (day < 1 || day > 31)
|
||||
throw new IllegalArgumentException("Day must be between 1 and 31 inclusive");
|
||||
this.day = day;
|
||||
}
|
||||
|
||||
public byte getHour() {
|
||||
return hour;
|
||||
}
|
||||
|
||||
public void setHour(byte hour) {
|
||||
if (hour < 0 || hour > 23)
|
||||
throw new IllegalArgumentException("Hour must be between 0 and 23 inclusive");
|
||||
this.hour = hour;
|
||||
}
|
||||
|
||||
public byte getMinute() {
|
||||
return minute;
|
||||
}
|
||||
|
||||
public void setMinute(byte minute) {
|
||||
if (minute < 0 || minute > 59)
|
||||
throw new IllegalArgumentException("Minute must be between 0 and 59 inclusive");
|
||||
this.minute = minute;
|
||||
}
|
||||
|
||||
public byte getSecond() {
|
||||
return second;
|
||||
}
|
||||
|
||||
public void setSecond(byte second) {
|
||||
if (second < 0 || second > 59)
|
||||
throw new IllegalArgumentException("Second must be between 0 and 59 inclusive");
|
||||
this.second = second;
|
||||
}
|
||||
|
||||
public boolean isSetSuccess() {
|
||||
return setSuccess;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeParams(byte id, ByteBuffer params) {
|
||||
validateId(id, LefunConstants.CMD_TIME);
|
||||
|
||||
int paramsLength = params.limit() - params.position();
|
||||
if (paramsLength < 1)
|
||||
throwUnexpectedLength();
|
||||
|
||||
op = params.get();
|
||||
if (op == OP_GET) {
|
||||
if (paramsLength != 7)
|
||||
throwUnexpectedLength();
|
||||
|
||||
year = params.get();
|
||||
month = params.get();
|
||||
day = params.get();
|
||||
hour = params.get();
|
||||
minute = params.get();
|
||||
second = params.get();
|
||||
} else if (op == OP_SET) {
|
||||
if (paramsLength != 2)
|
||||
throwUnexpectedLength();
|
||||
|
||||
setSuccess = params.get() == 1;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid operation type received");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte serializeParams(ByteBuffer params) {
|
||||
params.put(op);
|
||||
if (op == OP_SET) {
|
||||
params.put(year);
|
||||
params.put(month);
|
||||
params.put(day);
|
||||
params.put(hour);
|
||||
params.put(minute);
|
||||
params.put(second);
|
||||
}
|
||||
return LefunConstants.CMD_TIME;
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.commands;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
|
||||
public class UiPagesCommand extends BaseCommand {
|
||||
// I don't know which pages since they're not implemented in my watch, so no
|
||||
// constants here for now
|
||||
|
||||
private byte op;
|
||||
private short pages;
|
||||
|
||||
private boolean setSuccess;
|
||||
|
||||
public byte getOp() {
|
||||
return op;
|
||||
}
|
||||
|
||||
public void setOp(byte op) {
|
||||
if (op != OP_GET && op != OP_SET)
|
||||
throw new IllegalArgumentException("Operation must be get or set");
|
||||
this.op = op;
|
||||
}
|
||||
|
||||
public boolean getPage(int index) {
|
||||
if (index < 0 || index >= 16)
|
||||
throw new IllegalArgumentException("Index must be between 0 and 15 inclusive");
|
||||
return getBit(pages, 1 << index);
|
||||
}
|
||||
|
||||
public void setPage(int index, boolean enabled) {
|
||||
if (index < 0 || index >= 16)
|
||||
throw new IllegalArgumentException("Index must be between 0 and 15 inclusive");
|
||||
pages = setBit(pages, 1 << index, enabled);
|
||||
}
|
||||
|
||||
public boolean isSetSuccess() {
|
||||
return setSuccess;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeParams(byte id, ByteBuffer params) {
|
||||
validateId(id, LefunConstants.CMD_UI_PAGES);
|
||||
|
||||
int paramsLength = params.limit() - params.position();
|
||||
if (paramsLength < 1)
|
||||
throwUnexpectedLength();
|
||||
|
||||
op = params.get();
|
||||
if (op == OP_GET) {
|
||||
if (paramsLength != 3)
|
||||
throwUnexpectedLength();
|
||||
|
||||
pages = params.getShort();
|
||||
} else if (op == OP_SET) {
|
||||
if (paramsLength != 2)
|
||||
throwUnexpectedLength();
|
||||
|
||||
setSuccess = params.get() == 1;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid operation type received");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte serializeParams(ByteBuffer params) {
|
||||
params.put(op);
|
||||
if (op == OP_SET) {
|
||||
params.putShort(pages);
|
||||
}
|
||||
return LefunConstants.CMD_UI_PAGES;
|
||||
}
|
||||
}
|
@ -20,12 +20,13 @@ package nodomain.freeyourgadget.gadgetbridge.devices.miband;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
@ -133,9 +134,8 @@ public class MiBandFWHelper extends AbstractMiBandFWHelper {
|
||||
* @return
|
||||
* @throws IllegalArgumentException when the data is not recognized as firmware data
|
||||
*/
|
||||
public static
|
||||
@NonNull
|
||||
AbstractMiFirmwareInfo determineFirmwareInfoFor(byte[] wholeFirmwareBytes) {
|
||||
public static AbstractMiFirmwareInfo determineFirmwareInfoFor(byte[] wholeFirmwareBytes) {
|
||||
return AbstractMiFirmwareInfo.determineFirmwareInfoFor(wholeFirmwareBytes);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,114 @@
|
||||
/* Copyright (C) 2020 Taavi Eomäe
|
||||
|
||||
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.nut;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
|
||||
|
||||
public class NutConstants {
|
||||
/**
|
||||
* Just battery info
|
||||
*/
|
||||
public static final UUID SERVICE_BATTERY = GattService.UUID_SERVICE_BATTERY_SERVICE;
|
||||
public static final UUID CHARAC_BATTERY_INFO = UUID.fromString("00002a19-0000-1000-8000-00805f9b34fb");
|
||||
|
||||
/**
|
||||
* Device info available.
|
||||
**/
|
||||
public static final UUID SERVICE_DEVICE_INFO = GattService.UUID_SERVICE_DEVICE_INFORMATION;
|
||||
/**
|
||||
* Firmware version.
|
||||
* Used with {@link NutConstants#SERVICE_DEVICE_INFO}
|
||||
*/
|
||||
public static final UUID CHARAC_FIRMWARE_VERSION = UUID.fromString("00002a26-0000-1000-8000-00805f9b34fb");
|
||||
/**
|
||||
* System ID.
|
||||
* Used with {@link NutConstants#SERVICE_DEVICE_INFO}
|
||||
*/
|
||||
public static final UUID CHARAC_SYSTEM_ID = UUID.fromString("00002a23-0000-1000-8000-00805f9b34fb");
|
||||
/**
|
||||
* Hardware version.
|
||||
* Used with {@link NutConstants#SERVICE_DEVICE_INFO}
|
||||
*/
|
||||
public static final UUID CHARAC_HARDWARE_VERSION = UUID.fromString("00002a27-0000-1000-8000-00805f9b34fb");
|
||||
/**
|
||||
* Manufacturer name.
|
||||
* Used with {@link NutConstants#SERVICE_DEVICE_INFO}
|
||||
*/
|
||||
public static final UUID CHARAC_MANUFACTURER_NAME = UUID.fromString("00002a29-0000-1000-8000-00805f9b34fb");
|
||||
|
||||
|
||||
/**
|
||||
* Link loss alert service.
|
||||
*/
|
||||
public static final UUID SERVICE_LINK_LOSS = UUID.fromString("00001803-0000-1000-8000-00805f9b34fb");
|
||||
|
||||
|
||||
/**
|
||||
* Immediate alert service.
|
||||
*/
|
||||
public static final UUID SERVICE_IMMEDIATE_ALERT = UUID.fromString("00001802-0000-1000-8000-00805f9b34fb");
|
||||
/**
|
||||
* Immediate alert level
|
||||
* Used with {@link NutConstants#SERVICE_IMMEDIATE_ALERT}
|
||||
*/
|
||||
public static final UUID CHARAC_LINK_LOSS_ALERT_LEVEL = UUID.fromString("00002a06-0000-1000-8000-00805f9b34fb");
|
||||
|
||||
|
||||
/**
|
||||
* Proprietary command endpoint.
|
||||
* TODO: Anything else in this service on other devices?
|
||||
*/
|
||||
public static final UUID SERVICE_PROPRIETARY_NUT = UUID.fromString("0000ff00-0000-1000-8000-00805f9b34fb");
|
||||
/**
|
||||
* Shutdown or reset.
|
||||
* Used with {@link NutConstants#SERVICE_PROPRIETARY_NUT}
|
||||
*/
|
||||
public static final UUID CHARAC_CHANGE_POWER = UUID.fromString("0000ff01-0000-1000-8000-00805f9b34fb");
|
||||
/**
|
||||
* Commands for proprietary service.
|
||||
* Used with {@link NutConstants#SERVICE_PROPRIETARY_NUT}
|
||||
*/
|
||||
public static final UUID CHARAC_DFU_PW = UUID.fromString("0000ff02-0000-1000-8000-00805f9b34fb");
|
||||
/**
|
||||
* Authentication using 16-byte key?
|
||||
* Used with {@link NutConstants#SERVICE_PROPRIETARY_NUT}
|
||||
* TODO: Exists only on Nut Mini?
|
||||
*/
|
||||
public static final UUID CHARAC_AUTH_STATUS = UUID.fromString("0000ff05-0000-1000-8000-00805f9b34fb");
|
||||
|
||||
|
||||
/**
|
||||
* Ringing configuration.
|
||||
* TODO: Exact purpose?
|
||||
*/
|
||||
public static final UUID SERVICE_UNKNOWN_2 = UUID.fromString("0000ffe0-0000-1000-8000-00805f9b34fb");
|
||||
/**
|
||||
* Ringing configuration.
|
||||
* Used with {@link NutConstants#SERVICE_UNKNOWN_2}
|
||||
* TODO: Something else on other devices?
|
||||
*/
|
||||
public static final UUID CHARAC_UNKNOWN_2 = UUID.fromString("0000ffe1-0000-1000-8000-00805f9b34fb");
|
||||
|
||||
|
||||
/**
|
||||
* Very little mention online, specific to Nut devices?
|
||||
*/
|
||||
public static final UUID UNKNOWN_3 = UUID.fromString("00001530-0000-1000-8000-00805f9b34fb");
|
||||
}
|
@ -0,0 +1,164 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele Gobbetti, Taavi Eomäe
|
||||
|
||||
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.nut;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.bluetooth.le.ScanFilter;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
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.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
|
||||
public class NutCoordinator extends AbstractDeviceCoordinator {
|
||||
@Override
|
||||
@NonNull
|
||||
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
|
||||
String name = candidate.getDevice().getName();
|
||||
if (name != null && name.toLowerCase().startsWith("nut")) {
|
||||
return DeviceType.NUTMINI;
|
||||
}
|
||||
return DeviceType.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBondingStyle() {
|
||||
return BONDING_STYLE_ASK;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public Collection<? extends ScanFilter> createBLEScanFilters() {
|
||||
ScanFilter filter = new ScanFilter.Builder()
|
||||
.setDeviceName("nut") // Nut Mini
|
||||
.build();
|
||||
return Collections.singletonList(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceType getDeviceType() {
|
||||
return DeviceType.NUTMINI;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||
return new int[]{
|
||||
R.xml.devicesettings_nutmini,
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Activity> getPairingActivity() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstallHandler findInstallHandler(Uri uri, Context context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityDataFetching() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityTracking() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsScreenshots() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAlarmSlotCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsSmartWakeup(GBDevice device) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturer() {
|
||||
return "Nut";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAppsManagement() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Activity> getAppsManagementActivity() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCalendarEvents() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRealtimeData() {
|
||||
return false; //TODO: RRSI
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsWeather() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsFindDevice() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) {
|
||||
}
|
||||
}
|
@ -0,0 +1,266 @@
|
||||
/* Copyright (C) 2020 Taavi Eomäe
|
||||
|
||||
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.nut;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Map;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class NutKey {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(NutKey.class);
|
||||
|
||||
/**
|
||||
* Different from {@link GB#hexStringToByteArray} because
|
||||
* it returns an array of zero bytes when it's given zero bytes
|
||||
* <p>
|
||||
* https://stackoverflow.com/a/140430/4636860
|
||||
*
|
||||
* @param encoded hexadecimal string like "0xcafebabe", "DEADBEEF" or "feeddead"
|
||||
* @return resulting byte array
|
||||
*/
|
||||
public static byte[] hexStringToByteArrayNut(String encoded) {
|
||||
if (encoded.startsWith("0x")) {
|
||||
encoded = encoded.substring(2);
|
||||
}
|
||||
|
||||
if ((encoded.length() % 2) != 0) {
|
||||
throw new IllegalArgumentException("Input string must contain an even number of characters");
|
||||
}
|
||||
|
||||
final byte[] result = new byte[encoded.length() / 2];
|
||||
final char[] enc = encoded.toCharArray();
|
||||
for (int i = 0; i < enc.length; i += 2) {
|
||||
result[i / 2] = (byte) Integer.parseInt(String.valueOf(enc[i]) + enc[i + 1], 16);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array as hexadecimal string space delimited
|
||||
*
|
||||
* @param bytes bytes to return
|
||||
* @return returns
|
||||
*/
|
||||
public static String bytesToHex2(byte[] bytes) {
|
||||
char[] hexChars = new char[bytes.length * 3];
|
||||
for (int j = 0; j < bytes.length; j++) {
|
||||
hexChars[j * 3] = GB.HEX_CHARS[(bytes[j] & 0xFF) >>> 4];
|
||||
hexChars[j * 3 + 1] = GB.HEX_CHARS[(bytes[j] & 0xFF) & 0x0F];
|
||||
hexChars[j * 3 + 2] = " ".toCharArray()[0];
|
||||
}
|
||||
return new String(hexChars).toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an assembled packet based on inputs
|
||||
*
|
||||
* @param mac the mac of the target device (AA:BB:CC:DD:EE:FF)
|
||||
* @param challenge the received challenge, THE ENTIRE PAYLOAD WITH PREAMBLE!
|
||||
* @param key1 first key
|
||||
* @param key2 second key
|
||||
* @return assembled packet (without the preamble!)
|
||||
*/
|
||||
public static byte[] passwordGeneration(String mac, byte[] challenge, BigInteger key1, BigInteger key2) {
|
||||
if (challenge[0] != 0x01) {
|
||||
throw new IllegalArgumentException("Challenge must be given with the preamble");
|
||||
}
|
||||
|
||||
byte[] mac_as_bytes = macAsByteArray(mac);
|
||||
byte[] correct_challenge = new byte[challenge.length - 1];
|
||||
System.arraycopy(challenge, 1, correct_challenge, 0, challenge.length - 1);
|
||||
ArrayUtils.reverse(correct_challenge);
|
||||
BigInteger c = new BigInteger(1, mac_as_bytes).add(new BigInteger(1, correct_challenge));
|
||||
|
||||
BigInteger max64 = BigInteger.ONE.add(BigInteger.ONE).pow(64).subtract(BigInteger.ONE);
|
||||
BigInteger tmp1 = key2.xor(max64);
|
||||
BigInteger result1;
|
||||
if (c.compareTo(tmp1) > 0) {
|
||||
result1 = c.add(key1).subtract(tmp1);
|
||||
} else {
|
||||
result1 = key1;
|
||||
}
|
||||
|
||||
BigInteger result2;
|
||||
if (key2.remainder(BigInteger.ONE.add(BigInteger.ONE)).compareTo(BigInteger.ONE) == 0) {
|
||||
result2 = key2.add(c);
|
||||
} else {
|
||||
result2 = key2.multiply(BigInteger.ONE.add(BigInteger.ONE)).add(c);
|
||||
}
|
||||
|
||||
return byteArraysConcatReverseWithPad(result1.toByteArray(), result2.toByteArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverses the password generation into keys
|
||||
* <p>
|
||||
* This assumes you have:
|
||||
* The MAC of the device, the challenge and response payload
|
||||
* <p>
|
||||
* See also {@link NutKey#passwordGeneration}
|
||||
*
|
||||
* @param challenge the RECEIVED and COMPLETE payload (truncated accordingly)
|
||||
* @param response the SENT and COMPLETE payload
|
||||
* @param deviceMac colon-separated MAC address of the Nut as a string
|
||||
*/
|
||||
public static Map.Entry<BigInteger, BigInteger> reversePasswordGeneration(byte[] challenge,
|
||||
byte[] response,
|
||||
String deviceMac) {
|
||||
// The two arrays that were concat. with byteArraysConcatReverseWithPad(orig1, orig2)
|
||||
byte[] original1 = new byte[8];
|
||||
byte[] original2 = new byte[8];
|
||||
|
||||
// The response without preamble
|
||||
if (response[0] != 0x02) {
|
||||
throw new IllegalArgumentException("Response always begins with 0x02");
|
||||
}
|
||||
byte[] cleanResponse = new byte[16];
|
||||
System.arraycopy(response, 1, cleanResponse, 0, cleanResponse.length);
|
||||
|
||||
// The challenge without preamble
|
||||
byte[] cleanChall = new byte[4];
|
||||
System.arraycopy(challenge, 1, cleanChall, 0, cleanChall.length);
|
||||
|
||||
// Reverse the two arrays sent as a response
|
||||
byteArraysDeConcatReverseWithPad(cleanResponse, original1, original2);
|
||||
|
||||
// Two common components in the equation
|
||||
BigInteger a = new BigInteger(1, macAsByteArray(deviceMac));
|
||||
byte[] cleanChallTmp = cleanChall.clone();
|
||||
ArrayUtils.reverse(cleanChallTmp);
|
||||
BigInteger b = new BigInteger(1, cleanChallTmp);
|
||||
BigInteger c = a.add(b);
|
||||
BigInteger max64 = BigInteger.ONE.add(BigInteger.ONE).pow(64).subtract(BigInteger.ONE);
|
||||
|
||||
// We don't know actual keys used yet
|
||||
// There's two possibilities,
|
||||
// either it's directly what's in the packet
|
||||
// orig1 = key1
|
||||
BigInteger key1a = new BigInteger(1, original1);
|
||||
// Or it's derived from key2 using this formula :S
|
||||
// orig1 = c + key1 - (key2 XOR (2^64 - 1)
|
||||
// see below when it might be needed
|
||||
|
||||
// It's either just
|
||||
// orig2 = key2 + c
|
||||
BigInteger key2a = new BigInteger(1, original2).subtract(c);
|
||||
// alternatively
|
||||
// orig2 = 2 * key2 + c
|
||||
BigInteger key2b = new BigInteger(1, original2).multiply(BigInteger.ONE.add(BigInteger.ONE)).subtract(c);
|
||||
|
||||
// Now we have key2a, key2b, key1a,
|
||||
// trying to determine if we can do with just those,
|
||||
// or need to continue
|
||||
byte[] key1a2aresult = passwordGeneration(deviceMac, challenge, key1a, key2a);
|
||||
LOG.debug("Result1a2a:02 %s\n", bytesToHex2(key1a2aresult));
|
||||
|
||||
if (java.util.Arrays.equals(key1a2aresult, cleanResponse)) {
|
||||
LOG.debug("Found key1a & key2a are correct, DONE!");
|
||||
return new AbstractMap.SimpleEntry<>(key1a, key2a);
|
||||
}
|
||||
// Unsuccessful, let's try key1a with key2b
|
||||
|
||||
byte[] key1a2bresult = passwordGeneration(deviceMac, challenge, key1a, key2b);
|
||||
LOG.debug("Result1a2b:02 %s\n", bytesToHex2(key1a2bresult));
|
||||
|
||||
if (java.util.Arrays.equals(key1a2bresult, cleanResponse)) {
|
||||
LOG.debug("Found key1a & key2b are correct, DONE!");
|
||||
return new AbstractMap.SimpleEntry<>(key1a, key2b);
|
||||
}
|
||||
|
||||
// If we're still not done, we have to calculate two possible key1b-s
|
||||
// one for key2a, other for key2b
|
||||
// key1 = c + (key2 XOR (2^64 - 1) + orig1
|
||||
BigInteger key1b2a = c.add(key2a.xor(max64)).add(new BigInteger(1, original1));
|
||||
byte[] key1b2aresult = passwordGeneration(deviceMac, challenge, key1b2a, key2a);
|
||||
LOG.debug("Result1b2a:02 %s\n", bytesToHex2(key1b2aresult));
|
||||
|
||||
if (java.util.Arrays.equals(key1b2aresult, cleanResponse)) {
|
||||
LOG.debug("Found key1b2a & key2b are correct, DONE!");
|
||||
return new AbstractMap.SimpleEntry<>(key1b2a, key2b);
|
||||
}
|
||||
|
||||
BigInteger key1b2b = c.add(key2b.xor(max64)).add(new BigInteger(1, original1));
|
||||
byte[] key1b2bresult = passwordGeneration(deviceMac, challenge, key1b2b, key2b);
|
||||
LOG.debug("Result1b2b:02 %s\n", bytesToHex2(key1b2bresult));
|
||||
|
||||
if (java.util.Arrays.equals(key1b2bresult, cleanResponse)) {
|
||||
LOG.debug("Found key1b2b & key2b are correct, DONE!");
|
||||
return new AbstractMap.SimpleEntry<>(key1b2b, key2b);
|
||||
}
|
||||
|
||||
LOG.warn("Input might be incorrect, a correct key was not found");
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns the MAC address into an array of bytes
|
||||
*
|
||||
* @param address MAC address
|
||||
* @return byte[] containing the MAC address bytes
|
||||
*/
|
||||
public static byte[] macAsByteArray(String address) {
|
||||
//noinspection DynamicRegexReplaceableByCompiledPattern
|
||||
return hexStringToByteArrayNut(address.replace(":", ""));
|
||||
}
|
||||
|
||||
/**
|
||||
* Concatenates two byte arrays in reverse and pads with zeros
|
||||
*
|
||||
* @param arr1 first array to concatenate
|
||||
* @param arr2 second array to concatenate
|
||||
* @return 16 bytes that contain the array in reverse, zeros if any parameter is empty
|
||||
*/
|
||||
public static byte[] byteArraysConcatReverseWithPad(byte[] arr1, byte[] arr2) {
|
||||
byte[] result = new byte[16];
|
||||
for (int i = 0; i < Math.min(arr2.length, 8); i++) {
|
||||
// Reverse the array - 0-indexed - start shorter arrays from "0" - byte index to handle
|
||||
result[8 - 1 - (8 - Math.min(arr2.length, 8)) - i] = arr2[i + Math.max((arr2.length - 8), 0)];
|
||||
}
|
||||
for (int i = 0; i < Math.min(arr1.length, 8); i++) {
|
||||
result[16 - 1 - (8 - Math.min(arr1.length, 8)) - i] = arr1[i + Math.max((arr1.length - 8), 0)];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* De-concatenates two byte arrays in reverse,
|
||||
* places them in specified destinations,
|
||||
* <p>
|
||||
* 16 bytes that contain the array in reverse,
|
||||
* zeros if any parameter is empty
|
||||
*
|
||||
* @param input array to de-concatenate, 16 bytes
|
||||
*/
|
||||
public static void byteArraysDeConcatReverseWithPad(byte[] input, byte[] dest1, byte[] dest2) {
|
||||
if (input.length != 16) {
|
||||
throw new IllegalArgumentException("Input must be 16 bytes!");
|
||||
}
|
||||
for (int i = 0; i < 8; i++) {
|
||||
dest2[8 - 1 - i] = input[i];
|
||||
}
|
||||
for (int i = 8; i < 16; i++) {
|
||||
dest1[16 - 1 - i] = input[i];
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/* Copyright (C) 2020 Taavi Eomäe
|
||||
|
||||
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.pinetime;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import no.nordicsemi.android.dfu.DfuBaseService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.FwAppInstallerActivity;
|
||||
|
||||
public class PineTimeDFUService extends DfuBaseService {
|
||||
@Override
|
||||
protected Class<? extends Activity> getNotificationTarget() {
|
||||
return FwAppInstallerActivity.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDebug() {
|
||||
return BuildConfig.DEBUG;
|
||||
}
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
/* Copyright (C) 2020 Taavi Eomäe
|
||||
|
||||
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.pinetime;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
|
||||
|
||||
public class PineTimeInstallHandler implements InstallHandler {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PineTimeInstallHandler.class);
|
||||
|
||||
private final Context context;
|
||||
private boolean valid = false;
|
||||
private String version = "(Unknown version)";
|
||||
|
||||
public PineTimeInstallHandler(Uri uri, Context context) {
|
||||
this.context = context;
|
||||
UriHelper uriHelper;
|
||||
try {
|
||||
uriHelper = UriHelper.get(uri, this.context);
|
||||
} catch (IOException e) {
|
||||
valid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try (InputStream in = new BufferedInputStream(uriHelper.openInputStream())) {
|
||||
byte[] bytes = new byte[32];
|
||||
int read = in.read(bytes);
|
||||
if (read < 32) {
|
||||
valid = false;
|
||||
return;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
valid = false;
|
||||
return;
|
||||
}
|
||||
valid = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateInstallation(InstallActivity installActivity, GBDevice device) {
|
||||
if (device.isBusy()) {
|
||||
installActivity.setInfoText(device.getBusyTask());
|
||||
installActivity.setInstallEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (device.getType() != DeviceType.PINETIME_JF || !device.isConnected()) {
|
||||
installActivity.setInfoText("Firmware cannot be installed");
|
||||
installActivity.setInstallEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
GenericItem installItem = new GenericItem();
|
||||
installItem.setIcon(R.drawable.ic_firmware);
|
||||
installItem.setName("PineTime firmware");
|
||||
installItem.setDetails(version);
|
||||
|
||||
installActivity.setInfoText(context.getString(R.string.firmware_install_warning, "(unknown)"));
|
||||
installActivity.setInstallEnabled(true);
|
||||
installActivity.setInstallItem(installItem);
|
||||
LOG.debug("Initialized PineTimeInstallHandler");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onStartInstall(GBDevice device) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return valid;
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.pinetime;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class PineTimeJFConstants {
|
||||
public static final UUID UUID_SERVICE_MUSIC_CONTROL = UUID.fromString("c7e50001-00fc-48fe-8e23-433b3a1942d0");
|
||||
|
||||
public static final UUID UUID_CHARACTERISTICS_MUSIC_EVENT = UUID.fromString("c7e50002-00fc-48fe-8e23-433b3a1942d0");
|
||||
public static final UUID UUID_CHARACTERISTICS_MUSIC_STATUS = UUID.fromString("c7e50003-00fc-48fe-8e23-433b3a1942d0");
|
||||
public static final UUID UUID_CHARACTERISTICS_MUSIC_ARTIST = UUID.fromString("c7e50004-00fc-48fe-8e23-433b3a1942d0");
|
||||
public static final UUID UUID_CHARACTERISTICS_MUSIC_TRACK = UUID.fromString("c7e50005-00fc-48fe-8e23-433b3a1942d0");
|
||||
public static final UUID UUID_CHARACTERISTICS_MUSIC_ALBUM = UUID.fromString("c7e50006-00fc-48fe-8e23-433b3a1942d0");
|
||||
public static final UUID UUID_CHARACTERISTICS_MUSIC_POSITION = UUID.fromString("c7e50007-00fc-48fe-8e23-433b3a1942d0");
|
||||
public static final UUID UUID_CHARACTERISTICS_MUSIC_LENGTH_TOTAL = UUID.fromString("c7e50008-00fc-48fe-8e23-433b3a1942d0");
|
||||
public static final UUID UUID_CHARACTERISTICS_MUSIC_TRACK_NUMBER = UUID.fromString("c7e50009-00fc-48fe-8e23-433b3a1942d0");
|
||||
public static final UUID UUID_CHARACTERISTICS_MUSIC_TRACK_TOTAL = UUID.fromString("c7e5000a-00fc-48fe-8e23-433b3a1942d0");
|
||||
public static final UUID UUID_CHARACTERISTICS_MUSIC_PLAYBACK_SPEED = UUID.fromString("c7e5000b-00fc-48fe-8e23-433b3a1942d0");
|
||||
public static final UUID UUID_CHARACTERISTICS_MUSIC_REPEAT = UUID.fromString("c7e5000c-00fc-48fe-8e23-433b3a1942d0");
|
||||
public static final UUID UUID_CHARACTERISTICS_MUSIC_SHUFFLE = UUID.fromString("c7e5000d-00fc-48fe-8e23-433b3a1942d0");
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2020 Andreas Shimokawa
|
||||
/* Copyright (C) 2020 Andreas Shimokawa, Taavi Eomäe
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -55,7 +55,8 @@ public class PineTimeJFCoordinator extends AbstractDeviceCoordinator {
|
||||
|
||||
@Override
|
||||
public InstallHandler findInstallHandler(Uri uri, Context context) {
|
||||
return null;
|
||||
PineTimeInstallHandler handler = new PineTimeInstallHandler(uri, context);
|
||||
return handler.isValid() ? handler : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,147 @@
|
||||
/* Copyright (C) 2015-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, José Rebelo, Matthieu Baerts, Nephiel, vanous, opavlov
|
||||
|
||||
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.sonyswr12;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
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.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
|
||||
public class SonySWR12DeviceCoordinator extends AbstractDeviceCoordinator {
|
||||
|
||||
@Override
|
||||
public DeviceType getDeviceType() {
|
||||
return DeviceType.SONY_SWR12;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
|
||||
try {
|
||||
String name = candidate.getDevice().getName();
|
||||
if (name != null && !name.isEmpty() && name.toLowerCase().contains("swr12"))
|
||||
return getDeviceType();
|
||||
} catch (Exception exc){}
|
||||
return DeviceType.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturer() {
|
||||
return "Sony";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Class<? extends Activity> getPairingActivity() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityDataFetching() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityTracking() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
|
||||
return new SonySWR12SampleProvider(device, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstallHandler findInstallHandler(Uri uri, Context context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsScreenshots() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAlarmSlotCount() {
|
||||
return 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsSmartWakeup(GBDevice device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAppsManagement() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Activity> getAppsManagementActivity() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCalendarEvents() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRealtimeData() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsWeather() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsFindDevice() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||
return new int[]{R.xml.devicesettings_sonyswr12};
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, opavlov
|
||||
|
||||
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.sonyswr12;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import de.greenrobot.dao.AbstractDao;
|
||||
import de.greenrobot.dao.Property;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.SonySWR12Sample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.SonySWR12SampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.SonySWR12Constants;
|
||||
|
||||
public class SonySWR12SampleProvider extends AbstractSampleProvider<SonySWR12Sample> {
|
||||
public SonySWR12SampleProvider(GBDevice device, DaoSession session) {
|
||||
super(device, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractDao<SonySWR12Sample, ?> getSampleDao() {
|
||||
return getSession().getSonySWR12SampleDao();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Property getRawKindSampleProperty() {
|
||||
return SonySWR12SampleDao.Properties.RawKind;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Property getTimestampSampleProperty() {
|
||||
return SonySWR12SampleDao.Properties.Timestamp;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Property getDeviceIdentifierSampleProperty() {
|
||||
return SonySWR12SampleDao.Properties.DeviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int normalizeType(int rawType) {
|
||||
switch (rawType) {
|
||||
case SonySWR12Constants.TYPE_ACTIVITY:
|
||||
return ActivityKind.TYPE_ACTIVITY;
|
||||
case SonySWR12Constants.TYPE_LIGHT:
|
||||
return ActivityKind.TYPE_LIGHT_SLEEP;
|
||||
case SonySWR12Constants.TYPE_DEEP:
|
||||
return ActivityKind.TYPE_DEEP_SLEEP;
|
||||
case SonySWR12Constants.TYPE_NOT_WORN:
|
||||
return ActivityKind.TYPE_NOT_WORN;
|
||||
}
|
||||
return ActivityKind.TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int toRawActivityKind(int activityKind) {
|
||||
switch (activityKind) {
|
||||
case ActivityKind.TYPE_ACTIVITY:
|
||||
return SonySWR12Constants.TYPE_ACTIVITY;
|
||||
case ActivityKind.TYPE_LIGHT_SLEEP:
|
||||
return SonySWR12Constants.TYPE_LIGHT;
|
||||
case ActivityKind.TYPE_DEEP_SLEEP:
|
||||
return SonySWR12Constants.TYPE_DEEP;
|
||||
case ActivityKind.TYPE_NOT_WORN:
|
||||
return SonySWR12Constants.TYPE_NOT_WORN;
|
||||
}
|
||||
return SonySWR12Constants.TYPE_ACTIVITY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float normalizeIntensity(int rawIntensity) {
|
||||
return rawIntensity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SonySWR12Sample createActivitySample() {
|
||||
return new SonySWR12Sample();
|
||||
}
|
||||
}
|
@ -295,7 +295,7 @@ public class NotificationListener extends NotificationListenerService {
|
||||
}
|
||||
}
|
||||
|
||||
String source = sbn.getPackageName().toLowerCase();
|
||||
String source = sbn.getPackageName();
|
||||
Notification notification = sbn.getNotification();
|
||||
|
||||
Long notificationOldRepeatPreventionValue = notificationOldRepeatPrevention.get(source);
|
||||
|
@ -73,8 +73,11 @@ public enum DeviceType {
|
||||
TLW64(180, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_tlw64),
|
||||
PINETIME_JF(190, R.drawable.ic_device_pinetime, R.drawable.ic_device_pinetime_disabled, R.string.devicetype_pinetime_jf),
|
||||
MIJIA_LYWSD02(200, R.drawable.ic_device_pebble, R.drawable.ic_device_pebble_disabled, R.string.devicetype_mijia_lywsd02),
|
||||
LEFUN(210, R.drawable.ic_device_h30_h10, R.drawable.ic_device_h30_h10_disabled, R.string.devicetype_lefun),
|
||||
ITAG(250, R.drawable.ic_device_itag, R.drawable.ic_device_itag_disabled, R.string.devicetype_itag),
|
||||
NUTMINI(251, R.drawable.ic_device_itag, R.drawable.ic_device_itag_disabled, R.string.devicetype_nut_mini),
|
||||
VIBRATISSIMO(300, R.drawable.ic_device_lovetoy, R.drawable.ic_device_lovetoy_disabled, R.string.devicetype_vibratissimo),
|
||||
SONY_SWR12(310, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_sonyswr12),
|
||||
TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_test);
|
||||
private final int key;
|
||||
@DrawableRes
|
||||
|
@ -20,11 +20,13 @@ import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.text.Collator;
|
||||
import java.util.Objects;
|
||||
|
||||
public class GenericItem implements ItemWithDetails {
|
||||
private String name;
|
||||
private String details;
|
||||
private int icon;
|
||||
private boolean warning = false;
|
||||
|
||||
public static final Parcelable.Creator CREATOR = new Parcelable.Creator<GenericItem>() {
|
||||
@Override
|
||||
@ -56,9 +58,9 @@ public class GenericItem implements ItemWithDetails {
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(getName());
|
||||
dest.writeString(getDetails());
|
||||
dest.writeInt(getIcon());
|
||||
dest.writeString(name);
|
||||
dest.writeString(details);
|
||||
dest.writeInt(icon);
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
@ -66,6 +68,9 @@ public class GenericItem implements ItemWithDetails {
|
||||
}
|
||||
|
||||
public void setDetails(String details) {
|
||||
if (details.equals("(Unknown version)")) {
|
||||
this.warning = true;
|
||||
}
|
||||
this.details = details;
|
||||
}
|
||||
|
||||
@ -73,6 +78,14 @@ public class GenericItem implements ItemWithDetails {
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
public boolean getWarning() {
|
||||
return this.warning;
|
||||
}
|
||||
|
||||
public void setWarning(boolean enable) {
|
||||
this.warning = enable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
@ -95,32 +108,36 @@ public class GenericItem implements ItemWithDetails {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
|
||||
GenericItem that = (GenericItem) o;
|
||||
|
||||
return !(getName() != null ? !getName().equals(that.getName()) : that.getName() != null);
|
||||
|
||||
return Objects.equals(name, that.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getName() != null ? getName().hashCode() : 0;
|
||||
return name != null ? name.hashCode() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ItemWithDetails another) {
|
||||
if (getName().equals(another.getName())) {
|
||||
if (name.equals(another.getName())) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (getName() == null) {
|
||||
if (name == null) {
|
||||
return +1;
|
||||
} else if (another.getName() == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return Collator.getInstance().compare(getName(), another.getName());
|
||||
return Collator.getInstance().compare(name, another.getName());
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,8 @@ package nodomain.freeyourgadget.gadgetbridge.model;
|
||||
import java.util.Objects;
|
||||
|
||||
public class MusicSpec {
|
||||
public static final int MUSIC_UNKNOWN = -1;
|
||||
|
||||
public static final int MUSIC_UNDEFINED = 0;
|
||||
public static final int MUSIC_PLAY = 1;
|
||||
public static final int MUSIC_PAUSE = 2;
|
||||
@ -27,12 +29,12 @@ public class MusicSpec {
|
||||
public static final int MUSIC_NEXT = 4;
|
||||
public static final int MUSIC_PREVIOUS = 5;
|
||||
|
||||
public String artist;
|
||||
public String album;
|
||||
public String track;
|
||||
public int duration;
|
||||
public int trackCount;
|
||||
public int trackNr;
|
||||
public String artist = null;
|
||||
public String album = null;
|
||||
public String track = null;
|
||||
public int duration = MUSIC_UNKNOWN;
|
||||
public int trackCount = MUSIC_UNKNOWN;
|
||||
public int trackNr = MUSIC_UNKNOWN;
|
||||
|
||||
public MusicSpec() {
|
||||
|
||||
|
@ -17,20 +17,26 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.model;
|
||||
|
||||
/**
|
||||
* Created by steffen on 07.06.16.
|
||||
*/
|
||||
public class MusicStateSpec {
|
||||
public static final int STATE_PLAYING = 0;
|
||||
public static final int STATE_PAUSED = 1;
|
||||
public static final int STATE_STOPPED = 2;
|
||||
public static final int STATE_UNKNOWN = 3;
|
||||
public static final int STATE_UNKNOWN = -1;
|
||||
|
||||
public byte state;
|
||||
public int position; // Position of the current media in seconds
|
||||
public int playRate; // Speed of playback, usually 0 or 100 (full speed)
|
||||
public byte shuffle;
|
||||
public byte repeat;
|
||||
public static final int STATE_PLAYING = 0;
|
||||
public static final int STATE_PAUSED = 1;
|
||||
public static final int STATE_STOPPED = 2;
|
||||
|
||||
public static final int STATE_SHUFFLE_ENABLED = 1;
|
||||
|
||||
public byte state = STATE_UNKNOWN;
|
||||
/**
|
||||
* Position of the current media in seconds
|
||||
*/
|
||||
public int position = STATE_UNKNOWN;
|
||||
/**
|
||||
* Speed of playback, usually 0 or 100 (full speed)
|
||||
*/
|
||||
public int playRate = STATE_UNKNOWN;
|
||||
public byte shuffle = STATE_UNKNOWN;
|
||||
public byte repeat = STATE_UNKNOWN;
|
||||
|
||||
public MusicStateSpec() {
|
||||
|
||||
|
@ -21,6 +21,7 @@ import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.companion.CompanionDeviceManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
@ -29,6 +30,7 @@ import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.telephony.SmsManager;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.content.FileProvider;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
@ -195,24 +197,35 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
private void handleGBDeviceEventFindPhoneStartNotification() {
|
||||
LOG.info("Got handleGBDeviceEventFindPhoneStartNotification");
|
||||
Intent intent = new Intent(context, FindPhoneActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
|
||||
PendingIntent pi = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
NotificationCompat.Builder notification = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID );
|
||||
notification
|
||||
NotificationCompat.Builder notification = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID )
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setOngoing(false)
|
||||
.setFullScreenIntent(pi, true)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setAutoCancel(true)
|
||||
.setContentTitle( context.getString( R.string.find_my_phone_notification ) );
|
||||
|
||||
notification.setGroup("BackgroundService");
|
||||
|
||||
notificationManager.notify( GB.NOTIFICATION_ID_PHONE_FIND, notification.build());
|
||||
CompanionDeviceManager manager = (CompanionDeviceManager) context.getSystemService(Context.COMPANION_DEVICE_SERVICE);
|
||||
if (manager.getAssociations().size() > 0) {
|
||||
notificationManager.notify(GB.NOTIFICATION_ID_PHONE_FIND, notification.build());
|
||||
context.startActivity(intent);
|
||||
LOG.debug("CompanionDeviceManager associations were found, starting intent");
|
||||
} else {
|
||||
notificationManager.notify(GB.NOTIFICATION_ID_PHONE_FIND, notification.build());
|
||||
LOG.warn("CompanionDeviceManager associations were not found, can't start intent");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -51,16 +51,19 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.id115.ID115Support;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.itag.ITagSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.BFH16DeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.TeclastH30.TeclastH30Support;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.liveview.LiveviewSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.makibeshr3.MakibesHR3DeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.mijia_lywsd02.MijiaLywsd02Support;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miscale2.MiScale2DeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.no1f1.No1F1Support;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.nut.NutSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.pinetime.PineTimeJFSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.roidmi.RoidmiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.SonySWR12DeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.tlw64.TLW64Support;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.vibratissimo.VibratissimoSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.watch9.Watch9DeviceSupport;
|
||||
@ -248,6 +251,9 @@ public class DeviceSupportFactory {
|
||||
case ITAG:
|
||||
deviceSupport = new ServiceDeviceSupport(new ITagSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||
break;
|
||||
case NUTMINI:
|
||||
deviceSupport = new ServiceDeviceSupport(new NutSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||
break;
|
||||
case BANGLEJS:
|
||||
deviceSupport = new ServiceDeviceSupport(new BangleJSDeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||
break;
|
||||
@ -260,6 +266,12 @@ public class DeviceSupportFactory {
|
||||
case SG2:
|
||||
deviceSupport = new ServiceDeviceSupport(new HPlusSupport(DeviceType.SG2), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||
break;
|
||||
case LEFUN:
|
||||
deviceSupport = new ServiceDeviceSupport(new LefunDeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||
break;
|
||||
case SONY_SWR12:
|
||||
deviceSupport = new ServiceDeviceSupport(new SonySWR12DeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||
break;
|
||||
}
|
||||
if (deviceSupport != null) {
|
||||
deviceSupport.setContext(gbDevice, mBtAdapter, mContext);
|
||||
|
@ -40,11 +40,13 @@ public class MiBand5FirmwareInfo extends HuamiFirmwareInfo {
|
||||
crcToVersion.put(29062, "1.0.0.76");
|
||||
crcToVersion.put(26302, "1.0.1.16");
|
||||
crcToVersion.put(26666, "1.0.1.32");
|
||||
crcToVersion.put(54599, "1.0.2.08");
|
||||
|
||||
// resources
|
||||
crcToVersion.put(8009, "1.0.0.76");
|
||||
crcToVersion.put(47040, "1.0.1.16");
|
||||
crcToVersion.put(49094, "1.0.1.32");
|
||||
crcToVersion.put(18506, "1.0.2.08");
|
||||
|
||||
// font
|
||||
crcToVersion.put(31978, "1");
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,101 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.requests;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.NotificationCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
|
||||
|
||||
public abstract class AbstractSendNotificationRequest extends Request {
|
||||
protected AbstractSendNotificationRequest(LefunDeviceSupport support, TransactionBuilder builder) {
|
||||
super(support, builder);
|
||||
}
|
||||
|
||||
protected abstract String getMessage();
|
||||
|
||||
protected abstract byte getNotificationType();
|
||||
|
||||
protected abstract byte getExtendedNotificationType();
|
||||
|
||||
@Override
|
||||
public byte[] createRequest() {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPerform() throws IOException {
|
||||
byte notificationType = getNotificationType();
|
||||
byte extendedNotificationType = getExtendedNotificationType();
|
||||
boolean reserveSpaceForExtended = notificationType == NotificationCommand.SERVICE_TYPE_EXTENDED;
|
||||
byte[] encoded = getMessage().getBytes(StandardCharsets.UTF_8);
|
||||
ByteBuffer buffer = ByteBuffer.wrap(encoded);
|
||||
|
||||
BluetoothGattCharacteristic characteristic = getSupport()
|
||||
.getCharacteristic(LefunConstants.UUID_CHARACTERISTIC_LEFUN_WRITE);
|
||||
|
||||
List<NotificationCommand> commandList = new ArrayList<>();
|
||||
int charsWritten = 0;
|
||||
for (int i = 0; i < 0xff; ++i) {
|
||||
int maxPayloadLength = NotificationCommand.MAX_PAYLOAD_LENGTH;
|
||||
if (reserveSpaceForExtended) maxPayloadLength -= 1;
|
||||
maxPayloadLength = Math.min(maxPayloadLength, buffer.limit() - buffer.position());
|
||||
maxPayloadLength = Math.min(maxPayloadLength, NotificationCommand.MAX_MESSAGE_LENGTH - charsWritten);
|
||||
if (maxPayloadLength == 0 && i != 0) break;
|
||||
|
||||
byte[] payload = new byte[maxPayloadLength];
|
||||
buffer.get(payload);
|
||||
|
||||
NotificationCommand cmd = new NotificationCommand();
|
||||
cmd.setServiceType(notificationType);
|
||||
cmd.setExtendedServiceType(extendedNotificationType);
|
||||
cmd.setCurrentPiece((byte) (i + 1));
|
||||
cmd.setPayload(payload);
|
||||
charsWritten += maxPayloadLength;
|
||||
|
||||
commandList.add(cmd);
|
||||
}
|
||||
|
||||
for (NotificationCommand cmd : commandList) {
|
||||
cmd.setTotalPieces((byte) commandList.size());
|
||||
builder.write(characteristic, cmd.serialize());
|
||||
}
|
||||
|
||||
if (isSelfQueue())
|
||||
getSupport().performConnected(builder.getTransaction());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return LefunConstants.CMD_NOTIFICATION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean expectsResponse() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.requests;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.FindDeviceCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
|
||||
|
||||
public class FindDeviceRequest extends Request {
|
||||
public FindDeviceRequest(LefunDeviceSupport support, TransactionBuilder builder) {
|
||||
super(support, builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] createRequest() {
|
||||
FindDeviceCommand cmd = new FindDeviceCommand();
|
||||
return cmd.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return LefunConstants.CMD_FIND_DEVICE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(byte[] data) {
|
||||
FindDeviceCommand cmd = new FindDeviceCommand();
|
||||
cmd.deserialize(data);
|
||||
|
||||
if (!cmd.isSuccess())
|
||||
reportFailure("Could not initiate find device");
|
||||
|
||||
operationStatus = OperationStatus.FINISHED;
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.requests;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetActivityDataCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
|
||||
|
||||
public class GetActivityDataRequest extends MultiFetchRequest {
|
||||
private int daysAgo;
|
||||
|
||||
public GetActivityDataRequest(LefunDeviceSupport support) {
|
||||
super(support);
|
||||
}
|
||||
|
||||
public int getDaysAgo() {
|
||||
return daysAgo;
|
||||
}
|
||||
|
||||
public void setDaysAgo(int daysAgo) {
|
||||
this.daysAgo = daysAgo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] createRequest() {
|
||||
GetActivityDataCommand cmd = new GetActivityDataCommand();
|
||||
cmd.setDaysAgo((byte) daysAgo);
|
||||
return cmd.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(byte[] data) {
|
||||
GetActivityDataCommand cmd = new GetActivityDataCommand();
|
||||
cmd.deserialize(data);
|
||||
|
||||
if (daysAgo != (cmd.getDaysAgo() & 0xff)) {
|
||||
throw new IllegalArgumentException("Mismatching days ago");
|
||||
}
|
||||
|
||||
if (totalRecords == -1) {
|
||||
totalRecords = cmd.getTotalRecords() & 0xff;
|
||||
} else if (totalRecords != (cmd.getTotalRecords() & 0xff)) {
|
||||
throw new IllegalArgumentException("Total records mismatch");
|
||||
}
|
||||
|
||||
if (totalRecords != 0) {
|
||||
int currentRecord = cmd.getCurrentRecord() & 0xff;
|
||||
if (lastRecord + 1 != currentRecord) {
|
||||
throw new IllegalArgumentException("Records received out of sequence");
|
||||
}
|
||||
lastRecord = currentRecord;
|
||||
|
||||
getSupport().handleActivityData(cmd);
|
||||
} else {
|
||||
lastRecord = totalRecords;
|
||||
}
|
||||
|
||||
if (lastRecord == totalRecords)
|
||||
operationFinished();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return LefunConstants.CMD_ACTIVITY_DATA;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getOperationName() {
|
||||
return "Getting activity data";
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.requests;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetBatteryLevelCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
|
||||
|
||||
public class GetBatteryLevelRequest extends Request {
|
||||
public GetBatteryLevelRequest(LefunDeviceSupport support, TransactionBuilder builder) {
|
||||
super(support, builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] createRequest() {
|
||||
GetBatteryLevelCommand cmd = new GetBatteryLevelCommand();
|
||||
return cmd.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(byte[] data) {
|
||||
GetBatteryLevelCommand cmd = new GetBatteryLevelCommand();
|
||||
cmd.deserialize(data);
|
||||
|
||||
GBDevice device = getSupport().getDevice();
|
||||
device.setBatteryThresholdPercent((short)15);
|
||||
|
||||
GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
|
||||
batteryInfo.level = (short)((int)cmd.getBatteryLevel() & 0xff);
|
||||
getSupport().evaluateGBDeviceEvent(batteryInfo);
|
||||
|
||||
operationStatus = OperationStatus.FINISHED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return LefunConstants.CMD_BATTERY_LEVEL;
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.requests;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.FeaturesCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
|
||||
|
||||
public class GetEnabledFeaturesRequest extends Request {
|
||||
public GetEnabledFeaturesRequest(LefunDeviceSupport support, TransactionBuilder builder) {
|
||||
super(support, builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] createRequest() {
|
||||
FeaturesCommand cmd = new FeaturesCommand();
|
||||
|
||||
cmd.setOp(BaseCommand.OP_GET);
|
||||
|
||||
return cmd.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(byte[] data) {
|
||||
FeaturesCommand cmd = new FeaturesCommand();
|
||||
cmd.deserialize(data);
|
||||
if (cmd.getOp() == BaseCommand.OP_GET) {
|
||||
getSupport().receiveEnabledFeaturesSetting(cmd);
|
||||
}
|
||||
|
||||
operationStatus = OperationStatus.FINISHED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return LefunConstants.CMD_FEATURES;
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.requests;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetFirmwareInfoCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
|
||||
|
||||
public class GetFirmwareInfoRequest extends Request {
|
||||
public GetFirmwareInfoRequest(LefunDeviceSupport support, TransactionBuilder builder) {
|
||||
super(support, builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] createRequest() {
|
||||
GetFirmwareInfoCommand cmd = new GetFirmwareInfoCommand();
|
||||
return cmd.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(byte[] data) {
|
||||
GetFirmwareInfoCommand cmd = new GetFirmwareInfoCommand();
|
||||
cmd.deserialize(data);
|
||||
|
||||
int hardwareVersion = cmd.getHardwareVersion() & 0xffff;
|
||||
int softwareVersion = cmd.getSoftwareVersion() & 0xffff;
|
||||
|
||||
GBDeviceEventVersionInfo versionInfo = new GBDeviceEventVersionInfo();
|
||||
versionInfo.fwVersion = String.format("%d.%d", softwareVersion >> 8, softwareVersion & 0xff);
|
||||
// Last character is a \x1f? Not printable either way.
|
||||
versionInfo.hwVersion = cmd.getTypeCode().substring(0, 3);
|
||||
getSupport().evaluateGBDeviceEvent(versionInfo);
|
||||
|
||||
GBDevice device = getSupport().getDevice();
|
||||
device.setFirmwareVersion2(String.format("%d.%d", hardwareVersion >> 8, hardwareVersion & 0xff));
|
||||
|
||||
getSupport().completeInitialization();
|
||||
|
||||
operationStatus = OperationStatus.FINISHED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return LefunConstants.CMD_FIRMWARE_INFO;
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.requests;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.SettingsCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
|
||||
|
||||
public class GetGeneralSettingsRequest extends Request {
|
||||
public GetGeneralSettingsRequest(LefunDeviceSupport support, TransactionBuilder builder) {
|
||||
super(support, builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] createRequest() {
|
||||
SettingsCommand cmd = new SettingsCommand();
|
||||
|
||||
cmd.setOp(BaseCommand.OP_GET);
|
||||
|
||||
return cmd.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(byte[] data) {
|
||||
SettingsCommand cmd = new SettingsCommand();
|
||||
cmd.deserialize(data);
|
||||
if (cmd.getOp() == BaseCommand.OP_GET) {
|
||||
getSupport().receiveGeneralSettings(
|
||||
(int) cmd.getAmPmIndicator() & 0xff,
|
||||
(int) cmd.getMeasurementUnit() & 0xff
|
||||
);
|
||||
}
|
||||
|
||||
operationStatus = OperationStatus.FINISHED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return LefunConstants.CMD_SETTINGS;
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.requests;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.HydrationReminderIntervalCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
|
||||
|
||||
public class GetHydrationReminderIntervalRequest extends Request {
|
||||
public GetHydrationReminderIntervalRequest(LefunDeviceSupport support, TransactionBuilder builder) {
|
||||
super(support, builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] createRequest() {
|
||||
HydrationReminderIntervalCommand cmd = new HydrationReminderIntervalCommand();
|
||||
|
||||
cmd.setOp(BaseCommand.OP_GET);
|
||||
|
||||
return cmd.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(byte[] data) {
|
||||
HydrationReminderIntervalCommand cmd = new HydrationReminderIntervalCommand();
|
||||
cmd.deserialize(data);
|
||||
if (cmd.getOp() == BaseCommand.OP_GET) {
|
||||
getSupport().receiveHydrationReminderIntervalSetting((int) cmd.getHydrationReminderInterval() & 0xff);
|
||||
}
|
||||
|
||||
operationStatus = OperationStatus.FINISHED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return LefunConstants.CMD_HYDRATION_REMINDER_INTERVAL;
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.requests;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetPpgDataCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
|
||||
|
||||
public class GetPpgDataRequest extends MultiFetchRequest {
|
||||
private int ppgType;
|
||||
|
||||
public GetPpgDataRequest(LefunDeviceSupport support) {
|
||||
super(support);
|
||||
}
|
||||
|
||||
public int getPpgType() {
|
||||
return ppgType;
|
||||
}
|
||||
|
||||
public void setPpgType(int ppgType) {
|
||||
this.ppgType = ppgType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] createRequest() {
|
||||
GetPpgDataCommand cmd = new GetPpgDataCommand();
|
||||
cmd.setPpgType(ppgType);
|
||||
return cmd.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(byte[] data) {
|
||||
GetPpgDataCommand cmd = new GetPpgDataCommand();
|
||||
cmd.deserialize(data);
|
||||
|
||||
if (cmd.getPpgType() != ppgType) {
|
||||
throw new IllegalArgumentException("Mismatching PPG type");
|
||||
}
|
||||
|
||||
if (totalRecords == -1) {
|
||||
totalRecords = cmd.getTotalRecords() & 0xffff;
|
||||
} else if (totalRecords != (cmd.getTotalRecords() & 0xffff)) {
|
||||
throw new IllegalArgumentException("Total records mismatch");
|
||||
}
|
||||
|
||||
if (totalRecords != 0) {
|
||||
int currentRecord = cmd.getCurrentRecord() & 0xffff;
|
||||
if (lastRecord + 1 != currentRecord) {
|
||||
throw new IllegalArgumentException("Records received out of sequence");
|
||||
}
|
||||
lastRecord = currentRecord;
|
||||
|
||||
getSupport().handlePpgData(cmd);
|
||||
} else {
|
||||
lastRecord = totalRecords;
|
||||
}
|
||||
|
||||
if (lastRecord == totalRecords)
|
||||
operationFinished();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return LefunConstants.CMD_PPG_DATA;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getOperationName() {
|
||||
return "Getting PPG data";
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.requests;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.SedentaryReminderIntervalCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
|
||||
|
||||
public class GetSedentaryReminderIntervalRequest extends Request {
|
||||
public GetSedentaryReminderIntervalRequest(LefunDeviceSupport support, TransactionBuilder builder) {
|
||||
super(support, builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] createRequest() {
|
||||
SedentaryReminderIntervalCommand cmd = new SedentaryReminderIntervalCommand();
|
||||
|
||||
cmd.setOp(BaseCommand.OP_GET);
|
||||
|
||||
return cmd.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(byte[] data) {
|
||||
SedentaryReminderIntervalCommand cmd = new SedentaryReminderIntervalCommand();
|
||||
cmd.deserialize(data);
|
||||
if (cmd.getOp() == BaseCommand.OP_GET) {
|
||||
getSupport().receiveSedentaryReminderIntervalSetting((int) cmd.getSedentaryReminderInterval() & 0xff);
|
||||
}
|
||||
|
||||
operationStatus = OperationStatus.FINISHED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return LefunConstants.CMD_SEDENTARY_REMINDER_INTERVAL;
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.requests;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetSleepDataCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
|
||||
|
||||
public class GetSleepDataRequest extends MultiFetchRequest {
|
||||
private int daysAgo;
|
||||
|
||||
public GetSleepDataRequest(LefunDeviceSupport support) {
|
||||
super(support);
|
||||
}
|
||||
|
||||
public int getDaysAgo() {
|
||||
return daysAgo;
|
||||
}
|
||||
|
||||
public void setDaysAgo(int daysAgo) {
|
||||
this.daysAgo = daysAgo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] createRequest() {
|
||||
GetSleepDataCommand cmd = new GetSleepDataCommand();
|
||||
cmd.setDaysAgo((byte) daysAgo);
|
||||
return cmd.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(byte[] data) {
|
||||
GetSleepDataCommand cmd = new GetSleepDataCommand();
|
||||
cmd.deserialize(data);
|
||||
|
||||
if (daysAgo != (cmd.getDaysAgo() & 0xff)) {
|
||||
throw new IllegalArgumentException("Mismatching days ago");
|
||||
}
|
||||
|
||||
if (totalRecords == -1) {
|
||||
totalRecords = cmd.getTotalRecords() & 0xff;
|
||||
} else if (totalRecords != (cmd.getTotalRecords() & 0xff)) {
|
||||
throw new IllegalArgumentException("Total records mismatch");
|
||||
}
|
||||
|
||||
if (totalRecords != 0) {
|
||||
int currentRecord = cmd.getCurrentRecord() & 0xff;
|
||||
if (lastRecord + 1 != currentRecord) {
|
||||
throw new IllegalArgumentException("Records received out of sequence");
|
||||
}
|
||||
lastRecord = currentRecord;
|
||||
|
||||
getSupport().handleSleepData(cmd);
|
||||
} else {
|
||||
lastRecord = totalRecords;
|
||||
}
|
||||
|
||||
if (lastRecord == totalRecords)
|
||||
operationFinished();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return LefunConstants.CMD_SLEEP_DATA;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getOperationName() {
|
||||
return "Getting sleep data";
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.requests;
|
||||
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
/**
|
||||
* Represents a request that receives several responses
|
||||
*/
|
||||
public abstract class MultiFetchRequest extends Request {
|
||||
/**
|
||||
* Instantiates a new MultiFetchRequest
|
||||
* @param support the device support
|
||||
*/
|
||||
protected MultiFetchRequest(LefunDeviceSupport support) {
|
||||
super(support, null);
|
||||
removeAfterHandling = false;
|
||||
}
|
||||
|
||||
protected int lastRecord = 0;
|
||||
protected int totalRecords = -1;
|
||||
|
||||
@Override
|
||||
protected void prePerform() throws IOException {
|
||||
super.prePerform();
|
||||
builder = performInitialized(getClass().getSimpleName());
|
||||
if (getDevice().isBusy()) {
|
||||
throw new IllegalStateException("Device is busy");
|
||||
}
|
||||
builder.add(new SetDeviceBusyAction(getDevice(), getOperationName(), getContext()));
|
||||
builder.wait(1000); // Wait a bit (after previous operation), or device sometimes won't respond
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void operationFinished() {
|
||||
if (lastRecord == totalRecords)
|
||||
removeAfterHandling = true;
|
||||
try {
|
||||
super.operationFinished();
|
||||
TransactionBuilder builder = performInitialized("Finishing operation");
|
||||
builder.setGattCallback(null);
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException e) {
|
||||
GB.toast(getContext(), "Failed to reset callback", Toast.LENGTH_SHORT,
|
||||
GB.ERROR, e);
|
||||
}
|
||||
unsetBusy();
|
||||
operationStatus = OperationStatus.FINISHED;
|
||||
getSupport().runNextQueuedRequest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
|
||||
if (characteristic.getUuid().equals(LefunConstants.UUID_CHARACTERISTIC_LEFUN_NOTIFY)) {
|
||||
byte[] data = characteristic.getValue();
|
||||
// Parse response
|
||||
if (data.length >= LefunConstants.CMD_HEADER_LENGTH && data[0] == LefunConstants.CMD_RESPONSE_ID) {
|
||||
try {
|
||||
handleResponse(data);
|
||||
return true;
|
||||
} catch (IllegalArgumentException e) {
|
||||
log("Failed to handle response");
|
||||
operationFinished();
|
||||
}
|
||||
}
|
||||
|
||||
getSupport().logMessageContent(data);
|
||||
log("Invalid response received");
|
||||
return false;
|
||||
}
|
||||
|
||||
return super.onCharacteristicChanged(gatt, characteristic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSelfQueue() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the display operation name
|
||||
* @return the operation name
|
||||
*/
|
||||
protected abstract String getOperationName();
|
||||
}
|
@ -0,0 +1,153 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.requests;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
// Ripped from nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request
|
||||
|
||||
/**
|
||||
* Basic request for operations with Lefun devices
|
||||
*/
|
||||
public abstract class Request extends AbstractBTLEOperation<LefunDeviceSupport> {
|
||||
protected TransactionBuilder builder;
|
||||
protected boolean removeAfterHandling = true;
|
||||
private Logger logger = (Logger) LoggerFactory.getLogger(getName());
|
||||
|
||||
/**
|
||||
* Instantiates Request
|
||||
*
|
||||
* @param support the device support
|
||||
* @param builder the transaction builder to use
|
||||
*/
|
||||
protected Request(LefunDeviceSupport support, TransactionBuilder builder) {
|
||||
super(support);
|
||||
this.builder = builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the transaction builder
|
||||
*
|
||||
* @return the transaction builder
|
||||
*/
|
||||
public TransactionBuilder getTransactionBuilder() {
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPerform() throws IOException {
|
||||
BluetoothGattCharacteristic characteristic = getSupport()
|
||||
.getCharacteristic(LefunConstants.UUID_CHARACTERISTIC_LEFUN_WRITE);
|
||||
builder.write(characteristic, createRequest());
|
||||
if (isSelfQueue())
|
||||
getSupport().performConnected(builder.getTransaction());
|
||||
}
|
||||
|
||||
/**
|
||||
* When implemented in a subclass, provides the request bytes to send to the device
|
||||
*
|
||||
* @return the request bytes
|
||||
*/
|
||||
public abstract byte[] createRequest();
|
||||
|
||||
/**
|
||||
* When overridden in a subclass, handles the response to the current command
|
||||
*
|
||||
* @param data the response data
|
||||
*/
|
||||
public void handleResponse(byte[] data) {
|
||||
operationStatus = OperationStatus.FINISHED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the class name of this instance
|
||||
*
|
||||
* @return the class name
|
||||
*/
|
||||
public String getName() {
|
||||
Class thisClass = getClass();
|
||||
while (thisClass.isAnonymousClass()) thisClass = thisClass.getSuperclass();
|
||||
return thisClass.getSimpleName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a debug message
|
||||
*
|
||||
* @param message the message to log
|
||||
*/
|
||||
protected void log(String message) {
|
||||
logger.debug(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* When implemented in a subclass, returns the command ID associated with the current request
|
||||
*
|
||||
* @return the command ID
|
||||
*/
|
||||
public abstract int getCommandId();
|
||||
|
||||
/**
|
||||
* Gets whether the request will queue itself
|
||||
*
|
||||
* @return whether the request is self-queuing
|
||||
*/
|
||||
public boolean isSelfQueue() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the request expects a response
|
||||
*
|
||||
* @return whether the request expects a response
|
||||
*/
|
||||
public boolean expectsResponse() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the response should be removed from in progress requests list after handling
|
||||
*
|
||||
* @return whether the response should be removed after handling
|
||||
*/
|
||||
public boolean shouldRemoveAfterHandling() {
|
||||
return removeAfterHandling;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports an error to the user
|
||||
*
|
||||
* @param message the message to show
|
||||
*/
|
||||
protected void reportFailure(String message) {
|
||||
GB.toast(getContext(), message, Toast.LENGTH_SHORT, GB.ERROR);
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.requests;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.NotificationCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
|
||||
|
||||
public class SendCallNotificationRequest extends AbstractSendNotificationRequest {
|
||||
public SendCallNotificationRequest(LefunDeviceSupport support, TransactionBuilder builder) {
|
||||
super(support, builder);
|
||||
}
|
||||
|
||||
private CallSpec callNotification;
|
||||
|
||||
public CallSpec getCallNotification() {
|
||||
return callNotification;
|
||||
}
|
||||
|
||||
public void setCallNotification(CallSpec callNotification) {
|
||||
this.callNotification = callNotification;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMessage() {
|
||||
String message = "";
|
||||
if (callNotification.number != null &&!callNotification.number.isEmpty()) {
|
||||
message = callNotification.number;
|
||||
}
|
||||
|
||||
if (callNotification.name != null && !callNotification.name.isEmpty()) {
|
||||
if (message.length() > 0) {
|
||||
message += " - ";
|
||||
}
|
||||
message += callNotification.name;
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte getNotificationType() {
|
||||
return NotificationCommand.SERVICE_TYPE_CALL;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte getExtendedNotificationType() {
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.requests;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.NotificationCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
|
||||
|
||||
public class SendNotificationRequest extends AbstractSendNotificationRequest {
|
||||
NotificationSpec notification;
|
||||
|
||||
public SendNotificationRequest(LefunDeviceSupport support, TransactionBuilder builder) {
|
||||
super(support, builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte getNotificationType() {
|
||||
switch (notification.type) {
|
||||
case GENERIC_PHONE:
|
||||
return NotificationCommand.SERVICE_TYPE_CALL;
|
||||
case GENERIC_SMS:
|
||||
case GENERIC_EMAIL:
|
||||
default:
|
||||
return NotificationCommand.SERVICE_TYPE_TEXT;
|
||||
case WECHAT:
|
||||
return NotificationCommand.SERVICE_TYPE_WECHAT;
|
||||
case FACEBOOK:
|
||||
case FACEBOOK_MESSENGER:
|
||||
case TWITTER:
|
||||
case LINKEDIN:
|
||||
case WHATSAPP:
|
||||
case LINE:
|
||||
case KAKAO_TALK:
|
||||
return NotificationCommand.SERVICE_TYPE_EXTENDED;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte getExtendedNotificationType() {
|
||||
switch (notification.type) {
|
||||
case GENERIC_PHONE:
|
||||
case GENERIC_SMS:
|
||||
case GENERIC_EMAIL:
|
||||
default:
|
||||
case WECHAT:
|
||||
return 0;
|
||||
case FACEBOOK:
|
||||
case FACEBOOK_MESSENGER:
|
||||
return NotificationCommand.EXTENDED_SERVICE_TYPE_FACEBOOK;
|
||||
case TWITTER:
|
||||
return NotificationCommand.EXTENDED_SERVICE_TYPE_TWITTER;
|
||||
case LINKEDIN:
|
||||
return NotificationCommand.EXTENDED_SERVICE_TYPE_LINKEDIN;
|
||||
case WHATSAPP:
|
||||
return NotificationCommand.EXTENDED_SERVICE_TYPE_WHATSAPP;
|
||||
case LINE:
|
||||
return NotificationCommand.EXTENDED_SERVICE_TYPE_LINE;
|
||||
case KAKAO_TALK:
|
||||
return NotificationCommand.EXTENDED_SERVICE_TYPE_KAKAOTALK;
|
||||
}
|
||||
}
|
||||
|
||||
public NotificationSpec getNotification() {
|
||||
return notification;
|
||||
}
|
||||
|
||||
public void setNotification(NotificationSpec notification) {
|
||||
this.notification = notification;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMessage() {
|
||||
// Based on nodomain.freeyourgadget.gadgetbridge.service.devices.id115.SendNotificationOperation
|
||||
String message = "";
|
||||
|
||||
if (notification.phoneNumber != null && !notification.phoneNumber.isEmpty()) {
|
||||
message += notification.phoneNumber + ": ";
|
||||
}
|
||||
|
||||
if (notification.sender != null && !notification.sender.isEmpty()) {
|
||||
message += notification.sender + " - ";
|
||||
} else if (notification.title != null && !notification.title.isEmpty()) {
|
||||
message += notification.title + " - ";
|
||||
} else if (notification.subject != null && !notification.subject.isEmpty()) {
|
||||
message += notification.subject + " - ";
|
||||
}
|
||||
|
||||
if (notification.body != null && !notification.body.isEmpty()) {
|
||||
message += notification.body;
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
}
|
@ -0,0 +1,118 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.requests;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.AlarmCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
|
||||
|
||||
public class SetAlarmRequest extends Request {
|
||||
private int index;
|
||||
private boolean enabled;
|
||||
private int dayOfWeek;
|
||||
private int hour;
|
||||
private int minute;
|
||||
public SetAlarmRequest(LefunDeviceSupport support, TransactionBuilder builder) {
|
||||
super(support, builder);
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public void setIndex(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public int getDayOfWeek() {
|
||||
return dayOfWeek;
|
||||
}
|
||||
|
||||
public void setDayOfWeek(int dayOfWeek) {
|
||||
this.dayOfWeek = dayOfWeek;
|
||||
}
|
||||
|
||||
public int getHour() {
|
||||
return hour;
|
||||
}
|
||||
|
||||
public void setHour(int hour) {
|
||||
this.hour = hour;
|
||||
}
|
||||
|
||||
public int getMinute() {
|
||||
return minute;
|
||||
}
|
||||
|
||||
public void setMinute(int minute) {
|
||||
this.minute = minute;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] createRequest() {
|
||||
AlarmCommand cmd = new AlarmCommand();
|
||||
cmd.setOp(BaseCommand.OP_SET);
|
||||
cmd.setIndex((byte) index);
|
||||
cmd.setEnabled(enabled);
|
||||
cmd.setNumOfSnoozes((byte) 0);
|
||||
cmd.setHour((byte) hour);
|
||||
cmd.setMinute((byte) minute);
|
||||
|
||||
// Translate GB alarm day of week to Lefun day of week
|
||||
// GB starts on Monday, Lefun starts on Sunday
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
if ((dayOfWeek & (1 << i)) != 0) {
|
||||
cmd.setDayOfWeek(i + 1, true);
|
||||
}
|
||||
}
|
||||
if ((dayOfWeek & Alarm.ALARM_SUN) != 0) {
|
||||
cmd.setDayOfWeek(AlarmCommand.DOW_SUNDAY, true);
|
||||
}
|
||||
|
||||
return cmd.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(byte[] data) {
|
||||
AlarmCommand cmd = new AlarmCommand();
|
||||
cmd.deserialize(data);
|
||||
|
||||
if (cmd.getOp() != BaseCommand.OP_SET || cmd.getIndex() != index || !cmd.isSetSuccess())
|
||||
reportFailure("Could not set alarm");
|
||||
|
||||
operationStatus = OperationStatus.FINISHED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return LefunConstants.CMD_ALARM;
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.requests;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.FeaturesCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
|
||||
|
||||
public class SetEnabledFeaturesRequest extends Request {
|
||||
private FeaturesCommand cmd;
|
||||
|
||||
public SetEnabledFeaturesRequest(LefunDeviceSupport support, TransactionBuilder builder) {
|
||||
super(support, builder);
|
||||
}
|
||||
|
||||
public FeaturesCommand getCmd() {
|
||||
return cmd;
|
||||
}
|
||||
|
||||
public void setCmd(FeaturesCommand cmd) {
|
||||
this.cmd = cmd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] createRequest() {
|
||||
cmd.setOp(BaseCommand.OP_SET);
|
||||
return cmd.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(byte[] data) {
|
||||
FeaturesCommand cmd = new FeaturesCommand();
|
||||
cmd.deserialize(data);
|
||||
if (cmd.getOp() == BaseCommand.OP_SET && !cmd.isSetSuccess())
|
||||
reportFailure("Could not set enabled features");
|
||||
|
||||
operationStatus = OperationStatus.FINISHED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return LefunConstants.CMD_FEATURES;
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.requests;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.SettingsCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
|
||||
|
||||
public class SetGeneralSettingsRequest extends Request {
|
||||
private byte amPm;
|
||||
private byte units;
|
||||
|
||||
public SetGeneralSettingsRequest(LefunDeviceSupport support, TransactionBuilder builder) {
|
||||
super(support, builder);
|
||||
}
|
||||
|
||||
public byte getAmPm() {
|
||||
return amPm;
|
||||
}
|
||||
|
||||
public void setAmPm(byte amPm) {
|
||||
this.amPm = amPm;
|
||||
}
|
||||
|
||||
public byte getUnits() {
|
||||
return units;
|
||||
}
|
||||
|
||||
public void setUnits(byte units) {
|
||||
this.units = units;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] createRequest() {
|
||||
SettingsCommand cmd = new SettingsCommand();
|
||||
|
||||
cmd.setOp(BaseCommand.OP_SET);
|
||||
cmd.setOption1((byte) 0xff); // Don't set
|
||||
cmd.setAmPmIndicator(amPm);
|
||||
cmd.setMeasurementUnit(units);
|
||||
|
||||
return cmd.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(byte[] data) {
|
||||
SettingsCommand cmd = new SettingsCommand();
|
||||
cmd.deserialize(data);
|
||||
if (cmd.getOp() == BaseCommand.OP_SET && !cmd.isSetSuccess())
|
||||
reportFailure("Could not set settings");
|
||||
|
||||
operationStatus = OperationStatus.FINISHED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return LefunConstants.CMD_SETTINGS;
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.requests;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.HydrationReminderIntervalCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
|
||||
|
||||
public class SetHydrationReminderIntervalRequest extends Request {
|
||||
private int interval;
|
||||
|
||||
public SetHydrationReminderIntervalRequest(LefunDeviceSupport support, TransactionBuilder builder) {
|
||||
super(support, builder);
|
||||
}
|
||||
|
||||
public int getInterval() {
|
||||
return interval;
|
||||
}
|
||||
|
||||
public void setInterval(int interval) {
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] createRequest() {
|
||||
HydrationReminderIntervalCommand cmd = new HydrationReminderIntervalCommand();
|
||||
|
||||
cmd.setOp(BaseCommand.OP_SET);
|
||||
cmd.setHydrationReminderInterval((byte) interval);
|
||||
|
||||
return cmd.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(byte[] data) {
|
||||
HydrationReminderIntervalCommand cmd = new HydrationReminderIntervalCommand();
|
||||
cmd.deserialize(data);
|
||||
if (cmd.getOp() == BaseCommand.OP_SET && !cmd.isSetSuccess())
|
||||
reportFailure("Could not set hydration reminder interval");
|
||||
|
||||
operationStatus = OperationStatus.FINISHED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return LefunConstants.CMD_HYDRATION_REMINDER_INTERVAL;
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.requests;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.SetLanguageCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
|
||||
|
||||
public class SetLanguageRequest extends Request {
|
||||
private byte language;
|
||||
|
||||
public SetLanguageRequest(LefunDeviceSupport support, TransactionBuilder builder) {
|
||||
super(support, builder);
|
||||
}
|
||||
|
||||
public byte getLanguage() {
|
||||
return language;
|
||||
}
|
||||
|
||||
public void setLanguage(byte language) {
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] createRequest() {
|
||||
SetLanguageCommand cmd = new SetLanguageCommand();
|
||||
|
||||
cmd.setLanguage(language);
|
||||
|
||||
return cmd.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(byte[] data) {
|
||||
SetLanguageCommand cmd = new SetLanguageCommand();
|
||||
cmd.deserialize(data);
|
||||
if (!cmd.isSetSuccess())
|
||||
reportFailure("Could not set language");
|
||||
|
||||
operationStatus = OperationStatus.FINISHED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return LefunConstants.CMD_LANGUAGE;
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.requests;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.ProfileCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
|
||||
|
||||
public class SetProfileRequest extends Request {
|
||||
private ActivityUser user;
|
||||
|
||||
public SetProfileRequest(LefunDeviceSupport support, TransactionBuilder builder) {
|
||||
super(support, builder);
|
||||
}
|
||||
|
||||
public ActivityUser getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(ActivityUser user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] createRequest() {
|
||||
ProfileCommand cmd = new ProfileCommand();
|
||||
|
||||
cmd.setOp(BaseCommand.OP_SET);
|
||||
// No "other" option available, only male or female
|
||||
cmd.setGender(user.getGender() == ActivityUser.GENDER_FEMALE
|
||||
? ProfileCommand.GENDER_FEMALE
|
||||
: ProfileCommand.GENDER_MALE);
|
||||
cmd.setHeight((byte) user.getHeightCm());
|
||||
cmd.setWeight((byte) user.getWeightKg());
|
||||
cmd.setAge((byte) user.getAge());
|
||||
|
||||
return cmd.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(byte[] data) {
|
||||
ProfileCommand cmd = new ProfileCommand();
|
||||
cmd.deserialize(data);
|
||||
if (cmd.getOp() == BaseCommand.OP_SET && !cmd.isSetSuccess())
|
||||
reportFailure("Could not set profile");
|
||||
|
||||
operationStatus = OperationStatus.FINISHED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return LefunConstants.CMD_PROFILE;
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.requests;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.SedentaryReminderIntervalCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
|
||||
|
||||
public class SetSedentaryReminderIntervalRequest extends Request {
|
||||
private int interval;
|
||||
|
||||
public SetSedentaryReminderIntervalRequest(LefunDeviceSupport support, TransactionBuilder builder) {
|
||||
super(support, builder);
|
||||
}
|
||||
|
||||
public int getInterval() {
|
||||
return interval;
|
||||
}
|
||||
|
||||
public void setInterval(int interval) {
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] createRequest() {
|
||||
SedentaryReminderIntervalCommand cmd = new SedentaryReminderIntervalCommand();
|
||||
|
||||
cmd.setOp(BaseCommand.OP_SET);
|
||||
cmd.setSedentaryReminderInterval((byte) interval);
|
||||
|
||||
return cmd.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(byte[] data) {
|
||||
SedentaryReminderIntervalCommand cmd = new SedentaryReminderIntervalCommand();
|
||||
cmd.deserialize(data);
|
||||
if (cmd.getOp() == BaseCommand.OP_SET && !cmd.isSetSuccess())
|
||||
reportFailure("Could not set sedentary reminder interval");
|
||||
|
||||
operationStatus = OperationStatus.FINISHED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return LefunConstants.CMD_SEDENTARY_REMINDER_INTERVAL;
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.requests;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.TimeCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
|
||||
|
||||
public class SetTimeRequest extends Request {
|
||||
public SetTimeRequest(LefunDeviceSupport support, TransactionBuilder builder) {
|
||||
super(support, builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] createRequest() {
|
||||
TimeCommand cmd = new TimeCommand();
|
||||
Calendar c = Calendar.getInstance();
|
||||
|
||||
cmd.setOp(BaseCommand.OP_SET);
|
||||
cmd.setYear((byte)(c.get(Calendar.YEAR) - 2000));
|
||||
cmd.setMonth((byte)(c.get(Calendar.MONTH) + 1));
|
||||
cmd.setDay((byte)c.get(Calendar.DAY_OF_MONTH));
|
||||
cmd.setHour((byte)c.get(Calendar.HOUR_OF_DAY));
|
||||
cmd.setMinute((byte)c.get(Calendar.MINUTE));
|
||||
cmd.setSecond((byte)c.get(Calendar.SECOND));
|
||||
|
||||
return cmd.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(byte[] data) {
|
||||
TimeCommand cmd = new TimeCommand();
|
||||
cmd.deserialize(data);
|
||||
if (cmd.getOp() == BaseCommand.OP_SET && !cmd.isSetSuccess())
|
||||
reportFailure("Could not set time");
|
||||
|
||||
operationStatus = OperationStatus.FINISHED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return LefunConstants.CMD_TIME;
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
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.lefun.requests;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.StartPpgSensingCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
|
||||
|
||||
public class StartPpgRequest extends Request {
|
||||
public StartPpgRequest(LefunDeviceSupport support, TransactionBuilder builder) {
|
||||
super(support, builder);
|
||||
}
|
||||
|
||||
int ppgType;
|
||||
|
||||
public int getPpgType() {
|
||||
return ppgType;
|
||||
}
|
||||
|
||||
public void setPpgType(int ppgType) {
|
||||
this.ppgType = ppgType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] createRequest() {
|
||||
StartPpgSensingCommand cmd = new StartPpgSensingCommand();
|
||||
cmd.setPpgType(ppgType);
|
||||
return cmd.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return LefunConstants.CMD_PPG_START;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(byte[] data) {
|
||||
StartPpgSensingCommand cmd = new StartPpgSensingCommand();
|
||||
cmd.deserialize(data);
|
||||
|
||||
if (!cmd.isSetSuccess() || cmd.getPpgType() != ppgType)
|
||||
reportFailure("Could not start PPG sensing");
|
||||
|
||||
operationStatus = OperationStatus.FINISHED;
|
||||
}
|
||||
}
|
@ -84,7 +84,7 @@ public class UpdateFirmwareOperation extends AbstractMiBand1Operation {
|
||||
displayMessage(getContext(), "Error sending firmware info, aborting.", Toast.LENGTH_LONG, GB.ERROR);
|
||||
done();
|
||||
}
|
||||
//the firmware will be sent by the notification listener if the band confirms that the metadata are ok.
|
||||
/** the firmware will be sent by the {@link UpdateFirmwareOperation#handleNotificationNotif} if the band confirms that the metadata are ok. **/
|
||||
}
|
||||
|
||||
private void done() {
|
||||
|
@ -0,0 +1,632 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Taavi Eomäe
|
||||
|
||||
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.nut;
|
||||
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.nut.NutConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.nut.NutKey;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfoProfile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class NutSupport extends AbstractBTLEDeviceSupport {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(NutSupport.class);
|
||||
|
||||
private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo();
|
||||
|
||||
private final DeviceInfoProfile<NutSupport> deviceInfoProfile;
|
||||
private final BatteryInfoProfile<NutSupport> batteryInfoProfile;
|
||||
private final IntentListener listener = new IntentListener() {
|
||||
@Override
|
||||
public void notify(Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (action.equals(DeviceInfoProfile.ACTION_DEVICE_INFO)) {
|
||||
handleDeviceInfo((DeviceInfo) intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO));
|
||||
} else if (action.equals(BatteryInfoProfile.ACTION_BATTERY_INFO)) {
|
||||
handleBatteryInfo((BatteryInfo) intent.getParcelableExtra(BatteryInfoProfile.EXTRA_BATTERY_INFO));
|
||||
} else {
|
||||
LOG.warn("Unhandled intent given to listener");
|
||||
}
|
||||
}
|
||||
};
|
||||
private SharedPreferences prefs = null;
|
||||
/**
|
||||
* It uses the proprietary Nut interface.
|
||||
*/
|
||||
private boolean proprietary = false;
|
||||
/**
|
||||
* Proprietary Nut interface needs authentication.
|
||||
* <p>
|
||||
* Don't write characteristics until authenticated.
|
||||
* <p>
|
||||
* Will disconnect in a minute if you don't authenticate.
|
||||
*/
|
||||
private boolean authenticated = true;
|
||||
/**
|
||||
* The two keys used for authentication
|
||||
*/
|
||||
private BigInteger key1;
|
||||
private BigInteger key2;
|
||||
|
||||
|
||||
public NutSupport() {
|
||||
super(LOG);
|
||||
addSupportedService(NutConstants.SERVICE_BATTERY);
|
||||
addSupportedService(NutConstants.SERVICE_DEVICE_INFO);
|
||||
addSupportedService(NutConstants.SERVICE_IMMEDIATE_ALERT);
|
||||
addSupportedService(NutConstants.SERVICE_LINK_LOSS);
|
||||
addSupportedService(NutConstants.SERVICE_PROPRIETARY_NUT);
|
||||
addSupportedService(NutConstants.SERVICE_UNKNOWN_2);
|
||||
|
||||
deviceInfoProfile = new DeviceInfoProfile<>(this);
|
||||
deviceInfoProfile.addListener(listener);
|
||||
addSupportedProfile(deviceInfoProfile);
|
||||
|
||||
batteryInfoProfile = new BatteryInfoProfile<>(this);
|
||||
batteryInfoProfile.addListener(listener);
|
||||
addSupportedProfile(batteryInfoProfile);
|
||||
}
|
||||
|
||||
private void handleBatteryInfo(BatteryInfo info) {
|
||||
LOG.info("Received Nut battery info");
|
||||
batteryCmd.level = (short) info.getPercentCharged();
|
||||
handleGBDeviceEvent(batteryCmd);
|
||||
}
|
||||
|
||||
private void handleDeviceInfo(DeviceInfo info) {
|
||||
LOG.info("Received Nut device info");
|
||||
LOG.info(String.valueOf(info));
|
||||
GBDeviceEventVersionInfo versionInfo = new GBDeviceEventVersionInfo();
|
||||
if (info.getHardwareRevision() != null) {
|
||||
versionInfo.hwVersion = info.getHardwareRevision();
|
||||
}
|
||||
if (info.getFirmwareRevision() != null) {
|
||||
versionInfo.fwVersion = info.getFirmwareRevision();
|
||||
}
|
||||
|
||||
handleGBDeviceEvent(versionInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
|
||||
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
|
||||
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
|
||||
|
||||
// Init prefs
|
||||
prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
|
||||
loadKeysFromPrefs();
|
||||
|
||||
LOG.debug("Requesting device info!");
|
||||
deviceInfoProfile.requestDeviceInfo(builder);
|
||||
batteryInfoProfile.requestBatteryInfo(builder);
|
||||
|
||||
// If this characteristic exists, it has proprietary Nut interface
|
||||
this.proprietary = (getCharacteristic(NutConstants.CHARAC_AUTH_STATUS) != null);
|
||||
|
||||
if (proprietary) {
|
||||
this.authenticated = false;
|
||||
/**
|
||||
* Part of {@link NutConstants.SERVICE_PROPRIETARY_NUT}
|
||||
* Enables proprietary notification
|
||||
*/
|
||||
builder.notify(getCharacteristic(NutConstants.CHARAC_AUTH_STATUS), true);
|
||||
LOG.info("Enabled authentication status notify");
|
||||
|
||||
/**
|
||||
* Part of {@link NutConstants.SERVICE_UNKNOWN_2}
|
||||
* Enables button-press notify
|
||||
*/
|
||||
builder.notify(getCharacteristic(NutConstants.CHARAC_UNKNOWN_2), true);
|
||||
} else {
|
||||
/**
|
||||
* Part of {@link NutConstants.SERVICE_UNKNOWN_1_WEIRDNESS}
|
||||
* Enables button-press notify
|
||||
*/
|
||||
builder.notify(getCharacteristic(NutConstants.CHARAC_CHANGE_POWER), true);
|
||||
}
|
||||
|
||||
readDeviceInfo();
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useAutoConnect() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotification(NotificationSpec notificationSpec) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeleteNotification(int id) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetTime() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetCallState(CallSpec callSpec) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetMusicState(MusicStateSpec stateSpec) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetMusicInfo(MusicSpec musicSpec) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnableRealtimeSteps(boolean enable) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInstallApp(Uri uri) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppInfoReq() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppStart(UUID uuid, boolean start) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppDelete(UUID uuid) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppConfiguration(UUID appUuid, String config, Integer id) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppReorder(UUID[] uuids) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchRecordedData(int dataTypes) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReset(int flags) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeartRateTest() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFindDevice(boolean enable) {
|
||||
deviceImmediateAlert(enable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetConstantVibration(int intensity) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScreenshotReq() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnableHeartRateSleepSupport(boolean enable) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetHeartRateMeasurementInterval(int seconds) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeleteCalendarEvent(byte type, long id) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCharacteristicChanged(BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic) {
|
||||
if (super.onCharacteristicChanged(gatt, characteristic)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
UUID characteristicUUID = characteristic.getUuid();
|
||||
if (characteristicUUID.equals(NutConstants.CHARAC_AUTH_STATUS)) {
|
||||
handleAuthResult(characteristic.getValue());
|
||||
return true;
|
||||
}
|
||||
LOG.info("Unhandled characteristic changed: " + characteristicUUID);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCharacteristicRead(BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic,
|
||||
int status) {
|
||||
if (super.onCharacteristicRead(gatt, characteristic, status)) {
|
||||
return true;
|
||||
}
|
||||
UUID characteristicUUID = characteristic.getUuid();
|
||||
|
||||
if (characteristicUUID.equals(NutConstants.CHARAC_SYSTEM_ID)) {
|
||||
// TODO: Handle System ID read
|
||||
return true;
|
||||
}
|
||||
LOG.info("Unhandled characteristic read: " + characteristicUUID);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendConfiguration(String config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReadConfiguration(String config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTestNewFunction() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendWeather(WeatherSpec weatherSpec) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables link loss alert
|
||||
*/
|
||||
private void deviceLinkLossAlert(boolean enable) {
|
||||
UUID charac;
|
||||
if (this.proprietary) {
|
||||
/** Part of {@link NutConstants.SERVICE_PROPRIETARY_NUT} */
|
||||
charac = NutConstants.CHARAC_CHANGE_POWER;
|
||||
} else {
|
||||
/** Part of {@link NutConstants.SERVICE_IMMEDIATE_ALERT} */
|
||||
charac = NutConstants.CHARAC_LINK_LOSS_ALERT_LEVEL;
|
||||
}
|
||||
|
||||
byte[] payload = new byte[]{(byte) (enable ? 0x00 : 0x01)};
|
||||
if (enable) {
|
||||
writeCharacteristic("Enable link loss alert", charac, payload);
|
||||
} else {
|
||||
writeCharacteristic("Disable link loss alert", charac, payload);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should trigger an immediate alert
|
||||
*
|
||||
* @param enable turn on or not
|
||||
*/
|
||||
private void deviceImmediateAlert(boolean enable) {
|
||||
UUID charac;
|
||||
if (this.proprietary) {
|
||||
/** Part of {@link NutConstants.SERVICE_IMMEDIATE_ALERT} */
|
||||
charac = NutConstants.CHARAC_LINK_LOSS_ALERT_LEVEL;
|
||||
if (!authenticated) {
|
||||
LOG.warn("Not authenticated, can't alert");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
/** Part of {@link NutConstants.SERVICE_PROPRIETARY_NUT} */
|
||||
charac = NutConstants.CHARAC_CHANGE_POWER;
|
||||
}
|
||||
|
||||
if (enable) {
|
||||
writeCharacteristic("Start alert", charac, new byte[]{(byte) 0x04});
|
||||
} else {
|
||||
writeCharacteristic("Stop alert", charac, new byte[]{(byte) 0x03});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This will write a new key to the device
|
||||
* <p>
|
||||
* However, <b>it is irreversible</b>,
|
||||
* if you can't generate the right packets,
|
||||
* the device is basically bricked!
|
||||
* <p>
|
||||
* If you can generate the correct packets,
|
||||
* it can be reset... somehow
|
||||
*
|
||||
* @param key key
|
||||
*/
|
||||
private void deviceWriteNewKey(byte[] key) {
|
||||
// TODO: Determine each nuance of how this
|
||||
// works before using it!
|
||||
byte[] result_payload = new byte[key.length + 1];
|
||||
result_payload[0] = (byte) 0x04;
|
||||
System.arraycopy(key, 0, result_payload, 1, key.length);
|
||||
|
||||
writeCharacteristic("Write new key",
|
||||
NutConstants.CHARAC_DFU_PW,
|
||||
result_payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns the device off
|
||||
*/
|
||||
private void deviceShutdown() {
|
||||
writeCharacteristic("Shutdown", NutConstants.CHARAC_CHANGE_POWER, new byte[]{0x06});
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches the device to Nordic's DFU mode
|
||||
*/
|
||||
private void deviceDFU() {
|
||||
writeCharacteristic("Enable DFU mode", NutConstants.CHARAC_DFU_PW, new byte[]{0x14});
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies how long the alert lasts
|
||||
*
|
||||
* @param duration in seconds (I think)
|
||||
*/
|
||||
private void deviceWriteAlertDuration(int duration) {
|
||||
if (duration == 0) {
|
||||
duration = 15;
|
||||
}
|
||||
|
||||
UUID charac;
|
||||
if (this.proprietary) {
|
||||
charac = NutConstants.CHARAC_DFU_PW;
|
||||
} else {
|
||||
charac = NutConstants.CHARAC_CHANGE_POWER;
|
||||
}
|
||||
|
||||
writeCharacteristic("Write alert duration",
|
||||
charac,
|
||||
new byte[]{37, (byte) duration});
|
||||
}
|
||||
|
||||
private void readHardwareInfo() {
|
||||
BluetoothGattCharacteristic characteristic = getCharacteristic(NutConstants.CHARAC_HARDWARE_VERSION);
|
||||
if (characteristic != null &&
|
||||
((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) > 0)) {
|
||||
readCharacteristic("Device read hardware",
|
||||
NutConstants.CHARAC_HARDWARE_VERSION);
|
||||
}
|
||||
|
||||
BluetoothGattCharacteristic characteristic1 = getCharacteristic(NutConstants.CHARAC_MANUFACTURER_NAME);
|
||||
if (characteristic1 != null &&
|
||||
(characteristic1.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
|
||||
readCharacteristic("Read manufacturer",
|
||||
NutConstants.CHARAC_MANUFACTURER_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
private void readFirmwareInfo() {
|
||||
BluetoothGattCharacteristic characteristic = getCharacteristic(NutConstants.CHARAC_FIRMWARE_VERSION);
|
||||
if (characteristic != null &&
|
||||
(characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
|
||||
readCharacteristic("Read firmware version",
|
||||
NutConstants.CHARAC_FIRMWARE_VERSION);
|
||||
}
|
||||
}
|
||||
|
||||
private void readBatteryInfo() {
|
||||
BluetoothGattCharacteristic characteristic = getCharacteristic(NutConstants.CHARAC_BATTERY_INFO);
|
||||
if (characteristic != null &&
|
||||
(characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
|
||||
readCharacteristic("Read battery info",
|
||||
NutConstants.CHARAC_BATTERY_INFO);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the three keys from device-specific shared preferences
|
||||
*/
|
||||
private void loadKeysFromPrefs() {
|
||||
if (prefs != null) {
|
||||
LOG.info("Reading keys");
|
||||
key1 = new BigInteger(prefs.getString("nut_packet_key_1", "0"));
|
||||
key2 = new BigInteger(prefs.getString("nut_packet_key_2", "0"));
|
||||
if (key1.equals(BigInteger.ZERO) || key2.equals(BigInteger.ZERO)) {
|
||||
byte[] challenge = NutKey.hexStringToByteArrayNut(prefs.getString("nut_packet_challenge", "00"));
|
||||
byte[] response = NutKey.hexStringToByteArrayNut(prefs.getString("nut_response_challenge", "00"));
|
||||
if (Arrays.equals(challenge, new byte[]{0x00}) ||
|
||||
Arrays.equals(response, new byte[]{0x00})) {
|
||||
GB.toast("No key available for the device", Toast.LENGTH_LONG, GB.ERROR);
|
||||
return;
|
||||
}
|
||||
Map.Entry<BigInteger, BigInteger> key = NutKey.reversePasswordGeneration(
|
||||
challenge,
|
||||
response,
|
||||
gbDevice.getAddress()
|
||||
);
|
||||
if (key == null) {
|
||||
GB.toast("No correct key available for the device", Toast.LENGTH_LONG, GB.ERROR);
|
||||
return;
|
||||
}
|
||||
key1 = key.getKey();
|
||||
key2 = key.getValue();
|
||||
LOG.debug("Key was extracted from challenge-response packets");
|
||||
} else {
|
||||
LOG.debug("Key was preset");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the authentication flow of the proprietary Nut protocol
|
||||
* See more: {@link NutConstants#SERVICE_PROPRIETARY_NUT}
|
||||
*
|
||||
* @param received the notify characteristic's content
|
||||
*/
|
||||
public final void handleAuthResult(byte[] received) {
|
||||
if (received != null && received.length != 0) {
|
||||
if (received[0] == 0x01) {
|
||||
// Password is needed
|
||||
// Preamble, counter, rotating key, static key
|
||||
byte[] payload = new byte[1 + 1 + 3 + 12];
|
||||
|
||||
// This is a response to the challenge
|
||||
payload[0] = 0x02;
|
||||
|
||||
// Modify the challenge
|
||||
byte[] response = NutKey.passwordGeneration(gbDevice.getAddress(), received, key1, key2);
|
||||
System.arraycopy(response, 0, payload, 1, response.length);
|
||||
|
||||
writeCharacteristic("Authentication",
|
||||
NutConstants.CHARAC_DFU_PW,
|
||||
payload
|
||||
);
|
||||
LOG.debug("Successfully sent auth");
|
||||
} else if (received[0] == 0x03) {
|
||||
if (received[1] == 0x55) {
|
||||
LOG.debug("Successful password attempt or uninitialized");
|
||||
authenticated = true;
|
||||
initChara();
|
||||
} else {
|
||||
LOG.debug("Error authenticating");
|
||||
// TODO: Disconnect
|
||||
}
|
||||
} else if (received[0] == 0x05) {
|
||||
LOG.debug("Password has been set");
|
||||
} else {
|
||||
LOG.debug("Invalid packet");
|
||||
// TODO: Disconnect
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes required characteristics
|
||||
*/
|
||||
private void initChara() {
|
||||
if (proprietary) {
|
||||
writeCharacteristic("Init alert 1", NutConstants.CHARAC_LINK_LOSS_ALERT_LEVEL, new byte[]{(byte) 0x00});
|
||||
writeCharacteristic("Init alert 2", NutConstants.CHARAC_LINK_LOSS_ALERT_LEVEL, new byte[]{(byte) 0x00});
|
||||
writeCharacteristic("Init alert 3", NutConstants.CHARAC_LINK_LOSS_ALERT_LEVEL, new byte[]{(byte) 0x00});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates a read of all the device information characteristics
|
||||
*/
|
||||
private void readDeviceInfo() {
|
||||
readBatteryInfo();
|
||||
readHardwareInfo();
|
||||
readFirmwareInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Just wraps writing into a neat little function
|
||||
*
|
||||
* @param taskName something that describes the task a bit
|
||||
* @param charac the characteristic to write
|
||||
* @param data the data to write
|
||||
*/
|
||||
private void writeCharacteristic(String taskName, UUID charac, byte[] data) {
|
||||
BluetoothGattCharacteristic characteristic = getCharacteristic(charac);
|
||||
|
||||
TransactionBuilder builder = new TransactionBuilder(taskName);
|
||||
builder.write(characteristic, data);
|
||||
builder.queue(getQueue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Just wraps reading into a neat little function
|
||||
*
|
||||
* @param taskName something that describes the task a bit
|
||||
* @param charac the characteristic to read
|
||||
*/
|
||||
private void readCharacteristic(String taskName, UUID charac) {
|
||||
BluetoothGattCharacteristic characteristic = getCharacteristic(charac);
|
||||
|
||||
TransactionBuilder builder = new TransactionBuilder(taskName);
|
||||
builder.read(characteristic);
|
||||
builder.queue(getQueue());
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2020 Andreas Shimokawa
|
||||
/* Copyright (C) 2020 Andreas Shimokawa, Taavi Eomäe
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -20,17 +20,31 @@ import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
import no.nordicsemi.android.dfu.DfuLogListener;
|
||||
import no.nordicsemi.android.dfu.DfuProgressListener;
|
||||
import no.nordicsemi.android.dfu.DfuProgressListenerAdapter;
|
||||
import no.nordicsemi.android.dfu.DfuServiceController;
|
||||
import no.nordicsemi.android.dfu.DfuServiceInitiator;
|
||||
import no.nordicsemi.android.dfu.DfuServiceListenerHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.pinetime.PineTimeDFUService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.pinetime.PineTimeInstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.pinetime.PineTimeJFConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||
@ -52,26 +66,143 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotificat
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.NewAlert;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.OverflowStrategy;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class PineTimeJFSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
public class PineTimeJFSupport extends AbstractBTLEDeviceSupport implements DfuLogListener {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PineTimeJFSupport.class);
|
||||
private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
|
||||
private final DeviceInfoProfile<PineTimeJFSupport> deviceInfoProfile;
|
||||
/**
|
||||
* These are used to keep track when long strings haven't changed,
|
||||
* thus avoiding unnecessary transfers that are (potentially) very slow.
|
||||
* <p>
|
||||
* Makes the device's UI more responsive.
|
||||
*/
|
||||
String lastAlbum;
|
||||
String lastTrack;
|
||||
String lastArtist;
|
||||
PineTimeInstallHandler handler;
|
||||
DfuServiceController controller;
|
||||
|
||||
private static final UUID UUID_SERVICE_MUSICCONTROL = UUID.fromString("c7e50001-00fc-48fe-8e23-433b3a1942d0");
|
||||
private static final UUID UUID_CHARACTERISTICS_MUSIC_EVENT = UUID.fromString("c7e50002-00fc-48fe-8e23-433b3a1942d0");
|
||||
private static final UUID UUID_CHARACTERISTICS_MUSIC_STATUS = UUID.fromString("c7e50003-00fc-48fe-8e23-433b3a1942d0");
|
||||
private static final UUID UUID_CHARACTERISTICS_MUSIC_TRACK = UUID.fromString("c7e50004-00fc-48fe-8e23-433b3a1942d0");
|
||||
private static final UUID UUID_CHARACTERISTICS_MUSIC_ARTIST = UUID.fromString("c7e50005-00fc-48fe-8e23-433b3a1942d0");
|
||||
private static final UUID UUID_CHARACTERISTICS_MUSIC_ALBUM = UUID.fromString("c7e50006-00fc-48fe-8e23-433b3a1942d0");
|
||||
private final DfuProgressListener progressListener = new DfuProgressListenerAdapter() {
|
||||
private final LocalBroadcastManager manager = LocalBroadcastManager.getInstance(getContext());
|
||||
|
||||
/**
|
||||
* Sets the progress bar to indeterminate or not, also makes it visible
|
||||
*
|
||||
* @param indeterminate if indeterminate
|
||||
*/
|
||||
public void setIndeterminate(boolean indeterminate) {
|
||||
manager.sendBroadcast(new Intent(GB.ACTION_SET_PROGRESS_BAR).putExtra(GB.PROGRESS_BAR_INDETERMINATE, indeterminate));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the status text and logs it
|
||||
*/
|
||||
public void setProgress(int progress) {
|
||||
manager.sendBroadcast(new Intent(GB.ACTION_SET_PROGRESS_BAR).putExtra(GB.PROGRESS_BAR_PROGRESS, progress));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text that describes progress
|
||||
*
|
||||
* @param progressText text to display
|
||||
*/
|
||||
public void setProgressText(String progressText) {
|
||||
manager.sendBroadcast(new Intent(GB.ACTION_SET_PROGRESS_TEXT).putExtra(GB.DISPLAY_MESSAGE_MESSAGE, progressText));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceConnecting(final String mac) {
|
||||
this.setIndeterminate(true);
|
||||
this.setProgressText(getContext().getString(R.string.devicestatus_connecting));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceConnected(final String mac) {
|
||||
this.setIndeterminate(true);
|
||||
this.setProgressText(getContext().getString(R.string.devicestatus_connected));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnablingDfuMode(final String mac) {
|
||||
this.setIndeterminate(true);
|
||||
this.setProgressText(getContext().getString(R.string.devicestatus_upload_starting));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDfuProcessStarting(final String mac) {
|
||||
this.setIndeterminate(true);
|
||||
this.setProgressText(getContext().getString(R.string.devicestatus_upload_starting));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDfuProcessStarted(final String mac) {
|
||||
this.setIndeterminate(true);
|
||||
this.setProgressText(getContext().getString(R.string.devicestatus_upload_started));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceDisconnecting(final String mac) {
|
||||
this.setProgressText(getContext().getString(R.string.devicestatus_disconnecting));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceDisconnected(final String mac) {
|
||||
this.setIndeterminate(true);
|
||||
this.setProgressText(getContext().getString(R.string.devicestatus_disconnected));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDfuCompleted(final String mac) {
|
||||
this.setProgressText(getContext().getString(R.string.devicestatus_upload_completed));
|
||||
this.setIndeterminate(false);
|
||||
this.setProgress(100);
|
||||
|
||||
handler = null;
|
||||
controller = null;
|
||||
DfuServiceListenerHelper.unregisterProgressListener(getContext(), progressListener);
|
||||
|
||||
// TODO: Request reconnection
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFirmwareValidating(final String mac) {
|
||||
this.setIndeterminate(true);
|
||||
this.setProgressText(getContext().getString(R.string.devicestatus_upload_validating));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDfuAborted(final String mac) {
|
||||
this.setProgressText(getContext().getString(R.string.devicestatus_upload_aborted));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(final String mac, int error, int errorType, final String message) {
|
||||
this.setProgressText(getContext().getString(R.string.devicestatus_upload_failed));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(final String mac,
|
||||
int percent,
|
||||
float speed,
|
||||
float averageSpeed,
|
||||
int segment,
|
||||
int totalSegments) {
|
||||
this.setProgress(percent);
|
||||
this.setIndeterminate(false);
|
||||
this.setProgressText(String.format(Locale.ENGLISH,
|
||||
getContext().getString(R.string.firmware_update_progress),
|
||||
percent, speed, averageSpeed, segment, totalSegments));
|
||||
}
|
||||
};
|
||||
|
||||
public PineTimeJFSupport() {
|
||||
super(LOG);
|
||||
addSupportedService(GattService.UUID_SERVICE_ALERT_NOTIFICATION);
|
||||
addSupportedService(GattService.UUID_SERVICE_CURRENT_TIME);
|
||||
addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION);
|
||||
addSupportedService(UUID_SERVICE_MUSICCONTROL);
|
||||
addSupportedService(PineTimeJFConstants.UUID_SERVICE_MUSIC_CONTROL);
|
||||
deviceInfoProfile = new DeviceInfoProfile<>(this);
|
||||
IntentListener mListener = new IntentListener() {
|
||||
@Override
|
||||
@ -88,34 +219,6 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport {
|
||||
addSupportedProfile(deviceInfoProfile);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
|
||||
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
|
||||
requestDeviceInfo(builder);
|
||||
onSetTime();
|
||||
builder.notify(getCharacteristic(UUID_CHARACTERISTICS_MUSIC_EVENT), true);
|
||||
setInitialized(builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
private void setInitialized(TransactionBuilder builder) {
|
||||
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
|
||||
}
|
||||
|
||||
private void requestDeviceInfo(TransactionBuilder builder) {
|
||||
LOG.debug("Requesting Device Info!");
|
||||
deviceInfoProfile.requestDeviceInfo(builder);
|
||||
}
|
||||
|
||||
private void handleDeviceInfo(nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo info) {
|
||||
LOG.warn("Device info: " + info);
|
||||
versionCmd.hwVersion = info.getHardwareRevision();
|
||||
versionCmd.fwVersion = info.getFirmwareRevision();
|
||||
handleGBDeviceEvent(versionCmd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useAutoConnect() {
|
||||
return false;
|
||||
@ -137,7 +240,7 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
@Override
|
||||
public void onSetTime() {
|
||||
// since this is a standard we should generalize this in Gadgetbridge (properly)
|
||||
// Since this is a standard we should generalize this in Gadgetbridge (properly)
|
||||
GregorianCalendar now = BLETypeConversions.createCalendar();
|
||||
byte[] bytes = BLETypeConversions.calendarToRawBytes(now);
|
||||
byte[] tail = new byte[]{0, BLETypeConversions.mapTimeZone(now.getTimeZone(), BLETypeConversions.TZ_FLAG_INCLUDE_DST_IN_TZ)};
|
||||
@ -163,7 +266,6 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onEnableRealtimeSteps(boolean enable) {
|
||||
|
||||
@ -171,7 +273,35 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
@Override
|
||||
public void onInstallApp(Uri uri) {
|
||||
try {
|
||||
handler = new PineTimeInstallHandler(uri, getContext());
|
||||
|
||||
// TODO: Check validity more closely
|
||||
if (true) {
|
||||
DfuServiceInitiator starter = new DfuServiceInitiator(getDevice().getAddress())
|
||||
.setDeviceName(getDevice().getName())
|
||||
.setKeepBond(true)
|
||||
.setForeground(false)
|
||||
.setUnsafeExperimentalButtonlessServiceInSecureDfuEnabled(false)
|
||||
.setMtu(517)
|
||||
.setZip(uri);
|
||||
|
||||
controller = starter.start(getContext(), PineTimeDFUService.class);
|
||||
DfuServiceListenerHelper.registerProgressListener(getContext(), progressListener);
|
||||
DfuServiceListenerHelper.registerLogListener(getContext(), this);
|
||||
|
||||
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent(GB.ACTION_SET_PROGRESS_BAR)
|
||||
.putExtra(GB.PROGRESS_BAR_INDETERMINATE, true)
|
||||
);
|
||||
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent(GB.ACTION_SET_PROGRESS_TEXT)
|
||||
.putExtra(GB.DISPLAY_MESSAGE_MESSAGE, getContext().getString(R.string.devicestatus_upload_starting))
|
||||
);
|
||||
} else {
|
||||
// TODO: Handle invalid firmware files
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
GB.toast(getContext(), getContext().getString(R.string.updatefirmwareoperation_write_failed) + ":" + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -221,11 +351,14 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
@Override
|
||||
public void onFindDevice(boolean start) {
|
||||
onSetConstantVibration(start ? 0xff : 0x00);
|
||||
TransactionBuilder builder = new TransactionBuilder("Enable alert");
|
||||
builder.write(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_ALERT_LEVEL), new byte[]{(byte) (start ? 0x01 : 0x00)});
|
||||
builder.queue(getQueue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetConstantVibration(int intensity) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -253,24 +386,47 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
|
||||
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
|
||||
requestDeviceInfo(builder);
|
||||
onSetTime();
|
||||
builder.notify(getCharacteristic(PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_EVENT), true);
|
||||
setInitialized(builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetMusicInfo(MusicSpec musicSpec) {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("send playback info");
|
||||
|
||||
if (musicSpec.album != null) {
|
||||
builder.write(getCharacteristic(UUID_CHARACTERISTICS_MUSIC_TRACK), musicSpec.track.getBytes());
|
||||
if (musicSpec.album != null && !musicSpec.album.equals(lastAlbum)) {
|
||||
lastAlbum = musicSpec.album;
|
||||
safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_ALBUM, musicSpec.album.getBytes());
|
||||
}
|
||||
if (musicSpec.artist != null) {
|
||||
builder.write(getCharacteristic(UUID_CHARACTERISTICS_MUSIC_ARTIST), musicSpec.artist.getBytes());
|
||||
if (musicSpec.track != null && !musicSpec.track.equals(lastTrack)) {
|
||||
lastTrack = musicSpec.track;
|
||||
safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_TRACK, musicSpec.track.getBytes());
|
||||
}
|
||||
if (musicSpec.album != null) {
|
||||
builder.write(getCharacteristic(UUID_CHARACTERISTICS_MUSIC_ALBUM), musicSpec.album.getBytes());
|
||||
if (musicSpec.artist != null && !musicSpec.artist.equals(lastArtist)) {
|
||||
lastArtist = musicSpec.artist;
|
||||
safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_ARTIST, musicSpec.artist.getBytes());
|
||||
}
|
||||
|
||||
if (musicSpec.duration != MusicSpec.MUSIC_UNKNOWN) {
|
||||
safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_LENGTH_TOTAL, intToBytes(musicSpec.duration));
|
||||
}
|
||||
if (musicSpec.trackNr != MusicSpec.MUSIC_UNKNOWN) {
|
||||
safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_TRACK_NUMBER, intToBytes(musicSpec.trackNr));
|
||||
}
|
||||
if (musicSpec.trackCount != MusicSpec.MUSIC_UNKNOWN) {
|
||||
safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_TRACK_TOTAL, intToBytes(musicSpec.trackCount));
|
||||
}
|
||||
|
||||
builder.queue(getQueue());
|
||||
} catch (Exception e) {
|
||||
LOG.error("error sending music info", e);
|
||||
LOG.error("Error sending music info", e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -279,19 +435,65 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("send playback state");
|
||||
|
||||
byte[] state = new byte[]{0};
|
||||
if (stateSpec.state == MusicStateSpec.STATE_PLAYING) {
|
||||
state[0] = 1;
|
||||
if (stateSpec.state != MusicStateSpec.STATE_UNKNOWN) {
|
||||
byte[] state = new byte[1];
|
||||
if (stateSpec.state == MusicStateSpec.STATE_PLAYING) {
|
||||
state[0] = 0x01;
|
||||
}
|
||||
safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_STATUS, state);
|
||||
}
|
||||
builder.write(getCharacteristic(UUID_CHARACTERISTICS_MUSIC_STATUS), state);
|
||||
|
||||
if (stateSpec.playRate != MusicStateSpec.STATE_UNKNOWN) {
|
||||
safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_PLAYBACK_SPEED, intToBytes(stateSpec.playRate));
|
||||
}
|
||||
|
||||
if (stateSpec.position != MusicStateSpec.STATE_UNKNOWN) {
|
||||
safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_POSITION, intToBytes(stateSpec.position));
|
||||
}
|
||||
|
||||
if (stateSpec.repeat != MusicStateSpec.STATE_UNKNOWN) {
|
||||
safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_REPEAT, intToBytes(stateSpec.repeat));
|
||||
}
|
||||
|
||||
if (stateSpec.shuffle != MusicStateSpec.STATE_UNKNOWN) {
|
||||
safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_SHUFFLE, intToBytes(stateSpec.repeat));
|
||||
}
|
||||
|
||||
builder.queue(getQueue());
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.error("error sending music state", e);
|
||||
LOG.error("Error sending music state", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCharacteristicRead(BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic, int status) {
|
||||
if (super.onCharacteristicRead(gatt, characteristic, status)) {
|
||||
return true;
|
||||
}
|
||||
UUID characteristicUUID = characteristic.getUuid();
|
||||
|
||||
LOG.info("Unhandled characteristic read: " + characteristicUUID);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendConfiguration(String config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReadConfiguration(String config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTestNewFunction() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCharacteristicChanged(BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic) {
|
||||
@ -300,7 +502,7 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
UUID characteristicUUID = characteristic.getUuid();
|
||||
if (characteristicUUID.equals(UUID_CHARACTERISTICS_MUSIC_EVENT)) {
|
||||
if (characteristicUUID.equals(PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_EVENT)) {
|
||||
byte[] value = characteristic.getValue();
|
||||
GBDeviceEventMusicControl deviceEventMusicControl = new GBDeviceEventMusicControl();
|
||||
|
||||
@ -333,35 +535,52 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCharacteristicRead(BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic, int status) {
|
||||
if (super.onCharacteristicRead(gatt, characteristic, status)) {
|
||||
return true;
|
||||
}
|
||||
UUID characteristicUUID = characteristic.getUuid();
|
||||
|
||||
LOG.info("Unhandled characteristic read: " + characteristicUUID);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendConfiguration(String config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReadConfiguration(String config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTestNewFunction() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendWeather(WeatherSpec weatherSpec) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that just converts an integer into a byte array
|
||||
*/
|
||||
private static byte[] intToBytes(int source) {
|
||||
return ByteBuffer.allocate(4).putInt(source).array();
|
||||
}
|
||||
|
||||
/**
|
||||
* This will check if the characteristic exists and can be written
|
||||
* <p>
|
||||
* Keeps backwards compatibility with firmware that can't take all the information
|
||||
*/
|
||||
private void safeWriteToCharacteristic(TransactionBuilder builder, UUID uuid, byte[] data) {
|
||||
BluetoothGattCharacteristic characteristic = getCharacteristic(uuid);
|
||||
if (characteristic != null &&
|
||||
(characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) {
|
||||
builder.write(characteristic, data);
|
||||
}
|
||||
}
|
||||
|
||||
private void setInitialized(TransactionBuilder builder) {
|
||||
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
|
||||
}
|
||||
|
||||
private void requestDeviceInfo(TransactionBuilder builder) {
|
||||
LOG.debug("Requesting Device Info!");
|
||||
deviceInfoProfile.requestDeviceInfo(builder);
|
||||
}
|
||||
|
||||
private void handleDeviceInfo(nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo info) {
|
||||
LOG.warn("Device info: " + info);
|
||||
versionCmd.hwVersion = info.getHardwareRevision();
|
||||
versionCmd.fwVersion = info.getFirmwareRevision();
|
||||
handleGBDeviceEvent(versionCmd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Nordic DFU needs this function to log DFU-related messages
|
||||
*/
|
||||
@Override
|
||||
public void onLogEvent(final String deviceAddress, final int level, final String message) {
|
||||
LOG.debug(message);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,34 @@
|
||||
/* Copyright (C) 2019-2020 opavlov
|
||||
|
||||
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.sonyswr12;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class SonySWR12Constants {
|
||||
//accessory host service
|
||||
public static final String BASE_UUID_AHS = "0000%s-37CB-11E3-8682-0002A5D5C51B";
|
||||
public static final UUID UUID_SERVICE_AHS = UUID.fromString(String.format(BASE_UUID_AHS, "0200"));
|
||||
public static final UUID UUID_CHARACTERISTIC_ALARM = UUID.fromString(String.format(BASE_UUID_AHS, "0204"));
|
||||
public static final UUID UUID_CHARACTERISTIC_EVENT = UUID.fromString(String.format(BASE_UUID_AHS, "0205"));
|
||||
public static final UUID UUID_CHARACTERISTIC_TIME = UUID.fromString(String.format(BASE_UUID_AHS, "020B"));
|
||||
public static final UUID UUID_CHARACTERISTIC_CONTROL_POINT = UUID.fromString(String.format(BASE_UUID_AHS, "0208"));
|
||||
|
||||
public static final int TYPE_ACTIVITY = 0;
|
||||
public static final int TYPE_LIGHT = 1;
|
||||
public static final int TYPE_DEEP = 2;
|
||||
public static final int TYPE_NOT_WORN = 3;
|
||||
}
|
@ -0,0 +1,371 @@
|
||||
/* Copyright (C) 2015-2020 Andreas Böhler, Andreas Shimokawa, Carsten
|
||||
Pfeiffer, Daniel Dakhno, Daniele Gobbetti, JohnnySun, José Rebelo,
|
||||
opavlov
|
||||
|
||||
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.sonyswr12;
|
||||
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfoProfile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity.EventBase;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity.EventFactory;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.alarm.BandAlarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.alarm.BandAlarms;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.control.CommandCode;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.control.ControlPointLowVibration;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.control.ControlPointWithValue;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.time.BandTime;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
// done:
|
||||
// - time sync
|
||||
// - alarms (also smart)
|
||||
// - fetching activity(walking, sleep)
|
||||
// - stamina mode
|
||||
// - vibration intensity
|
||||
// - realtime heart rate
|
||||
// todo options:
|
||||
// - "get moving"
|
||||
// - get notified: -call, -notification, -notification from, -do not disturb
|
||||
// - media control: media/find phone(tap once for play pause, tap twice for next, tap triple for previous)
|
||||
|
||||
public class SonySWR12DeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SonySWR12DeviceSupport.class);
|
||||
private SonySWR12HandlerThread processor = null;
|
||||
|
||||
private final BatteryInfoProfile<SonySWR12DeviceSupport> batteryInfoProfile;
|
||||
private final IntentListener mListener = new IntentListener() {
|
||||
@Override
|
||||
public void notify(Intent intent) {
|
||||
if (intent.getAction().equals(BatteryInfoProfile.ACTION_BATTERY_INFO)) {
|
||||
BatteryInfo info = intent.getParcelableExtra(BatteryInfoProfile.EXTRA_BATTERY_INFO);
|
||||
GBDeviceEventBatteryInfo gbInfo = new GBDeviceEventBatteryInfo();
|
||||
gbInfo.level = (short) info.getPercentCharged();
|
||||
handleGBDeviceEvent(gbInfo);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public SonySWR12DeviceSupport() {
|
||||
super(LOG);
|
||||
addSupportedService(GattService.UUID_SERVICE_BATTERY_SERVICE);
|
||||
addSupportedService(SonySWR12Constants.UUID_SERVICE_AHS);
|
||||
batteryInfoProfile = new BatteryInfoProfile<>(this);
|
||||
batteryInfoProfile.addListener(mListener);
|
||||
addSupportedProfile(batteryInfoProfile);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
|
||||
initialize();
|
||||
setTime(builder);
|
||||
batteryInfoProfile.requestBatteryInfo(builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
private SonySWR12HandlerThread getProcessor() {
|
||||
if (processor == null) {
|
||||
processor = new SonySWR12HandlerThread(getDevice(), getContext());
|
||||
processor.start();
|
||||
}
|
||||
return processor;
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
if (gbDevice.getState() != GBDevice.State.INITIALIZED) {
|
||||
gbDevice.setFirmwareVersion("N/A");
|
||||
gbDevice.setFirmwareVersion2("N/A");
|
||||
gbDevice.setState(GBDevice.State.INITIALIZED);
|
||||
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useAutoConnect() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotification(NotificationSpec notificationSpec) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeleteNotification(int id) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetTime() {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("setTime");
|
||||
setTime(builder);
|
||||
builder.queue(getQueue());
|
||||
} catch (Exception e) {
|
||||
GB.toast(getContext(), "Error setting time: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private void setTime(TransactionBuilder builder) {
|
||||
BluetoothGattCharacteristic timeCharacteristic = getCharacteristic(SonySWR12Constants.UUID_CHARACTERISTIC_TIME);
|
||||
builder.write(timeCharacteristic, new BandTime(Calendar.getInstance()).toByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
|
||||
try {
|
||||
BluetoothGattCharacteristic alarmCharacteristic = getCharacteristic(SonySWR12Constants.UUID_CHARACTERISTIC_ALARM);
|
||||
TransactionBuilder builder = performInitialized("alarm");
|
||||
int prefInterval = Integer.valueOf(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress())
|
||||
.getString(DeviceSettingsPreferenceConst.PREF_SONYSWR12_SMART_INTERVAL, "0"));
|
||||
ArrayList<BandAlarm> bandAlarmList = new ArrayList<>();
|
||||
for (Alarm alarm : alarms) {
|
||||
BandAlarm bandAlarm = BandAlarm.fromAppAlarm(alarm, bandAlarmList.size(), alarm.getSmartWakeup() ? prefInterval : 0);
|
||||
if (bandAlarm != null)
|
||||
bandAlarmList.add(bandAlarm);
|
||||
}
|
||||
builder.write(alarmCharacteristic, new BandAlarms(bandAlarmList).toByteArray());
|
||||
builder.queue(getQueue());
|
||||
} catch (Exception e) {
|
||||
GB.toast(getContext(), "Error setting alarms: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
||||
return super.onCharacteristicRead(gatt, characteristic, status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
|
||||
if (super.onCharacteristicChanged(gatt, characteristic))
|
||||
return true;
|
||||
UUID uuid = characteristic.getUuid();
|
||||
if (uuid.equals(SonySWR12Constants.UUID_CHARACTERISTIC_EVENT)) {
|
||||
try {
|
||||
EventBase event = EventFactory.readEventFromByteArray(characteristic.getValue());
|
||||
getProcessor().process(event);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetCallState(CallSpec callSpec) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetMusicState(MusicStateSpec stateSpec) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetMusicInfo(MusicSpec musicSpec) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnableRealtimeSteps(boolean enable) {
|
||||
//doesn't support realtime steps
|
||||
//supports only realtime heart rate
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInstallApp(Uri uri) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppInfoReq() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppStart(UUID uuid, boolean start) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppDelete(UUID uuid) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppConfiguration(UUID appUuid, String config, Integer id) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppReorder(UUID[] uuids) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchRecordedData(int dataTypes) {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("fetchActivity");
|
||||
builder.notify(getCharacteristic(SonySWR12Constants.UUID_CHARACTERISTIC_EVENT), true);
|
||||
ControlPointWithValue flushControl = new ControlPointWithValue(CommandCode.FLUSH_ACTIVITY, 0);
|
||||
builder.write(getCharacteristic(SonySWR12Constants.UUID_CHARACTERISTIC_CONTROL_POINT), flushControl.toByteArray());
|
||||
builder.queue(getQueue());
|
||||
} catch (Exception e) {
|
||||
LOG.error("failed to fetch activity data", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReset(int flags) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeartRateTest() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("HeartRateTest");
|
||||
builder.notify(getCharacteristic(SonySWR12Constants.UUID_CHARACTERISTIC_EVENT), enable);
|
||||
ControlPointWithValue controlPointHeart = new ControlPointWithValue(CommandCode.HEARTRATE_REALTIME, enable ? 1 : 0);
|
||||
builder.write(getCharacteristic(SonySWR12Constants.UUID_CHARACTERISTIC_CONTROL_POINT), controlPointHeart.toByteArray());
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Unable to read heart rate from Sony device", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFindDevice(boolean start) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetConstantVibration(int integer) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScreenshotReq() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnableHeartRateSleepSupport(boolean enable) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetHeartRateMeasurementInterval(int seconds) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeleteCalendarEvent(byte type, long id) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendConfiguration(String config) {
|
||||
try {
|
||||
switch (config) {
|
||||
case DeviceSettingsPreferenceConst.PREF_SONYSWR12_STAMINA: {
|
||||
//stamina can be:
|
||||
//disabled = 0, enabled = 1 or todo auto on low battery = 2
|
||||
int status = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(config, false) ? 1 : 0;
|
||||
TransactionBuilder builder = performInitialized(config);
|
||||
ControlPointWithValue vibrationControl = new ControlPointWithValue(CommandCode.STAMINA_MODE, status);
|
||||
builder.write(getCharacteristic(SonySWR12Constants.UUID_CHARACTERISTIC_CONTROL_POINT), vibrationControl.toByteArray());
|
||||
builder.queue(getQueue());
|
||||
break;
|
||||
}
|
||||
case DeviceSettingsPreferenceConst.PREF_SONYSWR12_LOW_VIBRATION: {
|
||||
boolean isEnabled = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(config, false);
|
||||
TransactionBuilder builder = performInitialized(config);
|
||||
ControlPointLowVibration vibrationControl = new ControlPointLowVibration(isEnabled);
|
||||
builder.write(getCharacteristic(SonySWR12Constants.UUID_CHARACTERISTIC_CONTROL_POINT), vibrationControl.toByteArray());
|
||||
builder.queue(getQueue());
|
||||
break;
|
||||
}
|
||||
case DeviceSettingsPreferenceConst.PREF_SONYSWR12_SMART_INTERVAL: {
|
||||
onSetAlarms(new ArrayList(DBHelper.getAlarms(gbDevice)));
|
||||
}
|
||||
}
|
||||
} catch (Exception exc) {
|
||||
LOG.error("failed to send config " + config, exc);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReadConfiguration(String config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTestNewFunction() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendWeather(WeatherSpec weatherSpec) {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
/* Copyright (C) 2015-2020 Andreas Shimokawa, Carsten Pfeiffer, opavlov
|
||||
|
||||
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.sonyswr12;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sonyswr12.SonySWR12SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.SonySWR12Sample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity.ActivityBase;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity.ActivitySleep;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity.ActivityWithData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity.EventBase;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity.EventCode;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity.EventWithActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity.EventWithValue;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
|
||||
|
||||
public class SonySWR12HandlerThread extends GBDeviceIoThread {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SonySWR12HandlerThread.class);
|
||||
|
||||
public SonySWR12HandlerThread(GBDevice gbDevice, Context context) {
|
||||
super(gbDevice, context);
|
||||
}
|
||||
|
||||
public void process(EventBase event) {
|
||||
if (event instanceof EventWithValue) {
|
||||
if (event.getCode() == EventCode.HEART_RATE) {
|
||||
processRealTimeHeartRate((EventWithValue) event);
|
||||
}
|
||||
} else if (event instanceof EventWithActivity) {
|
||||
processWithActivity((EventWithActivity) event);
|
||||
}
|
||||
}
|
||||
|
||||
private void processRealTimeHeartRate(EventWithValue event) {
|
||||
try {
|
||||
DBHandler dbHandler = GBApplication.acquireDB();
|
||||
Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
|
||||
Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
|
||||
SonySWR12SampleProvider provider = new SonySWR12SampleProvider(getDevice(), dbHandler.getDaoSession());
|
||||
int timestamp = getTimestamp();
|
||||
SonySWR12Sample sample = new SonySWR12Sample(timestamp, deviceId, userId, (int) event.value, ActivitySample.NOT_MEASURED, 0, 1);
|
||||
provider.addGBActivitySample(sample);
|
||||
GBApplication.releaseDB();
|
||||
Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
|
||||
.putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample)
|
||||
.putExtra(DeviceService.EXTRA_TIMESTAMP, timestamp);
|
||||
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private int getTimestamp() {
|
||||
return (int) (System.currentTimeMillis() / 1000);
|
||||
}
|
||||
|
||||
private void processWithActivity(EventWithActivity event) {
|
||||
List<ActivityBase> payloadList = event.activityList;
|
||||
for (ActivityBase activity : payloadList) {
|
||||
switch (activity.getType()) {
|
||||
case WALK:
|
||||
case RUN:
|
||||
addActivity((ActivityWithData) activity);
|
||||
break;
|
||||
case SLEEP:
|
||||
addSleep((ActivitySleep) activity);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addActivity(ActivityWithData activity) {
|
||||
try {
|
||||
DBHandler dbHandler = GBApplication.acquireDB();
|
||||
Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
|
||||
Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
|
||||
SonySWR12SampleProvider provider = new SonySWR12SampleProvider(getDevice(), dbHandler.getDaoSession());
|
||||
int kind = SonySWR12Constants.TYPE_ACTIVITY;
|
||||
SonySWR12Sample sample = new SonySWR12Sample(activity.getTimeStampSec(), deviceId, userId, ActivitySample.NOT_MEASURED, activity.data, kind, 1);
|
||||
provider.addGBActivitySample(sample);
|
||||
GBApplication.releaseDB();
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void addSleep(ActivitySleep activity) {
|
||||
try {
|
||||
DBHandler dbHandler = GBApplication.acquireDB();
|
||||
Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
|
||||
Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
|
||||
SonySWR12SampleProvider provider = new SonySWR12SampleProvider(getDevice(), dbHandler.getDaoSession());
|
||||
int kind;
|
||||
switch (activity.sleepLevel) {
|
||||
case LIGHT:
|
||||
kind = SonySWR12Constants.TYPE_LIGHT;
|
||||
break;
|
||||
case DEEP:
|
||||
kind = SonySWR12Constants.TYPE_DEEP;
|
||||
break;
|
||||
default:
|
||||
kind = SonySWR12Constants.TYPE_ACTIVITY;
|
||||
break;
|
||||
}
|
||||
if (kind == SonySWR12Constants.TYPE_LIGHT || kind == SonySWR12Constants.TYPE_DEEP) {
|
||||
//need so much samples because sleep has exact duration
|
||||
//so empty samples are for right representation of sleep on activity charts
|
||||
SonySWR12Sample sample = new SonySWR12Sample(activity.getTimeStampSec(), deviceId, userId, ActivitySample.NOT_MEASURED, 0, SonySWR12Constants.TYPE_NOT_WORN, 1);
|
||||
provider.addGBActivitySample(sample);
|
||||
sample = new SonySWR12Sample(activity.getTimeStampSec() + 2, deviceId, userId, ActivitySample.NOT_MEASURED, 0, kind, 1);
|
||||
provider.addGBActivitySample(sample);
|
||||
sample = new SonySWR12Sample(activity.getTimeStampSec() + activity.durationMin * 60 - 2, deviceId, userId, ActivitySample.NOT_MEASURED, 0, kind, 1);
|
||||
provider.addGBActivitySample(sample);
|
||||
sample = new SonySWR12Sample(activity.getTimeStampSec() + activity.durationMin * 60, deviceId, userId, ActivitySample.NOT_MEASURED, 0, SonySWR12Constants.TYPE_NOT_WORN, 1);
|
||||
provider.addGBActivitySample(sample);
|
||||
}
|
||||
GBApplication.releaseDB();
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/* Copyright (C) 2019-2020 opavlov
|
||||
|
||||
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.sonyswr12;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
public class SonySWR12Util {
|
||||
|
||||
public static long secSince2013() {
|
||||
//sony uses time on band since 2013 for some reason
|
||||
final Calendar instance = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
instance.set(2013, 0, 1, 0, 0, 0);
|
||||
instance.set(14, 0);
|
||||
return instance.getTimeInMillis()/1000;
|
||||
}
|
||||
|
||||
public static String timeToString(long sec) {
|
||||
SimpleDateFormat format = new SimpleDateFormat("MM-dd HH:mm:ss");
|
||||
return format.format(new Date(sec * 1000));
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/* Copyright (C) 2019-2020 opavlov
|
||||
|
||||
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.sonyswr12.entities.activity;
|
||||
|
||||
public abstract class ActivityBase {
|
||||
protected final ActivityType type;
|
||||
private final long timeStampSec;
|
||||
|
||||
public ActivityBase(ActivityType type, int timeOffsetMin, long timeStampSec) {
|
||||
if (timeOffsetMin < 0 || timeOffsetMin > 1440) {
|
||||
throw new IllegalArgumentException("activity time offset out of range: " + timeOffsetMin);
|
||||
}
|
||||
this.type = type;
|
||||
this.timeStampSec = timeStampSec + timeOffsetMin * 60;
|
||||
}
|
||||
|
||||
public final int getTimeStampSec() {
|
||||
return (int) (timeStampSec);
|
||||
}
|
||||
|
||||
public final ActivityType getType() {
|
||||
return this.type;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/* Copyright (C) 2019-2020 opavlov
|
||||
|
||||
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.sonyswr12.entities.activity;
|
||||
|
||||
public class ActivityHeartRate extends ActivityBase {
|
||||
public final int bpm;
|
||||
|
||||
public ActivityHeartRate(int timeOffsetMin, int bpm, Long timeStampSec) {
|
||||
super(ActivityType.HEART_RATE, timeOffsetMin, timeStampSec);
|
||||
if (bpm < 0 || bpm > 65535) {
|
||||
throw new IllegalArgumentException("bpm out of range: " + bpm);
|
||||
}
|
||||
this.bpm = bpm;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user