mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-12-24 17:45:50 +01:00
Support for Withings Steel HR (#2831)
Co-authored-by: hrglpfrmpf <hrglpfrmpf@web.de> Reviewed-on: https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/2831 Co-authored-by: hrglpfrmpf <hrglpfrmpf@noreply.codeberg.org> Co-committed-by: hrglpfrmpf <hrglpfrmpf@noreply.codeberg.org>
This commit is contained in:
parent
606e20a065
commit
c1fd0b77ad
@ -90,6 +90,7 @@ public class GBDaoGenerator {
|
||||
addCasioGBX100Sample(schema, user, device);
|
||||
addFitProActivitySample(schema, user, device);
|
||||
addPineTimeActivitySample(schema, user, device);
|
||||
addWithingsSteelHRActivitySample(schema, user, device);
|
||||
addHybridHRActivitySample(schema, user, device);
|
||||
addVivomoveHrActivitySample(schema, user, device);
|
||||
addGarminFitFile(schema, user, device);
|
||||
@ -818,7 +819,7 @@ public class GBDaoGenerator {
|
||||
Property deviceId = batteryLevel.addLongProperty("deviceId").primaryKey().notNull().getProperty();
|
||||
batteryLevel.addToOne(device, deviceId);
|
||||
batteryLevel.addIntProperty("level").notNull();
|
||||
batteryLevel.addIntProperty("batteryIndex").notNull().primaryKey();;
|
||||
batteryLevel.addIntProperty("batteryIndex").notNull().primaryKey();
|
||||
return batteryLevel;
|
||||
}
|
||||
|
||||
@ -847,4 +848,18 @@ public class GBDaoGenerator {
|
||||
addHeartRateProperties(activitySample);
|
||||
return activitySample;
|
||||
}
|
||||
|
||||
private static Entity addWithingsSteelHRActivitySample(Schema schema, Entity user, Entity device) {
|
||||
Entity activitySample = addEntity(schema, "WithingsSteelHRActivitySample");
|
||||
activitySample.implementsSerializable();
|
||||
addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
|
||||
activitySample.addIntProperty("duration").notNull();
|
||||
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);
|
||||
activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
||||
return activitySample;
|
||||
}
|
||||
}
|
||||
|
@ -722,6 +722,10 @@
|
||||
android:name=".devices.qhybrid.CalibrationActivity"
|
||||
android:label="@string/qhybrid_title_calibration"
|
||||
android:parentActivityName=".devices.qhybrid.HRConfigActivity" />
|
||||
<activity
|
||||
android:name=".devices.withingssteelhr.WithingsCalibrationActivity"
|
||||
android:label="@string/qhybrid_title_calibration"
|
||||
android:parentActivityName=".devices.withingssteelhr.WithingsCalibrationActivity" />
|
||||
<activity
|
||||
android:name=".devices.qhybrid.CommuteActionsActivity"
|
||||
android:label="@string/qhybrid_pref_title_actions"
|
||||
|
@ -0,0 +1,201 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.DashPathEffect;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
|
||||
public class RotaryControl extends View {
|
||||
|
||||
public interface RotationListener {
|
||||
void onRotation(short movementAmount);
|
||||
}
|
||||
|
||||
private Path circlePath;
|
||||
|
||||
private int controlPointX;
|
||||
private int controlPointY;
|
||||
|
||||
private int controlCenterX;
|
||||
private int controlCenterY;
|
||||
private int controlRadius;
|
||||
|
||||
private int padding;
|
||||
private int controlPointSize;
|
||||
private int controlPointColor;
|
||||
private int lineColor;
|
||||
private int lineThickness;
|
||||
private double startAngle;
|
||||
private double angle ;
|
||||
private boolean isControlPointSelected = false;
|
||||
private Paint paint = new Paint();
|
||||
private Paint controlPointPaint = new Paint();
|
||||
private RotationListener rotationListener;
|
||||
|
||||
public RotaryControl(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public RotaryControl(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public RotaryControl(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RotaryControl, defStyleAttr, 0);
|
||||
|
||||
startAngle = a.getFloat(R.styleable.RotaryControl_start_angle, (float) Math.PI / 2);
|
||||
angle = startAngle;
|
||||
controlPointSize = a.getDimensionPixelSize(R.styleable.RotaryControl_controlpoint_size, 50);
|
||||
controlPointColor = a.getColor(R.styleable.RotaryControl_controlpoint_color, Color.GRAY);
|
||||
lineThickness = a.getDimensionPixelSize(R.styleable.RotaryControl_line_thickness, 20);
|
||||
lineColor = a.getColor(R.styleable.RotaryControl_line_color, Color.RED);
|
||||
calculateAndSetPadding();
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
private void calculateAndSetPadding() {
|
||||
int totalPadding = getPaddingLeft() + getPaddingRight() + getPaddingBottom() + getPaddingTop() + getPaddingEnd() + getPaddingStart();
|
||||
padding = totalPadding / 6;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
|
||||
int smallerDim = width > height ? height : width;
|
||||
int largestCenteredSquareLeft = (width - smallerDim) / 2;
|
||||
int largestCenteredSquareTop = (height - smallerDim) / 2;
|
||||
int largestCenteredSquareRight = largestCenteredSquareLeft + smallerDim;
|
||||
int largestCenteredSquareBottom = largestCenteredSquareTop + smallerDim;
|
||||
controlCenterX = largestCenteredSquareRight / 2 + (width - largestCenteredSquareRight) / 2;
|
||||
controlCenterY = largestCenteredSquareBottom / 2 + (height - largestCenteredSquareBottom) / 2;
|
||||
controlRadius = smallerDim / 2 - lineThickness / 2 - padding;
|
||||
|
||||
super.onSizeChanged(width, height, oldWidth, oldHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
drawRotationCircle(canvas);
|
||||
drawControlPoint(canvas);
|
||||
|
||||
}
|
||||
|
||||
private void drawControlPoint(Canvas canvas) {
|
||||
controlPointX = (int) (controlCenterX + controlRadius * Math.cos(angle));
|
||||
controlPointY = (int) (controlCenterY - controlRadius * Math.sin(angle));
|
||||
controlPointPaint.setColor(controlPointColor);
|
||||
controlPointPaint.setStyle(Paint.Style.FILL);
|
||||
controlPointPaint.setAlpha(128);
|
||||
Path controlPointPath = new Path();
|
||||
controlPointPath.addCircle(controlPointX, controlPointY, controlPointSize, Path.Direction.CW);
|
||||
canvas.drawPath(controlPointPath, controlPointPaint);
|
||||
}
|
||||
|
||||
private void drawRotationCircle(Canvas canvas) {
|
||||
DashPathEffect dashPath = new DashPathEffect(new float[]{8,22}, (float)1.0);
|
||||
paint.setPathEffect(dashPath);
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
paint.setStrokeWidth(lineThickness);
|
||||
paint.setAntiAlias(true);
|
||||
paint.setColor(lineColor);
|
||||
circlePath = new Path();
|
||||
circlePath.addCircle(controlCenterX, controlCenterY, controlRadius, Path.Direction.CW);
|
||||
canvas.drawPath(circlePath, paint);
|
||||
}
|
||||
|
||||
private void updateRotationPosition(double touchX, double touchY) {
|
||||
double distanceX = touchX - controlCenterX;
|
||||
double distanceY = controlCenterY - touchY;
|
||||
double c = Math.sqrt(Math.pow(distanceX, 2) + Math.pow(distanceY, 2));
|
||||
double currentAngle = Math.acos(distanceX / c);
|
||||
if (distanceY < 0) {
|
||||
currentAngle = -currentAngle;
|
||||
}
|
||||
|
||||
int movementAmount = (int) ((currentAngle - angle) * 100);
|
||||
|
||||
int i = (int) movementAmount;
|
||||
if (movementAmount != 0) {
|
||||
if (Math.abs(movementAmount) > 15) {
|
||||
movementAmount /= movementAmount;
|
||||
}
|
||||
|
||||
rotationListener.onRotation((short) -movementAmount);
|
||||
}
|
||||
|
||||
angle = currentAngle;
|
||||
}
|
||||
|
||||
public void setRotationListener(RotationListener listener) {
|
||||
rotationListener = listener;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
angle = startAngle;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
switch (ev.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
double x = ev.getX();
|
||||
double y = ev.getY();
|
||||
if (x < controlPointX + controlPointSize && x > controlPointX - controlPointSize && y < controlPointY + controlPointSize && y > controlPointY - controlPointSize) {
|
||||
getParent().requestDisallowInterceptTouchEvent(true);
|
||||
isControlPointSelected = true;
|
||||
updateRotationPosition(x, y);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
if (isControlPointSelected) {
|
||||
double x = ev.getX();
|
||||
double y = ev.getY();
|
||||
updateRotationPosition(x, y);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_UP: {
|
||||
getParent().requestDisallowInterceptTouchEvent(false);
|
||||
isControlPointSelected = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
invalidate();
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,159 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
|
||||
|
||||
public class WithingsCalibrationActivity extends AbstractGBActivity {
|
||||
|
||||
enum Hands {
|
||||
HOURS((short)1),
|
||||
MINUTES((short)0),
|
||||
ACTIVITY_TARGET((short)2);
|
||||
|
||||
private short code;
|
||||
private Hands(short code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private GBDevice device;
|
||||
private LocalBroadcastManager localBroadcastManager;
|
||||
private String[] calibrationAdvices = new String[3];
|
||||
private Hands[] hands = new Hands[]{Hands.HOURS, Hands.MINUTES, Hands.ACTIVITY_TARGET};
|
||||
private short handIndex = 0;
|
||||
private Button previousButton;
|
||||
private Button nextButton;
|
||||
private Button okButton;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_withings_calibration);
|
||||
List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
|
||||
for(GBDevice device : devices){
|
||||
if(device.getType() == DeviceType.WITHINGS_STEEL_HR ){
|
||||
this.device = device;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (device == null) {
|
||||
Toast.makeText(this, R.string.watch_not_connected, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
initView();
|
||||
localBroadcastManager = LocalBroadcastManager.getInstance(this);
|
||||
localBroadcastManager.sendBroadcast(new Intent(WithingsSteelHRDeviceSupport.START_HANDS_CALIBRATION_CMD));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (localBroadcastManager != null) {
|
||||
localBroadcastManager.sendBroadcast(new Intent(WithingsSteelHRDeviceSupport.STOP_HANDS_CALIBRATION_CMD));
|
||||
}
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
|
||||
calibrationAdvices[0] = getString(R.string.withings_calibration_text_hours);
|
||||
calibrationAdvices[1] = getString(R.string.withings_calibration_text_minutes);
|
||||
calibrationAdvices[2] = getString(R.string.withings_calibration_text_activity_target);
|
||||
|
||||
RotaryControl rotaryControl = findViewById(R.id.rotary_control);
|
||||
rotaryControl.setRotationListener(new RotaryControl.RotationListener() {
|
||||
@Override
|
||||
public void onRotation(short movementAmount) {
|
||||
Intent calibration = new Intent(WithingsSteelHRDeviceSupport.HANDS_CALIBRATION_CMD);
|
||||
calibration.putExtra("hand", hands[handIndex].code);
|
||||
calibration.putExtra("movementAmount", movementAmount);
|
||||
localBroadcastManager.sendBroadcast(calibration);
|
||||
}
|
||||
});
|
||||
|
||||
TextView textView = findViewById(R.id.withings_calibration_textview);
|
||||
textView.setText(calibrationAdvices[0]);
|
||||
previousButton = findViewById(R.id.withings_calibration_button_previous);
|
||||
previousButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
handIndex--;
|
||||
enableButtons();
|
||||
textView.setText(calibrationAdvices[handIndex]);
|
||||
rotaryControl.reset();
|
||||
}
|
||||
});
|
||||
nextButton = findViewById(R.id.withings_calibration_button_next);
|
||||
nextButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
handIndex++;
|
||||
enableButtons();
|
||||
textView.setText(calibrationAdvices[handIndex]);
|
||||
rotaryControl.reset();
|
||||
}
|
||||
});
|
||||
|
||||
okButton = findViewById(R.id.withings_calibration_button_ok);
|
||||
okButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
enableButtons();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void enableButtons() {
|
||||
nextButton.setEnabled(handIndex < 2);
|
||||
previousButton.setEnabled(handIndex > 0);
|
||||
okButton.setEnabled(handIndex == 2);
|
||||
}
|
||||
}
|
@ -0,0 +1,175 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import de.greenrobot.dao.query.QueryBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.WithingsSteelHRActivitySampleDao;
|
||||
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 WithingsSteelHRDeviceCoordinator extends AbstractDeviceCoordinator {
|
||||
|
||||
@Override
|
||||
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
|
||||
Long deviceId = device.getId();
|
||||
QueryBuilder<?> qb = session.getWithingsSteelHRActivitySampleDao().queryBuilder();
|
||||
qb.where(WithingsSteelHRActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
|
||||
String name = candidate.getDevice().getName();
|
||||
if (name != null && (name.toLowerCase(Locale.ROOT).startsWith("steel") || name.toLowerCase(Locale.ROOT).startsWith("activite"))) {
|
||||
return DeviceType.WITHINGS_STEEL_HR;
|
||||
}
|
||||
|
||||
return DeviceType.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||
return new int[]{
|
||||
R.xml.devicesettings_withingssteelhr
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceType getDeviceType() {
|
||||
return DeviceType.WITHINGS_STEEL_HR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBondingStyle(){
|
||||
return BONDING_STYLE_BOND;
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Class<? extends Activity> getPairingActivity() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRemSleep() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityDataFetching() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityTracking() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityTracks() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
|
||||
return new WithingsSteelHRSampleProvider(device, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstallHandler findInstallHandler(Uri uri, Context context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsScreenshots() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAlarmSlotCount(GBDevice gbDevice) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAlarmDescription(GBDevice device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsSmartWakeup(GBDevice device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturer() {
|
||||
return "Withings";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAppsManagement(GBDevice gbDevice) {
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import de.greenrobot.dao.AbstractDao;
|
||||
import de.greenrobot.dao.Property;
|
||||
import de.greenrobot.dao.query.Query;
|
||||
import de.greenrobot.dao.query.QueryBuilder;
|
||||
import de.greenrobot.dao.query.WhereCondition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.WithingsSteelHRActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.WithingsSteelHRActivitySampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation.ActivitySampleHandler;
|
||||
|
||||
public class WithingsSteelHRSampleProvider extends AbstractSampleProvider<WithingsSteelHRActivitySample> {
|
||||
private static final Logger logger = LoggerFactory.getLogger(WithingsSteelHRSampleProvider.class);
|
||||
|
||||
public WithingsSteelHRSampleProvider(GBDevice device, DaoSession session) {
|
||||
super(device, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractDao<WithingsSteelHRActivitySample, ?> getSampleDao() {
|
||||
return getSession().getWithingsSteelHRActivitySampleDao();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Property getRawKindSampleProperty() {
|
||||
return WithingsSteelHRActivitySampleDao.Properties.RawKind;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Property getTimestampSampleProperty() {
|
||||
return WithingsSteelHRActivitySampleDao.Properties.Timestamp;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Property getDeviceIdentifierSampleProperty() {
|
||||
return WithingsSteelHRActivitySampleDao.Properties.DeviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<WithingsSteelHRActivitySample> getActivitySamples(int timestamp_from, int timestamp_to) {
|
||||
return super.getGBActivitySamples(timestamp_from, timestamp_to, ActivityKind.TYPE_ALL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int normalizeType(int rawType) {
|
||||
return rawType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int toRawActivityKind(int activityKind) {
|
||||
switch (activityKind) {
|
||||
case ActivityKind.TYPE_UNKNOWN:
|
||||
return 0;
|
||||
case ActivityKind.TYPE_LIGHT_SLEEP:
|
||||
return 1;
|
||||
case ActivityKind.TYPE_DEEP_SLEEP:
|
||||
return 2;
|
||||
default:
|
||||
return activityKind;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float normalizeIntensity(int rawIntensity) {
|
||||
if (rawIntensity > 0) {
|
||||
return (float) (Math.log(rawIntensity) / 8);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WithingsSteelHRActivitySample createActivitySample() {
|
||||
return new WithingsSteelHRActivitySample();
|
||||
}
|
||||
}
|
@ -138,6 +138,7 @@ public enum DeviceType {
|
||||
SUPER_CARS(530, R.drawable.ic_device_supercars, R.drawable.ic_device_supercars_disabled, R.string.devicetype_super_cars),
|
||||
ASTEROIDOS(540, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_asteroidos),
|
||||
SOFLOW_SO6(550, R.drawable.ic_device_vesc, R.drawable.ic_device_vesc_disabled, R.string.devicetype_soflow_s06),
|
||||
WITHINGS_STEEL_HR(560, R.drawable.ic_device_watchxplus, R.drawable.ic_device_watchxplus_disabled, R.string.withings_steel_hr),
|
||||
TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_test);
|
||||
|
||||
private final int key;
|
||||
|
@ -111,6 +111,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.vibratissimo.Vibrati
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.vivomovehr.VivomoveHrSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.waspos.WaspOSDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.watch9.Watch9DeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xwatch.XWatchSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.zetime.ZeTimeDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
@ -375,6 +376,8 @@ public class DeviceSupportFactory {
|
||||
return new ServiceDeviceSupport(new AsteroidOSDeviceSupport());
|
||||
case SOFLOW_SO6:
|
||||
return new ServiceDeviceSupport(new SoFlowSupport());
|
||||
case WITHINGS_STEEL_HR:
|
||||
return new ServiceDeviceSupport(new WithingsSteelHRDeviceSupport());
|
||||
case VIVOMOVE_HR:
|
||||
return new ServiceDeviceSupport(new VivomoveHrSupport());
|
||||
}
|
||||
|
@ -26,6 +26,8 @@ import org.slf4j.LoggerFactory;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.NotifyAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.ReadAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.RequestConnectionPriorityAction;
|
||||
@ -61,6 +63,18 @@ public class TransactionBuilder {
|
||||
return add(action);
|
||||
}
|
||||
|
||||
public TransactionBuilder writeChunkedData(BluetoothGattCharacteristic characteristic, byte[] data, int chunkSize) {
|
||||
for (int start = 0; start < data.length; start += chunkSize) {
|
||||
int end = start + chunkSize;
|
||||
if (end > data.length) end = data.length;
|
||||
WriteAction action = new WriteAction(characteristic, Arrays.copyOfRange(data, start, end));
|
||||
add(action);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
public TransactionBuilder requestMtu(int mtu){
|
||||
return add(
|
||||
new RequestMtuAction(mtu)
|
||||
|
@ -0,0 +1,128 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.os.Build;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.WithingsUUID;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation.AbstractResponseHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.Challenge;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ChallengeResponse;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.Probe;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ProbeOsVersion;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ProbeReply;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructure;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessageType;
|
||||
|
||||
public class AuthenticationHandler extends AbstractResponseHandler {
|
||||
private static final Logger logger = LoggerFactory.getLogger(AuthenticationHandler.class);
|
||||
|
||||
// TODO: Save this somewhere if we actually decide to use te secret for more security:
|
||||
private final String secret = "2EM5zNP37QzM00hmP6BFTD92nG15XwNd";
|
||||
private WithingsSteelHRDeviceSupport support;
|
||||
private Challenge challengeToSend;
|
||||
|
||||
public AuthenticationHandler(WithingsSteelHRDeviceSupport support) {
|
||||
super(support);
|
||||
this.support = support;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(Message response) {
|
||||
short messageType = response.getType();
|
||||
if (messageType == WithingsMessageType.PROBE) {
|
||||
handleProbeReply(response);
|
||||
} else if (messageType == WithingsMessageType.CHALLENGE) {
|
||||
handleChallenge(response);
|
||||
} else {
|
||||
logger.warn("Received unkown message: " + messageType + ", will ignore this.");
|
||||
}
|
||||
}
|
||||
|
||||
private void handleChallenge(Message challengeMessage) {
|
||||
try {
|
||||
Challenge challenge = getTypeFromReply(Challenge.class, challengeMessage);
|
||||
ChallengeResponse challengeResponse = new ChallengeResponse();
|
||||
challengeResponse.setResponse(createResponse(challenge));
|
||||
Message message = new WithingsMessage(WithingsMessageType.CHALLENGE);
|
||||
message.addDataStructure(challengeResponse);
|
||||
challengeToSend = new Challenge();
|
||||
challengeToSend.setMacAddress(challenge.getMacAddress());
|
||||
byte[] bArr = new byte[16];
|
||||
new Random().nextBytes(bArr);
|
||||
challengeToSend.setChallenge(bArr);
|
||||
message.addDataStructure(challengeToSend);
|
||||
support.sendToDevice(message);
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to create response to challenge: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleProbeReply(Message message) {
|
||||
ProbeReply probeReply = getTypeFromReply(ProbeReply.class, message);
|
||||
if (probeReply == null) {
|
||||
throw new IllegalArgumentException("Message does not contain the required datastructure ProbeReply");
|
||||
}
|
||||
|
||||
ChallengeResponse response = getTypeFromReply(ChallengeResponse.class, message);
|
||||
|
||||
if (response == null || Arrays.equals(response.getResponse(), createResponse(challengeToSend))) {
|
||||
support.getDevice().setFirmwareVersion(String.valueOf(probeReply.getFirmwareVersion()));
|
||||
} else {
|
||||
throw new SecurityException("Response is not the one expected!");
|
||||
}
|
||||
|
||||
support.onAuthenticationFinished();
|
||||
}
|
||||
|
||||
private byte[] createResponse(Challenge challenge) {
|
||||
try {
|
||||
ByteBuffer allocate = ByteBuffer.allocate(challenge.getChallenge().length + challenge.getMacAddress().getBytes().length + secret.getBytes().length);
|
||||
allocate.put(challenge.getChallenge());
|
||||
allocate.put(challenge.getMacAddress().getBytes());
|
||||
allocate.put(secret.getBytes());
|
||||
return MessageDigest.getInstance("SHA1").digest(allocate.array());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
logger.error("Failed to create response to challenge: " + e.getMessage());
|
||||
}
|
||||
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
private <T extends WithingsStructure> T getTypeFromReply(Class<T> type, Message message) {
|
||||
for (WithingsStructure structure : message.getDataStructures()) {
|
||||
if (type.isInstance(structure)) {
|
||||
return (T)structure;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.Icon;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil;
|
||||
|
||||
public class IconHelper {
|
||||
|
||||
public static byte[] getIconBytesFromDrawable(Drawable drawable) {
|
||||
Bitmap bitmap = BitmapUtil.toBitmap(drawable);
|
||||
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, 22, 24, true);
|
||||
int size = scaledBitmap.getRowBytes() * scaledBitmap.getHeight();
|
||||
return toByteArray(scaledBitmap);
|
||||
}
|
||||
|
||||
public static byte[] toByteArray(Bitmap bitmap) {
|
||||
int width = bitmap.getWidth();
|
||||
int height = bitmap.getHeight();
|
||||
int bytesPerColumn = getBytesPerColumn(height);
|
||||
byte[] rawData = new byte[bytesPerColumn * width];
|
||||
for (int col = 0; col < width; col++) {
|
||||
for (int row = 0; row < height; row++) {
|
||||
int pixel = bitmap.getPixel(col, row);
|
||||
if (shouldPixelbeAdded(pixel)) {
|
||||
int bitIndex = bytesPerColumn * col + row / 8;
|
||||
rawData[bitIndex] = setBit(rawData[bitIndex], row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rawData;
|
||||
}
|
||||
|
||||
private static boolean shouldPixelbeAdded(int pixel) {
|
||||
double luma = ((Color.red(pixel) * 0.2126d) + (Color.green(pixel) * 0.7152d) + (Color.blue(pixel) * 0.0722d)) * (Color.alpha(pixel) / 255.0f);
|
||||
return luma > 0;
|
||||
}
|
||||
|
||||
private static byte setBit(byte bits, int position) {
|
||||
bits |= 1 << (position % 8);
|
||||
return bits;
|
||||
}
|
||||
|
||||
private static int getBytesPerColumn(int rowCount) {
|
||||
int result = (int) rowCount / 8;
|
||||
if (result * 8 < rowCount) {
|
||||
result++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,725 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.GBApplication.getContext;
|
||||
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.bluetooth.BluetoothGattDescriptor;
|
||||
import android.bluetooth.BluetoothGattService;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
|
||||
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.ServerTransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.activity.WithingsActivityType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.WithingsServerAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.WithingsUUID;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation.ActivitySampleHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation.BatteryStateHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation.Conversation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation.ConversationQueue;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation.HeartRateHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation.ResponseHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation.SetupFinishedHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation.SimpleConversation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation.SyncFinishedHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation.WorkoutScreenListHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ActivityTarget;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.AlarmName;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.AlarmSettings;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.AlarmStatus;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.AncsStatus;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.DataStructureFactory;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.EndOfTransmission;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.GetActivitySamples;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ImageData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ImageMetaData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.Locale;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.MoveHand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.Probe;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ProbeOsVersion;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ScreenSettings;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.Time;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.TypeVersion;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.UserUnit;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.UserUnitConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WorkoutScreen;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.ExpectedResponse;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.MessageBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.MessageFactory;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.SimpleHexToByteMessage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessageType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.incoming.IncomingMessageHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.incoming.IncomingMessageHandlerFactory;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.incoming.LiveWorkoutHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.notification.GetNotificationAttributes;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.notification.GetNotificationAttributesResponse;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.notification.NotificationProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.notification.NotificationSource;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||
|
||||
public class WithingsSteelHRDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(WithingsSteelHRDeviceSupport.class);
|
||||
public static final String LAST_ACTIVITY_SYNC = "lastActivitySync";
|
||||
public static final String HANDS_CALIBRATION_CMD = "withings_hands_calibration";
|
||||
public static final String START_HANDS_CALIBRATION_CMD = "start_withings_hands_calibration";
|
||||
public static final String STOP_HANDS_CALIBRATION_CMD = "stop_withings_hands_calibration";
|
||||
private static Prefs prefs = GBApplication.getPrefs();
|
||||
private MessageBuilder messageBuilder;
|
||||
private LiveWorkoutHandler liveWorkoutHandler;
|
||||
private ActivitySampleHandler activitySampleHandler;
|
||||
private ConversationQueue conversationQueue;
|
||||
private boolean firstTimeConnect;
|
||||
private BluetoothGattCharacteristic notificationSourceCharacteristic;
|
||||
private BluetoothGattCharacteristic dataSourceCharacteristic;
|
||||
private BluetoothDevice device;
|
||||
private boolean syncInProgress;
|
||||
private ActivityUser activityUser;
|
||||
private NotificationProvider notificationProvider;
|
||||
private IncomingMessageHandlerFactory incomingMessageHandlerFactory;
|
||||
private final BroadcastReceiver commandReceiver;
|
||||
private int mtuSize = 115;
|
||||
|
||||
public WithingsSteelHRDeviceSupport() {
|
||||
super(logger);
|
||||
notificationProvider = NotificationProvider.getInstance(this);
|
||||
messageBuilder = new MessageBuilder(this, new MessageFactory(new DataStructureFactory()));
|
||||
liveWorkoutHandler = new LiveWorkoutHandler(this);
|
||||
incomingMessageHandlerFactory = IncomingMessageHandlerFactory.getInstance(this);
|
||||
addSupportedService(WithingsUUID.WITHINGS_SERVICE_UUID);
|
||||
addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS);
|
||||
addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE);
|
||||
addANCSService();
|
||||
activityUser = new ActivityUser();
|
||||
|
||||
IntentFilter commandFilter = new IntentFilter(HANDS_CALIBRATION_CMD);
|
||||
commandFilter.addAction(START_HANDS_CALIBRATION_CMD);
|
||||
commandFilter.addAction(STOP_HANDS_CALIBRATION_CMD);
|
||||
commandReceiver = new BroadcastReceiver() {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent.getAction() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (intent.getAction()) {
|
||||
case HANDS_CALIBRATION_CMD:
|
||||
MoveHand moveHand = new MoveHand();
|
||||
moveHand.setHand(intent.getShortExtra("hand", (short)1));
|
||||
moveHand.setMovement(intent.getShortExtra("movementAmount", (short)1));
|
||||
sendToDevice(new WithingsMessage(WithingsMessageType.MOVE_HAND, moveHand));
|
||||
break;
|
||||
case START_HANDS_CALIBRATION_CMD:
|
||||
sendToDevice(new WithingsMessage(WithingsMessageType.START_HANDS_CALIBRATION));
|
||||
break;
|
||||
case STOP_HANDS_CALIBRATION_CMD:
|
||||
sendToDevice(new WithingsMessage(WithingsMessageType.STOP_HANDS_CALIBRATION));
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
LocalBroadcastManager.getInstance(GBApplication.getContext()).registerReceiver(commandReceiver, commandFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getSendWriteRequestResponse() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
|
||||
logger.debug("Starting initialization...");
|
||||
conversationQueue = new ConversationQueue(this);
|
||||
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
|
||||
getDevice().setFirmwareVersion("N/A");
|
||||
getDevice().setFirmwareVersion2("N/A");
|
||||
BluetoothGattCharacteristic characteristic = getCharacteristic(WithingsUUID.WITHINGS_WRITE_CHARACTERISTIC_UUID);
|
||||
builder.notify(characteristic, true);
|
||||
logger.debug("Requesting change of MTU...");
|
||||
builder.requestMtu(119);
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean connectFirstTime() {
|
||||
firstTimeConnect = true;
|
||||
return connect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
|
||||
logger.debug("MTU has changed to " + mtu);
|
||||
mtuSize = mtu;
|
||||
if (firstTimeConnect) {
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.INITIAL_CONNECT));
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SET_LOCALE, new Locale("de")));
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.START_HANDS_CALIBRATION));
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.STOP_HANDS_CALIBRATION));
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SET_TIME, new Time()));
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SET_USER_UNIT, new UserUnit(UserUnitConstants.DISTANCE, getUnit())));
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SET_USER_UNIT, new UserUnit(UserUnitConstants.CLOCK_MODE, getTimeMode())));
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SET_ACTIVITY_TARGET, new ActivityTarget(activityUser.getStepsGoal())));
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.GET_ANCS_STATUS));
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SET_ANCS_STATUS, new AncsStatus(true)));
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.GET_BATTERY_STATUS), new BatteryStateHandler(this));
|
||||
addScreenListCommands();
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SETUP_FINISHED), new SetupFinishedHandler(this));
|
||||
} else {
|
||||
Message message = new WithingsMessage(WithingsMessageType.PROBE);
|
||||
message.addDataStructure(new Probe((short) 1, (short) 1, 5100401));
|
||||
message.addDataStructure(new ProbeOsVersion((short) Build.VERSION.SDK_INT));
|
||||
conversationQueue.clear();
|
||||
addSimpleConversationToQueue(message, new AuthenticationHandler(this));
|
||||
}
|
||||
|
||||
if (!firstTimeConnect) {
|
||||
finishInitialization();
|
||||
}
|
||||
conversationQueue.send();
|
||||
}
|
||||
|
||||
public void doSync() {
|
||||
activitySampleHandler = new ActivitySampleHandler(this);
|
||||
conversationQueue.clear();
|
||||
try {
|
||||
if (syncInProgress || !shoudSync()) {
|
||||
return;
|
||||
}
|
||||
|
||||
getDevice().setBusyTask("Syncing");
|
||||
syncInProgress = true;
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.INITIAL_CONNECT));
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.GET_ANCS_STATUS));
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.GET_BATTERY_STATUS), new BatteryStateHandler(this));
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SET_TIME, new Time()));
|
||||
WithingsMessage message = new WithingsMessage(WithingsMessageType.SET_USER);
|
||||
message.addDataStructure(getUser());
|
||||
// The UserSecret appears in the original communication with the HealthMate app. Until now GB works without the secret.
|
||||
// This makes the "authentication" far easier. However if it turns out that this is needed, we would need to find a way to savely store a unique generated secret.
|
||||
// message.addDataStructure(new UserSecret());
|
||||
addSimpleConversationToQueue(message);
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SET_ACTIVITY_TARGET, new ActivityTarget(activityUser.getStepsGoal())));
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SET_USER_UNIT, new UserUnit(UserUnitConstants.DISTANCE, getUnit())));
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SET_USER_UNIT, new UserUnit(UserUnitConstants.CLOCK_MODE, getTimeMode())));
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.GET_ALARM_SETTINGS));
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.GET_SCREEN_SETTINGS));
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.GET_ALARM));
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.GET_ALARM_ENABLED));
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.GET_WORKOUT_SCREEN_LIST), new WorkoutScreenListHandler(this));
|
||||
Calendar c = Calendar.getInstance();
|
||||
c.setTimeInMillis(getLastSyncTimestamp());
|
||||
message = new WithingsMessage(WithingsMessageType.GET_ACTIVITY_SAMPLES, ExpectedResponse.EOT);
|
||||
message.addDataStructure(new GetActivitySamples(c.getTimeInMillis() / 1000, (short) 0));
|
||||
addSimpleConversationToQueue(message, activitySampleHandler);
|
||||
message = new WithingsMessage(WithingsMessageType.GET_MOVEMENT_SAMPLES, ExpectedResponse.EOT);
|
||||
message.addDataStructure(new GetActivitySamples(c.getTimeInMillis() / 1000, (short) 0));
|
||||
message.addDataStructure(new TypeVersion());
|
||||
addSimpleConversationToQueue(message, activitySampleHandler);
|
||||
message = new WithingsMessage(WithingsMessageType.GET_HEARTRATE_SAMPLES, ExpectedResponse.EOT);
|
||||
message.addDataStructure(new GetActivitySamples(c.getTimeInMillis() / 1000, (short) 0));
|
||||
message.addDataStructure(new TypeVersion());
|
||||
addSimpleConversationToQueue(message, activitySampleHandler);
|
||||
} catch (Exception e) {
|
||||
logger.error("Could not synchronize! ", e);
|
||||
conversationQueue.clear();
|
||||
} finally {
|
||||
// This must be done in all cases or the watch won't respond anymore!
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SYNC_OK), new SyncFinishedHandler(this));
|
||||
}
|
||||
conversationQueue.send();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onCharacteristicChanged(BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic) {
|
||||
if (super.onCharacteristicChanged(gatt, characteristic)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
byte[] data = characteristic.getValue();
|
||||
|
||||
boolean complete = messageBuilder.buildMessage(data);
|
||||
if (complete) {
|
||||
Message message = messageBuilder.getMessage();
|
||||
if (message.isIncomingMessage()) {
|
||||
logger.debug("received incoming message: " + message.getType());
|
||||
IncomingMessageHandler handler = incomingMessageHandlerFactory.getHandler(message);
|
||||
handler.handleMessage(message);
|
||||
} else {
|
||||
conversationQueue.processResponse(message);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetCallState(CallSpec callSpec) {
|
||||
if (callSpec.command == CallSpec.CALL_INCOMING) {
|
||||
NotificationSpec notificationSpec = new NotificationSpec();
|
||||
notificationSpec.sourceAppId = "incoming.call";
|
||||
notificationSpec.title = callSpec.number;
|
||||
notificationSpec.sender = callSpec.name;
|
||||
notificationSpec.type = NotificationType.GENERIC_PHONE;
|
||||
notificationProvider.notifyClient(notificationSpec);
|
||||
} else {
|
||||
logger.info("Received yet unhandled call command: " + callSpec.command);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotification(NotificationSpec notificationSpec) {
|
||||
notificationProvider.notifyClient(notificationSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
|
||||
if (alarms.size() > 3) {
|
||||
throw new IllegalArgumentException("Steel HR does only have three alarmslots!");
|
||||
}
|
||||
|
||||
if (alarms.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean noAlarmsEnabled = true;
|
||||
conversationQueue.clear();
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.GET_ALARM));
|
||||
for (Alarm alarm : alarms) {
|
||||
if (alarm.getEnabled() && !alarm.getUnused()) {
|
||||
noAlarmsEnabled = false;
|
||||
addAlarm(alarm);
|
||||
}
|
||||
}
|
||||
|
||||
if (noAlarmsEnabled) {
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SET_ALARM_ENABLED, new AlarmStatus(false)));
|
||||
}
|
||||
|
||||
conversationQueue.send();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
|
||||
this.device = device;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
|
||||
if (characteristic.getUuid().equals(WithingsUUID.CONTROL_POINT_CHARACTERISTIC_UUID)) {
|
||||
logger.debug("Got GetNotificationAttributesRequest: " + GB.hexdump(value));
|
||||
GetNotificationAttributes request = new GetNotificationAttributes();
|
||||
request.deserialize(value);
|
||||
notificationProvider.handleNotificationAttributeRequest(request);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchRecordedData(int dataTypes) {
|
||||
doSync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeartRateTest() {
|
||||
conversationQueue.clear();
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.GET_HR), new HeartRateHandler(this));
|
||||
conversationQueue.send();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendConfiguration(String config) {
|
||||
try {
|
||||
switch (config) {
|
||||
case HuamiConst.PREF_WORKOUT_ACTIVITY_TYPES_SORTABLE:
|
||||
setWorkoutActivityTypes();
|
||||
break;
|
||||
default:
|
||||
logger.debug("unknown configuration setting received: " + config);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
GB.toast("Error setting configuration", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTestNewFunction() {
|
||||
String hexMessage = "0105080015050900111006040102030507000000000000000000";
|
||||
conversationQueue.clear();
|
||||
addSimpleConversationToQueue(new SimpleHexToByteMessage(hexMessage));
|
||||
conversationQueue.send();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useAutoConnect() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void sendToDevice(Message message) {
|
||||
if (message == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
TransactionBuilder builder = createTransactionBuilder("conversation");
|
||||
builder.setCallback(this);
|
||||
BluetoothGattCharacteristic characteristic = getCharacteristic(WithingsUUID.WITHINGS_WRITE_CHARACTERISTIC_UUID);
|
||||
if (characteristic == null) {
|
||||
logger.info("Characteristic with UUID " + WithingsUUID.WITHINGS_WRITE_CHARACTERISTIC_UUID + " not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] rawData = message.getRawData();
|
||||
builder.writeChunkedData(characteristic, rawData, mtuSize - 4);
|
||||
builder.queue(getQueue());
|
||||
} catch (Exception e) {
|
||||
logger.warn("Could not send message because of " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void sendAncsNotificationSourceNotification(NotificationSource notificationSource) {
|
||||
try {
|
||||
ServerTransactionBuilder builder = performServer("notificationSourceNotification");
|
||||
notificationSourceCharacteristic.setValue(notificationSource.serialize());
|
||||
builder.add(new WithingsServerAction(device, notificationSourceCharacteristic));
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException e) {
|
||||
logger.error("Could not send notification.", e);
|
||||
GB.toast("Could not send notification.", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendAncsDataSourceNotification(GetNotificationAttributesResponse response) {
|
||||
try {
|
||||
ServerTransactionBuilder builder = performServer("dataSourceNotification");
|
||||
byte[] data = response.serialize();
|
||||
dataSourceCharacteristic.setValue(response.serialize());
|
||||
builder.add(new WithingsServerAction(device, dataSourceCharacteristic));
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException e) {
|
||||
logger.error("Could not send notification.", e);
|
||||
GB.toast("Could not send notification.", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
}
|
||||
}
|
||||
|
||||
public void finishInitialization() {
|
||||
TransactionBuilder builder = createTransactionBuilder("setupFinished");
|
||||
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
|
||||
builder.queue(getQueue());
|
||||
logger.debug("Finished initialization.");
|
||||
}
|
||||
|
||||
public void finishSync() {
|
||||
syncInProgress = false;
|
||||
if (getDevice().isBusy()) {
|
||||
getDevice().unsetBusyTask();
|
||||
getDevice().sendDeviceUpdateIntent(getContext());
|
||||
}
|
||||
activitySampleHandler.onSyncFinished();
|
||||
saveLastSyncTimestamp(new Date().getTime());
|
||||
}
|
||||
|
||||
void onAuthenticationFinished() {
|
||||
if (!firstTimeConnect) {
|
||||
addScreenListCommands();
|
||||
doSync();
|
||||
} else {
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SET_ANCS_STATUS, new AncsStatus(true)));
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.GET_ANCS_STATUS));
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.GET_BATTERY_STATUS), new BatteryStateHandler(this));
|
||||
conversationQueue.send();
|
||||
}
|
||||
}
|
||||
|
||||
private void addAlarm(Alarm alarm) {
|
||||
AlarmSettings alarmSettings = new AlarmSettings();
|
||||
alarmSettings.setHour((short) alarm.getHour());
|
||||
alarmSettings.setMinute((short) alarm.getMinute());
|
||||
alarmSettings.setDayOfWeek(mapRepetitionToWithingsValue(alarm));
|
||||
if (alarm.getSmartWakeup()) {
|
||||
// Healthmate has the possibility to change the minutecount, in GB we use a fixed value of 15
|
||||
alarmSettings.setSmartWakeupMinutes((short) 15);
|
||||
}
|
||||
|
||||
Message alarmMessage = new WithingsMessage(WithingsMessageType.SET_ALARM, alarmSettings);
|
||||
if (!StringUtils.isEmpty(alarm.getTitle())) {
|
||||
AlarmName alarmName = new AlarmName(alarm.getTitle());
|
||||
alarmMessage.addDataStructure(alarmName);
|
||||
}
|
||||
|
||||
addSimpleConversationToQueue(alarmMessage);
|
||||
addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SET_ALARM_ENABLED, new AlarmStatus(true)));
|
||||
}
|
||||
|
||||
private short mapRepetitionToWithingsValue(Alarm alarm) {
|
||||
int repetition = 0;
|
||||
if (alarm.getRepetition(Alarm.ALARM_MON)) {
|
||||
repetition += 0x02;
|
||||
}
|
||||
if (alarm.getRepetition(Alarm.ALARM_TUE)) {
|
||||
repetition += 0x04;
|
||||
}
|
||||
if (alarm.getRepetition(Alarm.ALARM_WED)) {
|
||||
repetition += 0x08;
|
||||
}
|
||||
if (alarm.getRepetition(Alarm.ALARM_THU)) {
|
||||
repetition += 0x10;
|
||||
}
|
||||
if (alarm.getRepetition(Alarm.ALARM_FRI)) {
|
||||
repetition += 0x20;
|
||||
}
|
||||
if (alarm.getRepetition(Alarm.ALARM_SAT)) {
|
||||
repetition += 0x40;
|
||||
}
|
||||
if (alarm.getRepetition(Alarm.ALARM_SUN)) {
|
||||
repetition += 0x01;
|
||||
}
|
||||
|
||||
return (short)(repetition + 0x80);
|
||||
}
|
||||
|
||||
private void addANCSService() {
|
||||
BluetoothGattService withingsGATTService = new BluetoothGattService(WithingsUUID.WITHINGS_ANCS_SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY);
|
||||
notificationSourceCharacteristic = new BluetoothGattCharacteristic(WithingsUUID.NOTIFICATION_SOURCE_CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_NOTIFY, BluetoothGattCharacteristic.PERMISSION_READ);
|
||||
notificationSourceCharacteristic.addDescriptor(new BluetoothGattDescriptor(WithingsUUID.CCC_DESCRIPTOR_UUID, BluetoothGattCharacteristic.PERMISSION_WRITE));
|
||||
withingsGATTService.addCharacteristic(notificationSourceCharacteristic);
|
||||
withingsGATTService.addCharacteristic(new BluetoothGattCharacteristic(WithingsUUID.CONTROL_POINT_CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_WRITE, BluetoothGattCharacteristic.PERMISSION_WRITE));
|
||||
dataSourceCharacteristic = new BluetoothGattCharacteristic(WithingsUUID.DATA_SOURCE_CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_NOTIFY, BluetoothGattCharacteristic.PERMISSION_READ);
|
||||
dataSourceCharacteristic.addDescriptor(new BluetoothGattDescriptor(WithingsUUID.CCC_DESCRIPTOR_UUID, BluetoothGattCharacteristic.PERMISSION_WRITE));
|
||||
withingsGATTService.addCharacteristic(dataSourceCharacteristic);
|
||||
addSupportedServerService(withingsGATTService);
|
||||
}
|
||||
|
||||
private void addSimpleConversationToQueue(Message message) {
|
||||
addSimpleConversationToQueue(message, null);
|
||||
}
|
||||
|
||||
private void addSimpleConversationToQueue(Message message, ResponseHandler handler) {
|
||||
Conversation conversation = new SimpleConversation(handler);
|
||||
conversation.setRequest(message);
|
||||
conversationQueue.addConversation(conversation);
|
||||
}
|
||||
|
||||
private void saveLastSyncTimestamp(@NonNull long timestamp) {
|
||||
SharedPreferences.Editor editor = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()).edit();
|
||||
editor.putLong(LAST_ACTIVITY_SYNC, timestamp);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
private long getLastSyncTimestamp() {
|
||||
SharedPreferences settings = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
|
||||
long lastSyncTime = settings.getLong(LAST_ACTIVITY_SYNC, 0);
|
||||
if (lastSyncTime > 0) {
|
||||
return lastSyncTime;
|
||||
} else {
|
||||
Date currentDate = new Date();
|
||||
Calendar c = Calendar.getInstance();
|
||||
c.setTimeInMillis(currentDate.getTime());
|
||||
c.add(Calendar.HOUR, - 10);
|
||||
return c.getTimeInMillis();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shoudSync() {
|
||||
long lastSynced = getLastSyncTimestamp();
|
||||
int minuteInMillis = 60 * 1000;
|
||||
return new Date().getTime() - lastSynced > minuteInMillis;
|
||||
}
|
||||
|
||||
private User getUser() {
|
||||
User user = new User();
|
||||
ActivityUser activityUser = new ActivityUser();
|
||||
user.setName(activityUser.getName());
|
||||
user.setGender((byte) activityUser.getGender());
|
||||
user.setHeight(activityUser.getHeightCm());
|
||||
user.setWeight(activityUser.getWeightKg());
|
||||
user.setBirthdate(activityUser.getUserBirthday());
|
||||
return user;
|
||||
}
|
||||
|
||||
private void addScreenListCommands() {
|
||||
// TODO: this needs to be more reworked, at the moment for example the notification screen is always on and this is full of magic numbers that need to be identified properly:
|
||||
Message message = new WithingsMessage(WithingsMessageType.SET_SCREEN_LIST);
|
||||
ScreenSettings settings = new ScreenSettings();
|
||||
settings.setId(0xff);
|
||||
settings.setIdOnDevice((byte)6);
|
||||
message.addDataStructure(settings);
|
||||
|
||||
settings = new ScreenSettings();
|
||||
settings.setId(0x3d);
|
||||
settings.setIdOnDevice((byte)1);
|
||||
message.addDataStructure(settings);
|
||||
|
||||
settings = new ScreenSettings();
|
||||
settings.setId(0x33);
|
||||
settings.setIdOnDevice((byte)4);
|
||||
message.addDataStructure(settings);
|
||||
|
||||
settings = new ScreenSettings();
|
||||
settings.setId(0x2d);
|
||||
settings.setIdOnDevice((byte)2);
|
||||
message.addDataStructure(settings);
|
||||
|
||||
settings = new ScreenSettings();
|
||||
settings.setId(0x2a);
|
||||
settings.setIdOnDevice((byte)3);
|
||||
message.addDataStructure(settings);
|
||||
|
||||
settings = new ScreenSettings();
|
||||
settings.setId(0x26);
|
||||
settings.setIdOnDevice((byte)7);
|
||||
message.addDataStructure(settings);
|
||||
|
||||
settings = new ScreenSettings();
|
||||
settings.setId(0x39);
|
||||
settings.setIdOnDevice((byte)9);
|
||||
message.addDataStructure(settings);
|
||||
|
||||
message.addDataStructure(new EndOfTransmission());
|
||||
addSimpleConversationToQueue(message);
|
||||
}
|
||||
|
||||
private void setWorkoutActivityTypes() {
|
||||
final SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress());
|
||||
|
||||
final List<String> allActivityTypes = Arrays.asList(getContext().getResources().getStringArray(R.array.pref_withings_steel_activity_types_values));
|
||||
final List<String> defaultActivityTypes = Arrays.asList(getContext().getResources().getStringArray(R.array.pref_withings_steel_activity_types_default));
|
||||
final String activityTypesPref = prefs.getString("workout_activity_types_sortable", null);
|
||||
|
||||
final List<String> enabledActivityTypes;
|
||||
if (activityTypesPref == null || activityTypesPref.equals("")) {
|
||||
enabledActivityTypes = defaultActivityTypes;
|
||||
} else {
|
||||
enabledActivityTypes = Arrays.asList(activityTypesPref.split(","));
|
||||
}
|
||||
|
||||
conversationQueue.clear();
|
||||
for (int i = 0; i < enabledActivityTypes.size(); i++) {
|
||||
String workoutType = enabledActivityTypes.get(i);
|
||||
try {
|
||||
Message message = createWorkoutScreenMessage(workoutType);
|
||||
if (i == enabledActivityTypes.size() - 1) {
|
||||
message.addDataStructure(new EndOfTransmission());
|
||||
}
|
||||
addSimpleConversationToQueue(message);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
conversationQueue.send();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Message createWorkoutScreenMessage(String workoutType) {
|
||||
WithingsActivityType withingsActivityType = WithingsActivityType.fromPrefValue(workoutType);
|
||||
int code = withingsActivityType.getCode();
|
||||
Message message = new WithingsMessage(WithingsMessageType.SET_WORKOUT_SCREEN, ExpectedResponse.NONE);
|
||||
WorkoutScreen workoutScreen = new WorkoutScreen();
|
||||
workoutScreen.setId(code);
|
||||
final int stringId = getContext().getResources().getIdentifier("activity_type_" + workoutType, "string", getContext().getPackageName());
|
||||
workoutScreen.setName(getContext().getString(stringId));
|
||||
message.addDataStructure(workoutScreen);
|
||||
|
||||
ImageMetaData imageMetaData = new ImageMetaData();
|
||||
imageMetaData.setHeight((byte)24);
|
||||
imageMetaData.setWidth((byte)22);
|
||||
message.addDataStructure(imageMetaData);
|
||||
|
||||
ImageData imageData = new ImageData();
|
||||
final int drawableId = ActivityKind.getIconId(withingsActivityType.toActivityKind());
|
||||
Drawable drawable = getContext().getDrawable(drawableId);
|
||||
imageData.setImageData(IconHelper.getIconBytesFromDrawable(drawable));
|
||||
message.addDataStructure(imageData);
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
private short getTimeMode() {
|
||||
GBPrefs gbPrefs = new GBPrefs(new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress())));
|
||||
String tmode = gbPrefs.getTimeFormat();
|
||||
|
||||
if ("24h".equals(tmode)) {
|
||||
return UserUnitConstants.UNIT_24H;
|
||||
} else {
|
||||
return UserUnitConstants.UNIT_12H;
|
||||
}
|
||||
}
|
||||
|
||||
private short getUnit() {
|
||||
String units = prefs.getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, GBApplication.getContext().getString(R.string.p_unit_metric));
|
||||
|
||||
if (units.equals(GBApplication.getContext().getString(R.string.p_unit_metric))) {
|
||||
return UserUnitConstants.UNIT_KM;
|
||||
} else {
|
||||
return UserUnitConstants.UNIT_MILES;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.activity;
|
||||
|
||||
public class ActivityEntry {
|
||||
private int timestamp;
|
||||
private int duration;
|
||||
private int rawKind = -1;
|
||||
private int heartrate;
|
||||
private int steps;
|
||||
private int calories;
|
||||
private int distance;
|
||||
private int rawIntensity;
|
||||
private boolean isHeartrate;
|
||||
|
||||
public int getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(int timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public int getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
public void setDuration(int duration) {
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
public int getHeartrate() {
|
||||
return heartrate;
|
||||
}
|
||||
|
||||
public void setIsHeartrate(int heartrate) {
|
||||
this.heartrate = heartrate;
|
||||
}
|
||||
|
||||
public int getRawKind() {
|
||||
return rawKind;
|
||||
}
|
||||
|
||||
public void setRawKind(int rawKind) {
|
||||
this.rawKind = rawKind;
|
||||
}
|
||||
|
||||
public int getSteps() {
|
||||
return steps;
|
||||
}
|
||||
|
||||
public void setSteps(int steps) {
|
||||
this.steps = steps;
|
||||
}
|
||||
|
||||
public int getCalories() {
|
||||
return calories;
|
||||
}
|
||||
|
||||
public void setCalories(int calories) {
|
||||
this.calories = calories;
|
||||
}
|
||||
|
||||
public int getDistance() {
|
||||
return distance;
|
||||
}
|
||||
|
||||
public void setDistance(int distance) {
|
||||
this.distance = distance;
|
||||
}
|
||||
|
||||
public int getRawIntensity() {
|
||||
return rawIntensity;
|
||||
}
|
||||
|
||||
public void setRawIntensity(int rawIntensity) {
|
||||
this.rawIntensity = rawIntensity;
|
||||
}
|
||||
|
||||
public boolean isHeartrate() {
|
||||
return isHeartrate;
|
||||
}
|
||||
|
||||
public void setIsHeartrate(boolean heartrate) {
|
||||
isHeartrate = heartrate;
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.activity;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.withingssteelhr.WithingsSteelHRSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.WithingsSteelHRActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
|
||||
/**
|
||||
* This class is needed for sleep tracking as the withings steel HR sends heartrate while sleeping in an extra activity.
|
||||
* This leads to breaking the sleep session in the sleep calculation of GB.
|
||||
*/
|
||||
public class SleepActivitySampleHelper {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(SleepActivitySampleHelper.class);
|
||||
private static int mergeCount;
|
||||
|
||||
public static WithingsSteelHRActivitySample mergeIfNecessary(WithingsSteelHRSampleProvider provider, WithingsSteelHRActivitySample sample) {
|
||||
if (!shouldMerge(sample)) {
|
||||
return sample;
|
||||
}
|
||||
|
||||
WithingsSteelHRActivitySample overlappingSample = getOverlappingSample(provider, (int)sample.getTimestamp());
|
||||
if (overlappingSample != null) {
|
||||
sample = doMerge(overlappingSample, sample);
|
||||
}
|
||||
|
||||
return sample;
|
||||
}
|
||||
|
||||
private static WithingsSteelHRActivitySample getOverlappingSample(WithingsSteelHRSampleProvider provider, long timestamp) {
|
||||
List<WithingsSteelHRActivitySample> samples = provider.getActivitySamples((int)timestamp - 500, (int)timestamp);
|
||||
if (samples.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (int i = samples.size()-1; i >= 0; i--) {
|
||||
WithingsSteelHRActivitySample lastSample = samples.get(i);
|
||||
if (isNotHeartRateOnly(lastSample, (int) timestamp)) {
|
||||
return lastSample;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isNotHeartRateOnly(WithingsSteelHRActivitySample lastSample, int timestamp) {
|
||||
return lastSample.getRawKind() != ActivityKind.TYPE_NOT_MEASURED; // && lastSample.getTimestamp() <= timestamp && (lastSample.getTimestamp() + lastSample.getDuration()) >= timestamp);
|
||||
}
|
||||
|
||||
private static boolean shouldMerge(WithingsSteelHRActivitySample sample) {
|
||||
return sample.getSteps() == 0
|
||||
&& sample.getDistance() == 0
|
||||
&& sample.getRawKind() == -1
|
||||
&& sample.getCalories() == 0
|
||||
&& sample.getHeartRate() > 1
|
||||
&& sample.getRawIntensity() == 0;
|
||||
}
|
||||
|
||||
private static WithingsSteelHRActivitySample doMerge(WithingsSteelHRActivitySample origin, WithingsSteelHRActivitySample update) {
|
||||
WithingsSteelHRActivitySample mergeResult = new WithingsSteelHRActivitySample();
|
||||
mergeResult.setTimestamp(update.getTimestamp());
|
||||
mergeResult.setRawKind(origin.getRawKind());
|
||||
mergeResult.setRawIntensity(origin.getRawIntensity());
|
||||
mergeResult.setDuration(origin.getDuration() - (update.getTimestamp() - origin.getTimestamp()));
|
||||
mergeResult.setDevice(origin.getDevice());
|
||||
mergeResult.setDeviceId(origin.getDeviceId());
|
||||
mergeResult.setUser(origin.getUser());
|
||||
mergeResult.setUserId(origin.getUserId());
|
||||
mergeResult.setProvider(origin.getProvider());
|
||||
mergeResult.setHeartRate(update.getHeartRate());
|
||||
return mergeResult;
|
||||
}
|
||||
}
|
@ -0,0 +1,168 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.activity;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiWorkoutScreenActivityType;
|
||||
|
||||
public enum WithingsActivityType {
|
||||
|
||||
WALKING(1),
|
||||
RUNNING(2),
|
||||
HIKING(3),
|
||||
BIKING(6),
|
||||
SWIMMING(7),
|
||||
SURFING(8),
|
||||
KITESURFING(9),
|
||||
WINDSURFING(10),
|
||||
TENNIS(12),
|
||||
PINGPONG(13),
|
||||
SQUASH(14),
|
||||
BADMINTON(15),
|
||||
WEIGHTLIFTING(16),
|
||||
GYMNASTICS(17),
|
||||
ELLIPTICAL(18),
|
||||
PILATES(19),
|
||||
BASKETBALL(20),
|
||||
SOCCER(21),
|
||||
FOOTBALL(22),
|
||||
RUGBY(23),
|
||||
VOLLEYBALL(24),
|
||||
GOLFING(227),
|
||||
YOGA(28),
|
||||
DANCING(29),
|
||||
BOXING(30),
|
||||
SKIING(34),
|
||||
SNOWBOARDING(35),
|
||||
ROWING(0), // The code has yet to be identified.
|
||||
ZUMBA(188),
|
||||
BASEBALL(191),
|
||||
HANDBALL(192),
|
||||
HOCKEY(193),
|
||||
ICEHOCKEY(194),
|
||||
CLIMBING(195),
|
||||
ICESKATING(196),
|
||||
RIDING(26),
|
||||
OTHER(36);
|
||||
|
||||
private int code;
|
||||
|
||||
WithingsActivityType(int typeCode) {
|
||||
this.code = typeCode;
|
||||
}
|
||||
|
||||
public static WithingsActivityType fromCode(int withingsCode) {
|
||||
for (WithingsActivityType type : values()) {
|
||||
if (type.code == withingsCode) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("No matching WithingsActivityType for code: " + withingsCode);
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public int toActivityKind() {
|
||||
switch (this) {
|
||||
case WALKING:
|
||||
return ActivityKind.TYPE_WALKING;
|
||||
case RUNNING:
|
||||
return ActivityKind.TYPE_RUNNING;
|
||||
case HIKING:
|
||||
return ActivityKind.TYPE_HIKING;
|
||||
case BIKING:
|
||||
return ActivityKind.TYPE_CYCLING;
|
||||
case SWIMMING:
|
||||
return ActivityKind.TYPE_SWIMMING;
|
||||
case SURFING:
|
||||
return ActivityKind.TYPE_ACTIVITY;
|
||||
case KITESURFING:
|
||||
return ActivityKind.TYPE_ACTIVITY;
|
||||
case WINDSURFING:
|
||||
return ActivityKind.TYPE_ACTIVITY;
|
||||
case TENNIS:
|
||||
return ActivityKind.TYPE_ACTIVITY;
|
||||
case PINGPONG:
|
||||
return ActivityKind.TYPE_PINGPONG;
|
||||
case SQUASH:
|
||||
return ActivityKind.TYPE_ACTIVITY;
|
||||
case BADMINTON:
|
||||
return ActivityKind.TYPE_BADMINTON;
|
||||
case WEIGHTLIFTING:
|
||||
return ActivityKind.TYPE_ACTIVITY;
|
||||
case GYMNASTICS:
|
||||
return ActivityKind.TYPE_EXERCISE;
|
||||
case ELLIPTICAL:
|
||||
return ActivityKind.TYPE_ELLIPTICAL_TRAINER;
|
||||
case PILATES:
|
||||
return ActivityKind.TYPE_YOGA;
|
||||
case BASKETBALL:
|
||||
return ActivityKind.TYPE_BASKETBALL;
|
||||
case SOCCER:
|
||||
return ActivityKind.TYPE_SOCCER;
|
||||
case FOOTBALL:
|
||||
return ActivityKind.TYPE_ACTIVITY;
|
||||
case RUGBY:
|
||||
return ActivityKind.TYPE_ACTIVITY;
|
||||
case VOLLEYBALL:
|
||||
return ActivityKind.TYPE_ACTIVITY;
|
||||
case GOLFING:
|
||||
return ActivityKind.TYPE_ACTIVITY;
|
||||
case YOGA:
|
||||
return ActivityKind.TYPE_YOGA;
|
||||
case DANCING:
|
||||
return ActivityKind.TYPE_ACTIVITY;
|
||||
case BOXING:
|
||||
return ActivityKind.TYPE_ACTIVITY;
|
||||
case SKIING:
|
||||
return ActivityKind.TYPE_ACTIVITY;
|
||||
case SNOWBOARDING:
|
||||
return ActivityKind.TYPE_ACTIVITY;
|
||||
case ROWING:
|
||||
return ActivityKind.TYPE_ROWING_MACHINE;
|
||||
case ZUMBA:
|
||||
return ActivityKind.TYPE_ACTIVITY;
|
||||
case BASEBALL:
|
||||
return ActivityKind.TYPE_CRICKET;
|
||||
case HANDBALL:
|
||||
return ActivityKind.TYPE_ACTIVITY;
|
||||
case HOCKEY:
|
||||
return ActivityKind.TYPE_ACTIVITY;
|
||||
case ICEHOCKEY:
|
||||
return ActivityKind.TYPE_ACTIVITY;
|
||||
case CLIMBING:
|
||||
return ActivityKind.TYPE_CLIMBING;
|
||||
case ICESKATING:
|
||||
return ActivityKind.TYPE_ACTIVITY;
|
||||
default:
|
||||
return ActivityKind.TYPE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
public static WithingsActivityType fromPrefValue(final String prefValue) {
|
||||
for (final WithingsActivityType type : values()) {
|
||||
if (type.name().toLowerCase(Locale.ROOT).equals(prefValue.replace("_", "").toLowerCase(Locale.ROOT))) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("No matching WithingsActivityType for pref value: " + prefValue);
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication;
|
||||
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.bluetooth.BluetoothGattServer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEServerAction;
|
||||
|
||||
public class WithingsServerAction extends BtLEServerAction
|
||||
{
|
||||
private BluetoothGattCharacteristic characteristic;
|
||||
|
||||
public WithingsServerAction(BluetoothDevice device, BluetoothGattCharacteristic characteristic) {
|
||||
super(device);
|
||||
this.characteristic = characteristic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean expectsResult() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean run(BluetoothGattServer server) {
|
||||
return server.notifyCharacteristicChanged(getDevice(), characteristic, false);
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public final class WithingsUUID {
|
||||
|
||||
public static final UUID WITHINGS_SERVICE_UUID = UUID.fromString("00000020-5749-5448-0037-000000000000");
|
||||
public static final UUID WITHINGS_WRITE_CHARACTERISTIC_UUID = UUID.fromString("00000024-5749-5448-0037-000000000000");
|
||||
public static final UUID WITHINGS_APP_CHARACTERISTIC_UUID = UUID.fromString("10000059-5749-5448-0037-000000000000");
|
||||
public static final UUID WITHINGS_APP_CHARACTERISTIC2_UUID = UUID.fromString("10000028-5749-5448-0037-000000000000");
|
||||
public static final UUID CCC_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
|
||||
public static final UUID WITHINGS_ANCS_SERVICE_UUID = UUID.fromString("10000057-5749-5448-0037-00000000000000");
|
||||
public static final UUID NOTIFICATION_SOURCE_CHARACTERISTIC_UUID = UUID.fromString("10000059-5749-5448-0037-00000000000000");
|
||||
public static final UUID CONTROL_POINT_CHARACTERISTIC_UUID = UUID.fromString("10000058-5749-5448-0037-00000000000000");
|
||||
public static final UUID DATA_SOURCE_CHARACTERISTIC_UUID = UUID.fromString("1000005a-5749-5448-0037-00000000000000");
|
||||
|
||||
private WithingsUUID() {}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.conversation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructure;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructureType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
|
||||
|
||||
public abstract class AbstractConversation implements Conversation {
|
||||
|
||||
private List<ConversationObserver> observers = new ArrayList();
|
||||
|
||||
private boolean complete;
|
||||
|
||||
protected Message request;
|
||||
|
||||
private short requestType;
|
||||
|
||||
protected ResponseHandler responseHandler;
|
||||
|
||||
public AbstractConversation(ResponseHandler responseHandler) {
|
||||
this.responseHandler = responseHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObserver(ConversationObserver observer) {
|
||||
observers.add(observer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeObserver(ConversationObserver observer) {
|
||||
observers.remove(observer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRequest(Message message) {
|
||||
this.request = message;
|
||||
this.requestType = message.getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message getRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(Message response) {
|
||||
if (response.getType() == requestType) {
|
||||
if (request.needsResponse()) {
|
||||
complete = true;
|
||||
} else if (request.needsEOT()) {
|
||||
complete = hasEOT(response);
|
||||
}
|
||||
|
||||
doHandleResponse(response);
|
||||
if (complete) {
|
||||
notifyObservers(requestType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isComplete() {
|
||||
return complete;
|
||||
}
|
||||
|
||||
protected void notifyObservers(short messageType) {
|
||||
for (ConversationObserver observer : observers) {
|
||||
observer.onConversationCompleted(messageType);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasEOT(Message message) {
|
||||
List<WithingsStructure> dataList = message.getDataStructures();
|
||||
if (dataList != null) {
|
||||
for (WithingsStructure strucuter :
|
||||
dataList) {
|
||||
if (strucuter.getType() == WithingsStructureType.END_OF_TRANSMISSION) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected abstract void doSendRequest(Message message);
|
||||
|
||||
protected abstract void doHandleResponse(Message message);
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.conversation;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
|
||||
|
||||
public abstract class AbstractResponseHandler implements ResponseHandler {
|
||||
protected GBDevice device;
|
||||
protected WithingsSteelHRDeviceSupport support;
|
||||
|
||||
public AbstractResponseHandler(WithingsSteelHRDeviceSupport support) {
|
||||
this.support = support;
|
||||
this.device = support.getDevice();
|
||||
}
|
||||
}
|
@ -0,0 +1,269 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.conversation;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
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.withingssteelhr.WithingsSteelHRSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.WithingsSteelHRActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.activity.ActivityEntry;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.activity.WithingsActivityType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WorkoutType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ActivitySampleCalories;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ActivitySampleCalories2;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ActivitySampleDuration;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ActivitySampleMovement;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ActivitySampleSleep;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ActivitySampleTime;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ActivityHeartrate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructure;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructureType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessageType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class ActivitySampleHandler extends AbstractResponseHandler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ActivitySampleHandler.class);
|
||||
private ActivityEntry activityEntry;
|
||||
private List<ActivityEntry> activityEntries = new ArrayList<>();
|
||||
private List<ActivityEntry> heartrateEntries = new ArrayList<>();
|
||||
|
||||
public ActivitySampleHandler(WithingsSteelHRDeviceSupport support) {
|
||||
super(support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(Message response) {
|
||||
List<WithingsStructure> data = response.getDataStructures();
|
||||
if (data != null) {
|
||||
handleActivityData(data, response.getType());
|
||||
}
|
||||
}
|
||||
|
||||
public void onSyncFinished() {
|
||||
mergeHeartrateSamplesIntoActivitySammples();
|
||||
saveData();
|
||||
}
|
||||
|
||||
private void handleActivityData(List<WithingsStructure> dataList, short activityType) {
|
||||
for (WithingsStructure data : dataList) {
|
||||
switch (data.getType()) {
|
||||
case WithingsStructureType.ACTIVITY_SAMPLE_TIME:
|
||||
handleTimestamp(data, activityType);
|
||||
break;
|
||||
case WithingsStructureType.ACTIVITY_SAMPLE_DURATION:
|
||||
handleDuration(data);
|
||||
break;
|
||||
case WithingsStructureType.ACTIVITY_SAMPLE_MOVEMENT:
|
||||
handleMovement(data);
|
||||
break;
|
||||
case WithingsStructureType.ACTIVITY_SAMPLE_CALORIES:
|
||||
handleCalories1(data);
|
||||
break;
|
||||
case WithingsStructureType.ACTIVITY_SAMPLE_CALORIES_2:
|
||||
handleCalories2(data);
|
||||
break;
|
||||
case WithingsStructureType.ACTIVITY_SAMPLE_SLEEP:
|
||||
handleSleep(data);
|
||||
break;
|
||||
case WithingsStructureType.ACTIVITY_SAMPLE_WALK:
|
||||
handleWalk(data);
|
||||
break;
|
||||
case WithingsStructureType.ACTIVITY_SAMPLE_RUN:
|
||||
handleRun(data);
|
||||
break;
|
||||
case WithingsStructureType.ACTIVITY_SAMPLE_SWIM:
|
||||
handleSwim(data);
|
||||
break;
|
||||
case WithingsStructureType.ACTIVITY_HR:
|
||||
handleHeartrate(data);
|
||||
break;
|
||||
case WithingsStructureType.WORKOUT_TYPE:
|
||||
handleWorkoutType(data);
|
||||
break;
|
||||
default:
|
||||
logger.info("Received yet unhandled activity data of type '" + data.getType() + "' with data '" + GB.hexdump(data.getRawData()) + "'.");
|
||||
}
|
||||
}
|
||||
|
||||
if (activityEntry != null) {
|
||||
addToList(activityEntry);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void handleTimestamp(WithingsStructure data, short activityType) {
|
||||
if (activityEntry != null) {
|
||||
addToList(activityEntry);
|
||||
}
|
||||
|
||||
activityEntry = new ActivityEntry();
|
||||
activityEntry.setIsHeartrate(activityType == WithingsMessageType.GET_HEARTRATE_SAMPLES);
|
||||
activityEntry.setTimestamp((int)(((ActivitySampleTime)data).getDate().getTime()/1000));
|
||||
}
|
||||
|
||||
private void handleWorkoutType(WithingsStructure data) {
|
||||
WithingsActivityType activityType = WithingsActivityType.fromCode(((WorkoutType)data).getActivityType());
|
||||
activityEntry.setRawKind(activityType.toActivityKind());
|
||||
}
|
||||
|
||||
private void handleDuration(WithingsStructure data) {
|
||||
activityEntry.setDuration(((ActivitySampleDuration)data).getDuration());
|
||||
}
|
||||
|
||||
private void handleHeartrate(WithingsStructure data) {
|
||||
activityEntry.setIsHeartrate(((ActivityHeartrate)data).getHeartrate());
|
||||
}
|
||||
|
||||
private void handleMovement(WithingsStructure data) {
|
||||
activityEntry.setRawKind(ActivityKind.TYPE_UNKNOWN);
|
||||
activityEntry.setSteps(((ActivitySampleMovement)data).getSteps());
|
||||
activityEntry.setDistance(((ActivitySampleMovement)data).getDistance());
|
||||
}
|
||||
|
||||
private void handleWalk(WithingsStructure data) {
|
||||
activityEntry.setRawKind(ActivityKind.TYPE_WALKING);
|
||||
}
|
||||
|
||||
private void handleRun(WithingsStructure data) {
|
||||
activityEntry.setRawKind(ActivityKind.TYPE_RUNNING);
|
||||
}
|
||||
|
||||
private void handleSwim(WithingsStructure data) {
|
||||
activityEntry.setRawKind(ActivityKind.TYPE_SWIMMING);
|
||||
}
|
||||
|
||||
private void handleSleep(WithingsStructure data) {
|
||||
int sleepType;
|
||||
switch (((ActivitySampleSleep)data).getSleepType()) {
|
||||
case 0:
|
||||
sleepType = ActivityKind.TYPE_LIGHT_SLEEP;
|
||||
activityEntry.setRawIntensity(10);
|
||||
break;
|
||||
case 2:
|
||||
sleepType = ActivityKind.TYPE_DEEP_SLEEP;
|
||||
activityEntry.setRawIntensity(70);
|
||||
break;
|
||||
case 3:
|
||||
sleepType = ActivityKind.TYPE_REM_SLEEP;
|
||||
activityEntry.setRawIntensity(80);
|
||||
break;
|
||||
default:
|
||||
sleepType = ActivityKind.TYPE_LIGHT_SLEEP;
|
||||
activityEntry.setRawIntensity(50);
|
||||
}
|
||||
|
||||
activityEntry.setRawKind(sleepType);
|
||||
}
|
||||
|
||||
private void handleCalories1(WithingsStructure data) {
|
||||
activityEntry.setRawIntensity(((ActivitySampleCalories)data).getMet());
|
||||
activityEntry.setCalories(((ActivitySampleCalories)data).getCalories());
|
||||
}
|
||||
|
||||
private void handleCalories2(WithingsStructure data) {
|
||||
activityEntry.setRawIntensity(((ActivitySampleCalories2)data).getMet());
|
||||
activityEntry.setCalories(((ActivitySampleCalories2)data).getCalories());
|
||||
|
||||
}
|
||||
|
||||
private void addToList(ActivityEntry activityEntry) {
|
||||
if (activityEntry.isHeartrate()) {
|
||||
heartrateEntries.add(activityEntry);
|
||||
} else {
|
||||
activityEntries.add(activityEntry);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveData() {
|
||||
List<WithingsSteelHRActivitySample> activitySamples = new ArrayList<>();
|
||||
for (ActivityEntry activityEntry : activityEntries) {
|
||||
convertToSampleAndAddToList(activitySamples, activityEntry);
|
||||
}
|
||||
for (ActivityEntry activityEntry : heartrateEntries) {
|
||||
convertToSampleAndAddToList(activitySamples, activityEntry);
|
||||
}
|
||||
|
||||
writeToDB(activitySamples);
|
||||
}
|
||||
|
||||
private void writeToDB(List<WithingsSteelHRActivitySample> activitySamples) {
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
|
||||
Long deviceId = DBHelper.getDevice(device, dbHandler.getDaoSession()).getId();
|
||||
WithingsSteelHRSampleProvider provider = new WithingsSteelHRSampleProvider(device, dbHandler.getDaoSession());
|
||||
for (WithingsSteelHRActivitySample sample : activitySamples) {
|
||||
sample.setDeviceId(deviceId);
|
||||
sample.setUserId(userId);
|
||||
}
|
||||
provider.addGBActivitySamples(activitySamples.toArray(new WithingsSteelHRActivitySample[0]));
|
||||
} catch (Exception ex) {
|
||||
logger.warn("Error saving activity data: " + ex.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void mergeHeartrateSamplesIntoActivitySammples() {
|
||||
for (ActivityEntry heartrateEntry : heartrateEntries) {
|
||||
for (ActivityEntry activityEntry : activityEntries) {
|
||||
if (doActivitiesOverlap(heartrateEntry, activityEntry)) {
|
||||
updateHeartrateEntry(heartrateEntry, activityEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean doActivitiesOverlap(ActivityEntry heartrateEntry, ActivityEntry activityEntry) {
|
||||
return activityEntry.getTimestamp() <= heartrateEntry.getTimestamp()
|
||||
&& (activityEntry.getTimestamp() + activityEntry.getDuration()) >= heartrateEntry.getTimestamp();
|
||||
}
|
||||
|
||||
private void updateHeartrateEntry(ActivityEntry heartRateEntry, ActivityEntry activityEntry) {
|
||||
heartRateEntry.setRawKind(activityEntry.getRawKind());
|
||||
heartRateEntry.setRawIntensity(activityEntry.getRawIntensity());
|
||||
heartRateEntry.setDuration(activityEntry.getDuration() - (heartRateEntry.getTimestamp() - activityEntry.getTimestamp()));
|
||||
// If timestamps are exactly the same and only then, the heartrate entry would overwrite the activity entry in the DB, so we set more values.
|
||||
// If we would do so everytime, steps and so on would be multiplicated.
|
||||
if (heartRateEntry.getTimestamp() == activityEntry.getTimestamp()) {
|
||||
heartRateEntry.setSteps(activityEntry.getSteps());
|
||||
heartRateEntry.setDistance(activityEntry.getDistance());
|
||||
heartRateEntry.setCalories(activityEntry.getCalories());
|
||||
}
|
||||
}
|
||||
|
||||
private void convertToSampleAndAddToList(List<WithingsSteelHRActivitySample> activitySamples, ActivityEntry activityEntry) {
|
||||
WithingsSteelHRActivitySample sample = new WithingsSteelHRActivitySample();
|
||||
sample.setTimestamp(activityEntry.getTimestamp());
|
||||
sample.setDuration(activityEntry.getDuration());
|
||||
sample.setHeartRate(activityEntry.getHeartrate());
|
||||
sample.setSteps(activityEntry.getSteps());
|
||||
sample.setRawKind(activityEntry.getRawKind());
|
||||
sample.setCalories(activityEntry.getCalories());
|
||||
sample.setDistance(activityEntry.getDistance());
|
||||
sample.setRawIntensity(activityEntry.getRawIntensity());
|
||||
activitySamples.add(sample);
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.conversation;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.BatteryValues;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
|
||||
|
||||
public class BatteryStateHandler extends AbstractResponseHandler {
|
||||
|
||||
public BatteryStateHandler(WithingsSteelHRDeviceSupport support) {
|
||||
super(support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(Message response) {
|
||||
handleBatteryState(response.getStructureByType(BatteryValues.class));
|
||||
}
|
||||
|
||||
private void handleBatteryState(BatteryValues batteryValues) {
|
||||
if (batteryValues == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
|
||||
batteryInfo.level = batteryValues.getPercent();
|
||||
switch (batteryValues.getStatus()) {
|
||||
case 0:
|
||||
batteryInfo.state = BatteryState.BATTERY_CHARGING;
|
||||
break;
|
||||
case 1:
|
||||
batteryInfo.state = BatteryState.BATTERY_LOW;
|
||||
break;
|
||||
default:
|
||||
batteryInfo.state = BatteryState.BATTERY_NORMAL;
|
||||
}
|
||||
batteryInfo.voltage = batteryValues.getVolt();
|
||||
support.evaluateGBDeviceEvent(batteryInfo);
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.conversation;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
|
||||
|
||||
public interface Conversation {
|
||||
void registerObserver(ConversationObserver observer);
|
||||
void removeObserver(ConversationObserver observer);
|
||||
void setRequest(Message message);
|
||||
Message getRequest();
|
||||
void handleResponse(Message mesage);
|
||||
boolean isComplete();
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.conversation;
|
||||
|
||||
public interface ConversationObserver {
|
||||
void onConversationCompleted(short conversationType);
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.conversation;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.WithingsUUID;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||
|
||||
public class ConversationQueue implements ConversationObserver
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(WithingsSteelHRDeviceSupport.class);
|
||||
private final LinkedList<Conversation> queue = new LinkedList<>();
|
||||
private WithingsSteelHRDeviceSupport support;
|
||||
|
||||
public ConversationQueue(WithingsSteelHRDeviceSupport support) {
|
||||
this.support = support;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConversationCompleted(short conversationType) {
|
||||
queue.remove(getConversation(conversationType));
|
||||
send();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
queue.clear();
|
||||
}
|
||||
|
||||
public void send() {
|
||||
logger.debug("Sending of queued messages has been requested.");
|
||||
if (!queue.isEmpty()) {
|
||||
Conversation nextInLine = queue.peek();
|
||||
if (nextInLine!= null) {
|
||||
logger.debug("Sending next queued message.");
|
||||
Message request = nextInLine.getRequest();
|
||||
support.sendToDevice(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addConversation(Conversation conversation) {
|
||||
if (conversation == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (conversation.getRequest().needsResponse() || conversation.getRequest().needsEOT()) {
|
||||
queue.add(conversation);
|
||||
conversation.registerObserver(this);
|
||||
} else {
|
||||
support.sendToDevice(conversation.getRequest());
|
||||
}
|
||||
}
|
||||
|
||||
public void processResponse(Message response) {
|
||||
Conversation conversation = getConversation(response.getType());
|
||||
if (conversation != null) {
|
||||
conversation.handleResponse(response);
|
||||
}
|
||||
}
|
||||
|
||||
private Conversation getConversation(short requestType) {
|
||||
for (Conversation conversation : queue) {
|
||||
if (conversation.getRequest() != null && conversation.getRequest().getType() == requestType) {
|
||||
return conversation;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.conversation;
|
||||
|
||||
import android.content.Intent;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.withingssteelhr.WithingsSteelHRSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.WithingsSteelHRActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.activity.SleepActivitySampleHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.HeartRate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.LiveHeartRate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructure;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
|
||||
|
||||
public class HeartRateHandler extends AbstractResponseHandler {
|
||||
private static final Logger logger = LoggerFactory.getLogger(HeartRateHandler.class);
|
||||
|
||||
public HeartRateHandler(WithingsSteelHRDeviceSupport support) {
|
||||
super(support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(Message response) {
|
||||
if (response.getDataStructures().size() > 0) {
|
||||
handleHeartRateData(response.getDataStructures().get(0));
|
||||
}
|
||||
}
|
||||
|
||||
private void handleHeartRateData(WithingsStructure structure) {
|
||||
int heartRate = 0;
|
||||
if (structure instanceof HeartRate) {
|
||||
heartRate = ((HeartRate)structure).getHeartrate();
|
||||
} else if (structure instanceof LiveHeartRate) {
|
||||
heartRate = ((LiveHeartRate)structure).getHeartrate();
|
||||
}
|
||||
|
||||
if (heartRate > 0) {
|
||||
saveHeartRateData(heartRate);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveHeartRateData(int heartRate) {
|
||||
WithingsSteelHRActivitySample sample = new WithingsSteelHRActivitySample();
|
||||
sample.setTimestamp((int) (GregorianCalendar.getInstance().getTimeInMillis() / 1000L));
|
||||
sample.setHeartRate(heartRate);
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
|
||||
Long deviceId = DBHelper.getDevice(device, dbHandler.getDaoSession()).getId();
|
||||
WithingsSteelHRSampleProvider provider = new WithingsSteelHRSampleProvider(device, dbHandler.getDaoSession());
|
||||
sample.setDeviceId(deviceId);
|
||||
sample.setUserId(userId);
|
||||
sample = SleepActivitySampleHelper.mergeIfNecessary(provider, sample);
|
||||
provider.addGBActivitySample(sample);
|
||||
Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
|
||||
.putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample);
|
||||
LocalBroadcastManager.getInstance(support.getContext()).sendBroadcast(intent);
|
||||
} catch (Exception ex) {
|
||||
logger.warn("Error saving current heart rate: " + ex.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.conversation;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
|
||||
|
||||
public interface ResponseHandler {
|
||||
void handleResponse(Message response);
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.conversation;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessageType;
|
||||
|
||||
public class SetupFinishedHandler extends AbstractResponseHandler {
|
||||
|
||||
public SetupFinishedHandler(WithingsSteelHRDeviceSupport support) {
|
||||
super(support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(Message response) {
|
||||
if (response.getType() == WithingsMessageType.SETUP_FINISHED) {
|
||||
support.finishInitialization();;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.conversation;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
|
||||
|
||||
public class SimpleConversation extends AbstractConversation {
|
||||
|
||||
public SimpleConversation(ResponseHandler responseHandler) {
|
||||
super(responseHandler);
|
||||
}
|
||||
|
||||
public SimpleConversation() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doSendRequest(Message message) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doHandleResponse(Message message) {
|
||||
if (responseHandler != null) {
|
||||
responseHandler.handleResponse(message);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.conversation;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessageType;
|
||||
|
||||
public class SyncFinishedHandler extends AbstractResponseHandler {
|
||||
|
||||
public SyncFinishedHandler(WithingsSteelHRDeviceSupport support) {
|
||||
super(support);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void handleResponse(Message response) {
|
||||
if (response.getType() == WithingsMessageType.SYNC_OK) {
|
||||
support.finishSync();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.conversation;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.activity.WithingsActivityType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructure;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WorkoutScreenList;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
|
||||
|
||||
public class WorkoutScreenListHandler extends AbstractResponseHandler {
|
||||
|
||||
public WorkoutScreenListHandler(WithingsSteelHRDeviceSupport support) {
|
||||
super(support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(Message response) {
|
||||
List<WithingsStructure> data = response.getDataStructures();
|
||||
if (data != null && !data.isEmpty()) {
|
||||
WorkoutScreenList screenList = (WorkoutScreenList) data.get(0);
|
||||
saveScreenList(screenList);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveScreenList(WorkoutScreenList screenList) {
|
||||
int[] workoutIds = screenList.getWorkoutIds();
|
||||
List<String> prefValues = new ArrayList<>();
|
||||
for (int i = 0; i < workoutIds.length; i++) {
|
||||
int currentId = workoutIds[i];
|
||||
if (currentId > 0) {
|
||||
WithingsActivityType type = WithingsActivityType.fromCode(currentId);
|
||||
prefValues.add(type.name().toLowerCase(Locale.ROOT));
|
||||
}
|
||||
}
|
||||
|
||||
String workoutActivityTypes = String.join(",", prefValues);
|
||||
GBDevice device = support.getDevice();
|
||||
final SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress());
|
||||
prefs.edit().putString("workout_activity_types_sortable", workoutActivityTypes).apply();
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ActivityHeartrate extends WithingsStructure
|
||||
{
|
||||
private int heartrate;
|
||||
|
||||
public int getHeartrate() {
|
||||
return heartrate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
|
||||
heartrate = rawDataBuffer.get(0) & 0xff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.ACTIVITY_HR;
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ActivitySampleCalories extends WithingsStructure {
|
||||
|
||||
private int calories;
|
||||
private int met;
|
||||
|
||||
public int getCalories() {
|
||||
return calories;
|
||||
}
|
||||
|
||||
public int getMet() {
|
||||
return met;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
|
||||
calories = rawDataBuffer.getShort() & 65535;
|
||||
met = rawDataBuffer.getShort() & 65535;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.ACTIVITY_SAMPLE_CALORIES;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
public class ActivitySampleCalories2 extends ActivitySampleCalories {
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.ACTIVITY_SAMPLE_CALORIES_2;
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
|
||||
public class ActivitySampleDuration extends WithingsStructure {
|
||||
|
||||
private short duration;
|
||||
|
||||
public short getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 6;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
|
||||
duration = rawDataBuffer.getShort();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.ACTIVITY_SAMPLE_DURATION;
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ActivitySampleMovement extends WithingsStructure {
|
||||
|
||||
private short steps;
|
||||
private int distance;
|
||||
private int asc;
|
||||
private int desc;
|
||||
|
||||
public short getSteps() {
|
||||
return steps;
|
||||
}
|
||||
|
||||
public void setSteps(short steps) {
|
||||
this.steps = steps;
|
||||
}
|
||||
|
||||
public int getDistance() {
|
||||
return distance;
|
||||
}
|
||||
|
||||
public void setDistance(int distance) {
|
||||
this.distance = distance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 18;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
buffer.putShort(steps);
|
||||
buffer.putInt(distance);
|
||||
buffer.putInt(asc);
|
||||
buffer.putInt(desc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
|
||||
steps = rawDataBuffer.getShort();
|
||||
distance = rawDataBuffer.getInt();
|
||||
asc = rawDataBuffer.getInt();
|
||||
desc = rawDataBuffer.getInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.ACTIVITY_SAMPLE_MOVEMENT;
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ActivitySampleRun extends WithingsStructure {
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.ACTIVITY_SAMPLE_RUN;
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
|
||||
public class ActivitySampleSleep extends WithingsStructure {
|
||||
|
||||
private short sleepType;
|
||||
|
||||
public short getSleepType() {
|
||||
return sleepType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 6;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
|
||||
sleepType = rawDataBuffer.getShort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.ACTIVITY_SAMPLE_SLEEP;
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ActivitySampleSwim extends WithingsStructure {
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.ACTIVITY_SAMPLE_SWIM;
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Date;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
|
||||
public class ActivitySampleTime extends WithingsStructure {
|
||||
|
||||
private Date date;
|
||||
|
||||
public Date getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
|
||||
long timestampInSeconds = rawDataBuffer.getInt() & 4294967295L;
|
||||
date = new Date(timestampInSeconds * 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.ACTIVITY_SAMPLE_TIME;
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ActivitySampleUnknown extends WithingsStructure {
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.ACTIVITY_SAMPLE_UNKNOWN;
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
|
||||
public class ActivitySampleWalk extends WithingsStructure {
|
||||
|
||||
private short level;
|
||||
|
||||
public short getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 6;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
|
||||
level = (short) (rawDataBuffer.getShort() & 65535);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.ACTIVITY_SAMPLE_WALK;
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ActivityTarget extends WithingsStructure {
|
||||
|
||||
private long targetCount;
|
||||
|
||||
public ActivityTarget(long targetCount) {
|
||||
this.targetCount = targetCount;
|
||||
}
|
||||
|
||||
public long getTargetCount() {
|
||||
return targetCount;
|
||||
}
|
||||
|
||||
public void setTargetCount(long targetCount) {
|
||||
this.targetCount = targetCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 12;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer rawDataBuffer) {
|
||||
rawDataBuffer.putLong(targetCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.ACTIVITY_TARGET;
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class AlarmName extends WithingsStructure {
|
||||
|
||||
private String name;
|
||||
|
||||
public AlarmName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return (short) ((name != null ? name.getBytes().length : 0) + 1 + HEADER_SIZE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer rawDataBuffer) {
|
||||
addStringAsBytesWithLengthByte(rawDataBuffer, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.ALARM_NAME;
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class AlarmSettings extends WithingsStructure {
|
||||
private short hour;
|
||||
private short minute;
|
||||
private short dayOfWeek;
|
||||
private short dayOfMonth;
|
||||
private short month;
|
||||
private short year;
|
||||
private short smartWakeupMinutes;
|
||||
|
||||
public short getHour() {
|
||||
return hour;
|
||||
}
|
||||
|
||||
public void setHour(short hour) {
|
||||
this.hour = hour;
|
||||
}
|
||||
|
||||
public short getMinute() {
|
||||
return minute;
|
||||
}
|
||||
|
||||
public void setMinute(short minute) {
|
||||
this.minute = minute;
|
||||
}
|
||||
|
||||
public short getDayOfWeek() {
|
||||
return dayOfWeek;
|
||||
}
|
||||
|
||||
public void setDayOfWeek(short dayOfWeek) {
|
||||
this.dayOfWeek = dayOfWeek;
|
||||
}
|
||||
|
||||
public short getDayOfMonth() {
|
||||
return dayOfMonth;
|
||||
}
|
||||
|
||||
public void setDayOfMonth(short dayOfMonth) {
|
||||
this.dayOfMonth = dayOfMonth;
|
||||
}
|
||||
|
||||
public short getMonth() {
|
||||
return month;
|
||||
}
|
||||
|
||||
public void setMonth(short month) {
|
||||
this.month = month;
|
||||
}
|
||||
|
||||
public short getYear() {
|
||||
return year;
|
||||
}
|
||||
|
||||
public void setYear(short year) {
|
||||
this.year = year;
|
||||
}
|
||||
|
||||
public short getYetUnkown() {
|
||||
return smartWakeupMinutes;
|
||||
}
|
||||
|
||||
public void setSmartWakeupMinutes(short smartWakeupMinutes) {
|
||||
this.smartWakeupMinutes = smartWakeupMinutes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 11;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
buffer.put((byte)hour);
|
||||
buffer.put((byte)minute);
|
||||
buffer.put((byte)dayOfWeek);
|
||||
buffer.put((byte)dayOfMonth);
|
||||
buffer.put((byte)month);
|
||||
buffer.put((byte)year);
|
||||
buffer.put((byte)smartWakeupMinutes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.ALARM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean withEndOfMessage() {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class AlarmStatus extends WithingsStructure {
|
||||
|
||||
private boolean enabled;
|
||||
|
||||
public AlarmStatus(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
buffer.put(enabled? (byte) 1 : (byte) 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.ALARM_STATUS;
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class AncsStatus extends WithingsStructure {
|
||||
|
||||
private boolean isOn;
|
||||
|
||||
public AncsStatus() {}
|
||||
|
||||
public AncsStatus(boolean isOn) {
|
||||
this.isOn = isOn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
buffer.put(isOn? (byte)0x01 : 0x00);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.ANCS_STATUS;
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
|
||||
public class BatteryValues extends WithingsStructure {
|
||||
|
||||
private short percent;
|
||||
private short status;
|
||||
private int volt;
|
||||
|
||||
public short getPercent() {
|
||||
return percent;
|
||||
}
|
||||
|
||||
public void setPercent(short percent) {
|
||||
this.percent = percent;
|
||||
}
|
||||
|
||||
public short getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(short status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public int getVolt() {
|
||||
return volt;
|
||||
}
|
||||
|
||||
public void setVolt(int volt) {
|
||||
this.volt = volt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 14;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
|
||||
percent = (short)(rawDataBuffer.get() & 255);
|
||||
status = (short)(rawDataBuffer.get() & 255);
|
||||
volt = rawDataBuffer.getInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.BATTERY_STATUS;
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class Challenge extends WithingsStructure {
|
||||
|
||||
private String macAddress;
|
||||
|
||||
private byte[] challenge;
|
||||
|
||||
public void setMacAddress(String macAddress) {
|
||||
this.macAddress = macAddress;
|
||||
}
|
||||
|
||||
public void setChallenge(byte[] challenge) {
|
||||
this.challenge = challenge;
|
||||
}
|
||||
|
||||
public String getMacAddress() {
|
||||
return macAddress;
|
||||
}
|
||||
|
||||
public byte[] getChallenge() {
|
||||
return challenge;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
int challengeLength = 0;
|
||||
int macAddressLength = (macAddress != null ? macAddress.getBytes().length : 0) + 1;
|
||||
if (challenge != null) {
|
||||
challengeLength = challenge.length;
|
||||
}
|
||||
|
||||
return (short) (macAddressLength + challengeLength + 5);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
addStringAsBytesWithLengthByte(buffer, macAddress);
|
||||
|
||||
if (challenge != null) {
|
||||
buffer.put((byte) challenge.length);
|
||||
buffer.put(challenge);
|
||||
} else {
|
||||
buffer.put((byte)0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
|
||||
macAddress = getNextString(rawDataBuffer);
|
||||
challenge = getNextByteArray(rawDataBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.CHALLENGE;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class ChallengeResponse extends WithingsStructure {
|
||||
|
||||
private byte[] response = new byte[0];
|
||||
|
||||
public byte[] getResponse() {
|
||||
return response;
|
||||
}
|
||||
|
||||
public void setResponse(byte[] response) {
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return (short) ((response != null ? response.length : 0) + 5);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
addByteArrayWithLengthByte(buffer, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
|
||||
response = getNextByteArray(rawDataBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.CHALLENGE_RESPONSE;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,168 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class DataStructureFactory {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DataStructureFactory.class.getSimpleName());
|
||||
private static final int HEADER_SIZE = 4;
|
||||
|
||||
public List<WithingsStructure> createStructuresFromRawData(byte[] rawData) {
|
||||
List<WithingsStructure> structures = new ArrayList<>();
|
||||
if (rawData == null) {
|
||||
return structures;
|
||||
}
|
||||
|
||||
List<byte[]> rawDataStructures = splitRawData(rawData);
|
||||
for (byte[] rawDataStructure : rawDataStructures) {
|
||||
WithingsStructure structure = null;
|
||||
|
||||
short structureTypeFromResponse = (short) BLETypeConversions.toInt16(rawDataStructure[1], rawDataStructure[0]);
|
||||
|
||||
switch (structureTypeFromResponse) {
|
||||
case WithingsStructureType.HR:
|
||||
structure = new HeartRate();
|
||||
break;
|
||||
case WithingsStructureType.LIVE_HR:
|
||||
structure = new LiveHeartRate();
|
||||
break;
|
||||
case WithingsStructureType.BATTERY_STATUS:
|
||||
structure = new BatteryValues();
|
||||
break;
|
||||
case WithingsStructureType.SCREEN_SETTINGS:
|
||||
structure = new ScreenSettings();
|
||||
break;
|
||||
case WithingsStructureType.ANCS_STATUS:
|
||||
structure = new AncsStatus();
|
||||
break;
|
||||
case WithingsStructureType.ACTIVITY_SAMPLE_TIME:
|
||||
structure = new ActivitySampleTime();
|
||||
break;
|
||||
case WithingsStructureType.ACTIVITY_SAMPLE_DURATION:
|
||||
structure = new ActivitySampleDuration();
|
||||
break;
|
||||
case WithingsStructureType.ACTIVITY_SAMPLE_MOVEMENT:
|
||||
structure = new ActivitySampleMovement();
|
||||
break;
|
||||
case WithingsStructureType.ACTIVITY_SAMPLE_CALORIES:
|
||||
structure = new ActivitySampleCalories();
|
||||
break;
|
||||
case WithingsStructureType.ACTIVITY_SAMPLE_CALORIES_2:
|
||||
structure = new ActivitySampleCalories2();
|
||||
break;
|
||||
case WithingsStructureType.ACTIVITY_SAMPLE_SLEEP:
|
||||
structure = new ActivitySampleSleep();
|
||||
break;
|
||||
case WithingsStructureType.ACTIVITY_SAMPLE_WALK:
|
||||
structure = new ActivitySampleWalk();
|
||||
break;
|
||||
case WithingsStructureType.ACTIVITY_SAMPLE_RUN:
|
||||
structure = new ActivitySampleRun();
|
||||
break;
|
||||
case WithingsStructureType.ACTIVITY_SAMPLE_SWIM:
|
||||
structure = new ActivitySampleSwim();
|
||||
break;
|
||||
case WithingsStructureType.ACTIVITY_HR:
|
||||
structure = new ActivityHeartrate();
|
||||
break;
|
||||
case WithingsStructureType.PROBE_REPLY:
|
||||
structure = new ProbeReply();
|
||||
break;
|
||||
case WithingsStructureType.CHALLENGE:
|
||||
structure = new Challenge();
|
||||
break;
|
||||
case WithingsStructureType.CHALLENGE_RESPONSE:
|
||||
structure = new ChallengeResponse();
|
||||
break;
|
||||
case WithingsStructureType.ACTIVITY_SAMPLE_UNKNOWN:
|
||||
structure = new ActivitySampleUnknown();
|
||||
break;
|
||||
case WithingsStructureType.END_OF_TRANSMISSION:
|
||||
structure = new EndOfTransmission();
|
||||
break;
|
||||
case WithingsStructureType.WORKOUT_TYPE:
|
||||
structure = new WorkoutType();
|
||||
break;
|
||||
case WithingsStructureType.LIVE_WORKOUT_START:
|
||||
structure = new LiveWorkoutStart();
|
||||
break;
|
||||
case WithingsStructureType.LIVE_WORKOUT_END:
|
||||
structure = new LiveWorkoutEnd();
|
||||
break;
|
||||
case WithingsStructureType.LIVE_WORKOUT_PAUSE_STATE:
|
||||
structure = new LiveWorkoutPauseState();
|
||||
break;
|
||||
case WithingsStructureType.WORKOUT_SCREEN_LIST:
|
||||
structure = new WorkoutScreenList();
|
||||
break;
|
||||
case WithingsStructureType.IMAGE_META_DATA:
|
||||
structure = new ImageMetaData();
|
||||
break;
|
||||
case WithingsStructureType.GLYPH_ID:
|
||||
structure = new GlyphId();
|
||||
break;
|
||||
case WithingsStructureType.NOTIFICATION_APP_ID:
|
||||
structure = new GlyphId();
|
||||
break;
|
||||
default:
|
||||
structure = null;
|
||||
logger.info("Received yet unknown structure type: " + structureTypeFromResponse);
|
||||
}
|
||||
|
||||
if (structure != null) {
|
||||
structure.fillFromRawData(removeHeaderBytes(rawDataStructure));
|
||||
structures.add(structure);
|
||||
}
|
||||
}
|
||||
|
||||
return structures;
|
||||
}
|
||||
|
||||
private List<byte[]> splitRawData(byte[] rawData) {
|
||||
int remainingBytes = rawData.length;
|
||||
List<byte[]> result = new ArrayList<>();
|
||||
|
||||
while(remainingBytes > 3) {
|
||||
short structureLength = (short) BLETypeConversions.toInt16(rawData[3], rawData[2]);
|
||||
remainingBytes -= (structureLength + HEADER_SIZE);
|
||||
try {
|
||||
result.add(Arrays.copyOfRange(rawData, 0, structureLength + HEADER_SIZE));
|
||||
if (remainingBytes > 0) {
|
||||
rawData = Arrays.copyOfRange(rawData, structureLength + HEADER_SIZE, rawData.length);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Splitting of data failed: " + GB.hexdump(rawData));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private byte[] removeHeaderBytes(byte[] data) {
|
||||
return Arrays.copyOfRange(data, HEADER_SIZE, data.length);
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class EndOfTransmission extends WithingsStructure {
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getRawData() {
|
||||
ByteBuffer rawDataBuffer = ByteBuffer.allocate(4);
|
||||
rawDataBuffer.putShort(getType());
|
||||
rawDataBuffer.putShort((short)0);
|
||||
return rawDataBuffer.array();
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.END_OF_TRANSMISSION;
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class GetActivitySamples extends WithingsStructure {
|
||||
|
||||
public long timestampFrom;
|
||||
|
||||
public short maxSampleCount;
|
||||
|
||||
public GetActivitySamples(long timestampFrom, short maxSampleCount) {
|
||||
this.timestampFrom = timestampFrom;
|
||||
this.maxSampleCount = maxSampleCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
buffer.putInt((int)(timestampFrom & 4294967295L));
|
||||
buffer.putShort((short)maxSampleCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.GET_ACTIVITY_SAMPLES;
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
public class GlyphId extends WithingsStructure {
|
||||
|
||||
private long unicode;
|
||||
|
||||
public long getUnicode() {
|
||||
return unicode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
|
||||
int value = rawDataBuffer.getInt();
|
||||
unicode = ByteBuffer.allocate(4).putInt(value).order(ByteOrder.LITTLE_ENDIAN).getInt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.GLYPH_ID;
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class HeartRate extends WithingsStructure {
|
||||
|
||||
private int heartrate;
|
||||
|
||||
public int getHeartrate() {
|
||||
return heartrate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
|
||||
heartrate = rawDataBuffer.get(1) & 0xff;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.HR;
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ImageData extends WithingsStructure {
|
||||
|
||||
byte [] imageData;
|
||||
|
||||
public void setImageData(byte[] imageData) {
|
||||
this.imageData = imageData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return imageData != null ? (short)(imageData.length + 1 + HEADER_SIZE) : 1 + HEADER_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
if (imageData != null) {
|
||||
addByteArrayWithLengthByte(buffer, imageData);
|
||||
} else {
|
||||
addByteArrayWithLengthByte(buffer, new byte[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.IMAGE_DATA;
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ImageMetaData extends WithingsStructure {
|
||||
|
||||
private byte unknown = 0x00;
|
||||
private byte width;
|
||||
private byte height;
|
||||
|
||||
public byte getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public void setWidth(byte width) {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
public byte getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setHeight(byte height) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 7;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
buffer.put(unknown);
|
||||
buffer.put(width);
|
||||
buffer.put(height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
|
||||
unknown = rawDataBuffer.get();
|
||||
width = rawDataBuffer.get();
|
||||
height = rawDataBuffer.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.IMAGE_META_DATA;
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class LiveHeartRate extends WithingsStructure {
|
||||
|
||||
private int heartrate;
|
||||
|
||||
public int getHeartrate() {
|
||||
return heartrate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
|
||||
heartrate = rawDataBuffer.get() & 0xff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.LIVE_HR;
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Date;
|
||||
|
||||
public class LiveWorkoutEnd extends WithingsStructure {
|
||||
|
||||
private Date endtime;
|
||||
|
||||
public Date getEndtime() {
|
||||
return endtime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
|
||||
long timestampInSeconds = rawDataBuffer.getInt() & 4294967295L;
|
||||
endtime = new Date(timestampInSeconds * 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.LIVE_WORKOUT_END;
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Date;
|
||||
|
||||
public class LiveWorkoutPauseState extends WithingsStructure {
|
||||
|
||||
private byte yetunknown;
|
||||
|
||||
// Is always the same as long as the actual pause continues:
|
||||
private Date starttime;
|
||||
|
||||
// This is just a guess, but observation show that this is quite possible the meaning of this value that is send when the pause is over
|
||||
private int lengthInSeconds;
|
||||
|
||||
public Date getStarttime() {
|
||||
return starttime;
|
||||
}
|
||||
|
||||
public int getLengthInSeconds() {
|
||||
return lengthInSeconds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 13;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
|
||||
yetunknown = rawDataBuffer.get();
|
||||
|
||||
long timestampInSeconds = rawDataBuffer.getInt() & 4294967295L;
|
||||
if (timestampInSeconds > 0) {
|
||||
starttime = new Date(timestampInSeconds * 1000);
|
||||
}
|
||||
|
||||
lengthInSeconds = rawDataBuffer.getInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.LIVE_WORKOUT_PAUSE_STATE;
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Date;
|
||||
|
||||
public class LiveWorkoutStart extends WithingsStructure {
|
||||
|
||||
private Date starttime;
|
||||
|
||||
public Date getStarttime() {
|
||||
return starttime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
|
||||
long timestampInSeconds = rawDataBuffer.getInt() & 4294967295L;
|
||||
starttime = new Date(timestampInSeconds * 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.LIVE_WORKOUT_START;
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class Locale extends WithingsStructure {
|
||||
|
||||
private String locale = "en";
|
||||
|
||||
public Locale(String locale) {
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return (short) ((locale != null ? locale.getBytes().length : 0) + 1 + HEADER_SIZE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer rawDataBuffer) {
|
||||
addStringAsBytesWithLengthByte(rawDataBuffer, locale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.LOCALE;
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class MoveHand extends WithingsStructure {
|
||||
|
||||
private short hand;
|
||||
private short movement;
|
||||
|
||||
public void setHand(short hand) {
|
||||
this.hand = hand;
|
||||
}
|
||||
|
||||
public void setMovement(short movement) {
|
||||
this.movement = movement;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 7;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
buffer.put((byte) (hand & 255));
|
||||
buffer.putShort(movement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.MOVE_HAND;
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class Probe extends WithingsStructure {
|
||||
|
||||
private short os;
|
||||
|
||||
private short app;
|
||||
|
||||
private long version;
|
||||
|
||||
public Probe(short os, short app, long version) {
|
||||
this.os = os;
|
||||
this.app = app;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
buffer.put((byte) (os & 255));
|
||||
buffer.put((byte) (app & 255));
|
||||
buffer.putInt((int) (version & 4294967295L));
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.PROBE;
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ProbeOsVersion extends WithingsStructure {
|
||||
|
||||
private short osVersion;
|
||||
|
||||
public ProbeOsVersion(short osVersion) {
|
||||
this.osVersion = osVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 6;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
buffer.putShort(osVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.PROBE_OS_VERSION;
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.AuthenticationHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||
|
||||
public class ProbeReply extends WithingsStructure {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ProbeReply.class);
|
||||
private int yetUnknown1;
|
||||
private String name;
|
||||
private String mac;
|
||||
private String secret;
|
||||
private int yetUnknown2;
|
||||
private String mId;
|
||||
private int yetUnknown3;
|
||||
private int firmwareVersion;
|
||||
private int yetUnknown4;
|
||||
|
||||
public int getYetUnknown1() {
|
||||
return yetUnknown1;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getMac() {
|
||||
return mac;
|
||||
}
|
||||
|
||||
public String getSecret() {
|
||||
return secret;
|
||||
}
|
||||
|
||||
public int getYetUnknown2() {
|
||||
return yetUnknown2;
|
||||
}
|
||||
|
||||
public String getmId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
public int getYetUnknown3() {
|
||||
return yetUnknown3;
|
||||
}
|
||||
|
||||
public int getFirmwareVersion() {
|
||||
return firmwareVersion;
|
||||
}
|
||||
|
||||
public int getYetUnknown4() {
|
||||
return yetUnknown4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
int length = (name != null ? name.getBytes(StandardCharsets.UTF_8).length : 0) + 1;
|
||||
length += (mac != null ? mac.getBytes().length : 0) + 1;
|
||||
length += (secret != null ? secret.getBytes().length : 0) + 1;
|
||||
length += (mId != null ? mId.getBytes().length : 0) + 1;
|
||||
return (short) (length + 24);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
|
||||
try {
|
||||
yetUnknown1 = rawDataBuffer.getInt();
|
||||
name = getNextString(rawDataBuffer);
|
||||
mac = getNextString(rawDataBuffer);
|
||||
secret = getNextString(rawDataBuffer);
|
||||
yetUnknown2 = rawDataBuffer.getInt();
|
||||
mId = getNextString(rawDataBuffer);
|
||||
yetUnknown3 = rawDataBuffer.getInt();
|
||||
firmwareVersion = rawDataBuffer.getInt();
|
||||
yetUnknown4 = rawDataBuffer.getInt();
|
||||
} catch (Exception e) {
|
||||
logger.warn("Could not handle buffer with data " + StringUtils.bytesToHex(rawDataBuffer.array()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.PROBE_REPLY;
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ScreenSettings extends WithingsStructure {
|
||||
|
||||
private int id;
|
||||
|
||||
// TODO change to an actual unique ID. Must then be changed in User too.
|
||||
private int userId = 123456;
|
||||
private int yetUnknown1 = 0;
|
||||
private int yetUnknown2 = 0;
|
||||
private byte idOnDevice;
|
||||
private byte yetUnkown3 = 0x01;
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public byte getIdOnDevice() {
|
||||
return idOnDevice;
|
||||
}
|
||||
|
||||
public void setIdOnDevice(byte idOnDevice) {
|
||||
this.idOnDevice = idOnDevice;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 22;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
buffer.putInt(this.id);
|
||||
buffer.putInt(this.userId);
|
||||
buffer.putInt(this.yetUnknown1);
|
||||
buffer.putInt(this.yetUnknown2);
|
||||
buffer.put(this.idOnDevice);
|
||||
buffer.put(this.yetUnkown3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.SCREEN_SETTINGS;
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class SourceAppId extends WithingsStructure {
|
||||
|
||||
private String appId;
|
||||
|
||||
public String getAppId() {
|
||||
return appId;
|
||||
}
|
||||
|
||||
public void setAppId(String appId) {
|
||||
this.appId = appId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return (short) ((appId != null ? appId.getBytes().length : 0) + 5);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
|
||||
appId = getNextString(rawDataBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
addStringAsBytesWithLengthByte(buffer, appId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.NOTIFICATION_APP_ID;
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class Status extends WithingsStructure {
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
buffer.put((byte) 0x01);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return (short) 2420;
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import org.threeten.bp.Instant;
|
||||
import org.threeten.bp.ZoneId;
|
||||
import org.threeten.bp.zone.ZoneOffsetTransition;
|
||||
import org.threeten.bp.zone.ZoneRules;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import ch.qos.logback.core.encoder.ByteArrayUtil;
|
||||
|
||||
public class Time extends WithingsStructure {
|
||||
|
||||
private Instant now;
|
||||
private int timeOffsetInSeconds;
|
||||
private Instant nextDaylightSavingTransition;
|
||||
private int nextDaylightSavingTransitionOffsetInSeconds;
|
||||
|
||||
public Time() {
|
||||
now = Instant.now();
|
||||
final TimeZone timezone = TimeZone.getDefault();
|
||||
timeOffsetInSeconds = timezone.getOffset(now.toEpochMilli()) / 1000;
|
||||
final ZoneId zoneId = ZoneId.systemDefault();
|
||||
final ZoneRules zoneRules = zoneId.getRules();
|
||||
final ZoneOffsetTransition nextTransition = zoneRules.nextTransition(Instant.now());
|
||||
long nextTransitionTs = 0;
|
||||
if (nextTransition != null) {
|
||||
nextTransitionTs = nextTransition.getDateTimeBefore().atZone(zoneId).toEpochSecond();
|
||||
nextDaylightSavingTransitionOffsetInSeconds = nextTransition.getOffsetAfter().getTotalSeconds();
|
||||
}
|
||||
|
||||
nextDaylightSavingTransition = Instant.ofEpochSecond(nextTransitionTs);
|
||||
}
|
||||
|
||||
public Instant getNow() {
|
||||
return now;
|
||||
}
|
||||
|
||||
public void setNow(Instant now) {
|
||||
this.now = now;
|
||||
}
|
||||
|
||||
public int getTimeOffsetInSeconds() {
|
||||
return timeOffsetInSeconds;
|
||||
}
|
||||
|
||||
public void setTimeOffsetInSeconds(int TimeOffsetInSeconds) {
|
||||
this.timeOffsetInSeconds = TimeOffsetInSeconds;
|
||||
}
|
||||
|
||||
public Instant getNextDaylightSavingTransition() {
|
||||
return nextDaylightSavingTransition;
|
||||
}
|
||||
|
||||
public void setNextDaylightSavingTransition(Instant nextDaylightSavingTransition) {
|
||||
this.nextDaylightSavingTransition = nextDaylightSavingTransition;
|
||||
}
|
||||
|
||||
public int getNextDaylightSavingTransitionOffsetInSeconds() {
|
||||
return nextDaylightSavingTransitionOffsetInSeconds;
|
||||
}
|
||||
|
||||
public void setNextDaylightSavingTransitionOffsetInSeconds(int nextDaylightSavingTransitionOffsetInSeconds) {
|
||||
this.nextDaylightSavingTransitionOffsetInSeconds = nextDaylightSavingTransitionOffsetInSeconds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.TIME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 20;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer rawDataBuffer) {
|
||||
rawDataBuffer.putInt((int)now.getEpochSecond());
|
||||
rawDataBuffer.putInt(timeOffsetInSeconds);
|
||||
rawDataBuffer.putInt((int)nextDaylightSavingTransition.getEpochSecond());
|
||||
rawDataBuffer.putInt(nextDaylightSavingTransitionOffsetInSeconds);
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class TypeVersion extends WithingsStructure {
|
||||
|
||||
private byte version = 0x01;
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
buffer.put(version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return 2401;
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Date;
|
||||
|
||||
public class User extends WithingsStructure {
|
||||
|
||||
// This is just a dummy value as this seems to be the withings account id,
|
||||
// which we do not need, but the watch expects:
|
||||
private int userID = 123456;
|
||||
private int weight;
|
||||
private int height;
|
||||
//Seems to be 0x00 for male and 0x01 for female. Found no other in my tests.
|
||||
private byte gender;
|
||||
private Date birthdate;
|
||||
private String name;
|
||||
|
||||
public int getUserID() {
|
||||
return userID;
|
||||
}
|
||||
|
||||
public void setUserID(int userID) {
|
||||
this.userID = userID;
|
||||
}
|
||||
|
||||
public int getWeight() {
|
||||
return weight;
|
||||
}
|
||||
|
||||
public void setWeight(int weight) {
|
||||
this.weight = weight;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setHeight(int height) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public byte getGender() {
|
||||
return gender;
|
||||
}
|
||||
|
||||
public void setGender(byte gender) {
|
||||
this.gender = gender;
|
||||
}
|
||||
|
||||
public Date getBirthdate() {
|
||||
return birthdate;
|
||||
}
|
||||
|
||||
public void setBirthdate(Date birthdate) {
|
||||
this.birthdate = birthdate;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return (short) ((name != null ? name.getBytes(StandardCharsets.UTF_8).length : 0) + 22);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer rawDataBuffer) {
|
||||
rawDataBuffer.putInt(userID);
|
||||
rawDataBuffer.putInt(weight);
|
||||
rawDataBuffer.putInt(height);
|
||||
rawDataBuffer.put(gender);
|
||||
rawDataBuffer.putInt((int)(birthdate.getTime()/1000));
|
||||
addStringAsBytesWithLengthByte(rawDataBuffer, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.USER;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class UserSecret extends WithingsStructure {
|
||||
|
||||
private String secret = "2EM5zNP37QzM00hmP6BFTD92nG15XwNd";
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 37;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
addStringAsBytesWithLengthByte(buffer, secret);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.USER_SECRET;
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class UserUnit extends WithingsStructure {
|
||||
|
||||
private byte unknown1 = 0;
|
||||
private byte unknown2 = 0;
|
||||
private short type;
|
||||
private short unit;
|
||||
|
||||
public UserUnit() {}
|
||||
|
||||
public UserUnit(short type, short unit) {
|
||||
this.type = type;
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
buffer.put(unknown1);
|
||||
buffer.put(unknown2);
|
||||
buffer.putShort(type);
|
||||
buffer.putShort(unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.USER_UNIT;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
public final class UserUnitConstants {
|
||||
|
||||
public static final short DISTANCE = 2;
|
||||
public static final short CLOCK_MODE = 4;
|
||||
public static final short UNIT_KM = 24;
|
||||
public static final short UNIT_MILES = 25;
|
||||
public static final short UNIT_24H = 26;
|
||||
public static final short UNIT_12H = 27;
|
||||
|
||||
private UserUnitConstants() {}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
|
||||
|
||||
/**
|
||||
* This abstract class is the common denominator for all data structures used inside commands and the corresponding responses.
|
||||
* @see Message
|
||||
*/
|
||||
public abstract class WithingsStructure {
|
||||
protected final static short HEADER_SIZE = 4;
|
||||
|
||||
/**
|
||||
* Some messages have some end bytes, some have not.
|
||||
* Subclasses that need to have the eom appended need to overwrite this class and return true.
|
||||
* The default value is false.
|
||||
*
|
||||
* @return true if some end of message should be appended
|
||||
*/
|
||||
public boolean withEndOfMessage() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public byte[] getRawData() {
|
||||
short length = (getLength());
|
||||
ByteBuffer rawDataBuffer = ByteBuffer.allocate(length);
|
||||
rawDataBuffer.putShort(getType());
|
||||
rawDataBuffer.putShort((short)(length - HEADER_SIZE));
|
||||
fillinTypeSpecificData(rawDataBuffer);
|
||||
return rawDataBuffer.array();
|
||||
}
|
||||
|
||||
protected void addStringAsBytesWithLengthByte(ByteBuffer buffer, String str) {
|
||||
if (str == null) {
|
||||
buffer.put((byte)0);
|
||||
} else {
|
||||
byte[] stringAsBytes = str.getBytes(StandardCharsets.UTF_8);
|
||||
buffer.put((byte)stringAsBytes.length);
|
||||
buffer.put(stringAsBytes);
|
||||
}
|
||||
}
|
||||
|
||||
protected void fillFromRawData(byte[] rawData) {
|
||||
fillFromRawDataAsBuffer(ByteBuffer.wrap(rawData));
|
||||
};
|
||||
|
||||
protected void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {};
|
||||
|
||||
public abstract short getLength();
|
||||
|
||||
protected abstract void fillinTypeSpecificData(ByteBuffer buffer);
|
||||
public abstract short getType();
|
||||
|
||||
protected String getNextString(ByteBuffer byteBuffer) {
|
||||
// For strings in the raw data the first byte of the data is the length of the string:
|
||||
int stringLength = (short)(byteBuffer.get() & 255);
|
||||
byte[] stringBytes = new byte[stringLength];
|
||||
byteBuffer.get(stringBytes);
|
||||
return new String(stringBytes, Charset.forName("UTF-8"));
|
||||
}
|
||||
|
||||
protected byte[] getNextByteArray(ByteBuffer byteBuffer) {
|
||||
int arrayLength = (short)(byteBuffer.get() & 255);
|
||||
byte[] nextByteArray = new byte[arrayLength];
|
||||
byteBuffer.get(nextByteArray);
|
||||
return nextByteArray;
|
||||
}
|
||||
|
||||
protected int[] getNextIntArray(ByteBuffer byteBuffer) {
|
||||
int arrayLength = (short)(byteBuffer.get() & 255);
|
||||
int[] nextIntArray = new int[arrayLength];
|
||||
for (int i = 0; i < arrayLength; i++) {
|
||||
nextIntArray[i] = byteBuffer.getInt();
|
||||
}
|
||||
return nextIntArray;
|
||||
}
|
||||
|
||||
protected void addByteArrayWithLengthByte(ByteBuffer buffer, byte[] data) {
|
||||
buffer.put((byte) data.length);
|
||||
if (data.length != 0) {
|
||||
buffer.put(data);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
public class WithingsStructureType {
|
||||
|
||||
public static final short END_OF_TRANSMISSION = 256;
|
||||
public static final short PROBE_REPLY = 257;
|
||||
public static final short PROBE = 298;
|
||||
public static final short CHALLENGE = 290;
|
||||
public static final short CHALLENGE_RESPONSE = 291;
|
||||
public static final short PROBE_OS_VERSION = 2344;
|
||||
public static final short TIME = 1281;
|
||||
public static final short SCREEN_SETTINGS = 1302;
|
||||
public static final short WORKOUT_SCREEN_SETTINGS = 317;
|
||||
public static final short BATTERY_STATUS = 1284;
|
||||
public static final short USER = 1283;
|
||||
public static final short USER_SECRET = 1299;
|
||||
public static final short USER_UNIT = 281;
|
||||
public static final short ACTIVITY_TARGET = 1297;
|
||||
public static final short LOCALE = 289;
|
||||
public static final short LIVE_HR = 2369;
|
||||
public static final short HR = 2343;
|
||||
public static final short ACTIVITY_HR = 2345;
|
||||
public static final short ALARM = 1298;
|
||||
public static final short ALARM_STATUS = 2329;
|
||||
public static final short ALARM_NAME = 1300;
|
||||
public static final short STEPS = 2390;
|
||||
public static final short IMAGE_META_DATA = 2397;
|
||||
public static final short IMAGE_DATA = 2398;
|
||||
public static final short ANCS_STATUS = 2346;
|
||||
public static final short NOTIFICATION_APP_ID = 2404;
|
||||
public static final short GLYPH_ID = 2396;
|
||||
public static final short MOVE_HAND = 1292;
|
||||
|
||||
public static final short GET_ACTIVITY_SAMPLES = 1286;
|
||||
public static final short ACTIVITY_SAMPLE_TIME = 1537;
|
||||
public static final short ACTIVITY_SAMPLE_DURATION = 1538;
|
||||
public static final short ACTIVITY_SAMPLE_MOVEMENT = 1539;
|
||||
public static final short ACTIVITY_SAMPLE_WALK = 1540;
|
||||
public static final short ACTIVITY_SAMPLE_RUN = 1541;
|
||||
public static final short ACTIVITY_SAMPLE_SWIM = 1549;
|
||||
public static final short ACTIVITY_SAMPLE_SLEEP = 1543;
|
||||
// There are two structure types containing information about calories:
|
||||
public static final short ACTIVITY_SAMPLE_CALORIES = 1544;
|
||||
public static final short ACTIVITY_SAMPLE_CALORIES_2 = 1546;
|
||||
// No idea what this is, however it is in the response to requesting activities:
|
||||
public static final short ACTIVITY_SAMPLE_UNKNOWN = 1547;
|
||||
public static final short WORKOUT_TYPE = 2409;
|
||||
public static final short LIVE_WORKOUT_START = 2418;
|
||||
public static final short LIVE_WORKOUT_END = 2419;
|
||||
public static final short LIVE_WORKOUT_PAUSE_STATE = 2439;
|
||||
public static final short WORKOUT_GPS_STATE = 321;
|
||||
public static final short WORKOUT_SCREEN_LIST = 316;
|
||||
public static final short WORKOUT_SCREEN_DATA = 317;
|
||||
|
||||
private WithingsStructureType() {}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class WorkoutGpsState extends WithingsStructure {
|
||||
|
||||
private final boolean gpsEnabled;
|
||||
|
||||
public WorkoutGpsState(boolean gpsEnabled) {
|
||||
this.gpsEnabled = gpsEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 6;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
buffer.putShort(gpsEnabled? (short)1 : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.WORKOUT_GPS_STATE;
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class WorkoutScreen extends WithingsStructure {
|
||||
|
||||
public static final byte MODE_PACE = 2;
|
||||
public static final byte MODE_SPEED = 3;
|
||||
public static final byte MODE_ELSE = 1;
|
||||
|
||||
public int id;
|
||||
|
||||
public byte yetunknown1 = 0;
|
||||
|
||||
public String name;
|
||||
|
||||
public byte mode = MODE_PACE;
|
||||
|
||||
public short yetunknown2 = 0;
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setMode(byte mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.WORKOUT_SCREEN_SETTINGS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return (short) ((name != null ? name.getBytes().length : 0) + 9 + HEADER_SIZE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer rawDataBuffer) {
|
||||
rawDataBuffer.putInt(id);
|
||||
rawDataBuffer.put(yetunknown1);
|
||||
addStringAsBytesWithLengthByte(rawDataBuffer, name);
|
||||
rawDataBuffer.put(mode);
|
||||
rawDataBuffer.putShort(yetunknown2);
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class WorkoutScreenData extends WithingsStructure {
|
||||
|
||||
public long id;
|
||||
public short version;
|
||||
public String name;
|
||||
public short faceMode;
|
||||
public int flag;
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return (short) ((name != null ? name.getBytes().length : 0) + 13);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
|
||||
this.id = rawDataBuffer.getInt() & 4294967295L;
|
||||
this.version = (short) (rawDataBuffer.get() & 255);
|
||||
this.name = getNextString(rawDataBuffer);
|
||||
this.faceMode = (short) (rawDataBuffer.get() & 255);
|
||||
this.flag = rawDataBuffer.getShort() & 65535;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.WORKOUT_SCREEN_DATA;
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class WorkoutScreenList extends WithingsStructure {
|
||||
|
||||
private int[] workoutIds;
|
||||
|
||||
public int[] getWorkoutIds() {
|
||||
return workoutIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 37;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
|
||||
workoutIds = getNextIntArray(rawDataBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.WORKOUT_SCREEN_LIST;
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.datastructures;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class WorkoutType extends WithingsStructure {
|
||||
|
||||
public static short RUNNING = 0;
|
||||
|
||||
private short activityType;
|
||||
|
||||
public short getActivityType() {
|
||||
return activityType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getLength() {
|
||||
return 6;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillinTypeSpecificData(ByteBuffer buffer) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
|
||||
activityType = rawDataBuffer.getShort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return WithingsStructureType.WORKOUT_TYPE;
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.message;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructure;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public abstract class AbstractMessage implements Message {
|
||||
|
||||
/**
|
||||
* The header consist of the first byte 0x01 (probably the message format identifier),
|
||||
* two bytes for the message type and 2 bytes for the actual datalength.
|
||||
*/
|
||||
private static final int HEADER_SIZE = 5;
|
||||
protected final static short EOM_SIZE = 4;
|
||||
|
||||
private List<WithingsStructure> dataStructures = new ArrayList<WithingsStructure>();
|
||||
|
||||
public List<WithingsStructure> getDataStructures() {
|
||||
return Collections.unmodifiableList(dataStructures);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDataStructure(WithingsStructure data) {
|
||||
dataStructures.add(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getRawData() {
|
||||
short structureLength = 0;
|
||||
boolean setEndOfMessage = false;
|
||||
for (WithingsStructure structure : dataStructures) {
|
||||
if (structure.withEndOfMessage()) {
|
||||
setEndOfMessage = true;
|
||||
}
|
||||
structureLength += (short)(structure.getLength());
|
||||
}
|
||||
|
||||
if (setEndOfMessage) {
|
||||
structureLength += EOM_SIZE;
|
||||
}
|
||||
|
||||
ByteBuffer rawDataBuffer = ByteBuffer.allocate(HEADER_SIZE + structureLength);
|
||||
rawDataBuffer.put((byte)0x01); // <= This seems to be always 0x01 for all commands
|
||||
rawDataBuffer.putShort(getType());
|
||||
rawDataBuffer.putShort(structureLength);
|
||||
|
||||
for (WithingsStructure structure : dataStructures) {
|
||||
rawDataBuffer.put(structure.getRawData());
|
||||
}
|
||||
|
||||
if (setEndOfMessage) {
|
||||
addEndOfMessageBytes(rawDataBuffer);
|
||||
}
|
||||
|
||||
return rawDataBuffer.array();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends WithingsStructure> T getStructureByType(Class<T> type) {
|
||||
for (WithingsStructure structure : this.getDataStructures()) {
|
||||
if (type.isInstance(structure)) {
|
||||
return (T)structure;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void addEndOfMessageBytes(ByteBuffer buffer) {
|
||||
buffer.putShort((short)256);
|
||||
buffer.putShort((short)0);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return GB.hexdump(this.getRawData()).toLowerCase(Locale.ROOT);
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.message;
|
||||
|
||||
public enum ExpectedResponse {
|
||||
NONE,
|
||||
SIMPLE,
|
||||
EOT
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.message;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.IconHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.GlyphId;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ImageData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ImageMetaData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.incoming.IncomingMessageHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class GlyphRequestHandler implements IncomingMessageHandler {
|
||||
private static final Logger logger = LoggerFactory.getLogger(GlyphRequestHandler.class);
|
||||
private final WithingsSteelHRDeviceSupport support;
|
||||
|
||||
public GlyphRequestHandler(WithingsSteelHRDeviceSupport support) {
|
||||
this.support = support;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
try {
|
||||
GlyphId glyphId = message.getStructureByType(GlyphId.class);
|
||||
ImageMetaData imageMetaData = message.getStructureByType(ImageMetaData.class);
|
||||
Message reply = new WithingsMessage(WithingsMessageType.GET_UNICODE_GLYPH);
|
||||
reply.addDataStructure(glyphId);
|
||||
reply.addDataStructure(imageMetaData);
|
||||
ImageData imageData = new ImageData();
|
||||
imageData.setImageData(createUnicodeImage(glyphId.getUnicode(), imageMetaData));
|
||||
reply.addDataStructure(imageData);
|
||||
logger.info("Sending reply to glyph request: " + reply);
|
||||
support.sendToDevice(reply);
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to respond to glyph request.", e);
|
||||
GB.toast("Failed to respond to glyph request:" + e.getMessage(), Toast.LENGTH_LONG, GB.WARN);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] createUnicodeImage(long unicode, ImageMetaData metaData) {
|
||||
String str = new String(Character.toChars((int)unicode));
|
||||
Paint paint = new Paint();
|
||||
paint.setTypeface(null);
|
||||
Rect rect = new Rect();
|
||||
paint.setTextSize(calculateTextsize(paint, metaData.getHeight()));
|
||||
paint.setAntiAlias(true);
|
||||
paint.getTextBounds(str, 0, str.length(), rect);
|
||||
paint.setColor(-1);
|
||||
Paint.FontMetricsInt fontMetricsInt = paint.getFontMetricsInt();
|
||||
int width = rect.width();
|
||||
if (width <= 0) {
|
||||
return new byte[0];
|
||||
}
|
||||
Bitmap createBitmap = Bitmap.createBitmap(width, metaData.getHeight(), Bitmap.Config.ARGB_8888);
|
||||
new Canvas(createBitmap).drawText(str, -rect.left, -fontMetricsInt.top, paint);
|
||||
return IconHelper.toByteArray(createBitmap);
|
||||
}
|
||||
|
||||
private int calculateTextsize(Paint paint, int height) {
|
||||
Paint.FontMetricsInt fontMetricsInt;
|
||||
int textsize = 0;
|
||||
do {
|
||||
textsize++;
|
||||
paint.setTextSize(textsize);
|
||||
fontMetricsInt = paint.getFontMetricsInt();
|
||||
} while (fontMetricsInt.bottom - fontMetricsInt.top < height);
|
||||
return textsize - 1;
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.message;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructure;
|
||||
|
||||
/**
|
||||
* This interface is the common denominator for all messages passed to and from the Steel HR.
|
||||
*
|
||||
*/
|
||||
public interface Message {
|
||||
List<WithingsStructure> getDataStructures();
|
||||
void addDataStructure(WithingsStructure data);
|
||||
short getType();
|
||||
byte[] getRawData();
|
||||
boolean needsResponse();
|
||||
boolean needsEOT();
|
||||
boolean isIncomingMessage();
|
||||
<T extends WithingsStructure> T getStructureByType(Class<T> type);
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.message;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||
|
||||
public class MessageBuilder {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MessageBuilder.class);
|
||||
private WithingsSteelHRDeviceSupport support;
|
||||
private MessageFactory messageFactory;
|
||||
private ByteArrayOutputStream pendingMessage;
|
||||
private Message message;
|
||||
|
||||
public MessageBuilder(WithingsSteelHRDeviceSupport support, MessageFactory messageFactory) {
|
||||
this.support = support;
|
||||
this.messageFactory = messageFactory;
|
||||
}
|
||||
|
||||
public synchronized boolean buildMessage(byte[] rawData) {
|
||||
if (pendingMessage == null && rawData[0] == 0x01) {
|
||||
pendingMessage = new ByteArrayOutputStream();
|
||||
} else if (pendingMessage == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
pendingMessage.write(rawData);
|
||||
} catch(IOException e) {
|
||||
logger.error("Could not write data to stream: " + StringUtils.bytesToHex(rawData));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isMessageComplete(pendingMessage.toByteArray())) {
|
||||
return false;
|
||||
} else {
|
||||
Message message = messageFactory.createMessageFromRawData(pendingMessage.toByteArray());
|
||||
pendingMessage = null;
|
||||
if (message == null) {
|
||||
logger.info("Cannot handle null message");
|
||||
return false;
|
||||
}
|
||||
|
||||
this.message = message;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public Message getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
private boolean isMessageComplete(byte[] messageData) {
|
||||
if (messageData.length < 5) {
|
||||
return false;
|
||||
}
|
||||
|
||||
short totalDataLength = (short) BLETypeConversions.toInt16(messageData[4], messageData[3]);
|
||||
byte[] rawStructureData = Arrays.copyOfRange(messageData, 5, messageData.length);
|
||||
if (rawStructureData.length == totalDataLength) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.message;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.DataStructureFactory;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructure;
|
||||
|
||||
|
||||
public class MessageFactory {
|
||||
private static final Logger logger = LoggerFactory.getLogger(MessageFactory.class);
|
||||
private DataStructureFactory dataStructureFactory;
|
||||
|
||||
public MessageFactory(DataStructureFactory dataStructureFactory) {
|
||||
this.dataStructureFactory = new DataStructureFactory();
|
||||
}
|
||||
|
||||
public Message createMessageFromRawData(byte[] rawData) {
|
||||
if (rawData.length < 5 || rawData[0] != 0x01) {
|
||||
return null;
|
||||
}
|
||||
|
||||
short messageTypeFromResponse = (short) (BLETypeConversions.toInt16(rawData[2], rawData[1]) & 16383);
|
||||
short totalDataLength = (short) BLETypeConversions.toInt16(rawData[4], rawData[3]);
|
||||
boolean isIncoming = rawData[1] == 65 || rawData[1] == -127;
|
||||
Message message = new WithingsMessage(messageTypeFromResponse, isIncoming);
|
||||
byte[] rawStructureData = Arrays.copyOfRange(rawData, 5, rawData.length);
|
||||
List<WithingsStructure> structures = dataStructureFactory.createStructuresFromRawData(rawStructureData);
|
||||
for (WithingsStructure structure : structures) {
|
||||
message.addDataStructure(structure);
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.message;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructure;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class SimpleHexToByteMessage implements Message {
|
||||
private String hexString;
|
||||
|
||||
public SimpleHexToByteMessage(String hexString) {
|
||||
this.hexString = hexString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<WithingsStructure> getDataStructures() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDataStructure(WithingsStructure data) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getRawData() {
|
||||
return GB.hexStringToByteArray(hexString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsResponse() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsEOT() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIncomingMessage() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends WithingsStructure> T getStructureByType(Class<T> type) {
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.message;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructure;
|
||||
|
||||
public class WithingsMessage extends AbstractMessage {
|
||||
private short type;
|
||||
private ExpectedResponse expectedResponse = ExpectedResponse.SIMPLE;
|
||||
private boolean isIncoming;
|
||||
|
||||
public WithingsMessage(short type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public WithingsMessage(short type, boolean incoming) {
|
||||
this.type = type;
|
||||
this.isIncoming = incoming;
|
||||
}
|
||||
|
||||
public WithingsMessage(short type, ExpectedResponse expectedResponse) {
|
||||
this.type = type;
|
||||
this.expectedResponse = expectedResponse;
|
||||
}
|
||||
|
||||
public WithingsMessage(short type, WithingsStructure dataStructure) {
|
||||
this.type = type;
|
||||
this.addDataStructure(dataStructure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsResponse() {
|
||||
return expectedResponse == ExpectedResponse.SIMPLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsEOT() {
|
||||
return expectedResponse == ExpectedResponse.EOT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIncomingMessage() {
|
||||
return isIncoming;
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.message;
|
||||
|
||||
/**
|
||||
* Contains all identified commandtypes in the used TLV format of the messages exchanged
|
||||
* between device and app.
|
||||
*/
|
||||
public final class WithingsMessageType {
|
||||
|
||||
public static final short PROBE = 257;
|
||||
public static final short CHALLENGE = 296;
|
||||
public static final short SET_TIME = 1281;
|
||||
public static final short GET_BATTERY_STATUS = 1284;
|
||||
public static final short SET_SCREEN_LIST = 1292;
|
||||
public static final short INITIAL_CONNECT = 273;
|
||||
public static final short START_HANDS_CALIBRATION = 286;
|
||||
public static final short STOP_HANDS_CALIBRATION = 287;
|
||||
public static final short MOVE_HAND = 284;
|
||||
public static final short SET_ACTIVITY_TARGET = 1290;
|
||||
public static final short SET_USER = 1282;
|
||||
public static final short GET_USER = 1283;
|
||||
public static final short SET_USER_UNIT = 274;
|
||||
public static final short SET_LOCALE = 282;
|
||||
public static final short SETUP_FINISHED = 275;
|
||||
public static final short GET_HR = 2343;
|
||||
public static final short GET_WORKOUT_SCREEN_LIST = 315;
|
||||
public static final short SET_WORKOUT_SCREEN = 316;
|
||||
public static final short START_LIVE_WORKOUT = 317;
|
||||
public static final short STOP_LIVE_WORKOUT = 318;
|
||||
public static final short SYNC = 321;
|
||||
public static final short SYNC_RESPONSE = 16705;
|
||||
public static final short SYNC_OK = 277;
|
||||
public static final short GET_ALARM_SETTINGS = 298;
|
||||
public static final short SET_ALARM = 325;
|
||||
public static final short GET_ALARM = 293;
|
||||
public static final short GET_ALARM_ENABLED = 2330;
|
||||
public static final short SET_ALARM_ENABLED = 2331;
|
||||
public static final short GET_ANCS_STATUS = 2353;
|
||||
public static final short SET_ANCS_STATUS = 2345;
|
||||
public static final short GET_SCREEN_SETTINGS = 1293;
|
||||
// The next two do nearly the same, when I look at the responses, though only the first seems to deliver sleep samples
|
||||
public static final short GET_ACTIVITY_SAMPLES = 2424;
|
||||
public static final short GET_MOVEMENT_SAMPLES = 1286;
|
||||
|
||||
public static final short GET_SPORT_MODE = 2371;
|
||||
public static final short GET_WORKOUT_GPS_STATUS = 323;
|
||||
public static final short GET_HEARTRATE_SAMPLES = 2344;
|
||||
public static final short LIVE_WORKOUT_DATA = 320;
|
||||
public static final short GET_NOTIFICATION = 2404;
|
||||
public static final short GET_UNICODE_GLYPH = 2403;
|
||||
|
||||
private WithingsMessageType() {}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.message.incoming;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
|
||||
|
||||
public interface IncomingMessageHandler {
|
||||
public void handleMessage(Message message);
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.message.incoming;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.GlyphRequestHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessageType;
|
||||
|
||||
public class IncomingMessageHandlerFactory {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(IncomingMessageHandlerFactory.class);
|
||||
private static IncomingMessageHandlerFactory instance;
|
||||
private final WithingsSteelHRDeviceSupport support;
|
||||
private Map<Short, IncomingMessageHandler> handlers = new HashMap<>();
|
||||
|
||||
private IncomingMessageHandlerFactory(WithingsSteelHRDeviceSupport support) {
|
||||
this.support = support;
|
||||
}
|
||||
|
||||
public static IncomingMessageHandlerFactory getInstance(WithingsSteelHRDeviceSupport support) {
|
||||
if (instance == null) {
|
||||
instance = new IncomingMessageHandlerFactory(support);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
public IncomingMessageHandler getHandler(Message message) {
|
||||
IncomingMessageHandler handler = handlers.get(message.getType());
|
||||
switch (message.getType()) {
|
||||
case WithingsMessageType.START_LIVE_WORKOUT:
|
||||
case WithingsMessageType.STOP_LIVE_WORKOUT:
|
||||
case WithingsMessageType.GET_WORKOUT_GPS_STATUS:
|
||||
if (handler == null) {
|
||||
handlers.put(message.getType(), new LiveWorkoutHandler(support));
|
||||
}
|
||||
break;
|
||||
case WithingsMessageType.LIVE_WORKOUT_DATA:
|
||||
if (handler == null) {
|
||||
handlers.put(message.getType(), new LiveHeartrateHandler(support));
|
||||
}
|
||||
break;
|
||||
case WithingsMessageType.GET_NOTIFICATION:
|
||||
if (handler == null) {
|
||||
handlers.put(message.getType(), new NotificationRequestHandler(support));
|
||||
}
|
||||
break;
|
||||
case WithingsMessageType.GET_UNICODE_GLYPH:
|
||||
if (handler == null) {
|
||||
handlers.put(message.getType(), new GlyphRequestHandler(support));
|
||||
}
|
||||
break;
|
||||
case WithingsMessageType.SYNC:
|
||||
if (handler == null) {
|
||||
handlers.put(message.getType(), new SyncRequestHandler(support));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logger.warn("Unhandled incoming message type: " + message.getType());
|
||||
}
|
||||
|
||||
return handlers.get(message.getType());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.message.incoming;
|
||||
|
||||
import android.content.Intent;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.GregorianCalendar;
|
||||
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.withingssteelhr.WithingsSteelHRSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.WithingsSteelHRActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.activity.SleepActivitySampleHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.LiveHeartRate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructure;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
|
||||
|
||||
public class LiveHeartrateHandler implements IncomingMessageHandler {
|
||||
private static final Logger logger = LoggerFactory.getLogger(LiveHeartrateHandler.class);
|
||||
private final WithingsSteelHRDeviceSupport support;
|
||||
|
||||
public LiveHeartrateHandler(WithingsSteelHRDeviceSupport support) {
|
||||
this.support = support;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
List<WithingsStructure> data = message.getDataStructures();
|
||||
if (data == null || data.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
WithingsStructure structure = data.get(0);
|
||||
int heartRate = 0;
|
||||
if (structure instanceof LiveHeartRate) {
|
||||
heartRate = ((LiveHeartRate)structure).getHeartrate();
|
||||
}
|
||||
|
||||
if (heartRate > 0) {
|
||||
saveHeartRateData(heartRate);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void saveHeartRateData(int heartRate) {
|
||||
WithingsSteelHRActivitySample sample = new WithingsSteelHRActivitySample();
|
||||
sample.setTimestamp((int) (GregorianCalendar.getInstance().getTimeInMillis() / 1000L));
|
||||
sample.setHeartRate(heartRate);
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
|
||||
Long deviceId = DBHelper.getDevice(support.getDevice(), dbHandler.getDaoSession()).getId();
|
||||
WithingsSteelHRSampleProvider provider = new WithingsSteelHRSampleProvider(support.getDevice(), dbHandler.getDaoSession());
|
||||
sample.setDeviceId(deviceId);
|
||||
sample.setUserId(userId);
|
||||
sample = SleepActivitySampleHelper.mergeIfNecessary(provider, sample);
|
||||
provider.addGBActivitySample(sample);
|
||||
} catch (Exception ex) {
|
||||
logger.warn("Error saving current heart rate: " + ex.getLocalizedMessage());
|
||||
}
|
||||
Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
|
||||
.putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample);
|
||||
LocalBroadcastManager.getInstance(support.getContext()).sendBroadcast(intent);
|
||||
}
|
||||
}
|
@ -0,0 +1,147 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.message.incoming;
|
||||
|
||||
import android.location.LocationManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
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.entities.BaseActivitySummary;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksController;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.activity.WithingsActivityType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.LiveWorkoutEnd;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.LiveWorkoutPauseState;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.LiveWorkoutStart;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructure;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructureType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WorkoutGpsState;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WorkoutType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessageType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class LiveWorkoutHandler implements IncomingMessageHandler {
|
||||
private static final Logger logger = LoggerFactory.getLogger(LiveWorkoutHandler.class);
|
||||
private final WithingsSteelHRDeviceSupport support;
|
||||
private BaseActivitySummary baseActivitySummary;
|
||||
|
||||
public LiveWorkoutHandler(WithingsSteelHRDeviceSupport support) {
|
||||
this.support = support;
|
||||
}
|
||||
|
||||
public void handleMessage(Message message) {
|
||||
List<WithingsStructure> data = message.getDataStructures();
|
||||
if (data != null) {
|
||||
handleLiveData(data);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleLiveData(List<WithingsStructure> dataList) {
|
||||
for (WithingsStructure data : dataList) {
|
||||
switch (data.getType()) {
|
||||
case WithingsStructureType.LIVE_WORKOUT_START:
|
||||
handleStart((LiveWorkoutStart) data);
|
||||
break;
|
||||
case WithingsStructureType.LIVE_WORKOUT_END:
|
||||
handleEnd((LiveWorkoutEnd) data);
|
||||
break;
|
||||
case WithingsStructureType.LIVE_WORKOUT_PAUSE_STATE:
|
||||
handlePause((LiveWorkoutPauseState) data);
|
||||
break;
|
||||
case WithingsStructureType.WORKOUT_TYPE:
|
||||
handleType((WorkoutType) data);
|
||||
break;
|
||||
default:
|
||||
logger.info("Received yet unhandled live workout data of type '" + data.getType() + "' with data '" + GB.hexdump(data.getRawData()) + "'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleStart(LiveWorkoutStart workoutStart) {
|
||||
sendGpsState();
|
||||
if (baseActivitySummary == null) {
|
||||
baseActivitySummary = new BaseActivitySummary();
|
||||
}
|
||||
|
||||
baseActivitySummary.setStartTime(workoutStart.getStarttime());
|
||||
}
|
||||
|
||||
private void handlePause(LiveWorkoutPauseState workoutPause) {
|
||||
// Not sure what to do with these events at the moment so we just log them.
|
||||
if (workoutPause.getStarttime() == null) {
|
||||
if (workoutPause.getLengthInSeconds() > 0) {
|
||||
logger.info("Got workout pause end with duration: " + workoutPause.getLengthInSeconds());
|
||||
} else {
|
||||
logger.info("Currently no pause happened");
|
||||
}
|
||||
} else {
|
||||
logger.info("Got workout pause started at: " + workoutPause.getStarttime());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleEnd(LiveWorkoutEnd workoutEnd) {
|
||||
OpenTracksController.stopRecording(support.getContext());
|
||||
baseActivitySummary.setEndTime(workoutEnd.getEndtime());
|
||||
saveBaseActivitySummary();
|
||||
baseActivitySummary = null;
|
||||
}
|
||||
|
||||
private void handleType(WorkoutType workoutType) {
|
||||
WithingsActivityType withingsWorkoutType = WithingsActivityType.fromCode(workoutType.getActivityType());
|
||||
OpenTracksController.startRecording(support.getContext(), withingsWorkoutType.toActivityKind());
|
||||
if (baseActivitySummary == null) {
|
||||
baseActivitySummary = new BaseActivitySummary();
|
||||
}
|
||||
|
||||
baseActivitySummary.setActivityKind(withingsWorkoutType.toActivityKind());
|
||||
}
|
||||
|
||||
private void sendGpsState() {
|
||||
Message message = new WithingsMessage((short)(WithingsMessageType.START_LIVE_WORKOUT | 16384), new WorkoutGpsState(isGpsEnabled()));
|
||||
support.sendToDevice(message);
|
||||
}
|
||||
|
||||
private boolean isGpsEnabled() {
|
||||
final LocationManager manager = (LocationManager) support.getContext().getSystemService(support.getContext().LOCATION_SERVICE );
|
||||
return manager.isProviderEnabled(LocationManager.GPS_PROVIDER );
|
||||
}
|
||||
|
||||
private void saveBaseActivitySummary() {
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
DaoSession session = dbHandler.getDaoSession();
|
||||
Device device = DBHelper.getDevice(support.getDevice(), session);
|
||||
User user = DBHelper.getUser(session);
|
||||
baseActivitySummary.setDevice(device);
|
||||
baseActivitySummary.setUser(user);
|
||||
session.getBaseActivitySummaryDao().insertOrReplace(baseActivitySummary);
|
||||
} catch (Exception ex) {
|
||||
GB.toast(support.getContext(), "Error saving activity summary", Toast.LENGTH_LONG, GB.ERROR, ex);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.message.incoming;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.IconHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ImageData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ImageMetaData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.SourceAppId;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessageType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.notification.NotificationProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class NotificationRequestHandler implements IncomingMessageHandler {
|
||||
private static final Logger logger = LoggerFactory.getLogger(NotificationRequestHandler.class);
|
||||
|
||||
private final WithingsSteelHRDeviceSupport support;
|
||||
private Map<String, byte[]> appIconCache = new HashMap<>();
|
||||
|
||||
public NotificationRequestHandler(WithingsSteelHRDeviceSupport support) {
|
||||
this.support = support;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
try {
|
||||
SourceAppId appId = message.getStructureByType(SourceAppId.class);
|
||||
ImageMetaData imageMetaData = message.getStructureByType(ImageMetaData.class);
|
||||
Message reply = new WithingsMessage(WithingsMessageType.GET_NOTIFICATION);
|
||||
reply.addDataStructure(appId);
|
||||
reply.addDataStructure(imageMetaData);
|
||||
ImageData imageData = new ImageData();
|
||||
imageData.setImageData(getImageData(appId.getAppId()));
|
||||
reply.addDataStructure(imageData);
|
||||
logger.info("Sending reply to notification request: " + reply);
|
||||
support.sendToDevice(reply);
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to respond to notification request.", e);
|
||||
GB.toast("Failed to respond to notification request:" + e.getMessage(), Toast.LENGTH_LONG, GB.WARN);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getImageData(String sourceAppId) {
|
||||
byte[] imageData = appIconCache.get(sourceAppId);
|
||||
if (imageData == null) {
|
||||
NotificationSpec notificationSpec = NotificationProvider.getInstance(support).getNotificationSpecForSourceAppId(sourceAppId);
|
||||
if (notificationSpec != null) {
|
||||
int iconId = notificationSpec.iconId;
|
||||
try {
|
||||
Drawable icon = null;
|
||||
if (notificationSpec.iconId != 0) {
|
||||
Context sourcePackageContext = support.getContext().createPackageContext(sourceAppId, 0);
|
||||
icon = sourcePackageContext.getResources().getDrawable(notificationSpec.iconId);
|
||||
}
|
||||
if (icon == null) {
|
||||
PackageManager pm = support.getContext().getPackageManager();
|
||||
icon = pm.getApplicationIcon(sourceAppId);
|
||||
}
|
||||
|
||||
imageData = IconHelper.getIconBytesFromDrawable(icon);
|
||||
appIconCache.put(sourceAppId, imageData);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
logger.error("Error while updating notification icons", e);
|
||||
imageData = new byte[0];
|
||||
}
|
||||
} else {
|
||||
imageData = new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
return imageData;
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.message.incoming;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.ExpectedResponse;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessageType;
|
||||
|
||||
public class SyncRequestHandler implements IncomingMessageHandler {
|
||||
|
||||
private final WithingsSteelHRDeviceSupport support;
|
||||
|
||||
public SyncRequestHandler(WithingsSteelHRDeviceSupport support) {
|
||||
this.support = support;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
support.sendToDevice(new WithingsMessage(WithingsMessageType.SYNC_RESPONSE));
|
||||
support.doSync();
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.notification;
|
||||
|
||||
public final class AncsConstants {
|
||||
|
||||
public static final byte EVENT_ID_NOTIFICATION_ADDED = 0;
|
||||
public static final byte EVENT_ID_NOTIFICATION_MODIFIED = 1;
|
||||
public static final byte EVENT_ID_NOTIFICATION_REMOVED = 2;
|
||||
|
||||
public static final byte EVENT_FLAGS_SILENT = (1 << 0);
|
||||
public static final byte EVENT_FLAGS_IMPORTANT = (1 << 1);
|
||||
public static final byte EVENT_FLAGS_PREEXISTING = (1 << 2);
|
||||
public static final byte EVENT_FLAGS_POSITIVE_ACTION = (1 << 3);
|
||||
public static final byte EVENT_FLAGS_NEGATIVE_ACTION = (1 << 4);
|
||||
|
||||
public static final byte CATEGORY_ID_OTHER = 0;
|
||||
public static final byte CATEGORY_ID_INCOMING_CALL = 1;
|
||||
public static final byte CATEGORY_ID_MISSED_CALL = 2;
|
||||
public static final byte CATEGORY_ID_VOICEMAIL = 3;
|
||||
public static final byte CATEGORY_ID_SOCIAL = 4;
|
||||
public static final byte CATEGORY_ID_SCHEDULE = 5;
|
||||
public static final byte CATEGORY_ID_EMAIL = 6;
|
||||
public static final byte CATEGORY_ID_NEWS = 7;
|
||||
public static final byte CATEGORY_ID_HEALTHANDFITNESS = 8;
|
||||
public static final byte CATEGORY_ID_BUSINESSANDFINANCE = 9;
|
||||
public static final byte CATEGORY_ID_LOCATION = 10;
|
||||
public static final byte CATEGORY_ID_ENTERTAINMENT = 11;
|
||||
|
||||
private AncsConstants(){}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.notification;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class GetNotificationAttributes {
|
||||
private byte commandID;
|
||||
private int notificationUID;
|
||||
private List<RequestedNotificationAttribute> attributes = new ArrayList<>();
|
||||
|
||||
public byte getCommandID() {
|
||||
return commandID;
|
||||
}
|
||||
|
||||
public void setCommandID(byte commandID) {
|
||||
this.commandID = commandID;
|
||||
}
|
||||
|
||||
public int getNotificationUID() {
|
||||
return notificationUID;
|
||||
}
|
||||
|
||||
public void setNotificationUID(int notificationUID) {
|
||||
this.notificationUID = notificationUID;
|
||||
}
|
||||
|
||||
public List<RequestedNotificationAttribute> getAttributes() {
|
||||
return Collections.unmodifiableList(attributes);
|
||||
}
|
||||
|
||||
public void addAttribute(RequestedNotificationAttribute attribute) {
|
||||
attributes.add(attribute);
|
||||
}
|
||||
|
||||
public void deserialize(byte[] rawData) {
|
||||
ByteBuffer buffer = ByteBuffer.wrap(rawData);
|
||||
commandID = buffer.get();
|
||||
notificationUID = buffer.getInt();
|
||||
while (buffer.hasRemaining()) {
|
||||
RequestedNotificationAttribute requestedNotificationAttribute = new RequestedNotificationAttribute();
|
||||
int length = 1;
|
||||
if (buffer.remaining() >= 3) {
|
||||
length = 3;
|
||||
}
|
||||
|
||||
byte[] rawAttributeData = new byte[length];
|
||||
buffer.get(rawAttributeData);
|
||||
requestedNotificationAttribute.deserialize(rawAttributeData);
|
||||
attributes.add(requestedNotificationAttribute);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] serialize() {
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.notification;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GetNotificationAttributesResponse {
|
||||
private byte commandID = 0;
|
||||
private int notificationUID;
|
||||
private List<NotificationAttribute> attributes = new ArrayList<>();
|
||||
|
||||
public GetNotificationAttributesResponse(int notificationUID) {
|
||||
this.notificationUID = notificationUID;
|
||||
}
|
||||
|
||||
public void addAttribute(NotificationAttribute attribute) {
|
||||
attributes.add(attribute);
|
||||
}
|
||||
|
||||
public byte[] serialize() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(getLength());
|
||||
buffer.put(commandID);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer.putInt(notificationUID);
|
||||
buffer.order(ByteOrder.BIG_ENDIAN);
|
||||
for (NotificationAttribute attribute : attributes) {
|
||||
buffer.put(attribute.serialize());
|
||||
}
|
||||
return buffer.array();
|
||||
}
|
||||
|
||||
private int getLength() {
|
||||
int length = 5;
|
||||
for (NotificationAttribute attribute : attributes) {
|
||||
length += attribute.getAttributeLength() + 3;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
/* Copyright (C) 2021 Frank Ertl
|
||||
|
||||
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.withingssteelhr.communication.notification;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||
|
||||
public class NotificationAttribute {
|
||||
private byte attributeID;
|
||||
private short attributeLength;
|
||||
private short attributeMaxLength;
|
||||
private String value;
|
||||
|
||||
public void setAttributeMaxLength(short attributeMaxLength) {
|
||||
this.attributeMaxLength = attributeMaxLength;
|
||||
}
|
||||
|
||||
public byte getAttributeID() {
|
||||
return attributeID;
|
||||
}
|
||||
|
||||
public void setAttributeID(byte attributeID) {
|
||||
this.attributeID = attributeID;
|
||||
}
|
||||
|
||||
public short getAttributeLength() {
|
||||
short length = (short)(value != null? value.getBytes(StandardCharsets.UTF_8).length : 0);
|
||||
if (attributeMaxLength > 0 && length > attributeMaxLength) {
|
||||
length = attributeMaxLength;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public byte[] serialize() {
|
||||
attributeLength = getAttributeLength();
|
||||
int length = attributeLength + 3;
|
||||
ByteBuffer buffer = ByteBuffer.allocate(length);
|
||||
buffer.put(attributeID);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer.putShort(attributeLength);
|
||||
if (value != null) {
|
||||
buffer.order(ByteOrder.BIG_ENDIAN);
|
||||
buffer.put(value.getBytes(StandardCharsets.UTF_8), 0, attributeLength);
|
||||
}
|
||||
|
||||
return buffer.array();
|
||||
}
|
||||
|
||||
public void deserialize(byte[] rawData) {
|
||||
ByteBuffer buffer = ByteBuffer.wrap(rawData);
|
||||
attributeID = buffer.get();
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
attributeLength = buffer.getShort();
|
||||
buffer.order(ByteOrder.BIG_ENDIAN);
|
||||
if (attributeLength > 0) {
|
||||
byte[] rawValue = new byte[attributeLength];
|
||||
buffer.get(rawValue);
|
||||
value = new String(rawValue, StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user