Huawei: Sync dict data over P2P. Sync skin temperature.

This commit is contained in:
Me7c7 2024-11-03 13:34:11 +02:00 committed by José Rebelo
parent 68caf6a60f
commit ae84678de8
9 changed files with 661 additions and 6 deletions

View File

@ -54,7 +54,7 @@ public class GBDaoGenerator {
public static void main(String[] args) throws Exception {
final Schema schema = new Schema(85, MAIN_PACKAGE + ".entities");
final Schema schema = new Schema(86, MAIN_PACKAGE + ".entities");
Entity userAttributes = addUserAttributes(schema);
Entity user = addUserInfo(schema, userAttributes);
@ -153,6 +153,9 @@ public class GBDaoGenerator {
addHuaweiWorkoutPaceSample(schema, huaweiWorkoutSummary);
addHuaweiWorkoutSwimSegmentsSample(schema, huaweiWorkoutSummary);
Entity huaweiDictData = addHuaweiDictData(schema, user, device);
addHuaweiDictDataValues(schema, huaweiDictData);
addCalendarSyncState(schema, device);
addAlarms(schema, user, device);
addReminders(schema, user, device);
@ -1468,6 +1471,42 @@ public class GBDaoGenerator {
return workoutSwimSegmentsSample;
}
private static Entity addHuaweiDictData(Schema schema, Entity user, Entity device) {
Entity dictData = addEntity(schema, "HuaweiDictData");
dictData.setJavaDoc("Contains Huawei Dict Data");
dictData.addLongProperty("dictId").primaryKey().autoincrement();
Property deviceId = dictData.addLongProperty("deviceId").notNull().getProperty();
dictData.addToOne(device, deviceId);
Property userId = dictData.addLongProperty("userId").notNull().getProperty();
dictData.addToOne(user, userId);
dictData.addIntProperty("dictClass").notNull();
dictData.addLongProperty("startTimestamp").notNull();
dictData.addLongProperty("endTimestamp");
dictData.addLongProperty("modifyTimestamp");
return dictData;
}
private static Entity addHuaweiDictDataValues(Schema schema, Entity summaryEntity) {
Entity dictDataValues = addEntity(schema, "HuaweiDictDataValues");
dictDataValues.setJavaDoc("Contains Huawei Dict data values");
Property id = dictDataValues.addLongProperty("dictId").primaryKey().notNull().getProperty();
dictDataValues.addToOne(summaryEntity, id);
dictDataValues.addIntProperty("dictType").notNull().primaryKey();
dictDataValues.addByteProperty("tag").notNull().primaryKey();
dictDataValues.addByteArrayProperty("value");
return dictDataValues;
}
private static void addTemperatureProperties(Entity activitySample) {
activitySample.addFloatProperty(SAMPLE_TEMPERATURE).notNull().codeBeforeGetter(OVERRIDE);
activitySample.addIntProperty(SAMPLE_TEMPERATURE_TYPE).notNull().codeBeforeGetter(OVERRIDE);

View File

@ -60,6 +60,7 @@ public class TemperatureChartFragment extends AbstractChartFragment<TemperatureC
protected final int TOTAL_DAYS = getRangeDays();
@Override
protected void init() {
BACKGROUND_COLOR = GBApplication.getBackgroundColor(requireContext());

View File

@ -41,6 +41,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiBRSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiWorkoutGbParser;
@ -187,6 +188,11 @@ public abstract class HuaweiBRCoordinator extends AbstractBLClassicDeviceCoordin
return huaweiCoordinator.supportsMusic();
}
@Override
public boolean supportsTemperatureMeasurement() {
return huaweiCoordinator.supportsTemperature();
}
@Override
public InstallHandler findInstallHandler(Uri uri, Context context) {
return huaweiCoordinator.getInstallHandler(uri, context);
@ -207,6 +213,11 @@ public abstract class HuaweiBRCoordinator extends AbstractBLClassicDeviceCoordin
return new HuaweiSpo2SampleProvider(device, session);
}
@Override
public TimeSampleProvider<? extends TemperatureSample> getTemperatureSampleProvider(final GBDevice device, final DaoSession session) {
return new HuaweiTemperatureSampleProvider(device, session);
}
public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
return huaweiCoordinator.getDeviceSpecificSettings(device);
}

View File

@ -46,6 +46,9 @@ import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictData;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataValuesDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutPaceSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySample;
@ -134,6 +137,16 @@ public class HuaweiCoordinator {
session.getHuaweiWorkoutSummarySampleDao().queryBuilder().where(HuaweiWorkoutSummarySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
session.getBaseActivitySummaryDao().queryBuilder().where(BaseActivitySummaryDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
QueryBuilder<HuaweiDictData> qb3 = session.getHuaweiDictDataDao().queryBuilder();
List<HuaweiDictData> dictData = qb3.where(HuaweiDictDataDao.Properties.DeviceId.eq(deviceId)).build().list();
for (HuaweiDictData data : dictData) {
session.getHuaweiDictDataValuesDao().queryBuilder().where(
HuaweiDictDataValuesDao.Properties.DictId.eq(data.getDictId())
).buildDelete().executeDeleteWithoutDetachingEntities();
}
session.getHuaweiDictDataDao().queryBuilder().where(HuaweiDictDataDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
}
private SharedPreferences getCapabilitiesSharedPreferences() {

View File

@ -42,6 +42,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiLESupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiWorkoutGbParser;
@ -196,6 +197,11 @@ public abstract class HuaweiLECoordinator extends AbstractBLEDeviceCoordinator i
return huaweiCoordinator.supportsMusic();
}
@Override
public boolean supportsTemperatureMeasurement() {
return huaweiCoordinator.supportsTemperature();
}
@Override
public InstallHandler findInstallHandler(Uri uri, Context context) {
return huaweiCoordinator.getInstallHandler(uri, context);
@ -216,6 +222,11 @@ public abstract class HuaweiLECoordinator extends AbstractBLEDeviceCoordinator i
return new HuaweiSpo2SampleProvider(device, session);
}
@Override
public TimeSampleProvider<? extends TemperatureSample> getTemperatureSampleProvider(final GBDevice device, final DaoSession session) {
return new HuaweiTemperatureSampleProvider(device, session);
}
public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
return huaweiCoordinator.getDeviceSpecificSettings(device);
}

View File

@ -243,6 +243,10 @@ public class HuaweiTLV {
return ByteBuffer.wrap(getBytes(tag)).getShort();
}
public Long getLong(int tag) throws HuaweiPacket.MissingTagException {
return ByteBuffer.wrap(getBytes(tag)).getLong();
}
public Integer getAsInteger(int tag) throws HuaweiPacket.MissingTagException {
byte[] bytes = getBytes(tag);
if(bytes.length == 1) {

View File

@ -0,0 +1,190 @@
package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import de.greenrobot.dao.Property;
import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictData;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataValues;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataValuesDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.p2p.HuaweiP2PDataDictionarySyncService;
public class HuaweiTemperatureSampleProvider implements TimeSampleProvider<TemperatureSample> {
private final Logger LOG = LoggerFactory.getLogger(HuaweiTemperatureSampleProvider.class);
protected static class HuaweiTemperatureSample implements TemperatureSample {
private final long timestamp;
private final float temperature;
public HuaweiTemperatureSample(long timestamp, float temperature) {
this.timestamp = timestamp;
this.temperature = temperature;
}
@Override
public long getTimestamp() {
return timestamp;
}
@Override
public float getTemperature() {
return temperature;
}
@Override
public int getTemperatureType() { return 0;}
}
private final GBDevice device;
private final DaoSession session;
public HuaweiTemperatureSampleProvider(GBDevice device, DaoSession session) {
this.device = device;
this.session = session;
}
private double conv2Double(byte[] b) {
return ByteBuffer.wrap(b).getDouble();
}
@NonNull
@Override
public List<TemperatureSample> getAllSamples(long timestampFrom, long timestampTo) {
List<TemperatureSample> ret = new ArrayList<>();
Long userId = DBHelper.getUser(this.session).getId();
Long deviceId = DBHelper.getDevice(this.device, this.session).getId();
if (deviceId == null || userId == null)
return ret;
QueryBuilder<HuaweiDictData> qb = this.session.getHuaweiDictDataDao().queryBuilder();
qb.where(HuaweiDictDataDao.Properties.DeviceId.eq(deviceId))
.where(HuaweiDictDataDao.Properties.UserId.eq(userId))
.where(HuaweiDictDataDao.Properties.DictClass.eq(400012))
.where(HuaweiDictDataDao.Properties.StartTimestamp.between(timestampFrom, timestampTo));
final List<HuaweiDictData> dictData = qb.build().list();
if (dictData.isEmpty())
return ret;
List<Long> ids = dictData.stream().map(HuaweiDictData::getDictId).collect(Collectors.toList());
QueryBuilder<HuaweiDictDataValues> qbv = this.session.getHuaweiDictDataValuesDao().queryBuilder();
qbv.where(HuaweiDictDataValuesDao.Properties.DictType.eq(400012430)).where(HuaweiDictDataValuesDao.Properties.Tag.eq(10)).where(HuaweiDictDataValuesDao.Properties.DictId.in(ids));
final List<HuaweiDictDataValues> valuesData = qbv.build().list();
if (valuesData.isEmpty())
return ret;
for(HuaweiDictDataValues vl: valuesData) {
double skinTemperature = conv2Double(vl.getValue());
if(skinTemperature >= 20 && skinTemperature <= 42) {
ret.add(new HuaweiTemperatureSample(vl.getHuaweiDictData().getStartTimestamp(), (float) skinTemperature));
}
}
return ret;
}
@Override
public void addSample(TemperatureSample timeSample) {
throw new UnsupportedOperationException("read-only sample provider");
}
@Override
public void addSamples(List<TemperatureSample> timeSamples) {
throw new UnsupportedOperationException("read-only sample provider");
}
@Override
public TemperatureSample createSample() {
throw new UnsupportedOperationException("read-only sample provider");
}
@Nullable
@Override
public TemperatureSample getLatestSample() {
Long userId = DBHelper.getUser(this.session).getId();
Long deviceId = DBHelper.getDevice(this.device, this.session).getId();
if (deviceId == null || userId == null)
return null;
QueryBuilder<HuaweiDictData> qb = this.session.getHuaweiDictDataDao().queryBuilder();
qb.where(HuaweiDictDataDao.Properties.DeviceId.eq(deviceId))
.where(HuaweiDictDataDao.Properties.UserId.eq(userId))
.where(HuaweiDictDataDao.Properties.DictClass.eq(400012));
qb.orderDesc(HuaweiDictDataDao.Properties.StartTimestamp).limit(1);
final List<HuaweiDictData> data = qb.build().list();
if (data.isEmpty())
return null;
QueryBuilder<HuaweiDictDataValues> qbv = this.session.getHuaweiDictDataValuesDao().queryBuilder();
qbv.where(HuaweiDictDataValuesDao.Properties.DictType.eq(400012430)).where(HuaweiDictDataValuesDao.Properties.Tag.eq(10)).where(HuaweiDictDataValuesDao.Properties.DictId.eq(data.get(0).getDictId()));
final List<HuaweiDictDataValues> valuesData = qbv.build().list();
if (valuesData.isEmpty())
return null;
return new HuaweiTemperatureSample(valuesData.get(0).getHuaweiDictData().getStartTimestamp(), (float) conv2Double(valuesData.get(0).getValue()));
}
@Nullable
@Override
public TemperatureSample getFirstSample() {
Long userId = DBHelper.getUser(this.session).getId();
Long deviceId = DBHelper.getDevice(this.device, this.session).getId();
if (deviceId == null || userId == null)
return null;
QueryBuilder<HuaweiDictData> qb = this.session.getHuaweiDictDataDao().queryBuilder();
qb.where(HuaweiDictDataDao.Properties.DeviceId.eq(deviceId))
.where(HuaweiDictDataDao.Properties.UserId.eq(userId))
.where(HuaweiDictDataDao.Properties.DictClass.eq(400012));
qb.orderAsc(HuaweiDictDataDao.Properties.StartTimestamp).limit(1);
final List<HuaweiDictData> data = qb.build().list();
if (data.isEmpty())
return null;
QueryBuilder<HuaweiDictDataValues> qbv = this.session.getHuaweiDictDataValuesDao().queryBuilder();
qbv.where(HuaweiDictDataValuesDao.Properties.DictType.eq(400012430)).where(HuaweiDictDataValuesDao.Properties.Tag.eq(10)).where(HuaweiDictDataValuesDao.Properties.DictId.eq(data.get(0).getDictId()));
final List<HuaweiDictDataValues> valuesData = qbv.build().list();
if (valuesData.isEmpty())
return null;
return new HuaweiTemperatureSample(valuesData.get(0).getHuaweiDictData().getStartTimestamp(), (float) conv2Double(valuesData.get(0).getValue()));
}
}

View File

@ -31,8 +31,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.util.ArrayList;
@ -70,6 +68,10 @@ import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictData;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataValues;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataValuesDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutPaceSample;
@ -105,6 +107,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.p2p.HuaweiP2PCalendarService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.p2p.HuaweiP2PTrackService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.p2p.HuaweiP2PDataDictionarySyncService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.AcceptAgreementsRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetAppInfoParams;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetContactsCount;
@ -887,6 +890,10 @@ public class HuaweiSupportProvider {
trackService.register();
}
}
if (HuaweiP2PDataDictionarySyncService.getRegisteredInstance(huaweiP2PManager) == null) {
HuaweiP2PDataDictionarySyncService trackService = new HuaweiP2PDataDictionarySyncService(huaweiP2PManager);
trackService.register();
}
}
}
@ -1184,6 +1191,7 @@ public class HuaweiSupportProvider {
private void fetchActivityData() {
syncState.setActivitySync(true);
fetchActivityDataP2P();
int sleepStart = 0;
int stepStart = 0;
@ -1242,6 +1250,7 @@ public class HuaweiSupportProvider {
}
});
getStepDataCountRequest.setFinalizeReq(new RequestCallback() {
@Override
public void call() {
@ -1286,6 +1295,18 @@ public class HuaweiSupportProvider {
}
}
private void fetchActivityDataP2P() {
HuaweiP2PDataDictionarySyncService P2PSyncService = HuaweiP2PDataDictionarySyncService.getRegisteredInstance(huaweiP2PManager);
if (P2PSyncService != null && getHuaweiCoordinator().supportsTemperature()) {
P2PSyncService.sendSyncRequest(400012, new HuaweiP2PDataDictionarySyncService.DictionarySyncCallback() {
@Override
public void onComplete(boolean complete) {
LOG.info("Sync P2P Temperature complete");
}
});
}
}
private void fetchWorkoutData() {
syncState.setWorkoutSync(true);
@ -1721,7 +1742,7 @@ public class HuaweiSupportProvider {
try (DBHandler db = GBApplication.acquireDB()) {
HuaweiWorkoutPaceSampleDao dao = db.getDaoSession().getHuaweiWorkoutPaceSampleDao();
if(number == 0) {
if (number == 0) {
final DeleteQuery<HuaweiWorkoutPaceSample> tableDeleteQuery = dao.queryBuilder()
.where(HuaweiWorkoutPaceSampleDao.Properties.WorkoutId.eq(workoutId))
.buildDelete();
@ -1756,7 +1777,7 @@ public class HuaweiSupportProvider {
try (DBHandler db = GBApplication.acquireDB()) {
HuaweiWorkoutSwimSegmentsSampleDao dao = db.getDaoSession().getHuaweiWorkoutSwimSegmentsSampleDao();
if(number == 0) {
if (number == 0) {
final DeleteQuery<HuaweiWorkoutSwimSegmentsSample> tableDeleteQuery = dao.queryBuilder()
.where(HuaweiWorkoutSwimSegmentsSampleDao.Properties.WorkoutId.eq(workoutId))
.buildDelete();
@ -1785,6 +1806,92 @@ public class HuaweiSupportProvider {
}
}
public void addDictData(List<HuaweiP2PDataDictionarySyncService.DictData> dictData) {
try (DBHandler db = GBApplication.acquireDB()) {
Long userId = DBHelper.getUser(db.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(gbDevice, db.getDaoSession()).getId();
for (HuaweiP2PDataDictionarySyncService.DictData data : dictData) {
// Avoid duplicates
QueryBuilder<HuaweiDictData> qb = db.getDaoSession().getHuaweiDictDataDao().queryBuilder().where(
HuaweiDictDataDao.Properties.UserId.eq(userId),
HuaweiDictDataDao.Properties.DeviceId.eq(deviceId),
HuaweiDictDataDao.Properties.DictClass.eq(data.getDictClass()),
HuaweiDictDataDao.Properties.StartTimestamp.eq(data.getStartTimestamp())
);
List<HuaweiDictData> results = qb.build().list();
Long dictId = null;
if (!results.isEmpty())
dictId = results.get(0).getDictId();
HuaweiDictData dictSample = new HuaweiDictData(
dictId,
deviceId,
userId,
data.getDictClass(),
data.getStartTimestamp(),
data.getEndTimestamp(),
data.getModifyTimestamp()
);
db.getDaoSession().getHuaweiDictDataDao().insertOrReplace(dictSample);
addDictDataValue(dictSample.getDictId(), data.getData());
}
} catch (Exception e) {
LOG.error("Failed to add dict data", e);
}
}
public void addDictDataValue(Long dictId, List<HuaweiP2PDataDictionarySyncService.DictData.DictDataValue> dictDataValues) {
if (dictId == null)
return;
try (DBHandler db = GBApplication.acquireDB()) {
HuaweiDictDataValuesDao dao = db.getDaoSession().getHuaweiDictDataValuesDao();
for (HuaweiP2PDataDictionarySyncService.DictData.DictDataValue dataValues : dictDataValues) {
HuaweiDictDataValues dictValue = new HuaweiDictDataValues(
dictId,
dataValues.getDataType(),
dataValues.getTag(),
dataValues.getValue()
);
dao.insertOrReplace(dictValue);
}
} catch (Exception e) {
LOG.error("Failed to add dict value to database", e);
}
}
public long getLastDataDictLastTimestamp(int dictClass) {
long lastTimestamp = 0;
if (dictClass == 0)
return lastTimestamp;
try (DBHandler db = GBApplication.acquireDB()) {
Long userId = DBHelper.getUser(db.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(gbDevice, db.getDaoSession()).getId();
QueryBuilder<HuaweiDictData> qb = db.getDaoSession().getHuaweiDictDataDao().queryBuilder().where(
HuaweiDictDataDao.Properties.UserId.eq(userId),
HuaweiDictDataDao.Properties.DeviceId.eq(deviceId),
HuaweiDictDataDao.Properties.DictClass.eq(dictClass)
);
List<HuaweiDictData> results = qb.build().list();
for (HuaweiDictData data : results) {
if (data.getModifyTimestamp() != null) {
lastTimestamp = Math.max(lastTimestamp, data.getModifyTimestamp());
}
if (data.getEndTimestamp() != null) {
lastTimestamp = Math.max(lastTimestamp, data.getEndTimestamp());
}
}
} catch (Exception e) {
LOG.error("Failed to select last timestsmp value to database", e);
}
return lastTimestamp;
}
public void setWearLocation() {
try {
@ -2021,7 +2128,7 @@ public class HuaweiSupportProvider {
HuaweiUploadManager.FileUploadInfo fileInfo = new HuaweiUploadManager.FileUploadInfo();
if(huaweiFwHelper.isMusic()) {
if (huaweiFwHelper.isMusic()) {
getHuaweiMusicManager().addUploadMusic(huaweiFwHelper.getMusicInfo());
}

View File

@ -0,0 +1,279 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.p2p;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiP2PManager;
public class HuaweiP2PDataDictionarySyncService extends HuaweiBaseP2PService {
private final Logger LOG = LoggerFactory.getLogger(HuaweiP2PDataDictionarySyncService.class);
public static final String MODULE = "hw.unitedevice.datadictionarysync";
private AtomicBoolean serviceAvailable = new AtomicBoolean(false);
public interface DictionarySyncCallback {
void onComplete(boolean complete);
}
private final Map<Integer, DictionarySyncCallback> currentRequests = new HashMap<>();
public HuaweiP2PDataDictionarySyncService(HuaweiP2PManager manager) {
super(manager);
LOG.info("P2PDataDictionarySyncService");
}
@Override
public String getModule() {
return HuaweiP2PDataDictionarySyncService.MODULE;
}
@Override
public String getPackage() {
return "hw.watch.health.filesync";
}
@Override
public String getFingerprint() {
return "SystemApp";
}
public static byte[] dictToBytes(int value) {
return new byte[]{
(byte) (value >>> 16),
(byte) (value >>> 8),
(byte) value};
}
public void sendSyncRequest(int dictClass, DictionarySyncCallback callback) {
if (!serviceAvailable.get()) {
LOG.info("P2PDataDictionarySyncService not available");
callback.onComplete(false);
return;
}
if(currentRequests.containsKey(dictClass)) {
LOG.info("P2PDataDictionarySyncService current class in progress");
callback.onComplete(false);
return;
}
long startTime = manager.getSupportProvider().getLastDataDictLastTimestamp(dictClass);
if(startTime > 0) {
startTime += 1000;
}
HuaweiTLV tlv = new HuaweiTLV()
.put(0x1, (byte) 1)
.put(0x2, dictToBytes(dictClass)) //-- skin temperature
.put(0x5, Long.valueOf(startTime))
.put(0x6, Long.valueOf(System.currentTimeMillis()))
.put(0x0d, (byte) 1);
byte[] data = tlv.serialize();
if (data == null) {
LOG.error("Incorrect data");
callback.onComplete(false);
return;
}
ByteBuffer packet = ByteBuffer.allocate(1 + data.length);
packet.put((byte) 0x1); // type tlv
packet.put(data);
packet.flip();
LOG.info("P2PDataDictionarySyncService send command");
currentRequests.put(dictClass, callback);
sendCommand(packet.array(), null);
}
@Override
public void registered() {
sendPing(new HuaweiP2PCallback() {
@Override
public void onResponse(int code, byte[] data) {
if ((byte) code != (byte) 0xca)
return;
serviceAvailable.set(true);
}
});
}
@Override
public void unregister() {
serviceAvailable.set(false);
}
public static class DictData {
public static class DictDataValue {
private final int dataType;
private final byte tag;
private final byte[] value;
public DictDataValue(int dataType, byte tag, byte[] value) {
this.dataType = dataType;
this.tag = tag;
this.value = value;
}
public int getDataType() {
return dataType;
}
public byte getTag() {
return tag;
}
public byte[] getValue() {
return value;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("HuaweiDictDataValue{");
sb.append("dataType=").append(dataType);
sb.append(", tag=").append(tag);
sb.append(", value=");
if (value == null) sb.append("null");
else {
sb.append('[');
for (int i = 0; i < value.length; ++i)
sb.append(i == 0 ? "" : ", ").append(value[i]);
sb.append(']');
}
sb.append('}');
return sb.toString();
}
}
private final int dictClass;
private final long startTimestamp;
private final long endTimestamp;
private final long modifyTimestamp;
private final List<DictDataValue> data;
public DictData(int dictClass, long startTimestamp, long endTimestamp, long modifyTimestamp, List<DictDataValue> data) {
this.dictClass = dictClass;
this.startTimestamp = startTimestamp;
this.endTimestamp = endTimestamp;
this.modifyTimestamp = modifyTimestamp;
this.data = data;
}
public int getDictClass() { return dictClass; }
public long getStartTimestamp() {
return startTimestamp;
}
public long getEndTimestamp() {
return endTimestamp;
}
public long getModifyTimestamp() {
return modifyTimestamp;
}
public List<DictDataValue> getData() {
return data;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("HuaweiDictSample{");
sb.append("startTime=").append(startTimestamp);
sb.append(", endTime=").append(endTimestamp);
sb.append(", modifyTime=").append(modifyTimestamp);
sb.append(", data=").append(data);
sb.append('}');
return sb.toString();
}
}
@Override
public void handleData(byte[] data) {
LOG.info("P2PDataDictionarySyncService handleData: {}", data.length);
if (data[0] == 1) {
DictionarySyncCallback callback = null;
try {
HuaweiTLV tlv = new HuaweiTLV();
tlv.parse(data, 1, data.length - 1);
int operation = tlv.getInteger(0x01); ///???
int dictClass = tlv.getInteger(0x02);
if(!currentRequests.containsKey(dictClass)) {
return;
}
callback = currentRequests.remove(dictClass);
if(callback == null) {
return;
}
//NOTE: all tags with high bit set should be parsed as container
List<DictData> result = new ArrayList<>();
for (HuaweiTLV blockTlv : tlv.getObjects(0x83)) {
for (HuaweiTLV l : blockTlv.getObjects(0x84)) {
//5 - start time, 6 - end time, 0xc - modify time
long startTimestamp = l.getLong(0x5);
long endTimestamp = 0;
long modifyTimestamp = 0;
if (l.contains(0x6))
endTimestamp = l.getLong(0x6);
if (l.contains(0xc))
modifyTimestamp = l.getLong(0xc);
List<DictData.DictDataValue> dataValues = new ArrayList<>();
for (HuaweiTLV l1 : l.getObjects(0x87)) {
for (HuaweiTLV ll : l1.getObjects(0x88)) {
int type = ll.getInteger(0x9);
// 10 - Double - data
// 11 - String - metadata
if (ll.contains(0xa))
dataValues.add(new DictData.DictDataValue(type, (byte) 0xa, ll.getBytes(0xa)));
if (ll.contains(0xb))
dataValues.add(new DictData.DictDataValue(type, (byte) 0xb, ll.getBytes(0xb)));
}
}
result.add(new DictData(dictClass, startTimestamp, endTimestamp, modifyTimestamp, dataValues));
}
}
manager.getSupportProvider().addDictData(result);
if (!result.isEmpty()) {
sendSyncRequest(dictClass, callback);
} else {
callback.onComplete(true);
}
} catch (HuaweiPacket.MissingTagException e) {
LOG.error("P2PDataDictionarySyncService parse error", e);
if(callback != null) {
callback.onComplete(false);
}
}
}
}
public static HuaweiP2PDataDictionarySyncService getRegisteredInstance(HuaweiP2PManager manager) {
return (HuaweiP2PDataDictionarySyncService) manager.getRegisteredService(HuaweiP2PDataDictionarySyncService.MODULE);
}
}