mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2025-01-10 17:11:56 +01:00
Compare commits
29 Commits
64c024a5ce
...
a54f953b6f
Author | SHA1 | Date | |
---|---|---|---|
|
a54f953b6f | ||
|
9a388ca386 | ||
|
c66051f580 | ||
|
1c2516186d | ||
|
5ebfe9b39f | ||
|
b231104a88 | ||
|
242f3c6dbc | ||
|
043f839a4d | ||
|
6a2186919c | ||
|
4b70f3fcdb | ||
|
d379b11535 | ||
|
ee14b5d821 | ||
|
65aa5faec5 | ||
|
f970b7482b | ||
|
287b720350 | ||
|
14eaba858c | ||
|
0b64408b33 | ||
|
2b1c5b5819 | ||
|
b2b176b65c | ||
|
3af5a412fe | ||
|
fa1f99642a | ||
|
c1f2f5bb4b | ||
|
7a79a1d0f5 | ||
|
cc5078332b | ||
|
31fc266f94 | ||
|
22e51f841b | ||
|
275b662188 | ||
|
89b6ae9f24 | ||
|
01ef422812 |
@ -1,5 +1,14 @@
|
|||||||
### Changelog
|
### Changelog
|
||||||
|
|
||||||
|
#### Next version (WIP)
|
||||||
|
* Initial support for Huawei Watch Fit
|
||||||
|
* Initial support for Xiaomi Redmi Watch 3
|
||||||
|
* Fossil/Skagen Hybrids: Fix crash on multi-byte unicode characters in menu
|
||||||
|
* Huawei: Add weather support
|
||||||
|
* Test Device: Add fake features and data
|
||||||
|
* Periodically (around every 2 days) synchronize time on connected devices
|
||||||
|
* Set alarm as used and enabled if time has changed
|
||||||
|
|
||||||
#### 0.79.0
|
#### 0.79.0
|
||||||
* Initial support for Honor Magic Watch 2
|
* Initial support for Honor Magic Watch 2
|
||||||
* Initial support for Mijia MHO-C303
|
* Initial support for Mijia MHO-C303
|
||||||
|
@ -67,7 +67,7 @@ def getGitHashShort = { ->
|
|||||||
standardOutput = stdout
|
standardOutput = stdout
|
||||||
}
|
}
|
||||||
return stdout.toString().trim()
|
return stdout.toString().trim()
|
||||||
} catch (ignored){
|
} catch (ignored) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -84,9 +84,10 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "nodomain.freeyourgadget.gadgetbridge"
|
applicationId "nodomain.freeyourgadget.gadgetbridge"
|
||||||
|
|
||||||
minSdkVersion 21
|
//noinspection OldTargetApi
|
||||||
targetSdkVersion 33
|
targetSdkVersion 33
|
||||||
compileSdk 33
|
compileSdk 33
|
||||||
|
minSdkVersion 21
|
||||||
|
|
||||||
// Note: always bump BOTH versionCode and versionName!
|
// Note: always bump BOTH versionCode and versionName!
|
||||||
versionName "0.79.0"
|
versionName "0.79.0"
|
||||||
@ -94,13 +95,8 @@ android {
|
|||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
buildConfigField "String", "GIT_HASH_SHORT", "\"${getGitHashShort()}\""
|
buildConfigField "String", "GIT_HASH_SHORT", "\"${getGitHashShort()}\""
|
||||||
buildConfigField "boolean", "INTERNET_ACCESS", "false"
|
buildConfigField "boolean", "INTERNET_ACCESS", "false"
|
||||||
resValue "string", "pebble_content_provider", "com.getpebble.android.provider"
|
|
||||||
resValue "string", "app_name", "@string/application_name_generic"
|
|
||||||
resValue "string", "title_activity_controlcenter", "@string/title_activity_controlcenter_generic"
|
|
||||||
resValue "string", "about_activity_title", "@string/about_activity_title_generic"
|
|
||||||
resValue "string", "about_description", "@string/about_description_generic"
|
|
||||||
resValue "string", "gadgetbridge_running", "@string/gadgetbridge_running_generic"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
nightly {
|
nightly {
|
||||||
if (System.getProperty("nightly_store_file") != null) {
|
if (System.getProperty("nightly_store_file") != null) {
|
||||||
@ -122,19 +118,13 @@ android {
|
|||||||
//applicationIdSuffix ""
|
//applicationIdSuffix ""
|
||||||
//versionNameSuffix ""
|
//versionNameSuffix ""
|
||||||
}
|
}
|
||||||
|
|
||||||
banglejs {
|
banglejs {
|
||||||
dimension "device_type"
|
dimension "device_type"
|
||||||
applicationId "com.espruino.gadgetbridge"
|
applicationId "com.espruino.gadgetbridge"
|
||||||
applicationIdSuffix ".banglejs"
|
applicationIdSuffix ".banglejs"
|
||||||
versionNameSuffix "-banglejs"
|
versionNameSuffix "-banglejs"
|
||||||
buildConfigField "boolean", "INTERNET_ACCESS", "true"
|
buildConfigField "boolean", "INTERNET_ACCESS", "true"
|
||||||
// Disable pebble provider to allow Bangle.js Gadgetbridge to coexist with Gadgetbridge
|
|
||||||
resValue "string", "pebble_content_provider", "com.getpebble.android.nopebble.bangle.provider"
|
|
||||||
resValue "string", "app_name", "@string/application_name_banglejs_main"
|
|
||||||
resValue "string", "title_activity_controlcenter", "@string/title_activity_controlcenter_banglejs_main"
|
|
||||||
resValue "string", "about_activity_title", "@string/about_activity_title_banglejs_main"
|
|
||||||
resValue "string", "about_description", "@string/about_description_banglejs_main"
|
|
||||||
resValue "string", "gadgetbridge_running", "@string/gadgetbridge_running_banglejs_main"
|
|
||||||
targetSdkVersion 33
|
targetSdkVersion 33
|
||||||
// Note: app/src/banglejs/AndroidManifest.xml contains some extra permissions
|
// Note: app/src/banglejs/AndroidManifest.xml contains some extra permissions
|
||||||
}
|
}
|
||||||
@ -150,62 +140,34 @@ android {
|
|||||||
release {
|
release {
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
||||||
resValue "string", "pebble_content_provider", "com.getpebble.android.provider"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nightly {
|
nightly {
|
||||||
applicationIdSuffix ".nightly"
|
applicationIdSuffix ".nightly"
|
||||||
versionNameSuffix "-${getGitHashShort}"
|
versionNameSuffix "-${getGitHashShort}"
|
||||||
minifyEnabled true
|
|
||||||
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
||||||
resValue "string", "pebble_content_provider", "com.getpebble.android.provider"
|
minifyEnabled true
|
||||||
resValue "string", "app_name", "@string/application_name_main_nightly"
|
|
||||||
resValue "string", "title_activity_controlcenter", "@string/title_activity_controlcenter_main_nightly"
|
|
||||||
resValue "string", "about_activity_title", "@string/about_activity_title_main_nightly"
|
|
||||||
resValue "string", "about_description", "@string/about_description_main_nightly"
|
|
||||||
resValue "string", "gadgetbridge_running", "@string/gadgetbridge_running_main_nightly"
|
|
||||||
|
|
||||||
debuggable true
|
debuggable true
|
||||||
|
|
||||||
if (System.getProperty("nightly_store_file") != null) {
|
if (System.getProperty("nightly_store_file") != null) {
|
||||||
signingConfig signingConfigs.nightly
|
signingConfig signingConfigs.nightly
|
||||||
|
|
||||||
//this was conflicting with regular debug type (it should not), so it is only available for CI builds:
|
|
||||||
productFlavors.mainline.resValue "string", "pebble_content_provider", "com.getpebble.android.provider"
|
|
||||||
productFlavors.mainline.resValue "string", "app_name", "@string/application_name_main_nightly"
|
|
||||||
productFlavors.mainline.resValue "string", "title_activity_controlcenter", "@string/title_activity_controlcenter_main_nightly"
|
|
||||||
productFlavors.mainline.resValue "string", "about_activity_title", "@string/about_activity_title_main_nightly"
|
|
||||||
productFlavors.mainline.resValue "string", "about_description", "@string/about_description_main_nightly"
|
|
||||||
productFlavors.mainline.resValue "string", "gadgetbridge_running", "@string/gadgetbridge_running_main_nightly"
|
|
||||||
|
|
||||||
//keep the pebble provider for people who want this. In case of need we can create Banglejs Nopebble
|
|
||||||
productFlavors.banglejs.resValue "string", "pebble_content_provider", "com.getpebble.android.provider"
|
|
||||||
productFlavors.banglejs.resValue "string", "app_name", "@string/application_name_banglejs_nightly"
|
|
||||||
productFlavors.banglejs.resValue "string", "title_activity_controlcenter", "@string/title_activity_controlcenter_banglejs_nightly"
|
|
||||||
productFlavors.banglejs.resValue "string", "about_activity_title", "@string/about_activity_title_banglejs_nightly"
|
|
||||||
productFlavors.banglejs.resValue "string", "about_description", "@string/about_description_banglejs_nightly"
|
|
||||||
productFlavors.banglejs.resValue "string", "gadgetbridge_running", "@string/gadgetbridge_running_banglejs_nightly"
|
|
||||||
} else {
|
} else {
|
||||||
signingConfig signingConfigs.debug
|
signingConfig signingConfigs.debug
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nopebble {
|
nopebble {
|
||||||
applicationIdSuffix ".nightly_nopebble"
|
applicationIdSuffix ".nightly_nopebble"
|
||||||
versionNameSuffix "-${getGitHashShort}"
|
versionNameSuffix "-${getGitHashShort}"
|
||||||
|
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
|
debuggable true
|
||||||
|
|
||||||
if (System.getProperty("nightly_store_file") != null) {
|
if (System.getProperty("nightly_store_file") != null) {
|
||||||
signingConfig signingConfigs.nightly
|
signingConfig signingConfigs.nightly
|
||||||
} else {
|
} else {
|
||||||
signingConfig signingConfigs.debug
|
signingConfig signingConfigs.debug
|
||||||
}
|
}
|
||||||
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
|
||||||
resValue "string", "pebble_content_provider", "com.getpebble.android.nopebble.provider"
|
|
||||||
resValue "string", "app_name", "@string/application_name_main_nopebble"
|
|
||||||
resValue "string", "title_activity_controlcenter", "@string/title_activity_controlcenter_main_nopebble"
|
|
||||||
resValue "string", "about_activity_title", "@string/about_activity_title_main_nopebble"
|
|
||||||
resValue "string", "about_description", "@string/about_description_main_nopebble"
|
|
||||||
resValue "string", "gadgetbridge_running", "@string/gadgetbridge_running_main_nopebble"
|
|
||||||
debuggable true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
applicationVariants.all { variant ->
|
applicationVariants.all { variant ->
|
||||||
@ -221,7 +183,6 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lint {
|
lint {
|
||||||
@ -367,8 +328,8 @@ task pmd(type: Pmd) {
|
|||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main {
|
main {
|
||||||
main.java.srcDirs += "${protobuf.generatedFilesBaseDir}"
|
java.srcDirs += "${protobuf.generatedFilesBaseDir}"
|
||||||
main.java.srcDirs += "build/generated/source/buildConfig"
|
java.srcDirs += "build/generated/source/buildConfig"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -387,10 +348,10 @@ tasks.withType(SpotBugsTask) {
|
|||||||
xml.enabled = false
|
xml.enabled = false
|
||||||
html.enabled = true
|
html.enabled = true
|
||||||
xml {
|
xml {
|
||||||
destination file ("$project.buildDir/reports/spotbugs/spotbugs-output.xml")
|
destination file("$project.buildDir/reports/spotbugs/spotbugs-output.xml")
|
||||||
}
|
}
|
||||||
html {
|
html {
|
||||||
destination file ("$project.buildDir/reports/spotbugs/spotbugs-output.html")
|
destination file("$project.buildDir/reports/spotbugs/spotbugs-output.html")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
9
app/src/banglejs/res/values/strings.xml
Normal file
9
app/src/banglejs/res/values/strings.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string translatable="false" name="pebble_content_provider">com.getpebble.android.nopebble.bangle.provider</string>
|
||||||
|
<string translatable="false" name="app_name">@string/application_name_banglejs_main</string>
|
||||||
|
<string translatable="false" name="title_activity_controlcenter">@string/title_activity_controlcenter_banglejs_main</string>
|
||||||
|
<string translatable="false" name="about_activity_title">@string/about_activity_title_banglejs_main</string>
|
||||||
|
<string translatable="false" name="about_description">@string/about_description_banglejs_main</string>
|
||||||
|
<string translatable="false" name="gadgetbridge_running">@string/gadgetbridge_running_banglejs_main</string>
|
||||||
|
</resources>
|
10
app/src/banglejsNightly/res/values/strings.xml
Normal file
10
app/src/banglejsNightly/res/values/strings.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- enable Pebble content provider for Bangle.js nightly, so can that it can be installed besides regular Bangle.js release -->
|
||||||
|
<string translatable="false" name="pebble_content_provider">com.getpebble.android.provider</string>
|
||||||
|
<string translatable="false" name="app_name">@string/application_name_banglejs_nightly</string>
|
||||||
|
<string translatable="false" name="title_activity_controlcenter">@string/title_activity_controlcenter_banglejs_nightly</string>
|
||||||
|
<string translatable="false" name="about_activity_title">@string/about_activity_title_banglejs_nightly</string>
|
||||||
|
<string translatable="false" name="about_description">@string/about_description_banglejs_nightly</string>
|
||||||
|
<string translatable="false" name="gadgetbridge_running">@string/gadgetbridge_running_banglejs_nightly</string>
|
||||||
|
</resources>
|
@ -35,6 +35,9 @@
|
|||||||
<!-- Schedule exact alarms (eg. for DST changes) -->
|
<!-- Schedule exact alarms (eg. for DST changes) -->
|
||||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||||
|
|
||||||
|
<!-- Take wake locks (e.g. for time sync) -->
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
|
||||||
<!-- Read loyalty cards from Catima -->
|
<!-- Read loyalty cards from Catima -->
|
||||||
<uses-permission android:name="me.hackerchick.catima.READ_CARDS"/>
|
<uses-permission android:name="me.hackerchick.catima.READ_CARDS"/>
|
||||||
<uses-permission android:name="me.hackerchick.catima.debug.READ_CARDS"/>
|
<uses-permission android:name="me.hackerchick.catima.debug.READ_CARDS"/>
|
||||||
|
@ -246,7 +246,6 @@ public class GBApplication extends Application {
|
|||||||
loadAppsPebbleBlackList();
|
loadAppsPebbleBlackList();
|
||||||
|
|
||||||
PeriodicExporter.enablePeriodicExport(context);
|
PeriodicExporter.enablePeriodicExport(context);
|
||||||
TimeChangeReceiver.scheduleNextDstChange(context);
|
|
||||||
|
|
||||||
if (isRunningMarshmallowOrLater()) {
|
if (isRunningMarshmallowOrLater()) {
|
||||||
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
@ -125,9 +125,11 @@ public class AlarmDetails extends AbstractGBActivity {
|
|||||||
timePicker.setCurrentHour(alarm.getHour());
|
timePicker.setCurrentHour(alarm.getHour());
|
||||||
timePicker.setCurrentMinute(alarm.getMinute());
|
timePicker.setCurrentMinute(alarm.getMinute());
|
||||||
|
|
||||||
cbSmartWakeup.setChecked(alarm.getSmartWakeup());
|
boolean smartAlarmForced = forcedSmartWakeup(alarm.getPosition());
|
||||||
int smartAlarmVisibility = supportsSmartWakeup() ? View.VISIBLE : View.GONE;
|
cbSmartWakeup.setChecked(alarm.getSmartWakeup() || smartAlarmForced);
|
||||||
|
int smartAlarmVisibility = supportsSmartWakeup(alarm.getPosition()) ? View.VISIBLE : View.GONE;
|
||||||
cbSmartWakeup.setVisibility(smartAlarmVisibility);
|
cbSmartWakeup.setVisibility(smartAlarmVisibility);
|
||||||
|
cbSmartWakeup.setEnabled(!smartAlarmForced);
|
||||||
|
|
||||||
cbSnooze.setChecked(alarm.getSnooze());
|
cbSnooze.setChecked(alarm.getSnooze());
|
||||||
int snoozeVisibility = supportsSnoozing() ? View.VISIBLE : View.GONE;
|
int snoozeVisibility = supportsSnoozing() ? View.VISIBLE : View.GONE;
|
||||||
@ -153,10 +155,21 @@ public class AlarmDetails extends AbstractGBActivity {
|
|||||||
cbSunday.setChecked(alarm.getRepetition(Alarm.ALARM_SUN));
|
cbSunday.setChecked(alarm.getRepetition(Alarm.ALARM_SUN));
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean supportsSmartWakeup() {
|
private boolean supportsSmartWakeup(int position) {
|
||||||
if (device != null) {
|
if (device != null) {
|
||||||
DeviceCoordinator coordinator = device.getDeviceCoordinator();
|
DeviceCoordinator coordinator = device.getDeviceCoordinator();
|
||||||
return coordinator.supportsSmartWakeup(device);
|
return coordinator.supportsSmartWakeup(device, position);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The alarm at this position *must* be a smart alarm
|
||||||
|
*/
|
||||||
|
private boolean forcedSmartWakeup(int position) {
|
||||||
|
if (device != null) {
|
||||||
|
DeviceCoordinator coordinator = device.getDeviceCoordinator();
|
||||||
|
return coordinator.forcedSmartWakeup(device, position);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -205,7 +218,12 @@ public class AlarmDetails extends AbstractGBActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateAlarm() {
|
private void updateAlarm() {
|
||||||
alarm.setSmartWakeup(supportsSmartWakeup() && cbSmartWakeup.isChecked());
|
// Set alarm as used and enabled if time has changed
|
||||||
|
if (alarm.getUnused() && alarm.getHour() != timePicker.getCurrentHour() || alarm.getMinute() != timePicker.getCurrentMinute()) {
|
||||||
|
alarm.setUnused(false);
|
||||||
|
alarm.setEnabled(true);
|
||||||
|
}
|
||||||
|
alarm.setSmartWakeup(supportsSmartWakeup(alarm.getPosition()) && cbSmartWakeup.isChecked());
|
||||||
alarm.setSnooze(supportsSnoozing() && cbSnooze.isChecked());
|
alarm.setSnooze(supportsSnoozing() && cbSnooze.isChecked());
|
||||||
int repetitionMask = AlarmUtils.createRepetitionMask(cbMonday.isChecked(), cbTuesday.isChecked(), cbWednesday.isChecked(), cbThursday.isChecked(), cbFriday.isChecked(), cbSaturday.isChecked(), cbSunday.isChecked());
|
int repetitionMask = AlarmUtils.createRepetitionMask(cbMonday.isChecked(), cbTuesday.isChecked(), cbWednesday.isChecked(), cbThursday.isChecked(), cbFriday.isChecked(), cbSaturday.isChecked(), cbSunday.isChecked());
|
||||||
alarm.setRepetition(repetitionMask);
|
alarm.setRepetition(repetitionMask);
|
||||||
|
@ -71,6 +71,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPreferencesActi
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleSettingsActivity;
|
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleSettingsActivity;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.ConfigActivity;
|
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.ConfigActivity;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimePreferenceActivity;
|
import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimePreferenceActivity;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.externalevents.TimeChangeReceiver;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.Weather;
|
import nodomain.freeyourgadget.gadgetbridge.model.Weather;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||||
@ -173,6 +174,17 @@ public class SettingsActivity extends AbstractSettingsActivityV2 {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pref = findPreference("datetime_synconconnect");
|
||||||
|
if (pref != null) {
|
||||||
|
pref.setOnPreferenceChangeListener((preference, newVal) -> {
|
||||||
|
if (Boolean.TRUE.equals(newVal)) {
|
||||||
|
TimeChangeReceiver.scheduleNextDstChangeOrPeriodicSync(requireContext());
|
||||||
|
GBApplication.deviceService().onSetTime();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pref = findPreference("log_to_file");
|
pref = findPreference("log_to_file");
|
||||||
if (pref != null) {
|
if (pref != null) {
|
||||||
pref.setOnPreferenceChangeListener((preference, newVal) -> {
|
pref.setOnPreferenceChangeListener((preference, newVal) -> {
|
||||||
|
@ -319,7 +319,12 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsSmartWakeup(final GBDevice device) {
|
public boolean supportsSmartWakeup(GBDevice device, int alarmPosition) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean forcedSmartWakeup(GBDevice device, int alarmPosition) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -332,10 +332,17 @@ public interface DeviceCoordinator {
|
|||||||
int getAlarmSlotCount(GBDevice device);
|
int getAlarmSlotCount(GBDevice device);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if this device/coordinator supports alarms with smart wakeup
|
* Returns true if this device/coordinator supports an alarm with smart wakeup for the current position
|
||||||
* @return
|
* @param alarmPosition Position of the alarm
|
||||||
*/
|
*/
|
||||||
boolean supportsSmartWakeup(GBDevice device);
|
boolean supportsSmartWakeup(GBDevice device, int alarmPosition);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the alarm at the specified position *must* be a smart alarm for this device/coordinator
|
||||||
|
* @param alarmPosition Position of the alarm
|
||||||
|
* @return True if it must be a smart alarm, false otherwise
|
||||||
|
*/
|
||||||
|
boolean forcedSmartWakeup(GBDevice device, int alarmPosition);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if this device/coordinator supports alarm snoozing
|
* Returns true if this device/coordinator supports alarm snoozing
|
||||||
|
@ -151,11 +151,6 @@ public class UnknownDeviceCoordinator extends AbstractDeviceCoordinator {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -156,11 +156,6 @@ public class AsteroidOSDeviceCoordinator extends AbstractDeviceCoordinator {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -112,11 +112,6 @@ public class BangleJSCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -118,11 +118,6 @@ public class BinarySensorCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -111,11 +111,6 @@ public class CasioGB6900DeviceCoordinator extends CasioDeviceCoordinator {
|
|||||||
return 5; // 4 regular and one snooze
|
return 5; // 4 regular and one snooze
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -142,11 +142,6 @@ public class CasioGBX100DeviceCoordinator extends CasioDeviceCoordinator {
|
|||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -115,11 +115,6 @@ public class CasioGWB5600DeviceCoordinator extends CasioDeviceCoordinator {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsAppsManagement(final GBDevice device) {
|
public boolean supportsAppsManagement(final GBDevice device) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -80,11 +80,6 @@ public class DomyosT540Coordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -105,11 +105,6 @@ public class FitProDeviceCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return 8;
|
return 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -90,11 +90,6 @@ public class FlipperZeroCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getManufacturer() {
|
public String getManufacturer() {
|
||||||
return "Flipper devices";
|
return "Flipper devices";
|
||||||
|
@ -80,11 +80,6 @@ public abstract class GalaxyBudsGenericCoordinator extends AbstractBLClassicDevi
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -136,11 +136,6 @@ public class HPlusCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return 3; // FIXME - check the real value
|
return 3; // FIXME - check the real value
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -53,7 +53,7 @@ public class SG2Coordinator extends HPlusCoordinator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
public boolean supportsSmartWakeup(GBDevice device, int position) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -525,11 +525,6 @@ public abstract class HuamiCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsFindDevice() {
|
public boolean supportsFindDevice() {
|
||||||
return true;
|
return true;
|
||||||
|
@ -307,7 +307,7 @@ public abstract class ZeppOsCoordinator extends HuamiCoordinator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsSmartWakeup(final GBDevice device) {
|
public boolean supportsSmartWakeup(final GBDevice device, int position) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,8 +112,13 @@ public abstract class HuaweiBRCoordinator extends AbstractBLClassicDeviceCoordin
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
public boolean supportsSmartWakeup(GBDevice device, int position) {
|
||||||
return huaweiCoordinator.supportsSmartAlarm(device);
|
return huaweiCoordinator.supportsSmartAlarm(device, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean forcedSmartWakeup(GBDevice device, int alarmPosition) {
|
||||||
|
return huaweiCoordinator.forcedSmartWakeup(device, alarmPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -139,7 +144,7 @@ public abstract class HuaweiBRCoordinator extends AbstractBLClassicDeviceCoordin
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsWeather() {
|
public boolean supportsWeather() {
|
||||||
return false;
|
return huaweiCoordinator.supportsWeather();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -36,6 +36,7 @@ public final class HuaweiConstants {
|
|||||||
|
|
||||||
public static final int TAG_RESULT = 127;
|
public static final int TAG_RESULT = 127;
|
||||||
public static final byte[] RESULT_SUCCESS = new byte[]{0x00, 0x01, (byte)0x86, (byte)0xA0};
|
public static final byte[] RESULT_SUCCESS = new byte[]{0x00, 0x01, (byte)0x86, (byte)0xA0};
|
||||||
|
public static final int RESULT_SUCCESS_INT = 0x186a0;
|
||||||
|
|
||||||
public static class CryptoTags {
|
public static class CryptoTags {
|
||||||
public static final int encryption = 124;
|
public static final int encryption = 124;
|
||||||
|
@ -308,10 +308,19 @@ public class HuaweiCoordinator {
|
|||||||
public boolean supportsSmartAlarm() {
|
public boolean supportsSmartAlarm() {
|
||||||
return supportsCommandForService(0x08, 0x02) ;
|
return supportsCommandForService(0x08, 0x02) ;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean supportsSmartAlarm(GBDevice gbDevice) {
|
public boolean supportsSmartAlarm(GBDevice gbDevice) {
|
||||||
return supportsSmartAlarm() || getForceOption(gbDevice, PREF_FORCE_ENABLE_SMART_ALARM);
|
return supportsSmartAlarm() || getForceOption(gbDevice, PREF_FORCE_ENABLE_SMART_ALARM);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean supportsSmartAlarm(GBDevice gbDevice, int alarmPosition) {
|
||||||
|
return supportsSmartAlarm(gbDevice) && alarmPosition == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean forcedSmartWakeup(GBDevice device, int alarmPosition) {
|
||||||
|
return supportsSmartAlarm(device, alarmPosition) && alarmPosition == 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return True if alarms can be changed on the device, false otherwise
|
* @return True if alarms can be changed on the device, false otherwise
|
||||||
*/
|
*/
|
||||||
@ -327,6 +336,30 @@ public class HuaweiCoordinator {
|
|||||||
return supportsCommandForService(0x0c, 0x01);
|
return supportsCommandForService(0x0c, 0x01);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean supportsWeather() {
|
||||||
|
return supportsCommandForService(0x0f, 0x01);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean supportsWeatherUnit() {
|
||||||
|
return supportsCommandForService(0x0f, 0x05);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean supportsWeatherExtended() {
|
||||||
|
return supportsCommandForService(0x0f, 0x06);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean supportsWeatherForecasts() {
|
||||||
|
return supportsCommandForService(0x0f, 0x08);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean supportsWeatherMoonRiseSet() {
|
||||||
|
return supportsCommandForService(0x0f, 0x0a);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean supportsWeatherTides() {
|
||||||
|
return supportsCommandForService(0x0f, 0x0b);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean supportsWorkouts() {
|
public boolean supportsWorkouts() {
|
||||||
return supportsCommandForService(0x17, 0x01);
|
return supportsCommandForService(0x17, 0x01);
|
||||||
}
|
}
|
||||||
|
@ -112,8 +112,13 @@ public abstract class HuaweiLECoordinator extends AbstractBLEDeviceCoordinator i
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
public boolean supportsSmartWakeup(GBDevice device, int position) {
|
||||||
return huaweiCoordinator.supportsSmartAlarm(device);
|
return huaweiCoordinator.supportsSmartAlarm(device, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean forcedSmartWakeup(GBDevice device, int alarmPosition) {
|
||||||
|
return huaweiCoordinator.forcedSmartWakeup(device, alarmPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -139,7 +144,7 @@ public abstract class HuaweiLECoordinator extends AbstractBLEDeviceCoordinator i
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsWeather() {
|
public boolean supportsWeather() {
|
||||||
return false;
|
return huaweiCoordinator.supportsWeather();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -32,6 +32,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Alarms;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Alarms;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.AccountRelated;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.AccountRelated;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Calls;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Calls;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Workout;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Workout;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FindPhone;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FindPhone;
|
||||||
@ -475,6 +476,20 @@ public class HuaweiPacket {
|
|||||||
return new FindPhone.Response(paramsProvider).fromPacket(this);
|
return new FindPhone.Response(paramsProvider).fromPacket(this);
|
||||||
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
|
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
|
||||||
return this;
|
return this;
|
||||||
|
case Weather.id:
|
||||||
|
switch (this.commandId) {
|
||||||
|
case Weather.WeatherSupport.id:
|
||||||
|
return new Weather.WeatherSupport.Response(paramsProvider).fromPacket(this);
|
||||||
|
case Weather.WeatherExtendedSupport.id:
|
||||||
|
return new Weather.WeatherExtendedSupport.Response(paramsProvider).fromPacket(this);
|
||||||
|
case Weather.WeatherStart.id:
|
||||||
|
return new Weather.WeatherStart.Response(paramsProvider).fromPacket(this);
|
||||||
|
case Weather.WeatherSunMoonSupport.id:
|
||||||
|
return new Weather.WeatherSunMoonSupport.Response(paramsProvider).fromPacket(this);
|
||||||
|
default:
|
||||||
|
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
|
||||||
|
return this;
|
||||||
|
}
|
||||||
case Workout.id:
|
case Workout.id:
|
||||||
switch (this.commandId) {
|
switch (this.commandId) {
|
||||||
case Workout.WorkoutCount.id:
|
case Workout.WorkoutCount.id:
|
||||||
@ -538,6 +553,13 @@ public class HuaweiPacket {
|
|||||||
default:
|
default:
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
case Weather.id:
|
||||||
|
switch (this.commandId) {
|
||||||
|
case Weather.WeatherForecastData.id:
|
||||||
|
return new Weather.WeatherForecastData.OutgoingRequest(paramsProvider).fromPacket(this);
|
||||||
|
default:
|
||||||
|
return this;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2024 Damien Gaignon, Martin.JM
|
/* Copyright (C) 2024 Damien Gaignon, Martin.JM, Vitalii Tomin
|
||||||
|
|
||||||
This file is part of Gadgetbridge.
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
@ -24,6 +24,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -181,6 +182,10 @@ public class HuaweiTLV {
|
|||||||
return put(tag, ByteBuffer.allocate(8).putLong(value).array());
|
return put(tag, ByteBuffer.allocate(8).putLong(value).array());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HuaweiTLV put(int tag, Double value) {
|
||||||
|
return put(tag, ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putDouble(value).array());
|
||||||
|
}
|
||||||
|
|
||||||
public HuaweiTLV put(int tag, int value) {
|
public HuaweiTLV put(int tag, int value) {
|
||||||
return put(tag, ByteBuffer.allocate(4).putInt(value).array());
|
return put(tag, ByteBuffer.allocate(4).putInt(value).array());
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
/* Copyright (C) 2024 Vitalii Tomin, Martin.JM
|
||||||
|
|
||||||
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
|
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Gadgetbridge is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
|
||||||
|
|
||||||
|
public class GpsAndTime {
|
||||||
|
public static final byte id = 0x18;
|
||||||
|
|
||||||
|
public static class CurrentGPSRequest extends HuaweiPacket {
|
||||||
|
public static final byte id = 0x07;
|
||||||
|
public CurrentGPSRequest (
|
||||||
|
ParamsProvider paramsProvider,
|
||||||
|
int timestamp,
|
||||||
|
double lat,
|
||||||
|
double lon
|
||||||
|
) {
|
||||||
|
super(paramsProvider);
|
||||||
|
|
||||||
|
this.serviceId = GpsAndTime.id;
|
||||||
|
this.commandId = id;
|
||||||
|
this.tlv = new HuaweiTLV()
|
||||||
|
.put(0x01, timestamp)
|
||||||
|
.put(0x02, lon)
|
||||||
|
.put(0x03, lat);
|
||||||
|
this.isEncrypted = true;
|
||||||
|
this.complete = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,763 @@
|
|||||||
|
/* Copyright (C) 2024 Martin.JM
|
||||||
|
|
||||||
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
|
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Gadgetbridge is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
|
||||||
|
|
||||||
|
public class Weather {
|
||||||
|
public static final byte id = 0x0f;
|
||||||
|
|
||||||
|
public static class Settings {
|
||||||
|
// WeatherSupport
|
||||||
|
public boolean weatherSupported = false;
|
||||||
|
public boolean windSupported = false;
|
||||||
|
public boolean pm25Supported = false;
|
||||||
|
public boolean temperatureSupported = false;
|
||||||
|
public boolean locationNameSupported = false;
|
||||||
|
public boolean currentTemperatureSupported = false;
|
||||||
|
public boolean unitSupported = false;
|
||||||
|
public boolean airQualityIndexSupported = false;
|
||||||
|
|
||||||
|
// WeatherExtendedSupport
|
||||||
|
public boolean timeSupported = false;
|
||||||
|
public boolean sourceSupported = false;
|
||||||
|
public boolean weatherIconSupported = false;
|
||||||
|
|
||||||
|
// WeatherSunMoonSupport
|
||||||
|
public boolean sunRiseSetSupported = false;
|
||||||
|
public boolean moonPhaseSupported = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum WeatherIcon {
|
||||||
|
// Also used for the text on the watch
|
||||||
|
SUNNY,
|
||||||
|
CLOUDY,
|
||||||
|
OVERCAST,
|
||||||
|
SHOWERS,
|
||||||
|
THUNDERSTORMS,
|
||||||
|
THUNDER_AND_HAIL,
|
||||||
|
SLEET,
|
||||||
|
LIGHT_RAIN,
|
||||||
|
RAIN,
|
||||||
|
HEAVY_RAIN,
|
||||||
|
RAIN_STORM,
|
||||||
|
HEAVY_RAIN_STORMS,
|
||||||
|
SEVERE_RAIN_STORMS,
|
||||||
|
SNOW_FLURRIES,
|
||||||
|
LIGHT_SNOW,
|
||||||
|
SNOW,
|
||||||
|
HEAVY_SNOW,
|
||||||
|
SNOWSTORMS,
|
||||||
|
FOG,
|
||||||
|
FREEZING_RAIN,
|
||||||
|
DUST_STORM,
|
||||||
|
LIGHT_TO_MODERATE_RAIN,
|
||||||
|
MODERATE_TO_HEAVY_RAIN,
|
||||||
|
HEAVY_TO_SEVERE_RAIN,
|
||||||
|
HEAVY_TO_TORRENTIAL_RAIN,
|
||||||
|
SEVERE_TO_TORRENTIAL_RAIN,
|
||||||
|
LIGHT_TO_MODERATE_SNOW,
|
||||||
|
MODERATE_TO_HEAVY_SNOW,
|
||||||
|
HEAVY_SNOW_TO_BLIZZARD,
|
||||||
|
DUST,
|
||||||
|
SAND,
|
||||||
|
SANDSTORMS,
|
||||||
|
FREEZING, // misses small/non-moving icon
|
||||||
|
HOT, // misses small/non-moving icon
|
||||||
|
COLD, // misses small/non-moving icon
|
||||||
|
WINDY,
|
||||||
|
HAZY,
|
||||||
|
UNKNOWN // Good to have probably
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte iconToByte(WeatherIcon weatherIcon) {
|
||||||
|
switch (weatherIcon) {
|
||||||
|
case SUNNY:
|
||||||
|
return 0x00;
|
||||||
|
case CLOUDY:
|
||||||
|
return 0x01;
|
||||||
|
case OVERCAST:
|
||||||
|
return 0x02;
|
||||||
|
case SHOWERS:
|
||||||
|
return 0x03;
|
||||||
|
case THUNDERSTORMS:
|
||||||
|
return 0x04;
|
||||||
|
case THUNDER_AND_HAIL:
|
||||||
|
return 0x05;
|
||||||
|
case SLEET:
|
||||||
|
return 0x06;
|
||||||
|
case LIGHT_RAIN:
|
||||||
|
return 0x07;
|
||||||
|
case RAIN:
|
||||||
|
return 0x08;
|
||||||
|
case HEAVY_RAIN:
|
||||||
|
return 0x09;
|
||||||
|
case RAIN_STORM:
|
||||||
|
return 0x0a;
|
||||||
|
case HEAVY_RAIN_STORMS:
|
||||||
|
return 0x0b;
|
||||||
|
case SEVERE_RAIN_STORMS:
|
||||||
|
return 0x0c;
|
||||||
|
case SNOW_FLURRIES:
|
||||||
|
return 0x0d;
|
||||||
|
case LIGHT_SNOW:
|
||||||
|
return 0x0e;
|
||||||
|
case SNOW:
|
||||||
|
return 0x0f;
|
||||||
|
case HEAVY_SNOW:
|
||||||
|
return 0x10;
|
||||||
|
case SNOWSTORMS:
|
||||||
|
return 0x11;
|
||||||
|
case FOG:
|
||||||
|
return 0x12;
|
||||||
|
case FREEZING_RAIN:
|
||||||
|
return 0x13;
|
||||||
|
case DUST_STORM:
|
||||||
|
return 0x14;
|
||||||
|
case LIGHT_TO_MODERATE_RAIN:
|
||||||
|
return 0x15;
|
||||||
|
case MODERATE_TO_HEAVY_RAIN:
|
||||||
|
return 0x16;
|
||||||
|
case HEAVY_TO_SEVERE_RAIN:
|
||||||
|
return 0x17;
|
||||||
|
case HEAVY_TO_TORRENTIAL_RAIN:
|
||||||
|
return 0x18;
|
||||||
|
case SEVERE_TO_TORRENTIAL_RAIN:
|
||||||
|
return 0x19;
|
||||||
|
case LIGHT_TO_MODERATE_SNOW:
|
||||||
|
return 0x1a;
|
||||||
|
case MODERATE_TO_HEAVY_SNOW:
|
||||||
|
return 0x1b;
|
||||||
|
case HEAVY_SNOW_TO_BLIZZARD:
|
||||||
|
return 0x1c;
|
||||||
|
case DUST:
|
||||||
|
return 0x1d;
|
||||||
|
case SAND:
|
||||||
|
return 0x1e;
|
||||||
|
case SANDSTORMS:
|
||||||
|
return 0x1f;
|
||||||
|
case FREEZING:
|
||||||
|
return 0x20;
|
||||||
|
case HOT:
|
||||||
|
return 0x21;
|
||||||
|
case COLD:
|
||||||
|
return 0x22;
|
||||||
|
case WINDY:
|
||||||
|
return 0x23;
|
||||||
|
case HAZY:
|
||||||
|
return 0x35;
|
||||||
|
default:
|
||||||
|
return 0x63; // Any higher and the current weather breaks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static WeatherIcon byteToIcon(byte weatherIcon) {
|
||||||
|
switch (weatherIcon) {
|
||||||
|
case 0x00:
|
||||||
|
return WeatherIcon.SUNNY;
|
||||||
|
case 0x01:
|
||||||
|
return WeatherIcon.CLOUDY;
|
||||||
|
case 0x02:
|
||||||
|
return WeatherIcon.OVERCAST;
|
||||||
|
case 0x03:
|
||||||
|
return WeatherIcon.SHOWERS;
|
||||||
|
case 0x04:
|
||||||
|
return WeatherIcon.THUNDERSTORMS;
|
||||||
|
case 0x05:
|
||||||
|
return WeatherIcon.THUNDER_AND_HAIL;
|
||||||
|
case 0x06:
|
||||||
|
return WeatherIcon.SLEET;
|
||||||
|
case 0x07:
|
||||||
|
return WeatherIcon.LIGHT_RAIN;
|
||||||
|
case 0x08:
|
||||||
|
return WeatherIcon.RAIN;
|
||||||
|
case 0x09:
|
||||||
|
return WeatherIcon.HEAVY_RAIN;
|
||||||
|
case 0x0a:
|
||||||
|
return WeatherIcon.RAIN_STORM;
|
||||||
|
case 0x0b:
|
||||||
|
return WeatherIcon.HEAVY_RAIN_STORMS;
|
||||||
|
case 0x0c:
|
||||||
|
return WeatherIcon.SEVERE_RAIN_STORMS;
|
||||||
|
case 0x0d:
|
||||||
|
return WeatherIcon.SNOW_FLURRIES;
|
||||||
|
case 0x0e:
|
||||||
|
return WeatherIcon.LIGHT_SNOW;
|
||||||
|
case 0x0f:
|
||||||
|
return WeatherIcon.SNOW;
|
||||||
|
case 0x10:
|
||||||
|
return WeatherIcon.HEAVY_SNOW;
|
||||||
|
case 0x11:
|
||||||
|
return WeatherIcon.SNOWSTORMS;
|
||||||
|
case 0x12:
|
||||||
|
return WeatherIcon.FOG;
|
||||||
|
case 0x13:
|
||||||
|
return WeatherIcon.FREEZING_RAIN;
|
||||||
|
case 0x14:
|
||||||
|
return WeatherIcon.DUST_STORM;
|
||||||
|
case 0x15:
|
||||||
|
return WeatherIcon.LIGHT_TO_MODERATE_RAIN;
|
||||||
|
case 0x16:
|
||||||
|
return WeatherIcon.MODERATE_TO_HEAVY_RAIN;
|
||||||
|
case 0x17:
|
||||||
|
return WeatherIcon.HEAVY_TO_SEVERE_RAIN;
|
||||||
|
case 0x18:
|
||||||
|
return WeatherIcon.HEAVY_TO_TORRENTIAL_RAIN;
|
||||||
|
case 0x19:
|
||||||
|
return WeatherIcon.SEVERE_TO_TORRENTIAL_RAIN;
|
||||||
|
case 0x1a:
|
||||||
|
return WeatherIcon.LIGHT_TO_MODERATE_SNOW;
|
||||||
|
case 0x1b:
|
||||||
|
return WeatherIcon.MODERATE_TO_HEAVY_SNOW;
|
||||||
|
case 0x1c:
|
||||||
|
return WeatherIcon.HEAVY_SNOW_TO_BLIZZARD;
|
||||||
|
case 0x1d:
|
||||||
|
return WeatherIcon.DUST;
|
||||||
|
case 0x1e:
|
||||||
|
return WeatherIcon.SAND;
|
||||||
|
case 0x1f:
|
||||||
|
return WeatherIcon.SANDSTORMS;
|
||||||
|
case 0x20:
|
||||||
|
return WeatherIcon.FREEZING;
|
||||||
|
case 0x21:
|
||||||
|
return WeatherIcon.HOT;
|
||||||
|
case 0x22:
|
||||||
|
return WeatherIcon.COLD;
|
||||||
|
case 0x23:
|
||||||
|
return WeatherIcon.WINDY;
|
||||||
|
case 0x35:
|
||||||
|
return WeatherIcon.HAZY;
|
||||||
|
default:
|
||||||
|
return WeatherIcon.UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum HuaweiTemperatureFormat {
|
||||||
|
CELSIUS,
|
||||||
|
FAHRENHEIT
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte temperatureFormatToByte(HuaweiTemperatureFormat temperatureFormat) {
|
||||||
|
if (temperatureFormat == HuaweiTemperatureFormat.FAHRENHEIT)
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum MoonPhase {
|
||||||
|
NEW_MOON,
|
||||||
|
WAXING_CRESCENT,
|
||||||
|
FIRST_QUARTER,
|
||||||
|
WAXING_GIBBOUS,
|
||||||
|
FULL_MOON,
|
||||||
|
WANING_GIBBOUS,
|
||||||
|
THIRD_QUARTER,
|
||||||
|
WANING_CRESCENT
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MoonPhase degreesToMoonPhase(int degrees) {
|
||||||
|
final int leeway = 6; // Give some leeway for the new moon, first quarter, full moon, and third quarter
|
||||||
|
if (degrees < 0 || degrees > 360)
|
||||||
|
return null;
|
||||||
|
else if (degrees >= 360 - leeway || degrees <= leeway)
|
||||||
|
return MoonPhase.NEW_MOON;
|
||||||
|
else if (degrees < 90)
|
||||||
|
return MoonPhase.WAXING_CRESCENT;
|
||||||
|
else if (degrees <= 90 + leeway)
|
||||||
|
return MoonPhase.FIRST_QUARTER;
|
||||||
|
else if (degrees < 180 - leeway)
|
||||||
|
return MoonPhase.WAXING_GIBBOUS;
|
||||||
|
else if (degrees <= 180 + leeway)
|
||||||
|
return MoonPhase.FULL_MOON;
|
||||||
|
else if (degrees < 270 - leeway)
|
||||||
|
return MoonPhase.WANING_GIBBOUS;
|
||||||
|
else if (degrees <= 270 + leeway)
|
||||||
|
return MoonPhase.THIRD_QUARTER;
|
||||||
|
else
|
||||||
|
return MoonPhase.WANING_CRESCENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte moonPhaseToByte (MoonPhase moonPhase) {
|
||||||
|
switch (moonPhase) {
|
||||||
|
case NEW_MOON:
|
||||||
|
return 1;
|
||||||
|
case WAXING_CRESCENT:
|
||||||
|
return 2;
|
||||||
|
case FIRST_QUARTER:
|
||||||
|
return 3;
|
||||||
|
case WAXING_GIBBOUS:
|
||||||
|
return 4;
|
||||||
|
case FULL_MOON:
|
||||||
|
return 5;
|
||||||
|
case WANING_GIBBOUS:
|
||||||
|
return 6;
|
||||||
|
case THIRD_QUARTER:
|
||||||
|
return 7;
|
||||||
|
case WANING_CRESCENT:
|
||||||
|
return 8;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MoonPhase byteToMoonPhase(byte moonPhase) {
|
||||||
|
switch (moonPhase) {
|
||||||
|
case 1:
|
||||||
|
return MoonPhase.NEW_MOON;
|
||||||
|
case 2:
|
||||||
|
return MoonPhase.WAXING_CRESCENT;
|
||||||
|
case 3:
|
||||||
|
return MoonPhase.FIRST_QUARTER;
|
||||||
|
case 4:
|
||||||
|
return MoonPhase.WAXING_GIBBOUS;
|
||||||
|
case 5:
|
||||||
|
return MoonPhase.FULL_MOON;
|
||||||
|
case 6:
|
||||||
|
return MoonPhase.WANING_GIBBOUS;
|
||||||
|
case 7:
|
||||||
|
return MoonPhase.THIRD_QUARTER;
|
||||||
|
case 8:
|
||||||
|
return MoonPhase.WANING_CRESCENT;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class CurrentWeatherRequest extends HuaweiPacket {
|
||||||
|
public static final byte id = 0x01;
|
||||||
|
|
||||||
|
public CurrentWeatherRequest(
|
||||||
|
ParamsProvider paramsProvider,
|
||||||
|
Settings settings,
|
||||||
|
WeatherIcon icon,
|
||||||
|
Byte windDirection,
|
||||||
|
Byte windSpeed,
|
||||||
|
Byte lowestTemperature,
|
||||||
|
Byte highestTemperature,
|
||||||
|
Short pm25, // TODO: might be float?
|
||||||
|
String locationName,
|
||||||
|
Byte currentTemperature,
|
||||||
|
HuaweiTemperatureFormat temperatureUnit,
|
||||||
|
Short airQualityIndex,
|
||||||
|
Integer observationTime,
|
||||||
|
String sourceName
|
||||||
|
) {
|
||||||
|
super(paramsProvider);
|
||||||
|
|
||||||
|
this.serviceId = Weather.id;
|
||||||
|
this.commandId = id;
|
||||||
|
this.tlv = new HuaweiTLV();
|
||||||
|
|
||||||
|
HuaweiTLV tlv81 = new HuaweiTLV();
|
||||||
|
|
||||||
|
if (icon != null && settings.weatherIconSupported) {
|
||||||
|
tlv81.put(0x02, iconToByte(icon));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.windSupported) {
|
||||||
|
short wind = 0;
|
||||||
|
if (windSpeed != null)
|
||||||
|
wind = (short) windSpeed;
|
||||||
|
|
||||||
|
if (windDirection != null) {
|
||||||
|
if (windDirection > 0)
|
||||||
|
wind |= ((short) (windDirection * 8 / 360)) << 8;
|
||||||
|
else
|
||||||
|
wind |= ((short) (360 + windDirection) * 8 / 360) << 8;
|
||||||
|
}
|
||||||
|
tlv81.put(0x03, wind);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.weatherIconSupported || settings.windSupported)
|
||||||
|
this.tlv.put(0x81, tlv81);
|
||||||
|
|
||||||
|
if (lowestTemperature != null && highestTemperature != null && settings.temperatureSupported) {
|
||||||
|
this.tlv.put(0x85, new HuaweiTLV()
|
||||||
|
.put(0x06, lowestTemperature)
|
||||||
|
.put(0x07, highestTemperature)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (windDirection != null && windSpeed != null && settings.windSupported)
|
||||||
|
this.tlv.put(0x03, (short) ((((short) windDirection) << 8) | ((short) windSpeed)));
|
||||||
|
if (pm25 != null && settings.pm25Supported)
|
||||||
|
this.tlv.put(0x04, pm25);
|
||||||
|
if (locationName != null && settings.locationNameSupported)
|
||||||
|
this.tlv.put(0x08, locationName);
|
||||||
|
if (currentTemperature != null && settings.currentTemperatureSupported)
|
||||||
|
this.tlv.put(0x09, currentTemperature);
|
||||||
|
if (temperatureUnit != null && settings.unitSupported)
|
||||||
|
this.tlv.put(0x0a, temperatureFormatToByte(temperatureUnit));
|
||||||
|
if (airQualityIndex != null && settings.airQualityIndexSupported)
|
||||||
|
this.tlv.put(0x0b, airQualityIndex);
|
||||||
|
if (observationTime != null && settings.timeSupported)
|
||||||
|
this.tlv.put(0x0c, observationTime);
|
||||||
|
if (sourceName != null && settings.sourceSupported)
|
||||||
|
this.tlv.put(0x0e, sourceName);
|
||||||
|
this.tlv.put(0x0f, (byte) 0);
|
||||||
|
|
||||||
|
this.isEncrypted = true;
|
||||||
|
this.complete = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class WeatherSupport {
|
||||||
|
public static final byte id = 0x02;
|
||||||
|
|
||||||
|
public static class Request extends HuaweiPacket {
|
||||||
|
public Request(ParamsProvider paramsProvider) {
|
||||||
|
super(paramsProvider);
|
||||||
|
|
||||||
|
this.serviceId = Weather.id;
|
||||||
|
this.commandId = id;
|
||||||
|
this.tlv = new HuaweiTLV().put(0x01);
|
||||||
|
this.isEncrypted = true;
|
||||||
|
this.complete = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Response extends HuaweiPacket {
|
||||||
|
public byte supportedBitmap = 0;
|
||||||
|
|
||||||
|
public boolean weatherSupported = false;
|
||||||
|
public boolean windSupported = false;
|
||||||
|
public boolean pm25Supported = false;
|
||||||
|
public boolean temperatureSupported = false;
|
||||||
|
public boolean locationNameSupported = false;
|
||||||
|
public boolean currentTemperatureSupported = false;
|
||||||
|
public boolean unitSupported = false;
|
||||||
|
public boolean airQualityIndexSupported = false;
|
||||||
|
|
||||||
|
public Response(ParamsProvider paramsProvider) {
|
||||||
|
super(paramsProvider);
|
||||||
|
this.serviceId = Weather.id;
|
||||||
|
this.commandId = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void parseTlv() throws ParseException {
|
||||||
|
if (!this.tlv.contains(0x01))
|
||||||
|
throw new MissingTagException(0x01);
|
||||||
|
this.supportedBitmap = this.tlv.getByte(0x01);
|
||||||
|
|
||||||
|
this.weatherSupported = (this.supportedBitmap & 0x01) != 0;
|
||||||
|
this.windSupported = (this.supportedBitmap & 0x02) != 0;
|
||||||
|
this.pm25Supported = (this.supportedBitmap & 0x04) != 0;
|
||||||
|
this.temperatureSupported = (this.supportedBitmap & 0x08) != 0;
|
||||||
|
this.locationNameSupported = (this.supportedBitmap & 0x10) != 0;
|
||||||
|
this.currentTemperatureSupported = (this.supportedBitmap & 0x20) != 0;
|
||||||
|
this.unitSupported = (this.supportedBitmap & 0x40) != 0;
|
||||||
|
this.airQualityIndexSupported = (this.supportedBitmap & 0x80) != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class WeatherDeviceRequest extends HuaweiPacket {
|
||||||
|
public static final byte id = 0x04;
|
||||||
|
|
||||||
|
public WeatherDeviceRequest(ParamsProvider paramsProvider, int responseValue) {
|
||||||
|
super(paramsProvider);
|
||||||
|
|
||||||
|
this.serviceId = Weather.id;
|
||||||
|
this.commandId = id;
|
||||||
|
this.tlv = new HuaweiTLV().put(0x01, responseValue);
|
||||||
|
this.isEncrypted = false;
|
||||||
|
this.complete = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class WeatherUnitRequest extends HuaweiPacket {
|
||||||
|
public static final byte id = 0x05;
|
||||||
|
|
||||||
|
public WeatherUnitRequest(ParamsProvider paramsProvider, HuaweiTemperatureFormat temperatureFormat) {
|
||||||
|
super(paramsProvider);
|
||||||
|
|
||||||
|
this.serviceId = Weather.id;
|
||||||
|
this.commandId = id;
|
||||||
|
this.tlv = new HuaweiTLV().put(0x01, temperatureFormatToByte(temperatureFormat));
|
||||||
|
this.isEncrypted = true;
|
||||||
|
this.complete = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class WeatherExtendedSupport {
|
||||||
|
public static final byte id = 0x06;
|
||||||
|
|
||||||
|
public static class Request extends HuaweiPacket {
|
||||||
|
public Request(ParamsProvider paramsProvider) {
|
||||||
|
super(paramsProvider);
|
||||||
|
|
||||||
|
this.serviceId = Weather.id;
|
||||||
|
this.commandId = id;
|
||||||
|
this.tlv = new HuaweiTLV().put(0x01);
|
||||||
|
this.isEncrypted = true;
|
||||||
|
this.complete = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Response extends HuaweiPacket {
|
||||||
|
public short supportedBitmap = 0;
|
||||||
|
|
||||||
|
public boolean timeSupported = false;
|
||||||
|
public boolean sourceSupported = false;
|
||||||
|
public boolean weatherIconSupported = false;
|
||||||
|
|
||||||
|
public Response(ParamsProvider paramsProvider) {
|
||||||
|
super(paramsProvider);
|
||||||
|
this.serviceId = Weather.id;
|
||||||
|
this.commandId = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void parseTlv() throws ParseException {
|
||||||
|
if (!this.tlv.contains(0x01))
|
||||||
|
throw new MissingTagException(0x01);
|
||||||
|
this.supportedBitmap = this.tlv.getShort(0x01);
|
||||||
|
|
||||||
|
this.timeSupported = (this.supportedBitmap & 0x01) != 0;
|
||||||
|
this.sourceSupported = (this.supportedBitmap & 0x02) != 0;
|
||||||
|
this.weatherIconSupported = (this.supportedBitmap & 0x04) != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class WeatherForecastData {
|
||||||
|
public static final byte id = 0x08;
|
||||||
|
|
||||||
|
public static class TimeData {
|
||||||
|
public int timestamp;
|
||||||
|
public WeatherIcon icon;
|
||||||
|
public Byte temperature;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
String timestampStr = new Date(timestamp * 1000L).toString();
|
||||||
|
return "TimeData{" +
|
||||||
|
"timestamp=" + timestamp +
|
||||||
|
", timestamp=" + timestampStr +
|
||||||
|
", icon=" + icon +
|
||||||
|
", temperature=" + temperature +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DayData {
|
||||||
|
public int timestamp;
|
||||||
|
public WeatherIcon icon;
|
||||||
|
public Byte highTemperature;
|
||||||
|
public Byte lowTemperature;
|
||||||
|
public Integer sunriseTime;
|
||||||
|
public Integer sunsetTime;
|
||||||
|
public Integer moonRiseTime;
|
||||||
|
public Integer moonSetTime;
|
||||||
|
public MoonPhase moonPhase;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
String timestampStr = new Date(timestamp * 1000L).toString();
|
||||||
|
return "DayData{" +
|
||||||
|
"timestamp=" + timestamp +
|
||||||
|
", timestamp=" + timestampStr +
|
||||||
|
", icon=" + icon +
|
||||||
|
", highTemperature=" + highTemperature +
|
||||||
|
", lowTemperature=" + lowTemperature +
|
||||||
|
", sunriseTime=" + sunriseTime +
|
||||||
|
", sunsetTime=" + sunsetTime +
|
||||||
|
", moonRiseTime=" + moonRiseTime +
|
||||||
|
", moonSetTime=" + moonSetTime +
|
||||||
|
", moonPhase=" + moonPhase +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Request extends HuaweiPacket {
|
||||||
|
public Request(
|
||||||
|
ParamsProvider paramsProvider,
|
||||||
|
Settings settings,
|
||||||
|
List<TimeData> timeDataList,
|
||||||
|
List<DayData> dayDataList
|
||||||
|
) {
|
||||||
|
super(paramsProvider);
|
||||||
|
|
||||||
|
this.serviceId = Weather.id;
|
||||||
|
this.commandId = id;
|
||||||
|
this.tlv = new HuaweiTLV();
|
||||||
|
|
||||||
|
if (timeDataList != null && !timeDataList.isEmpty()) {
|
||||||
|
HuaweiTLV timeDataTlv = new HuaweiTLV();
|
||||||
|
for (TimeData timeData : timeDataList) {
|
||||||
|
HuaweiTLV timeTlv = new HuaweiTLV();
|
||||||
|
timeTlv.put(0x03, timeData.timestamp);
|
||||||
|
if (timeData.icon != null && settings.weatherIconSupported)
|
||||||
|
timeTlv.put(0x04, iconToByte(timeData.icon));
|
||||||
|
if (timeData.temperature != null && (settings.temperatureSupported || settings.currentTemperatureSupported))
|
||||||
|
timeTlv.put(0x05, timeData.temperature);
|
||||||
|
timeDataTlv.put(0x82, timeTlv);
|
||||||
|
}
|
||||||
|
this.tlv.put(0x81, timeDataTlv);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dayDataList != null && !dayDataList.isEmpty()) {
|
||||||
|
HuaweiTLV dayDataTlv = new HuaweiTLV();
|
||||||
|
for (DayData dayData : dayDataList) {
|
||||||
|
HuaweiTLV dayTlv = new HuaweiTLV();
|
||||||
|
dayTlv.put(0x12, dayData.timestamp);
|
||||||
|
if (dayData.icon != null && settings.weatherIconSupported)
|
||||||
|
dayTlv.put(0x13, iconToByte(dayData.icon));
|
||||||
|
if (settings.temperatureSupported) {
|
||||||
|
if (dayData.highTemperature != null)
|
||||||
|
dayTlv.put(0x14, dayData.highTemperature);
|
||||||
|
if (dayData.lowTemperature != null)
|
||||||
|
dayTlv.put(0x15, dayData.lowTemperature);
|
||||||
|
}
|
||||||
|
if (settings.sunRiseSetSupported) {
|
||||||
|
if (dayData.sunriseTime != null)
|
||||||
|
dayTlv.put(0x16, dayData.sunriseTime);
|
||||||
|
if (dayData.sunsetTime != null)
|
||||||
|
dayTlv.put(0x17, dayData.sunsetTime);
|
||||||
|
if (dayData.moonRiseTime != null)
|
||||||
|
dayTlv.put(0x1a, dayData.moonRiseTime);
|
||||||
|
if (dayData.moonSetTime != null)
|
||||||
|
dayTlv.put(0x1b, dayData.moonSetTime);
|
||||||
|
}
|
||||||
|
if (dayData.moonPhase != null && settings.moonPhaseSupported)
|
||||||
|
dayTlv.put(0x1e, moonPhaseToByte(dayData.moonPhase));
|
||||||
|
dayDataTlv.put(0x91, dayTlv);
|
||||||
|
}
|
||||||
|
this.tlv.put(0x90, dayDataTlv);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isEncrypted = true;
|
||||||
|
this.isSliced = true;
|
||||||
|
this.complete = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class OutgoingRequest extends HuaweiPacket {
|
||||||
|
List<TimeData> timeDataList;
|
||||||
|
List<DayData> dayDataList;
|
||||||
|
|
||||||
|
public OutgoingRequest(ParamsProvider paramsProvider) {
|
||||||
|
super(paramsProvider);
|
||||||
|
this.complete = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void parseTlv() throws ParseException {
|
||||||
|
timeDataList = new ArrayList<>(this.tlv.getObject(0x81).getObjects(0x82).size());
|
||||||
|
for (HuaweiTLV timeTlv : this.tlv.getObject(0x81).getObjects(0x82)) {
|
||||||
|
TimeData timeData = new TimeData();
|
||||||
|
timeData.timestamp = timeTlv.getInteger(0x03);
|
||||||
|
timeData.icon = byteToIcon(timeTlv.getByte(0x04));
|
||||||
|
timeData.temperature = timeTlv.getByte(0x05);
|
||||||
|
timeDataList.add(timeData);
|
||||||
|
}
|
||||||
|
|
||||||
|
dayDataList = new ArrayList<>(this.tlv.getObject(0x90).getObjects(0x91).size());
|
||||||
|
for (HuaweiTLV dayTlv : this.tlv.getObject(0x90).getObjects(0x91)) {
|
||||||
|
DayData dayData = new DayData();
|
||||||
|
dayData.timestamp = dayTlv.getInteger(0x12);
|
||||||
|
dayData.icon = byteToIcon(dayTlv.getByte(0x13));
|
||||||
|
dayData.highTemperature = dayTlv.getByte(0x14);
|
||||||
|
dayData.lowTemperature = dayTlv.getByte(0x15);
|
||||||
|
dayData.sunriseTime = dayTlv.getInteger(0x16);
|
||||||
|
dayData.sunsetTime = dayTlv.getInteger(0x17);
|
||||||
|
dayData.moonRiseTime = dayTlv.getInteger(0x1a);
|
||||||
|
dayData.moonSetTime = dayTlv.getInteger(0x1b);
|
||||||
|
dayData.moonPhase = byteToMoonPhase(dayTlv.getByte(0x1e));
|
||||||
|
dayDataList.add(dayData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class WeatherStart {
|
||||||
|
public static final byte id = 0x09;
|
||||||
|
|
||||||
|
public static class Request extends HuaweiPacket {
|
||||||
|
|
||||||
|
public Request(ParamsProvider paramsProvider) {
|
||||||
|
super(paramsProvider);
|
||||||
|
|
||||||
|
this.serviceId = Weather.id;
|
||||||
|
this.commandId = id;
|
||||||
|
this.tlv = new HuaweiTLV().put(0x01, (byte) 0x03); // TODO: find out what this means
|
||||||
|
this.isEncrypted = true;
|
||||||
|
this.complete = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Response extends HuaweiPacket {
|
||||||
|
public int successCode = -1;
|
||||||
|
public boolean success = false;
|
||||||
|
|
||||||
|
public Response(ParamsProvider paramsProvider) {
|
||||||
|
super(paramsProvider);
|
||||||
|
this.serviceId = Weather.id;
|
||||||
|
this.commandId = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void parseTlv() throws ParseException {
|
||||||
|
this.successCode = this.tlv.getInteger(0x7f);
|
||||||
|
this.success = this.successCode == 0x000186A0 || this.successCode == 0x000186A3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class WeatherSunMoonSupport {
|
||||||
|
public static final byte id = 0x0a;
|
||||||
|
|
||||||
|
public static class Request extends HuaweiPacket {
|
||||||
|
public Request(ParamsProvider paramsProvider) {
|
||||||
|
super(paramsProvider);
|
||||||
|
|
||||||
|
this.serviceId = Weather.id;
|
||||||
|
this.commandId = id;
|
||||||
|
this.tlv = new HuaweiTLV().put(0x01);
|
||||||
|
this.isEncrypted = true;
|
||||||
|
this.complete = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Response extends HuaweiPacket {
|
||||||
|
public int supportedBitmap = 0;
|
||||||
|
|
||||||
|
public boolean sunRiseSetSupported = false;
|
||||||
|
public boolean moonPhaseSupported = false;
|
||||||
|
|
||||||
|
public Response(ParamsProvider paramsProvider) {
|
||||||
|
super(paramsProvider);
|
||||||
|
this.serviceId = Weather.id;
|
||||||
|
this.commandId = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void parseTlv() throws ParseException {
|
||||||
|
if (!this.tlv.contains(0x01))
|
||||||
|
throw new MissingTagException(0x01);
|
||||||
|
this.supportedBitmap = this.tlv.getInteger(0x01);
|
||||||
|
|
||||||
|
this.sunRiseSetSupported = (this.supportedBitmap & 0x01) != 0;
|
||||||
|
this.moonPhaseSupported = (this.supportedBitmap & 0x02) != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -105,11 +105,6 @@ public class ID115Coordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -102,11 +102,6 @@ public class ITagCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -188,12 +188,6 @@ public class BFH16DeviceCoordinator extends AbstractBLEDeviceCoordinator
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsWeather()
|
public boolean supportsWeather()
|
||||||
{
|
{
|
||||||
|
@ -140,11 +140,6 @@ public class TeclastH30Coordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -92,7 +92,7 @@ public class Y5Coordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
public boolean supportsSmartWakeup(GBDevice device, int position) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,11 +108,6 @@ public class LefunDeviceCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return NUM_ALARM_SLOTS;
|
return NUM_ALARM_SLOTS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -140,11 +140,6 @@ public class WatchXPlusDeviceCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -80,11 +80,6 @@ public class LiveviewCoordinator extends AbstractBLClassicDeviceCoordinator {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -223,11 +223,6 @@ public class MakibesHR3Coordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return 8;
|
return 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -139,7 +139,7 @@ public class MiBandCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
public boolean supportsSmartWakeup(GBDevice device, int position) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,11 +115,6 @@ public class MiScale2DeviceCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -101,11 +101,6 @@ public class No1F1Coordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -109,11 +109,6 @@ public class NutCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -131,11 +131,6 @@ public class PebbleCoordinator extends AbstractBLClassicDeviceCoordinator {
|
|||||||
return 16;
|
return 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return PebbleUtils.hasHRM(device.getModel());
|
return PebbleUtils.hasHRM(device.getModel());
|
||||||
|
@ -84,11 +84,6 @@ public class PineTimeJFCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -99,11 +99,6 @@ public class QC35Coordinator extends AbstractBLClassicDeviceCoordinator {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -166,11 +166,6 @@ public class QHybridCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return isHybridHR();
|
return isHybridHR();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return this.isHybridHR();
|
return this.isHybridHR();
|
||||||
|
@ -89,11 +89,6 @@ public abstract class RoidmiCoordinator extends AbstractBLClassicDeviceCoordinat
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -107,12 +107,6 @@ public class SMAQ2OSSCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -79,11 +79,6 @@ public class SoFlowCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -98,11 +98,6 @@ public abstract class SonyHeadphonesCoordinator extends AbstractBLClassicDeviceC
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -213,7 +213,7 @@ public class SonyWena3Coordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
public boolean supportsSmartWakeup(GBDevice device, int position) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ public class SonySWR12DeviceCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
public boolean supportsSmartWakeup(GBDevice device, int position) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,11 +113,6 @@ public class SuperCarsCoordinator extends AbstractDeviceCoordinator {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getManufacturer() {
|
public String getManufacturer() {
|
||||||
return "Brand Base";
|
return "Brand Base";
|
||||||
|
@ -203,12 +203,17 @@ public class TestDeviceCoordinator extends AbstractDeviceCoordinator {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getAlarmSlotCount(final GBDevice device) {
|
public int getAlarmSlotCount(final GBDevice device) {
|
||||||
return super.getAlarmSlotCount(device);
|
return 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsSmartWakeup(final GBDevice device) {
|
public boolean supportsSmartWakeup(final GBDevice device, int position) {
|
||||||
return supports(getTestDevice(), TestFeature.SMART_WAKEUP);
|
return supports(getTestDevice(), TestFeature.SMART_WAKEUP) && position <= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean forcedSmartWakeup(final GBDevice device, final int alarmPosition) {
|
||||||
|
return supports(getTestDevice(), TestFeature.SMART_WAKEUP_FORCED_SLOT) && alarmPosition == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -56,6 +56,7 @@ public enum TestFeature {
|
|||||||
SCREENSHOTS,
|
SCREENSHOTS,
|
||||||
SLEEP_RESPIRATORY_RATE,
|
SLEEP_RESPIRATORY_RATE,
|
||||||
SMART_WAKEUP,
|
SMART_WAKEUP,
|
||||||
|
SMART_WAKEUP_FORCED_SLOT,
|
||||||
SPO2,
|
SPO2,
|
||||||
STRESS_MEASUREMENT,
|
STRESS_MEASUREMENT,
|
||||||
UNICODE_EMOJIS,
|
UNICODE_EMOJIS,
|
||||||
|
@ -88,11 +88,6 @@ public class TLW64Coordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -118,11 +118,6 @@ public class UM25Coordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -125,11 +125,6 @@ public class VescCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -81,11 +81,6 @@ public class VibratissimoCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -94,11 +94,6 @@ public class VivomoveHrCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -104,11 +104,6 @@ public class WaspOSCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -116,10 +116,6 @@ public class Watch9DeviceCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return 3; // FIXME - check the real value
|
return 3; // FIXME - check the real value
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
|
@ -131,7 +131,7 @@ public class WithingsSteelHRDeviceCoordinator extends AbstractDeviceCoordinator
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
public boolean supportsSmartWakeup(GBDevice device, int position) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,7 +174,7 @@ public abstract class XiaomiCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsSmartWakeup(final GBDevice device) {
|
public boolean supportsSmartWakeup(final GBDevice device, int position) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,11 +87,6 @@ public class XWatchCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -129,11 +129,6 @@ public class ZeTimeCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSmartWakeup(GBDevice device) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsRealtimeData() {
|
public boolean supportsRealtimeData() {
|
||||||
return true;
|
return true;
|
||||||
|
@ -35,6 +35,7 @@ import java.util.Date;
|
|||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils;
|
import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils;
|
||||||
@ -44,7 +45,9 @@ import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
|||||||
public class TimeChangeReceiver extends BroadcastReceiver {
|
public class TimeChangeReceiver extends BroadcastReceiver {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(TimeChangeReceiver.class);
|
private static final Logger LOG = LoggerFactory.getLogger(TimeChangeReceiver.class);
|
||||||
|
|
||||||
public static final String ACTION_DST_CHANGED = "nodomain.freeyourgadget.gadgetbridge.DST_CHANGED";
|
public static final String ACTION_DST_CHANGED_OR_PERIODIC_SYNC = "nodomain.freeyourgadget.gadgetbridge.DST_CHANGED_OR_PERIODIC_SYNC";
|
||||||
|
public static final long PERIODIC_SYNC_INTERVAL_MS = 158003000; // 43:53:23.000
|
||||||
|
public static final long PERIODIC_SYNC_INTERVAL_MAX_MS = 172800000; // 48 hours
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
@ -63,7 +66,7 @@ public class TimeChangeReceiver extends BroadcastReceiver {
|
|||||||
switch (action) {
|
switch (action) {
|
||||||
case Intent.ACTION_TIME_CHANGED:
|
case Intent.ACTION_TIME_CHANGED:
|
||||||
case Intent.ACTION_TIMEZONE_CHANGED:
|
case Intent.ACTION_TIMEZONE_CHANGED:
|
||||||
case ACTION_DST_CHANGED:
|
case ACTION_DST_CHANGED_OR_PERIODIC_SYNC:
|
||||||
// Continue after the switch
|
// Continue after the switch
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -71,49 +74,62 @@ public class TimeChangeReceiver extends BroadcastReceiver {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// acquire wake lock, otherwise device might enter deep sleep immediately after returning from onReceive()
|
||||||
|
AndroidUtils.acquirePartialWakeLock(context, "TimeSyncWakeLock", 10100);
|
||||||
|
|
||||||
final Date newTime = GregorianCalendar.getInstance().getTime();
|
final Date newTime = GregorianCalendar.getInstance().getTime();
|
||||||
LOG.info("Time or Timezone changed, syncing with device: {} ({}), {}", DateTimeUtils.formatDate(newTime), newTime.toGMTString(), intent.getAction());
|
LOG.info("Time/Timezone changed or periodic sync, syncing with device: {} ({}), {}", DateTimeUtils.formatDate(newTime), newTime.toGMTString(), intent.getAction());
|
||||||
GBApplication.deviceService().onSetTime();
|
GBApplication.deviceService().onSetTime();
|
||||||
|
|
||||||
// Reschedule the next DST change, since the timezone may have changed
|
// Reschedule the next DST change (since the timezone may have changed) or periodic sync
|
||||||
scheduleNextDstChange(context);
|
scheduleNextDstChangeOrPeriodicSync(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Schedule an alarm to trigger on the next DST change, since ACTION_TIMEZONE_CHANGED is not broadcast otherwise.
|
* Schedule an alarm to trigger on the next DST change, since ACTION_TIMEZONE_CHANGED is not broadcast otherwise
|
||||||
|
* or schedule an alarm to trigger after PERIODIC_SYNC_INTERVAL_MS (whichever is earlier).
|
||||||
*
|
*
|
||||||
* @param context the context
|
* @param context the context
|
||||||
*/
|
*/
|
||||||
public static void scheduleNextDstChange(final Context context) {
|
public static void scheduleNextDstChangeOrPeriodicSync(final Context context) {
|
||||||
final ZoneId zoneId = ZoneId.systemDefault();
|
final ZoneId zoneId = ZoneId.systemDefault();
|
||||||
final ZoneRules zoneRules = zoneId.getRules();
|
final ZoneRules zoneRules = zoneId.getRules();
|
||||||
final Instant now = Instant.now();
|
final Instant now = Instant.now();
|
||||||
final ZoneOffsetTransition transition = zoneRules.nextTransition(now);
|
final ZoneOffsetTransition transition = zoneRules.nextTransition(now);
|
||||||
if (transition == null) {
|
|
||||||
LOG.warn("No DST transition found for {}", zoneId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final long nextDstMillis = transition.getInstant().toEpochMilli();
|
final Intent i = new Intent(ACTION_DST_CHANGED_OR_PERIODIC_SYNC);
|
||||||
final long delayMillis = nextDstMillis - now.toEpochMilli() + 5000L;
|
|
||||||
|
|
||||||
final Intent i = new Intent(ACTION_DST_CHANGED);
|
|
||||||
final PendingIntent pi = PendingIntentUtils.getBroadcast(context, 0, i, 0, false);
|
final PendingIntent pi = PendingIntentUtils.getBroadcast(context, 0, i, 0, false);
|
||||||
|
|
||||||
final AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
final AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||||
final boolean exactAlarm = canScheduleExactAlarms(context, am);
|
|
||||||
|
|
||||||
LOG.info("Scheduling next DST change: {} (in {} millis) (exact = {})", nextDstMillis, delayMillis, exactAlarm);
|
boolean exactAlarm = false;
|
||||||
|
long delayMillis = PERIODIC_SYNC_INTERVAL_MS;
|
||||||
|
|
||||||
|
if (transition != null) {
|
||||||
|
final long nextDstMillis = transition.getInstant().toEpochMilli();
|
||||||
|
final long dstDelayMillis = nextDstMillis - now.toEpochMilli() + 5000L;
|
||||||
|
if (dstDelayMillis < PERIODIC_SYNC_INTERVAL_MAX_MS) {
|
||||||
|
exactAlarm = canScheduleExactAlarms(context, am);
|
||||||
|
delayMillis = dstDelayMillis;
|
||||||
|
LOG.info("Scheduling next DST change: {} (in {} millis) (exact = {})", nextDstMillis, delayMillis, exactAlarm);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG.warn("No DST transition found for {}", zoneId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delayMillis == PERIODIC_SYNC_INTERVAL_MS) {
|
||||||
|
LOG.info("Scheduling next periodic time sync in {} millis (exact = {})", delayMillis, exactAlarm);
|
||||||
|
}
|
||||||
|
|
||||||
am.cancel(pi);
|
am.cancel(pi);
|
||||||
|
|
||||||
boolean scheduledExact = false;
|
boolean scheduledExact = false;
|
||||||
if (exactAlarm) {
|
if (exactAlarm) {
|
||||||
try {
|
try {
|
||||||
am.setExact(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + delayMillis, pi);
|
am.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + delayMillis, pi);
|
||||||
scheduledExact = true;
|
scheduledExact = true;
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
LOG.error("Failed to schedule exact alarm for next DST change", e);
|
LOG.error("Failed to schedule exact alarm for next DST change or periodic time sync", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,16 +137,22 @@ public class TimeChangeReceiver extends BroadcastReceiver {
|
|||||||
if (!scheduledExact) {
|
if (!scheduledExact) {
|
||||||
try {
|
try {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
am.setAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + delayMillis, pi);
|
am.setAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + delayMillis, pi);
|
||||||
} else {
|
} else {
|
||||||
am.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + delayMillis, pi);
|
am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + delayMillis, pi);
|
||||||
}
|
}
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
LOG.error("Failed to schedule inexact alarm next DST change", e);
|
LOG.error("Failed to schedule inexact alarm for next DST change or periodic time sync", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void ifEnabledScheduleNextDstChangeOrPeriodicSync(final Context context) {
|
||||||
|
if (GBApplication.getPrefs().getBoolean("datetime_synconconnect", true)) {
|
||||||
|
scheduleNextDstChangeOrPeriodicSync(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean canScheduleExactAlarms(final Context context, final AlarmManager am) {
|
private static boolean canScheduleExactAlarms(final Context context, final AlarmManager am) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
return am.canScheduleExactAlarms();
|
return am.canScheduleExactAlarms();
|
||||||
|
@ -1179,8 +1179,11 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
|||||||
IntentFilter filter = new IntentFilter();
|
IntentFilter filter = new IntentFilter();
|
||||||
filter.addAction("android.intent.action.TIME_SET");
|
filter.addAction("android.intent.action.TIME_SET");
|
||||||
filter.addAction("android.intent.action.TIMEZONE_CHANGED");
|
filter.addAction("android.intent.action.TIMEZONE_CHANGED");
|
||||||
filter.addAction(TimeChangeReceiver.ACTION_DST_CHANGED);
|
filter.addAction(TimeChangeReceiver.ACTION_DST_CHANGED_OR_PERIODIC_SYNC);
|
||||||
registerReceiver(mTimeChangeReceiver, filter);
|
registerReceiver(mTimeChangeReceiver, filter);
|
||||||
|
// Ensure alarm is scheduled after registering broadcast receiver
|
||||||
|
// (this is important in case receiver was unregistered when the previous alarm arrived).
|
||||||
|
TimeChangeReceiver.ifEnabledScheduleNextDstChangeOrPeriodicSync(this);
|
||||||
}
|
}
|
||||||
if (mBlueToothPairingRequestReceiver == null) {
|
if (mBlueToothPairingRequestReceiver == null) {
|
||||||
mBlueToothPairingRequestReceiver = new BluetoothPairingRequestReceiver(this);
|
mBlueToothPairingRequestReceiver = new BluetoothPairingRequestReceiver(this);
|
||||||
|
@ -125,7 +125,7 @@ public class ZeppOsAlarmsService extends AbstractZeppOsService {
|
|||||||
if (alarm.getEnabled()) {
|
if (alarm.getEnabled()) {
|
||||||
alarmFlags = FLAG_ENABLED;
|
alarmFlags = FLAG_ENABLED;
|
||||||
}
|
}
|
||||||
if (coordinator.supportsSmartWakeup(getSupport().getDevice()) && alarm.getSmartWakeup()) {
|
if (coordinator.supportsSmartWakeup(getSupport().getDevice(), alarm.getPosition()) && alarm.getSmartWakeup()) {
|
||||||
alarmFlags |= FLAG_SMART;
|
alarmFlags |= FLAG_SMART;
|
||||||
}
|
}
|
||||||
alarmMessage = new byte[]{
|
alarmMessage = new byte[]{
|
||||||
|
@ -47,9 +47,11 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FindPhone;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FindPhone;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Menstrual;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Menstrual;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.MusicControl;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.MusicControl;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.Request;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.Request;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetPhoneInfoRequest;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetPhoneInfoRequest;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendMenstrualModifyTimeRequest;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendMenstrualModifyTimeRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherDeviceRequest;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetMusicStatusRequest;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetMusicStatusRequest;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
@ -95,6 +97,7 @@ public class AsynchronousResponse {
|
|||||||
handleCallControls(response);
|
handleCallControls(response);
|
||||||
handlePhoneInfo(response);
|
handlePhoneInfo(response);
|
||||||
handleMenstrualModifyTime(response);
|
handleMenstrualModifyTime(response);
|
||||||
|
handleWeatherCheck(response);
|
||||||
} catch (Request.ResponseParseException e) {
|
} catch (Request.ResponseParseException e) {
|
||||||
LOG.error("Response parse exception", e);
|
LOG.error("Response parse exception", e);
|
||||||
}
|
}
|
||||||
@ -378,4 +381,18 @@ public class AsynchronousResponse {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleWeatherCheck(HuaweiPacket response) {
|
||||||
|
if (response.serviceId == Weather.id && response.commandId == 0x04) {
|
||||||
|
// Send back ok
|
||||||
|
try {
|
||||||
|
SendWeatherDeviceRequest sendWeatherDeviceRequest = new SendWeatherDeviceRequest(this.support);
|
||||||
|
sendWeatherDeviceRequest.doPerform();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("Could not send weather device request", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: send back weather?
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btbr.AbstractBTBRDeviceSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.btbr.AbstractBTBRDeviceSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder;
|
import nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder;
|
||||||
|
|
||||||
@ -117,4 +118,9 @@ public class HuaweiBRSupport extends AbstractBTBRDeviceSupport {
|
|||||||
if (!start)
|
if (!start)
|
||||||
supportProvider.onStopFindPhone();
|
supportProvider.onStopFindPhone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSendWeather(WeatherSpec weatherSpec) {
|
||||||
|
supportProvider.onSendWeather(weatherSpec);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||||
@ -125,4 +126,9 @@ public class HuaweiLESupport extends AbstractBTLEDeviceSupport {
|
|||||||
if (!start)
|
if (!start)
|
||||||
supportProvider.onStopFindPhone();
|
supportProvider.onStopFindPhone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSendWeather(WeatherSpec weatherSpec) {
|
||||||
|
supportProvider.onSendWeather(weatherSpec);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2024 Damien Gaignon, Martin.JM
|
/* Copyright (C) 2024 Damien Gaignon, Martin.JM, Vitalii Tomin
|
||||||
|
|
||||||
This file is part of Gadgetbridge.
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
@ -50,6 +50,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCoordinatorSupp
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCrypto;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCrypto;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiSampleProvider;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiSampleProvider;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Workout;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Workout;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
|
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
|
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
|
||||||
@ -73,11 +74,20 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
|
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetEventAlarmList;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetEventAlarmList;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetNotificationConstraintsRequest;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetNotificationConstraintsRequest;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetSmartAlarmList;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetSmartAlarmList;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendGpsAndTimeToDeviceRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherCurrentRequest;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendNotifyHeartRateCapabilityRequest;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendNotifyHeartRateCapabilityRequest;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendNotifyRestHeartRateCapabilityRequest;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendNotifyRestHeartRateCapabilityRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherExtendedSupportRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherForecastRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherStartRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherSunMoonSupportRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherSupportRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherUnitRequest;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetAutomaticHeartrateRequest;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetAutomaticHeartrateRequest;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetAutomaticSpoRequest;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetAutomaticSpoRequest;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetDisconnectNotification;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetDisconnectNotification;
|
||||||
@ -1637,4 +1647,115 @@ public class HuaweiSupportProvider {
|
|||||||
LOG.error("Failed to set language settings request", e);
|
LOG.error("Failed to set language settings request", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Weather.WeatherIcon openWeatherMapConditionCodeToHuaweiIcon(int conditionCode) {
|
||||||
|
// More exact first, groups after
|
||||||
|
switch (conditionCode) {
|
||||||
|
case 500:
|
||||||
|
return Weather.WeatherIcon.LIGHT_RAIN;
|
||||||
|
case 501:
|
||||||
|
return Weather.WeatherIcon.RAIN;
|
||||||
|
case 502:
|
||||||
|
return Weather.WeatherIcon.HEAVY_RAIN;
|
||||||
|
case 503:
|
||||||
|
return Weather.WeatherIcon.RAIN_STORM;
|
||||||
|
case 504:
|
||||||
|
return Weather.WeatherIcon.SEVERE_RAIN_STORMS;
|
||||||
|
case 511:
|
||||||
|
return Weather.WeatherIcon.FREEZING_RAIN;
|
||||||
|
case 600:
|
||||||
|
return Weather.WeatherIcon.LIGHT_SNOW;
|
||||||
|
case 601:
|
||||||
|
return Weather.WeatherIcon.SNOW;
|
||||||
|
case 602:
|
||||||
|
return Weather.WeatherIcon.HEAVY_SNOW;
|
||||||
|
case 611:
|
||||||
|
return Weather.WeatherIcon.SLEET;
|
||||||
|
case 701:
|
||||||
|
case 741:
|
||||||
|
return Weather.WeatherIcon.FOG;
|
||||||
|
case 721:
|
||||||
|
return Weather.WeatherIcon.HAZY;
|
||||||
|
case 751:
|
||||||
|
return Weather.WeatherIcon.SAND;
|
||||||
|
case 761:
|
||||||
|
return Weather.WeatherIcon.DUST;
|
||||||
|
case 800:
|
||||||
|
return Weather.WeatherIcon.SUNNY;
|
||||||
|
case 801:
|
||||||
|
case 802:
|
||||||
|
return Weather.WeatherIcon.CLOUDY;
|
||||||
|
case 803:
|
||||||
|
case 804:
|
||||||
|
return Weather.WeatherIcon.OVERCAST;
|
||||||
|
}
|
||||||
|
if (conditionCode >= 200 && conditionCode < 300)
|
||||||
|
return Weather.WeatherIcon.THUNDERSTORMS;
|
||||||
|
if (conditionCode >= 300 && conditionCode < 400)
|
||||||
|
return Weather.WeatherIcon.LIGHT_RAIN;
|
||||||
|
if (conditionCode >= 500 && conditionCode < 600)
|
||||||
|
return Weather.WeatherIcon.RAIN;
|
||||||
|
if (conditionCode >= 600 && conditionCode < 700)
|
||||||
|
return Weather.WeatherIcon.SNOW;
|
||||||
|
return Weather.WeatherIcon.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onSendWeather(WeatherSpec weatherSpec) {
|
||||||
|
// Initialize weather settings and send weather
|
||||||
|
if (!getHuaweiCoordinator().supportsWeather()) {
|
||||||
|
LOG.error("onSendWeather called while weather is not supported.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Weather.Settings weatherSettings = new Weather.Settings();
|
||||||
|
|
||||||
|
SendWeatherStartRequest weatherStartRequest = new SendWeatherStartRequest(this, weatherSettings);
|
||||||
|
Request lastRequest = weatherStartRequest;
|
||||||
|
|
||||||
|
if (getHuaweiCoordinator().supportsWeatherUnit()) {
|
||||||
|
SendWeatherUnitRequest weatherUnitRequest = new SendWeatherUnitRequest(this);
|
||||||
|
lastRequest.nextRequest(weatherUnitRequest);
|
||||||
|
lastRequest = weatherUnitRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
SendWeatherSupportRequest weatherSupportRequest = new SendWeatherSupportRequest(this, weatherSettings);
|
||||||
|
lastRequest.nextRequest(weatherSupportRequest);
|
||||||
|
lastRequest = weatherSupportRequest;
|
||||||
|
|
||||||
|
if (getHuaweiCoordinator().supportsWeatherExtended()) {
|
||||||
|
SendWeatherExtendedSupportRequest weatherExtendedSupportRequest = new SendWeatherExtendedSupportRequest(this, weatherSettings);
|
||||||
|
lastRequest.nextRequest(weatherExtendedSupportRequest);
|
||||||
|
lastRequest = weatherExtendedSupportRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getHuaweiCoordinator().supportsWeatherMoonRiseSet()) {
|
||||||
|
SendWeatherSunMoonSupportRequest weatherSunMoonSupportRequest = new SendWeatherSunMoonSupportRequest(this, weatherSettings);
|
||||||
|
lastRequest.nextRequest(weatherSunMoonSupportRequest);
|
||||||
|
lastRequest = weatherSunMoonSupportRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
// End of initialization and start of actually sending weather
|
||||||
|
|
||||||
|
SendWeatherCurrentRequest sendWeatherCurrentRequest = new SendWeatherCurrentRequest(this, weatherSettings, weatherSpec);
|
||||||
|
lastRequest.nextRequest(sendWeatherCurrentRequest);
|
||||||
|
lastRequest = sendWeatherCurrentRequest;
|
||||||
|
|
||||||
|
SendGpsAndTimeToDeviceRequest sendGpsAndTimeToDeviceRequest = new SendGpsAndTimeToDeviceRequest(this);
|
||||||
|
lastRequest.nextRequest(sendGpsAndTimeToDeviceRequest);
|
||||||
|
lastRequest = sendGpsAndTimeToDeviceRequest;
|
||||||
|
|
||||||
|
if (getHuaweiCoordinator().supportsWeatherForecasts()) {
|
||||||
|
SendWeatherForecastRequest sendWeatherForecastRequest = new SendWeatherForecastRequest(this, weatherSettings, weatherSpec);
|
||||||
|
lastRequest.nextRequest(sendWeatherForecastRequest);
|
||||||
|
lastRequest = sendWeatherForecastRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
weatherStartRequest.doPerform();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// TODO: Use translatable string
|
||||||
|
GB.toast(context, "Failed to send weather", Toast.LENGTH_SHORT, GB.ERROR, e);
|
||||||
|
LOG.error("Failed to send weather", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
/* Copyright (C) 2024 Vitalii Tomin, Martin.JM
|
||||||
|
|
||||||
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
|
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Gadgetbridge is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
|
||||||
|
|
||||||
|
import android.location.Location;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.GpsAndTime;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.CurrentPosition;
|
||||||
|
|
||||||
|
public class SendGpsAndTimeToDeviceRequest extends Request {
|
||||||
|
|
||||||
|
public SendGpsAndTimeToDeviceRequest(HuaweiSupportProvider support) {
|
||||||
|
super(support);
|
||||||
|
this.serviceId = GpsAndTime.id;
|
||||||
|
this.commandId = GpsAndTime.CurrentGPSRequest.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<byte[]> createRequest() throws RequestCreationException {
|
||||||
|
try {
|
||||||
|
Location location = new CurrentPosition().getLastKnownLocation();
|
||||||
|
return new GpsAndTime.CurrentGPSRequest(
|
||||||
|
this.paramsProvider,
|
||||||
|
(int) (Calendar.getInstance().getTime().getTime() / 1000L) - 60, // Backdating a bit seems to work better
|
||||||
|
location.getLatitude(),
|
||||||
|
location.getLongitude()
|
||||||
|
).serialize();
|
||||||
|
} catch (HuaweiPacket.CryptoException e) {
|
||||||
|
throw new RequestCreationException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
/* Copyright (C) 2024 Martin.JM
|
||||||
|
|
||||||
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
|
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Gadgetbridge is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
|
||||||
|
|
||||||
|
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.huawei.HuaweiPacket;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||||
|
|
||||||
|
public class SendWeatherCurrentRequest extends Request {
|
||||||
|
Weather.Settings settings;
|
||||||
|
WeatherSpec weatherSpec;
|
||||||
|
|
||||||
|
public SendWeatherCurrentRequest(HuaweiSupportProvider support, Weather.Settings settings, WeatherSpec weatherSpec) {
|
||||||
|
super(support);
|
||||||
|
this.serviceId = Weather.id;
|
||||||
|
this.commandId = Weather.CurrentWeatherRequest.id;
|
||||||
|
this.settings = settings;
|
||||||
|
this.weatherSpec = weatherSpec;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<byte[]> createRequest() throws RequestCreationException {
|
||||||
|
Weather.HuaweiTemperatureFormat temperatureFormat = Weather.HuaweiTemperatureFormat.CELSIUS;
|
||||||
|
String unit = GBApplication.getPrefs().getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, GBApplication.getContext().getString(R.string.p_unit_metric));
|
||||||
|
if (unit.equals(GBApplication.getContext().getString(R.string.p_unit_imperial)))
|
||||||
|
temperatureFormat = Weather.HuaweiTemperatureFormat.FAHRENHEIT;
|
||||||
|
try {
|
||||||
|
Short pm25 = null;
|
||||||
|
Short aqi = null;
|
||||||
|
if (weatherSpec.airQuality != null) {
|
||||||
|
pm25 = (short) weatherSpec.airQuality.pm25; // TODO: does this work?
|
||||||
|
aqi = (short) weatherSpec.airQuality.aqi;
|
||||||
|
}
|
||||||
|
return new Weather.CurrentWeatherRequest(
|
||||||
|
this.paramsProvider,
|
||||||
|
settings,
|
||||||
|
supportProvider.openWeatherMapConditionCodeToHuaweiIcon(weatherSpec.currentConditionCode),
|
||||||
|
(byte) weatherSpec.windDirection,
|
||||||
|
(byte) weatherSpec.windSpeedAsBeaufort(),
|
||||||
|
(byte) (weatherSpec.todayMinTemp - 273),
|
||||||
|
(byte) (weatherSpec.todayMaxTemp - 273),
|
||||||
|
pm25,
|
||||||
|
weatherSpec.location,
|
||||||
|
(byte) (weatherSpec.currentTemp - 273),
|
||||||
|
temperatureFormat,
|
||||||
|
aqi,
|
||||||
|
weatherSpec.timestamp,
|
||||||
|
"Gadgetbridge"
|
||||||
|
).serialize();
|
||||||
|
} catch (HuaweiPacket.CryptoException e) {
|
||||||
|
throw new RequestCreationException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
/* Copyright (C) 2024 Martin.JM
|
||||||
|
|
||||||
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
|
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Gadgetbridge is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
|
||||||
|
|
||||||
|
public class SendWeatherDeviceRequest extends Request {
|
||||||
|
|
||||||
|
public SendWeatherDeviceRequest(HuaweiSupportProvider support) {
|
||||||
|
super(support);
|
||||||
|
this.serviceId = Weather.id;
|
||||||
|
this.commandId = 0x04;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<byte[]> createRequest() throws RequestCreationException {
|
||||||
|
try {
|
||||||
|
return new Weather.WeatherDeviceRequest(paramsProvider, HuaweiConstants.RESULT_SUCCESS_INT).serialize();
|
||||||
|
} catch (HuaweiPacket.CryptoException e) {
|
||||||
|
throw new RequestCreationException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
/* Copyright (C) 2024 Martin.JM
|
||||||
|
|
||||||
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
|
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Gadgetbridge is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
|
||||||
|
|
||||||
|
public class SendWeatherExtendedSupportRequest extends Request {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(SendWeatherExtendedSupportRequest.class);
|
||||||
|
|
||||||
|
private Weather.Settings settings;
|
||||||
|
|
||||||
|
public SendWeatherExtendedSupportRequest(HuaweiSupportProvider support, Weather.Settings settings) {
|
||||||
|
super(support);
|
||||||
|
this.serviceId = Weather.id;
|
||||||
|
this.commandId = Weather.WeatherExtendedSupport.id;
|
||||||
|
this.settings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<byte[]> createRequest() throws RequestCreationException {
|
||||||
|
try {
|
||||||
|
return new Weather.WeatherExtendedSupport.Request(this.paramsProvider).serialize();
|
||||||
|
} catch (HuaweiPacket.CryptoException e) {
|
||||||
|
throw new RequestCreationException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void processResponse() throws ResponseParseException {
|
||||||
|
if (receivedPacket instanceof Weather.WeatherExtendedSupport.Response) {
|
||||||
|
this.settings.timeSupported = ((Weather.WeatherExtendedSupport.Response) receivedPacket).timeSupported;
|
||||||
|
this.settings.sourceSupported = ((Weather.WeatherExtendedSupport.Response) receivedPacket).sourceSupported;
|
||||||
|
this.settings.weatherIconSupported = ((Weather.WeatherExtendedSupport.Response) receivedPacket).weatherIconSupported;
|
||||||
|
} else {
|
||||||
|
LOG.error("WeatherExtendedSupport response is not of type WeatherExtendedSupport response");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
/* Copyright (C) 2024 Martin.JM
|
||||||
|
|
||||||
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
|
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Gadgetbridge is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather.WeatherForecastData;
|
||||||
|
|
||||||
|
public class SendWeatherForecastRequest extends Request {
|
||||||
|
Weather.Settings weatherSettings;
|
||||||
|
WeatherSpec weatherSpec;
|
||||||
|
|
||||||
|
public SendWeatherForecastRequest(HuaweiSupportProvider support, Weather.Settings weatherSettings, WeatherSpec weatherSpec) {
|
||||||
|
super(support);
|
||||||
|
this.serviceId = Weather.id;
|
||||||
|
this.commandId = Weather.WeatherForecastData.id;
|
||||||
|
this.weatherSettings = weatherSettings;
|
||||||
|
this.weatherSpec = weatherSpec;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<byte[]> createRequest() throws RequestCreationException {
|
||||||
|
int hourlyCount = Math.min(weatherSpec.hourly.size(), 24);
|
||||||
|
int dayCount = Math.min(weatherSpec.forecasts.size(), 8);
|
||||||
|
|
||||||
|
ArrayList<WeatherForecastData.TimeData> timeDataArrayList = new ArrayList<>(hourlyCount);
|
||||||
|
ArrayList<WeatherForecastData.DayData> dayDataArrayList = new ArrayList<>(dayCount);
|
||||||
|
for (int i = 0; i < hourlyCount; i++) {
|
||||||
|
WeatherSpec.Hourly hourly = weatherSpec.hourly.get(i);
|
||||||
|
WeatherForecastData.TimeData timeData = new WeatherForecastData.TimeData();
|
||||||
|
timeData.timestamp = hourly.timestamp;
|
||||||
|
timeData.icon = supportProvider.openWeatherMapConditionCodeToHuaweiIcon(hourly.conditionCode);
|
||||||
|
timeData.temperature = (byte) (hourly.temp - 273);
|
||||||
|
timeDataArrayList.add(timeData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add today as well
|
||||||
|
WeatherForecastData.DayData today = new WeatherForecastData.DayData();
|
||||||
|
today.timestamp = weatherSpec.timestamp;
|
||||||
|
today.icon = supportProvider.openWeatherMapConditionCodeToHuaweiIcon(weatherSpec.currentConditionCode);
|
||||||
|
today.highTemperature = (byte) (weatherSpec.todayMaxTemp - 273);
|
||||||
|
today.lowTemperature = (byte) (weatherSpec.todayMinTemp - 273);
|
||||||
|
today.sunriseTime = weatherSpec.sunRise;
|
||||||
|
today.sunsetTime = weatherSpec.sunSet;
|
||||||
|
today.moonRiseTime = weatherSpec.moonRise;
|
||||||
|
today.moonSetTime = weatherSpec.moonSet;
|
||||||
|
today.moonPhase = Weather.degreesToMoonPhase(weatherSpec.moonPhase);
|
||||||
|
dayDataArrayList.add(today);
|
||||||
|
|
||||||
|
for (int i = 0; i < dayCount - 1; i++) {
|
||||||
|
WeatherSpec.Daily daily = weatherSpec.forecasts.get(i);
|
||||||
|
WeatherForecastData.DayData dayData = new WeatherForecastData.DayData();
|
||||||
|
dayData.timestamp = weatherSpec.timestamp + (60*60*24 * (i + 1));
|
||||||
|
dayData.icon = supportProvider.openWeatherMapConditionCodeToHuaweiIcon(daily.conditionCode);
|
||||||
|
dayData.highTemperature = (byte) (daily.maxTemp - 273);
|
||||||
|
dayData.lowTemperature = (byte) (daily.minTemp - 273);
|
||||||
|
dayData.sunriseTime = daily.sunRise;
|
||||||
|
dayData.sunsetTime = daily.sunSet;
|
||||||
|
dayData.moonRiseTime = daily.moonRise;
|
||||||
|
dayData.moonSetTime = daily.moonSet;
|
||||||
|
dayData.moonPhase = Weather.degreesToMoonPhase(daily.moonPhase);
|
||||||
|
dayDataArrayList.add(dayData);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return new WeatherForecastData.Request(
|
||||||
|
this.paramsProvider,
|
||||||
|
this.weatherSettings,
|
||||||
|
timeDataArrayList,
|
||||||
|
dayDataArrayList
|
||||||
|
).serialize();
|
||||||
|
} catch (HuaweiPacket.CryptoException e) {
|
||||||
|
throw new RequestCreationException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
/* Copyright (C) 2024 Martin.JM
|
||||||
|
|
||||||
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
|
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Gadgetbridge is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
|
||||||
|
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
|
public class SendWeatherStartRequest extends Request {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(SendWeatherStartRequest.class);
|
||||||
|
|
||||||
|
public int response = -1;
|
||||||
|
private Weather.Settings weatherSettings;
|
||||||
|
|
||||||
|
public SendWeatherStartRequest(HuaweiSupportProvider support, Weather.Settings weatherSettings) {
|
||||||
|
super(support);
|
||||||
|
this.serviceId = Weather.id;
|
||||||
|
this.commandId = Weather.WeatherStart.id;
|
||||||
|
this.weatherSettings = weatherSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<byte[]> createRequest() throws RequestCreationException {
|
||||||
|
try {
|
||||||
|
return new Weather.WeatherStart.Request(this.paramsProvider).serialize();
|
||||||
|
} catch (HuaweiPacket.CryptoException e) {
|
||||||
|
throw new RequestCreationException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void processResponse() throws ResponseParseException {
|
||||||
|
if (receivedPacket instanceof Weather.WeatherStart.Response) {
|
||||||
|
if (!((Weather.WeatherStart.Response) receivedPacket).success) {
|
||||||
|
this.stopChain();
|
||||||
|
GB.toast(supportProvider.getContext(), "Received non-ok status for WeatherStart response", Toast.LENGTH_SHORT, GB.INFO);
|
||||||
|
LOG.info("Received non-ok status for WeatherStart response");
|
||||||
|
} else {
|
||||||
|
weatherSettings.weatherSupported = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.stopChain();
|
||||||
|
GB.toast(supportProvider.getContext(), "WeatherStart response is not of type WeatherStart response", Toast.LENGTH_SHORT, GB.ERROR);
|
||||||
|
LOG.error("WeatherStart response is not of type WeatherStart response");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
/* Copyright (C) 2024 Martin.JM
|
||||||
|
|
||||||
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
|
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Gadgetbridge is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
|
||||||
|
|
||||||
|
public class SendWeatherSunMoonSupportRequest extends Request {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(SendWeatherSunMoonSupportRequest.class);
|
||||||
|
|
||||||
|
private Weather.Settings settings;
|
||||||
|
|
||||||
|
public SendWeatherSunMoonSupportRequest(HuaweiSupportProvider support, Weather.Settings settings) {
|
||||||
|
super(support);
|
||||||
|
this.serviceId = Weather.id;
|
||||||
|
this.commandId = Weather.WeatherSunMoonSupport.id;
|
||||||
|
this.settings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<byte[]> createRequest() throws RequestCreationException {
|
||||||
|
try {
|
||||||
|
return new Weather.WeatherSunMoonSupport.Request(this.paramsProvider).serialize();
|
||||||
|
} catch (HuaweiPacket.CryptoException e) {
|
||||||
|
throw new RequestCreationException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void processResponse() throws ResponseParseException {
|
||||||
|
if (receivedPacket instanceof Weather.WeatherSunMoonSupport.Response) {
|
||||||
|
this.settings.sunRiseSetSupported = ((Weather.WeatherSunMoonSupport.Response) receivedPacket).sunRiseSetSupported;
|
||||||
|
this.settings.moonPhaseSupported = ((Weather.WeatherSunMoonSupport.Response) receivedPacket).moonPhaseSupported;
|
||||||
|
} else {
|
||||||
|
LOG.error("WeatherExtendedSupport response is not of type WeatherExtendedSupport response");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
/* Copyright (C) 2024 Martin.JM
|
||||||
|
|
||||||
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
|
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Gadgetbridge is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
|
||||||
|
|
||||||
|
public class SendWeatherSupportRequest extends Request {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(SendWeatherSupportRequest.class);
|
||||||
|
|
||||||
|
private Weather.Settings settings;
|
||||||
|
|
||||||
|
public SendWeatherSupportRequest(HuaweiSupportProvider support, Weather.Settings settings) {
|
||||||
|
super(support);
|
||||||
|
this.serviceId = Weather.id;
|
||||||
|
this.commandId = Weather.WeatherSupport.id;
|
||||||
|
this.settings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<byte[]> createRequest() throws RequestCreationException {
|
||||||
|
try {
|
||||||
|
return new Weather.WeatherSupport.Request(this.paramsProvider).serialize();
|
||||||
|
} catch (HuaweiPacket.CryptoException e) {
|
||||||
|
throw new RequestCreationException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void processResponse() throws ResponseParseException {
|
||||||
|
if (receivedPacket instanceof Weather.WeatherSupport.Response) {
|
||||||
|
this.settings.weatherSupported = ((Weather.WeatherSupport.Response) receivedPacket).weatherSupported;
|
||||||
|
this.settings.windSupported = ((Weather.WeatherSupport.Response) receivedPacket).windSupported;
|
||||||
|
this.settings.pm25Supported = ((Weather.WeatherSupport.Response) receivedPacket).pm25Supported;
|
||||||
|
this.settings.temperatureSupported = ((Weather.WeatherSupport.Response) receivedPacket).temperatureSupported;
|
||||||
|
this.settings.locationNameSupported = ((Weather.WeatherSupport.Response) receivedPacket).locationNameSupported;
|
||||||
|
this.settings.currentTemperatureSupported = ((Weather.WeatherSupport.Response) receivedPacket).currentTemperatureSupported;
|
||||||
|
this.settings.unitSupported = ((Weather.WeatherSupport.Response) receivedPacket).unitSupported;
|
||||||
|
this.settings.airQualityIndexSupported = ((Weather.WeatherSupport.Response) receivedPacket).airQualityIndexSupported;
|
||||||
|
} else {
|
||||||
|
LOG.error("WeatherSupport response is not of type WeatherSupport response");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
/* Copyright (C) 2024 Martin.JM
|
||||||
|
|
||||||
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
|
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Gadgetbridge is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
|
||||||
|
|
||||||
|
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.huawei.HuaweiPacket;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
|
||||||
|
|
||||||
|
public class SendWeatherUnitRequest extends Request {
|
||||||
|
|
||||||
|
public SendWeatherUnitRequest(HuaweiSupportProvider support) {
|
||||||
|
super(support);
|
||||||
|
this.serviceId = Weather.id;
|
||||||
|
this.commandId = Weather.WeatherUnitRequest.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<byte[]> createRequest() throws RequestCreationException {
|
||||||
|
Weather.HuaweiTemperatureFormat temperatureFormat = Weather.HuaweiTemperatureFormat.CELSIUS;
|
||||||
|
String unit = GBApplication.getPrefs().getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, GBApplication.getContext().getString(R.string.p_unit_metric));
|
||||||
|
if (unit.equals(GBApplication.getContext().getString(R.string.p_unit_imperial)))
|
||||||
|
temperatureFormat = Weather.HuaweiTemperatureFormat.FAHRENHEIT;
|
||||||
|
try {
|
||||||
|
return new Weather.WeatherUnitRequest(this.paramsProvider, temperatureFormat).serialize();
|
||||||
|
} catch (HuaweiPacket.CryptoException e) {
|
||||||
|
throw new RequestCreationException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -32,6 +32,7 @@ import android.net.Uri;
|
|||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.os.ParcelUuid;
|
import android.os.ParcelUuid;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
import android.os.PowerManager;
|
||||||
import android.provider.DocumentsContract;
|
import android.provider.DocumentsContract;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
@ -337,4 +338,16 @@ public class AndroidUtils {
|
|||||||
}
|
}
|
||||||
GBApplication.getContext().startActivity(launchIntent);
|
GBApplication.getContext().startActivity(launchIntent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static PowerManager.WakeLock acquirePartialWakeLock(Context context, String tag, long timeout) {
|
||||||
|
try {
|
||||||
|
PowerManager powermanager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||||
|
PowerManager.WakeLock wl = powermanager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Gadgetbridge:" + tag);
|
||||||
|
wl.acquire(timeout);
|
||||||
|
return wl;
|
||||||
|
} catch (final Exception e) {
|
||||||
|
LOG.error("Failed to take partial wake lock {}: ", tag, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -214,7 +214,7 @@
|
|||||||
<string name="pref_default">Default</string>
|
<string name="pref_default">Default</string>
|
||||||
<string name="pref_header_datetime">Date and Time</string>
|
<string name="pref_header_datetime">Date and Time</string>
|
||||||
<string name="pref_title_datetime_syctimeonconnect">Sync time</string>
|
<string name="pref_title_datetime_syctimeonconnect">Sync time</string>
|
||||||
<string name="pref_summary_datetime_syctimeonconnect">Sync time to Gadgetbridge device when connecting, and when time or time zone changes on Android device</string>
|
<string name="pref_summary_datetime_syctimeonconnect">Sync time to Gadgetbridge device(s) when connecting, when time or time zone changes on Android device, and periodically</string>
|
||||||
<string name="pref_title_theme">Theme</string>
|
<string name="pref_title_theme">Theme</string>
|
||||||
<string name="pref_theme_light">Light</string>
|
<string name="pref_theme_light">Light</string>
|
||||||
<string name="pref_theme_dark">Dark</string>
|
<string name="pref_theme_dark">Dark</string>
|
||||||
|
9
app/src/mainline/res/values/strings.xml
Normal file
9
app/src/mainline/res/values/strings.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string translatable="false" name="pebble_content_provider">com.getpebble.android.provider</string>
|
||||||
|
<string translatable="false" name="app_name">@string/application_name_generic</string>
|
||||||
|
<string translatable="false" name="title_activity_controlcenter">@string/title_activity_controlcenter_generic</string>
|
||||||
|
<string translatable="false" name="about_activity_title">@string/about_activity_title_generic</string>
|
||||||
|
<string translatable="false" name="about_description">@string/about_description_generic</string>
|
||||||
|
<string translatable="false" name="gadgetbridge_running">@string/gadgetbridge_running_generic</string>
|
||||||
|
</resources>
|
BIN
app/src/nightly/res/drawable-hdpi/ic_launcher.png
Normal file
BIN
app/src/nightly/res/drawable-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.6 KiB |
BIN
app/src/nightly/res/drawable-mdpi/ic_launcher.png
Normal file
BIN
app/src/nightly/res/drawable-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
BIN
app/src/nightly/res/drawable-xhdpi/ic_launcher.png
Normal file
BIN
app/src/nightly/res/drawable-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.6 KiB |
BIN
app/src/nightly/res/drawable-xxhdpi/ic_launcher.png
Normal file
BIN
app/src/nightly/res/drawable-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
BIN
app/src/nightly/res/drawable-xxxhdpi/ic_launcher.png
Normal file
BIN
app/src/nightly/res/drawable-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
8
app/src/nightly/res/values/strings.xml
Normal file
8
app/src/nightly/res/values/strings.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<resources>
|
||||||
|
<string translatable="false" name="app_name">@string/application_name_main_nightly</string>
|
||||||
|
<string translatable="false" name="title_activity_controlcenter">@string/title_activity_controlcenter_main_nightly</string>
|
||||||
|
<string translatable="false" name="about_activity_title">@string/about_activity_title_main_nightly</string>
|
||||||
|
<string translatable="false" name="about_description">@string/about_description_main_nightly</string>
|
||||||
|
<string translatable="false" name="gadgetbridge_running">@string/gadgetbridge_running_main_nightly</string>
|
||||||
|
</resources>
|
9
app/src/nopebble/res/values/strings.xml
Normal file
9
app/src/nopebble/res/values/strings.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string translatable="false" name="pebble_content_provider">com.getpebble.android.nopebble.provider</string>
|
||||||
|
<string translatable="false" name="app_name">@string/application_name_main_nopebble</string>
|
||||||
|
<string translatable="false" name="title_activity_controlcenter">@string/title_activity_controlcenter_main_nopebble</string>
|
||||||
|
<string translatable="false" name="about_activity_title">@string/about_activity_title_main_nopebble</string>
|
||||||
|
<string translatable="false" name="about_description">@string/about_description_main_nopebble</string>
|
||||||
|
<string translatable="false" name="gadgetbridge_running">@string/gadgetbridge_running_main_nopebble</string>
|
||||||
|
</resources>
|
Loading…
Reference in New Issue
Block a user