Compare commits

...

29 Commits

Author SHA1 Message Date
José Rebelo
a54f953b6f Update changelog 2024-02-18 20:57:31 +00:00
Martin.JM
9a388ca386 Updated according to review
Thank you José Rebelo!
2024-02-18 20:54:59 +00:00
Martin.JM
c66051f580 Cleanup of Huawei weather implementation 2024-02-18 20:54:59 +00:00
Martin.JM
1c2516186d Small cleanup 2024-02-18 20:54:59 +00:00
Martin.JM
5ebfe9b39f Add weather settings to forecast 2024-02-18 20:54:59 +00:00
Martin.JM
b231104a88 Add moon phase support 2024-02-18 20:54:59 +00:00
Martin.JM
242f3c6dbc Add unit support and some cleanup 2024-02-18 20:54:59 +00:00
Martin.JM
043f839a4d Fix tomorrow appearing twice 2024-02-18 20:54:59 +00:00
Martin.JM
6a2186919c Always initialize weather before sending 2024-02-18 20:54:59 +00:00
Martin.JM
4b70f3fcdb Hookup Huawei GPS values 2024-02-18 20:54:59 +00:00
Vitaliy Tomin
d379b11535 Add HuaweiTLV put for double 2024-02-18 20:54:59 +00:00
Martin.JM
ee14b5d821 Small changes 2024-02-18 20:54:59 +00:00
Martin.JM
65aa5faec5 Implement Huawei weather icons 2024-02-18 20:54:59 +00:00
Martin.JM
f970b7482b Set wind values 2024-02-18 20:54:59 +00:00
Vitaliy Tomin
287b720350 Send gps and time before forecast 2024-02-18 20:54:59 +00:00
Martin.JM
14eaba858c Weather fix 2024-02-18 20:54:59 +00:00
Martin.JM
0b64408b33 Add outgoing parsing 2024-02-18 20:54:59 +00:00
Martin.JM
2b1c5b5819 Add weather forecast 2024-02-18 20:54:59 +00:00
Martin.JM
b2b176b65c Add weather support for Huawei 2024-02-18 20:54:59 +00:00
MrYoranimo
3af5a412fe Add missing ic_launcher drawables in nightly 2024-02-18 20:52:53 +00:00
MrYoranimo
fa1f99642a Move build variant-specific strings from build.gradle to separate resource files 2024-02-18 20:52:53 +00:00
Davis Mosenkovs
c1f2f5bb4b Periodic time sync: update changelog and strings 2024-02-18 22:15:24 +02:00
Davis Mosenkovs
7a79a1d0f5 Add wake lock and wakeup for time sync
Wake lock with around 10 second timeout is a quick and dirty solution,
however as the time sync should happen once per several days the 10
second wake time should not be an issue.
2024-02-18 19:12:30 +00:00
Davis Mosenkovs
cc5078332b TimeChangeReceiver: Ensure the alarm is set
Ensure TimeChangeReceiver alarm is scheduled when enabling
datetime_synconconnect and registering TimeChangeReceiver broadcast
receiver.

It is important to re-schedule the alarm after registering broadcast
receiver, because:
1. if broadcast receiver was unregistered while previous alarm arrived,
there is no alarm scheduled;
2. re-scheduling the alarm resets the periodic time sync timer when
first device is connected (which is desired).

It is important to re-schedule the alarm when datetime_synconconnect
gets enabled, because there might be no alarm scheduled.

Call onSetTime() when enabling datetime_synconconnect.
2024-02-18 19:12:30 +00:00
Davis Mosenkovs
31fc266f94 Periodically sync time on all connected devices
Sync time every 43 hours, 53 minutes and 23 seconds.
Interval is a bit smaller than 2 days.
Interval is a prime (in seconds) so time of sync will slide over time.

If next DST change is less than 48 hours in future, wait for it.
2024-02-18 19:12:30 +00:00
Martin.JM
22e51f841b Add additional smart wakeup functionality in test device
Code by José Rebelo:
https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/3583#issuecomment-1571835
2024-02-18 19:01:19 +01:00
Martin.JM
275b662188 Always check smart wakeup if forced 2024-02-18 18:55:54 +01:00
Martin.JM
89b6ae9f24 Add more granular smart wakeup support
Specifically:
- Add position to smart wakeup support coordinator function
- Add interface to stop smart alarm checkbox from being changed
2024-02-18 17:24:29 +01:00
Martin.JM
01ef422812 Set alarm as used and enabled if time has changed 2024-02-18 16:33:50 +01:00
96 changed files with 1802 additions and 318 deletions

View File

@ -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

View File

@ -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")
} }
} }
} }

View 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>

View 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>

View File

@ -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"/>

View File

@ -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);

View File

@ -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);

View File

@ -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) -> {

View File

@ -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;
} }

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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";

View File

@ -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;

View File

@ -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;

View File

@ -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;
} }

View File

@ -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;

View File

@ -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;
} }

View File

@ -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

View File

@ -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;

View File

@ -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);
} }

View File

@ -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

View File

@ -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;
} }

View File

@ -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());
} }

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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()
{ {

View File

@ -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;

View File

@ -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;
} }

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;
} }

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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());

View File

@ -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;

View File

@ -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;

View File

@ -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();

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;
} }

View File

@ -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;
} }

View File

@ -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";

View File

@ -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

View File

@ -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,

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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) {

View File

@ -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;
} }

View File

@ -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;
} }

View File

@ -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;

View File

@ -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;

View File

@ -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();

View File

@ -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);

View File

@ -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[]{

View File

@ -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?
}
}
} }

View File

@ -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);
}
} }

View File

@ -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);
}
} }

View File

@ -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);
}
}
} }

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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");
}
}
}

View File

@ -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);
}
}
}

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
} }

View File

@ -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>

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View 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>

View 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>