1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2025-02-17 21:06:48 +01:00

Merge branch 'y5_patch_test' of DaPa/Gadgetbridge into master

Closes #1287
This commit is contained in:
Andreas Shimokawa 2020-01-04 20:51:46 +01:00 committed by Gitea
commit 22fd3f2a8c
14 changed files with 781 additions and 108 deletions

View File

@ -70,7 +70,7 @@ public class GBDaoGenerator {
addXWatchActivitySample(schema, user, device);
addZeTimeActivitySample(schema, user, device);
addID115ActivitySample(schema, user, device);
addJYouActivitySample(schema, user, device);
addCalendarSyncState(schema, device);
addAlarms(schema, user, device);
@ -328,6 +328,19 @@ public class GBDaoGenerator {
return activitySample;
}
private static Entity addJYouActivitySample(Schema schema, Entity user, Entity device) {
Entity activitySample = addEntity(schema, "JYouActivitySample");
activitySample.implementsSerializable();
addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
activitySample.addIntProperty("caloriesBurnt");
activitySample.addIntProperty("distanceMeters");
activitySample.addIntProperty("activeTimeMinutes");
addHeartRateProperties(activitySample);
return activitySample;
}
private static void addCommonActivitySampleProperties(String superClass, Entity activitySample, Entity user, Entity device) {
activitySample.setSuperclass(superClass);
activitySample.addImport(MAIN_PACKAGE + ".devices.SampleProvider");

View File

@ -36,12 +36,17 @@ public final class JYouConstants {
public static final byte CMD_SET_SLEEP_TIME = 0x27;
public static final byte CMD_SET_DND_SETTINGS = 0x39;
public static final byte CMD_SET_INACTIVITY_WARNING_TIME = 0x24;
public static final byte CMD_ACTION_HEARTRATE_SWITCH = 0x0D;
public static final byte CMD_ACTION_SHOW_NOTIFICATION = 0x2C;
public static final byte CMD_ACTION_REBOOT_DEVICE = 0x0E;
public static final byte RECEIVE_BATTERY_LEVEL = (byte)0xF7;
public static final byte RECEIVE_HISTORY_SLEEP_COUNT = 0x32;
public static final byte RECEIVE_BLOOD_PRESSURE = (byte) 0xE8;
public static final byte RECEIVE_WATCH_MAC = (byte)0xEC;
public static final byte RECEIVE_GET_PHOTO = (byte)0xF3;
public static final byte RECEIVE_DEVICE_INFO = (byte)0xF6;
public static final byte RECEIVE_BATTERY_LEVEL = (byte)0xF7;
public static final byte RECEIVE_STEPS_DATA = (byte)0xF9;
public static final byte RECEIVE_HEARTRATE = (byte)0xFC;
@ -54,4 +59,4 @@ public final class JYouConstants {
public static final byte ICON_TWITTER = 6;
public static final byte ICON_WHATSAPP = 7;
public static final byte ICON_LINE = 8;
}
}

View File

@ -0,0 +1,70 @@
package nodomain.freeyourgadget.gadgetbridge.devices.jyou;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.Property;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.JYouActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.JYouActivitySampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class JYouSampleProvider extends AbstractSampleProvider<JYouActivitySample> {
public static final int TYPE_ACTIVITY = -1;
private final float movementDivisor = 6000.0f;
private GBDevice mDevice;
private DaoSession mSession;
public JYouSampleProvider(GBDevice device, DaoSession session) {
super(device, session);
mSession = session;
mDevice = device;
}
@Override
public int normalizeType(int rawType) {
return rawType;
}
@Override
public int toRawActivityKind(int activityKind) {
return activityKind;
}
@Override
public float normalizeIntensity(int rawIntensity) {
return rawIntensity/movementDivisor;
}
@Override
public JYouActivitySample createActivitySample() {
return new JYouActivitySample();
}
@Override
public AbstractDao<JYouActivitySample, ?> getSampleDao() {
return getSession().getJYouActivitySampleDao();
}
@Nullable
@Override
protected Property getRawKindSampleProperty() {
return JYouActivitySampleDao.Properties.RawKind;
}
@NonNull
@Override
protected Property getTimestampSampleProperty() {
return JYouActivitySampleDao.Properties.Timestamp;
}
@NonNull
@Override
protected Property getDeviceIdentifierSampleProperty() {
return JYouActivitySampleDao.Properties.DeviceId;
}
}

View File

@ -15,7 +15,7 @@
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.jyou;
package nodomain.freeyourgadget.gadgetbridge.devices.jyou.TeclastH30;
import android.annotation.TargetApi;
import android.app.Activity;
@ -38,6 +38,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.jyou.JYouConstants;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;

View File

@ -0,0 +1,132 @@
package nodomain.freeyourgadget.gadgetbridge.devices.jyou.y5;
import android.app.Activity;
import android.content.Context;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.jyou.JYouSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.JYouActivitySampleDao;
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 Y5Coordinator extends AbstractDeviceCoordinator {
@Override
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
Long deviceId = device.getId();
QueryBuilder<?> qb = session.getJYouActivitySampleDao().queryBuilder();
qb.where(JYouActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
}
@NonNull
@Override
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
try {
String name = candidate.getDevice().getName();
if (name != null) {
if (name.contains("Y5")) {
return DeviceType.Y5;
}
}
} catch (Exception ex) {
ex.getLocalizedMessage();
}
return DeviceType.UNKNOWN;
}
@Override
public DeviceType getDeviceType() {
return DeviceType.Y5;
}
@Nullable
@Override
public Class<? extends Activity> getPairingActivity() {
return null;
}
@Override
public boolean supportsActivityDataFetching() {
return true;
}
@Override
public boolean supportsActivityTracking() {
return true;
}
@Override
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
return new JYouSampleProvider(device, session);
}
@Override
public InstallHandler findInstallHandler(Uri uri, Context context) {
return null;
}
@Override
public boolean supportsScreenshots() {
return false;
}
@Override
public int getAlarmSlotCount() {
return 3;
}
@Override
public boolean supportsSmartWakeup(GBDevice device) {
return true;
}
@Override
public boolean supportsHeartRateMeasurement(GBDevice device) {
return true;
}
@Override
public String getManufacturer() {
return "Y5";
}
@Override
public boolean supportsAppsManagement() {
return false;
}
@Override
public Class<? extends Activity> getAppsManagementActivity() {
return null;
}
@Override
public boolean supportsCalendarEvents() {
return false;
}
@Override
public boolean supportsRealtimeData() {
return true;
}
@Override
public boolean supportsWeather() {
return false;
}
@Override
public boolean supportsFindDevice() {
return true;
}
}

View File

@ -58,6 +58,7 @@ public enum DeviceType {
WATCH9(100, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_watch9),
ROIDMI(110, R.drawable.ic_device_roidmi, R.drawable.ic_device_roidmi_disabled, R.string.devicetype_roidmi),
ROIDMI3(112, R.drawable.ic_device_roidmi, R.drawable.ic_device_roidmi_disabled, R.string.devicetype_roidmi3),
Y5(113, R.drawable.ic_device_h30_h10, R.drawable.ic_device_roidmi_disabled, R.string.devicetype_y5),
CASIOGB6900(120, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_casiogb6900),
MISCALE2(131, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_miscale2),
BFH16(140, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_bfh16),

View File

@ -45,7 +45,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband3.MiBand
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband4.MiBand4Support;
import nodomain.freeyourgadget.gadgetbridge.service.devices.id115.ID115Support;
import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.BFH16DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.TeclastH30Support;
import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.TeclastH30.TeclastH30Support;
import nodomain.freeyourgadget.gadgetbridge.service.devices.liveview.LiveviewSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.makibeshr3.MakibesHR3DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport;
@ -57,6 +57,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSuppo
import nodomain.freeyourgadget.gadgetbridge.service.devices.roidmi.RoidmiSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.vibratissimo.VibratissimoSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.watch9.Watch9DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.y5.Y5Support;
import nodomain.freeyourgadget.gadgetbridge.service.devices.xwatch.XWatchSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.zetime.ZeTimeDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
@ -203,6 +204,9 @@ public class DeviceSupportFactory {
case ROIDMI3:
deviceSupport = new ServiceDeviceSupport(new RoidmiSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
break;
case Y5:
deviceSupport = new ServiceDeviceSupport(new Y5Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
break;
case CASIOGB6900:
deviceSupport = new ServiceDeviceSupport(new CasioGB6900DeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
break;

View File

@ -0,0 +1,65 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.jyou;
/*
* @author Pavel Elagin &lt;elagin.pasha@gmail.com&gt;
*/
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
public class JYouDataRecord {
public final static int TYPE_UNKNOWN = 0;
public final static int TYPE_SLEEP = 100;
public final static int TYPE_DAY_SUMMARY = 101;
public final static int TYPE_DAY_SLOT = 102;
public final static int TYPE_REALTIME = 103;
public int type = TYPE_UNKNOWN;
public int activityKind = ActivityKind.TYPE_UNKNOWN;
/**
* Time of this record in seconds
*/
public int timestamp;
/**
* Raw data as sent from the device
*/
public byte[] rawData;
protected JYouDataRecord(){
}
protected JYouDataRecord(byte[] data, int type){
this.rawData = data;
this.type = type;
}
public byte[] getRawData() {
return rawData;
}
public class RecordInterval {
/**
* Start time of this interval in seconds
*/
public int timestampFrom;
/**
* End time of this interval in seconds
*/
public int timestampTo;
/**
* Type of activity {@link ActivityKind}
*/
public int activityKind;
RecordInterval(int timestampFrom, int timestampTo, int activityKind) {
this.timestampFrom = timestampFrom;
this.timestampTo = timestampTo;
this.activityKind = activityKind;
}
}
}

View File

@ -23,7 +23,6 @@ import android.net.Uri;
import android.widget.Toast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
@ -50,29 +49,32 @@ import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
public class TeclastH30Support extends AbstractBTLEDeviceSupport {
public class JYouSupport extends AbstractBTLEDeviceSupport {
private static final Logger LOG = LoggerFactory.getLogger(TeclastH30Support.class);
private Logger logger;
public BluetoothGattCharacteristic ctrlCharacteristic = null;
public BluetoothGattCharacteristic measureCharacteristic = null;
protected BluetoothGattCharacteristic ctrlCharacteristic = null;
private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo();
protected final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
protected final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo();
public TeclastH30Support() {
super(LOG);
public JYouSupport(Logger logger) {
super(logger);
this.logger = logger;
if (logger == null) {
throw new IllegalArgumentException("logger must not be null");
}
addSupportedService(JYouConstants.UUID_SERVICE_JYOU);
}
@Override
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
LOG.info("Initializing");
logger.info("Initializing");
gbDevice.setState(GBDevice.State.INITIALIZING);
gbDevice.sendDeviceUpdateIntent(getContext());
measureCharacteristic = getCharacteristic(JYouConstants.UUID_CHARACTERISTIC_MEASURE);
BluetoothGattCharacteristic measureCharacteristic = getCharacteristic(JYouConstants.UUID_CHARACTERISTIC_MEASURE);
ctrlCharacteristic = getCharacteristic(JYouConstants.UUID_CHARACTERISTIC_CONTROL);
builder.setGattCallback(this);
@ -83,7 +85,7 @@ public class TeclastH30Support extends AbstractBTLEDeviceSupport {
gbDevice.setState(GBDevice.State.INITIALIZED);
gbDevice.sendDeviceUpdateIntent(getContext());
LOG.info("Initialization Done");
logger.info("Initialization Done");
return builder;
}
@ -91,41 +93,10 @@ public class TeclastH30Support extends AbstractBTLEDeviceSupport {
@Override
public boolean onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
if (super.onCharacteristicChanged(gatt, characteristic)) {
return true;
}
UUID characteristicUUID = characteristic.getUuid();
byte[] data = characteristic.getValue();
if (data.length == 0)
return true;
switch (data[0]) {
case JYouConstants.RECEIVE_DEVICE_INFO:
int fwVerNum = data[4] & 0xFF;
versionCmd.fwVersion = (fwVerNum / 100) + "." + ((fwVerNum % 100) / 10) + "." + ((fwVerNum % 100) % 10);
handleGBDeviceEvent(versionCmd);
LOG.info("Firmware version is: " + versionCmd.fwVersion);
return true;
case JYouConstants.RECEIVE_BATTERY_LEVEL:
batteryCmd.level = data[8];
handleGBDeviceEvent(batteryCmd);
LOG.info("Battery level is: " + batteryCmd.level);
return true;
case JYouConstants.RECEIVE_STEPS_DATA:
int steps = ByteBuffer.wrap(data, 5, 4).getInt();
LOG.info("Number of walked steps: " + steps);
return true;
case JYouConstants.RECEIVE_HEARTRATE:
LOG.info("Current heart rate: " + data[8]);
return true;
default:
LOG.info("Unhandled characteristic change: " + characteristicUUID + " code: " + String.format("0x%1x ...", data[0]));
return true;
}
return super.onCharacteristicChanged(gatt, characteristic);
}
private void syncDateAndTime(TransactionBuilder builder) {
protected void syncDateAndTime(TransactionBuilder builder) {
Calendar cal = Calendar.getInstance();
String strYear = String.valueOf(cal.get(Calendar.YEAR));
byte year1 = (byte)Integer.parseInt(strYear.substring(0, 2));
@ -144,45 +115,7 @@ public class TeclastH30Support extends AbstractBTLEDeviceSupport {
));
}
private void syncSettings(TransactionBuilder builder) {
syncDateAndTime(builder);
// TODO: unhardcode and separate stuff
builder.write(ctrlCharacteristic, commandWithChecksum(
JYouConstants.CMD_SET_HEARTRATE_WARNING_VALUE, 0, 152
));
builder.write(ctrlCharacteristic, commandWithChecksum(
JYouConstants.CMD_SET_TARGET_STEPS, 0, 10000
));
builder.write(ctrlCharacteristic, commandWithChecksum(
JYouConstants.CMD_GET_STEP_COUNT, 0, 0
));
builder.write(ctrlCharacteristic, commandWithChecksum(
JYouConstants.CMD_GET_SLEEP_TIME, 0, 0
));
builder.write(ctrlCharacteristic, commandWithChecksum(
JYouConstants.CMD_SET_NOON_TIME, 12 * 60 * 60, 14 * 60 * 60 // 12:00 - 14:00
));
builder.write(ctrlCharacteristic, commandWithChecksum(
JYouConstants.CMD_SET_SLEEP_TIME, 21 * 60 * 60, 8 * 60 * 60 // 21:00 - 08:00
));
builder.write(ctrlCharacteristic, commandWithChecksum(
JYouConstants.CMD_SET_INACTIVITY_WARNING_TIME, 0, 0
));
// do not disturb and a couple more features
byte dndStartHour = 22;
byte dndStartMin = 0;
byte dndEndHour = 8;
byte dndEndMin = 0;
boolean dndToggle = false;
boolean vibrationToggle = true;
boolean wakeOnRaiseToggle = true;
builder.write(ctrlCharacteristic, commandWithChecksum(
JYouConstants.CMD_SET_DND_SETTINGS,
(dndStartHour << 24) | (dndStartMin << 16) | (dndEndHour << 8) | dndEndMin,
((dndToggle ? 0 : 1) << 2) | ((vibrationToggle ? 1 : 0) << 1) | (wakeOnRaiseToggle ? 1 : 0)
));
protected void syncSettings(TransactionBuilder builder) {
}
private void showNotification(byte icon, String title, String message) {
@ -217,9 +150,9 @@ public class TeclastH30Support extends AbstractBTLEDeviceSupport {
}
builder.write(ctrlCharacteristic, currentPacket);
}
builder.queue(getQueue());
performConnected(builder.getTransaction());
} catch (IOException e) {
LOG.warn(e.getMessage());
logger.warn(e.getMessage());
}
}
@ -286,10 +219,10 @@ public class TeclastH30Support extends AbstractBTLEDeviceSupport {
alarms.get(i).getEnabled() ? cal.get(Calendar.MINUTE) : -1
));
}
builder.queue(getQueue());
performConnected(builder.getTransaction());
GB.toast(getContext(), "Alarm settings applied - do note that the current device does not support day specification", Toast.LENGTH_LONG, GB.INFO);
} catch(IOException e) {
LOG.warn(e.getMessage());
logger.warn(e.getMessage());
}
}
@ -298,18 +231,16 @@ public class TeclastH30Support extends AbstractBTLEDeviceSupport {
try {
TransactionBuilder builder = performInitialized("SetTime");
syncDateAndTime(builder);
builder.queue(getQueue());
performConnected(builder.getTransaction());
} catch(IOException e) {
LOG.warn(e.getMessage());
logger.warn(e.getMessage());
}
}
@Override
public void onSetCallState(CallSpec callSpec) {
switch (callSpec.command) {
case CallSpec.CALL_INCOMING:
showNotification(JYouConstants.ICON_CALL, callSpec.name, callSpec.number);
break;
if(callSpec.command == CallSpec.CALL_INCOMING) {
showNotification(JYouConstants.ICON_CALL, callSpec.name, callSpec.number);
}
}
@ -375,9 +306,9 @@ public class TeclastH30Support extends AbstractBTLEDeviceSupport {
builder.write(ctrlCharacteristic, commandWithChecksum(
JYouConstants.CMD_ACTION_REBOOT_DEVICE, 0, 0
));
builder.queue(getQueue());
performConnected(builder.getTransaction());
} catch(Exception e) {
LOG.warn(e.getMessage());
logger.warn(e.getMessage());
}
}
@ -388,15 +319,14 @@ public class TeclastH30Support extends AbstractBTLEDeviceSupport {
builder.write(ctrlCharacteristic, commandWithChecksum(
JYouConstants.CMD_ACTION_HEARTRATE_SWITCH, 0, 1
));
builder.queue(getQueue());
performConnected(builder.getTransaction());
} catch(Exception e) {
LOG.warn(e.getMessage());
logger.warn(e.getMessage());
}
}
@Override
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
// TODO: test
try {
TransactionBuilder builder = performInitialized("RealTimeHeartMeasurement");
builder.write(ctrlCharacteristic, commandWithChecksum(
@ -404,7 +334,7 @@ public class TeclastH30Support extends AbstractBTLEDeviceSupport {
));
builder.queue(getQueue());
} catch(Exception e) {
LOG.warn(e.getMessage());
logger.warn(e.getMessage());
}
}
@ -466,7 +396,7 @@ public class TeclastH30Support extends AbstractBTLEDeviceSupport {
}
private byte[] commandWithChecksum(byte cmd, int argSlot1, int argSlot2)
protected byte[] commandWithChecksum(byte cmd, int argSlot1, int argSlot2)
{
ByteBuffer buf = ByteBuffer.allocate(10);
buf.put(cmd);
@ -505,7 +435,7 @@ public class TeclastH30Support extends AbstractBTLEDeviceSupport {
}
}
} catch (UnsupportedEncodingException e) {
LOG.warn(e.getMessage());
logger.warn(e.getMessage());
}
return null;
}

View File

@ -0,0 +1,95 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.jyou;
import java.util.Timer;
import java.util.TimerTask;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
public abstract class RealtimeSamplesSupport {
private final long delay;
private final long period;
protected int steps;
protected int heartrateBpm;
private int lastSteps;
// subclasses may add more
private Timer realtimeStorageTimer;
public RealtimeSamplesSupport(long delay, long period) {
this.delay = delay;
this.period = period;
}
public synchronized void start() {
if (isRunning()) {
return; // already running
}
realtimeStorageTimer = new Timer("JYou Realtime Storage Timer");
realtimeStorageTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
triggerCurrentSample();
}
}, delay, period);
}
public synchronized void stop() {
if (realtimeStorageTimer != null) {
realtimeStorageTimer.cancel();
realtimeStorageTimer.purge();
realtimeStorageTimer = null;
}
}
public synchronized boolean isRunning() {
return realtimeStorageTimer != null;
}
public synchronized void setSteps(int stepsPerMinute) {
this.steps = stepsPerMinute;
}
/**
* Returns the number of steps recorded since the last measurements. If no
* steps are available yet, ActivitySample.NOT_MEASURED is returned.
* @return
*/
public synchronized int getSteps() {
if (steps == ActivitySample.NOT_MEASURED) {
return ActivitySample.NOT_MEASURED;
}
if (lastSteps == 0) {
return ActivitySample.NOT_MEASURED; // wait until we have a delta between two samples
}
int delta = steps - lastSteps;
if (delta < 0) {
return 0;
}
return delta;
}
public void setHeartrateBpm(int hrBpm) {
this.heartrateBpm = hrBpm;
}
public int getHeartrateBpm() {
return heartrateBpm;
}
public void triggerCurrentSample() {
doCurrentSample();
resetCurrentValues();
}
protected synchronized void resetCurrentValues() {
if (steps >= lastSteps) {
lastSteps = steps;
}
steps = ActivitySample.NOT_MEASURED;
heartrateBpm = ActivitySample.NOT_MEASURED;
}
protected abstract void doCurrentSample();
}

View File

@ -0,0 +1,121 @@
/* Copyright (C) 2017-2019 Andreas Shimokawa, Carsten Pfeiffer, Sami Alaoui,
Sebastian Kranz
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.jyou.TeclastH30;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.devices.jyou.JYouConstants;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.JYouSupport;
public class TeclastH30Support extends JYouSupport {
private static final Logger LOG = LoggerFactory.getLogger(TeclastH30Support.class);
public TeclastH30Support() {
super(LOG);
addSupportedService(JYouConstants.UUID_SERVICE_JYOU);
}
@Override
public boolean onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
if (super.onCharacteristicChanged(gatt, characteristic)) {
return true;
}
UUID characteristicUUID = characteristic.getUuid();
byte[] data = characteristic.getValue();
if (data.length == 0)
return true;
switch (data[0]) {
case JYouConstants.RECEIVE_DEVICE_INFO:
int fwVerNum = data[4] & 0xFF;
versionCmd.fwVersion = (fwVerNum / 100) + "." + ((fwVerNum % 100) / 10) + "." + ((fwVerNum % 100) % 10);
handleGBDeviceEvent(versionCmd);
LOG.info("Firmware version is: " + versionCmd.fwVersion);
return true;
case JYouConstants.RECEIVE_BATTERY_LEVEL:
batteryCmd.level = data[8];
handleGBDeviceEvent(batteryCmd);
LOG.info("Battery level is: " + batteryCmd.level);
return true;
case JYouConstants.RECEIVE_STEPS_DATA:
int steps = ByteBuffer.wrap(data, 5, 4).getInt();
LOG.info("Number of walked steps: " + steps);
return true;
case JYouConstants.RECEIVE_HEARTRATE:
LOG.info("Current heart rate: " + data[8]);
return true;
default:
LOG.info("Unhandled characteristic change: " + characteristicUUID + " code: " + String.format("0x%1x ...", data[0]));
return true;
}
}
@Override
protected void syncSettings(TransactionBuilder builder) {
syncDateAndTime(builder);
// TODO: unhardcode and separate stuff
builder.write(ctrlCharacteristic, commandWithChecksum(
JYouConstants.CMD_SET_HEARTRATE_WARNING_VALUE, 0, 152
));
builder.write(ctrlCharacteristic, commandWithChecksum(
JYouConstants.CMD_SET_TARGET_STEPS, 0, 10000
));
builder.write(ctrlCharacteristic, commandWithChecksum(
JYouConstants.CMD_GET_STEP_COUNT, 0, 0
));
builder.write(ctrlCharacteristic, commandWithChecksum(
JYouConstants.CMD_GET_SLEEP_TIME, 0, 0
));
builder.write(ctrlCharacteristic, commandWithChecksum(
JYouConstants.CMD_SET_NOON_TIME, 12 * 60 * 60, 14 * 60 * 60 // 12:00 - 14:00
));
builder.write(ctrlCharacteristic, commandWithChecksum(
JYouConstants.CMD_SET_SLEEP_TIME, 21 * 60 * 60, 8 * 60 * 60 // 21:00 - 08:00
));
builder.write(ctrlCharacteristic, commandWithChecksum(
JYouConstants.CMD_SET_INACTIVITY_WARNING_TIME, 0, 0
));
// do not disturb and a couple more features
byte dndStartHour = 22;
byte dndStartMin = 0;
byte dndEndHour = 8;
byte dndEndMin = 0;
boolean dndToggle = false;
boolean vibrationToggle = true;
boolean wakeOnRaiseToggle = true;
builder.write(ctrlCharacteristic, commandWithChecksum(
JYouConstants.CMD_SET_DND_SETTINGS,
(dndStartHour << 24) | (dndStartMin << 16) | (dndEndHour << 8) | dndEndMin,
((dndToggle ? 0 : 1) << 2) | ((vibrationToggle ? 1 : 0) << 1) | (wakeOnRaiseToggle ? 1 : 0)
));
}
}

View File

@ -0,0 +1,233 @@
/* Copyright (C) 2017-2019 Andreas Shimokawa, Carsten Pfeiffer, Sami Alaoui,
Sebastian Kranz
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.jyou.y5;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.Intent;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.jyou.JYouConstants;
import nodomain.freeyourgadget.gadgetbridge.devices.jyou.JYouSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.JYouActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.User;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.JYouSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.RealtimeSamplesSupport;
public class Y5Support extends JYouSupport {
private static final Logger LOG = LoggerFactory.getLogger(Y5Support.class);
private RealtimeSamplesSupport realtimeSamplesSupport;
public Y5Support() {
super(LOG);
addSupportedService(JYouConstants.UUID_SERVICE_JYOU);
}
@Override
public boolean onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
if (super.onCharacteristicChanged(gatt, characteristic)) {
return true;
}
UUID characteristicUUID = characteristic.getUuid();
byte[] data = characteristic.getValue();
if (data.length == 0)
return true;
switch (data[0]) {
case JYouConstants.RECEIVE_HISTORY_SLEEP_COUNT:
LOG.info("onCharacteristicChanged: " + data[0]);
return true;
case JYouConstants.RECEIVE_BLOOD_PRESSURE:
int heartRate = data[2];
int bloodPressureHigh = data[3];
int bloodPressureLow = data[4];
int bloodOxygen = data[5];
int Fatigue = data[6];
LOG.info("RECEIVE_BLOOD_PRESSURE: Heart rate: " + heartRate + " Pressure high: " + bloodPressureHigh+ " pressure low: " + bloodPressureLow);
return true;
case JYouConstants.RECEIVE_DEVICE_INFO:
int model = data[7];
int fwVerNum = data[4] & 0xFF;
versionCmd.fwVersion = (fwVerNum / 100) + "." + ((fwVerNum % 100) / 10) + "." + ((fwVerNum % 100) % 10);
handleGBDeviceEvent(versionCmd);
LOG.info("Firmware version is: " + versionCmd.fwVersion);
return true;
case JYouConstants.RECEIVE_BATTERY_LEVEL:
batteryCmd.level = data[8];
handleGBDeviceEvent(batteryCmd);
LOG.info("Battery level is: " + batteryCmd.level);
return true;
case JYouConstants.RECEIVE_STEPS_DATA:
int steps = ByteBuffer.wrap(data, 5, 4).getInt();
LOG.info("Number of walked steps: " + steps);
handleRealtimeSteps(steps);
return true;
case JYouConstants.RECEIVE_HEARTRATE:
handleHeartrate(data[8]);
return true;
case JYouConstants.RECEIVE_WATCH_MAC:
return true;
case JYouConstants.RECEIVE_GET_PHOTO:
return true;
default:
LOG.info("Unhandled characteristic change: " + characteristicUUID + " code: " + String.format("0x%1x ...", data[0]));
return true;
}
}
private void handleRealtimeSteps(int value) {
//todo Call on connect the device
if (LOG.isDebugEnabled()) {
LOG.debug("realtime steps: " + value);
}
getRealtimeSamplesSupport().setSteps(value);
}
private void handleHeartrate(int value) {
if (LOG.isDebugEnabled()) {
LOG.debug("heart rate: " + value);
}
RealtimeSamplesSupport realtimeSamplesSupport = getRealtimeSamplesSupport();
realtimeSamplesSupport.setHeartrateBpm(value);
if (!realtimeSamplesSupport.isRunning()) {
// single shot measurement, manually invoke storage and result publishing
realtimeSamplesSupport.triggerCurrentSample();
}
}
public JYouActivitySample createActivitySample(Device device, User user, int timestampInSeconds, SampleProvider provider) {
JYouActivitySample sample = new JYouActivitySample();
sample.setDevice(device);
sample.setUser(user);
sample.setTimestamp(timestampInSeconds);
sample.setProvider(provider);
return sample;
}
private void enableRealtimeSamplesTimer(boolean enable) {
if (enable) {
getRealtimeSamplesSupport().start();
} else {
if (realtimeSamplesSupport != null) {
realtimeSamplesSupport.stop();
}
}
}
private RealtimeSamplesSupport getRealtimeSamplesSupport() {
if (realtimeSamplesSupport == null) {
realtimeSamplesSupport = new RealtimeSamplesSupport(1000, 1000) {
@Override
public void doCurrentSample() {
try (DBHandler handler = GBApplication.acquireDB()) {
DaoSession session = handler.getDaoSession();
int ts = (int) (System.currentTimeMillis() / 1000);
JYouSampleProvider provider = new JYouSampleProvider(gbDevice, session);
JYouActivitySample sample = createActivitySample(DBHelper.getDevice(getDevice(), session), DBHelper.getUser(session), ts, provider);
sample.setHeartRate(getHeartrateBpm());
sample.setRawIntensity(ActivitySample.NOT_MEASURED);
sample.setRawKind(JYouSampleProvider.TYPE_ACTIVITY); // to make it visible in the charts TODO: add a MANUAL kind for that?
provider.addGBActivitySample(sample);
// set the steps only afterwards, since realtime steps are also recorded
// in the regular samples and we must not count them twice
// Note: we know that the DAO sample is never committed again, so we simply
// change the value here in memory.
sample.setSteps(getSteps());
if(steps > 1){
LOG.debug("Have steps: " + getSteps());
}
if (LOG.isDebugEnabled()) {
LOG.debug("realtime sample: " + sample);
}
Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
.putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample);
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
} catch (Exception e) {
LOG.warn("Unable to acquire db for saving realtime samples", e);
}
}
};
}
return realtimeSamplesSupport;
}
@Override
protected void syncSettings(TransactionBuilder builder) {
syncDateAndTime(builder);
}
@Override
public void dispose() {
LOG.info("Dispose");
super.dispose();
}
@Override
public void onHeartRateTest() {
try {
TransactionBuilder builder = performInitialized("HeartRateTest");
builder.write(ctrlCharacteristic, commandWithChecksum(
JYouConstants.CMD_SET_HEARTRATE_AUTO, 0, 0
));
performConnected(builder.getTransaction());
} catch(Exception e) {
LOG.warn(e.getMessage());
}
}
@Override
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
try {
TransactionBuilder builder = performInitialized("RealTimeHeartMeasurement");
builder.write(ctrlCharacteristic, commandWithChecksum(
JYouConstants.CMD_ACTION_HEARTRATE_SWITCH, 0, enable ? 1 : 0
));
performConnected(builder.getTransaction());
enableRealtimeSamplesTimer(enable);
} catch (Exception e) {
LOG.warn(e.getMessage());
}
}
}

View File

@ -60,8 +60,9 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband2.MiBand2HRXCoor
import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband3.MiBand3Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband4.MiBand4Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.id115.ID115Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.jyou.TeclastH30.TeclastH30Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.jyou.y5.Y5Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.jyou.BFH16DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.jyou.TeclastH30Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.liveview.LiveviewCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator;
@ -235,6 +236,7 @@ public class DeviceHelper {
result.add(new Watch9DeviceCoordinator());
result.add(new Roidmi1Coordinator());
result.add(new Roidmi3Coordinator());
result.add(new Y5Coordinator());
result.add(new CasioGB6900DeviceCoordinator());
result.add(new BFH16DeviceCoordinator());
result.add(new MijiaLywsd02Coordinator());

View File

@ -684,6 +684,7 @@
<string name="devicetype_watch9">Watch 9</string>
<string name="devicetype_roidmi">Roidmi</string>
<string name="devicetype_roidmi3">Roidmi 3</string>
<string name="devicetype_y5">Y5</string>
<string name="devicetype_casiogb6900">Casio GB-6900</string>
<string name="devicetype_miscale2">Mi Scale 2</string>
<string name="devicetype_bfh16">BFH-16</string>