Sony Wena 3: Initial support (#3311)

Co-authored-by: vladkorotnev <vladkorotnev@noreply.codeberg.org>
Co-committed-by: vladkorotnev <vladkorotnev@noreply.codeberg.org>
This commit is contained in:
vladkorotnev 2023-09-23 10:31:41 +00:00 committed by José Rebelo
parent 9fe5bc049f
commit 889a1db801
107 changed files with 7364 additions and 6 deletions

View File

@ -43,7 +43,7 @@ public class GBDaoGenerator {
public static void main(String[] args) throws Exception {
final Schema schema = new Schema(51, MAIN_PACKAGE + ".entities");
final Schema schema = new Schema(59, MAIN_PACKAGE + ".entities");
Entity userAttributes = addUserAttributes(schema);
Entity user = addUserInfo(schema, userAttributes);
@ -94,12 +94,20 @@ public class GBDaoGenerator {
addHybridHRActivitySample(schema, user, device);
addVivomoveHrActivitySample(schema, user, device);
addGarminFitFile(schema, user, device);
addWena3EnergySample(schema, user, device);
addWena3BehaviorSample(schema, user, device);
addWena3CaloriesSample(schema, user, device);
addWena3ActivitySample(schema, user, device);
addWena3HeartRateSample(schema, user, device);
addWena3Vo2Sample(schema, user, device);
addWena3StressSample(schema, user, device);
addCalendarSyncState(schema, device);
addAlarms(schema, user, device);
addReminders(schema, user, device);
addWorldClocks(schema, user, device);
addContacts(schema, user, device);
addAppSpecificNotificationSettings(schema, device);
Entity notificationFilter = addNotificationFilters(schema);
@ -863,4 +871,71 @@ public class GBDaoGenerator {
activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
return activitySample;
}
private static Entity addWena3BehaviorSample(Schema schema, Entity user, Entity device) {
Entity activitySample = addEntity(schema, "Wena3BehaviorSample");
addCommonTimeSampleProperties("AbstractTimeSample", activitySample, user, device);
activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull();
activitySample.addLongProperty(TIMESTAMP_FROM).notNull();
activitySample.addLongProperty(TIMESTAMP_TO).notNull();
return activitySample;
}
private static Entity addWena3Vo2Sample(Schema schema, Entity user, Entity device) {
Entity activitySample = addEntity(schema, "Wena3Vo2Sample");
addCommonTimeSampleProperties("AbstractTimeSample", activitySample, user, device);
activitySample.addIntProperty("vo2").notNull();
activitySample.addIntProperty("datapoint").notNull().primaryKey();
return activitySample;
}
private static Entity addWena3StressSample(Schema schema, Entity user, Entity device) {
Entity stressSample = addEntity(schema, "Wena3StressSample");
addCommonTimeSampleProperties("AbstractStressSample", stressSample, user, device);
stressSample.addIntProperty("typeNum").notNull().codeBeforeGetterAndSetter(OVERRIDE);
stressSample.addIntProperty("stress").notNull().codeBeforeGetter(OVERRIDE);
return stressSample;
}
private static Entity addWena3ActivitySample(Schema schema, Entity user, Entity device) {
Entity activitySample = addEntity(schema, "Wena3ActivitySample");
addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
activitySample.addIntProperty(SAMPLE_STEPS).notNull();
activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetter(OVERRIDE);
addHeartRateProperties(activitySample);
return activitySample;
}
private static Entity addWena3HeartRateSample(Schema schema, Entity user, Entity device) {
Entity activitySample = addEntity(schema, "Wena3HeartRateSample");
addCommonTimeSampleProperties("AbstractHeartRateSample", activitySample, user, device);
activitySample.addIntProperty(SAMPLE_HEART_RATE).notNull();
return activitySample;
}
private static Entity addWena3EnergySample(Schema schema, Entity user, Entity device) {
Entity activitySample = addEntity(schema, "Wena3EnergySample");
addCommonTimeSampleProperties("AbstractTimeSample", activitySample, user, device);
activitySample.addIntProperty("energy").notNull();
return activitySample;
}
private static Entity addWena3CaloriesSample(Schema schema, Entity user, Entity device) {
Entity activitySample = addEntity(schema, "Wena3CaloriesSample");
addCommonTimeSampleProperties("AbstractTimeSample", activitySample, user, device);
activitySample.addIntProperty("calories").notNull();
return activitySample;
}
private static Entity addAppSpecificNotificationSettings(Schema schema, Entity device) {
Entity perAppSetting = addEntity(schema, "AppSpecificNotificationSetting");
perAppSetting.addStringProperty("packageId").notNull().primaryKey();
Property deviceId = perAppSetting.addLongProperty("deviceId").primaryKey().notNull().getProperty();
perAppSetting.addToOne(device, deviceId);
perAppSetting.addStringProperty("ledPattern");
perAppSetting.addStringProperty("vibrationPattern");
perAppSetting.addStringProperty("vibrationRepetition");
return perAppSetting;
}
}

View File

@ -103,11 +103,13 @@ vendor's servers.
- PineTime (InfiniTime Firmware)
- Roidmi, Roidmi 3, Mojietu 3 (Bluetooth FM Transmitters)
- [SMA](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/SMA) Q2 (SMA-Q2-OSS Firmware)
- [Sony Headphones](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Sony-Headphones)
- LinkBuds S
- WH-1000XM2, WH-1000XM3, WH-1000XM4, WH-1000XM5
- WF-SP800N
- WF-1000XM3, WF-1000XM4
- Sony
- [Headphones](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Sony-Headphones)
- LinkBuds S
- WH-1000XM2, WH-1000XM3, WH-1000XM4, WH-1000XM5
- WF-SP800N
- WF-1000XM3, WF-1000XM4
- Wena 3
- Teclast H10, H30
- TLW64
- Vibratissimo (Experimental)

View File

@ -396,6 +396,22 @@
<data android:mimeType="*/*" />
</intent-filter>
</activity>
<activity
android:exported="false"
android:name=".activities.app_specific_notifications.AppSpecificNotificationSettingsActivity"
android:label="@string/prefs_wena3_notification_per_app_settings_title"
android:parentActivityName=".activities.devicesettings.DeviceSettingsActivity">
<intent-filter>
<action android:name=".SonyWena3PerAppNotificationSettingsActivity" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:exported="false"
android:name=".activities.app_specific_notifications.AppSpecificNotificationSettingsDetailActivity"
android:label="@string/prefs_wena3_notification_per_app_settings_title"
android:parentActivityName=".activities.app_specific_notifications.AppSpecificNotificationSettingsActivity"
/>
<service
android:name=".externalevents.NotificationListener"

View File

@ -0,0 +1,79 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.activities.app_specific_notifications;
import android.os.Bundle;
import android.view.MenuItem;
import androidx.appcompat.widget.SearchView;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
import nodomain.freeyourgadget.gadgetbridge.adapter.AppSpecificNotificationSettingsAppListAdapter;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class AppSpecificNotificationSettingsActivity extends AbstractGBActivity {
private static final Logger LOG = LoggerFactory.getLogger(AppSpecificNotificationSettingsActivity.class);
private AppSpecificNotificationSettingsAppListAdapter appListAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_app_specific_notification_settings);
RecyclerView appListView = findViewById(R.id.appListView);
appListView.setLayoutManager(new LinearLayoutManager(this));
GBDevice device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
appListAdapter = new AppSpecificNotificationSettingsAppListAdapter(R.layout.item_app_specific_notification_app_list, this, device);
appListView.setAdapter(appListAdapter);
SearchView searchView = findViewById(R.id.appListViewSearch);
searchView.setIconifiedByDefault(false);
searchView.setIconified(false);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
appListAdapter.getFilter().filter(newText);
return true;
}
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
}

View File

@ -0,0 +1,187 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.activities.app_specific_notifications;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.Spinner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
import nodomain.freeyourgadget.gadgetbridge.adapter.AppSpecificNotificationSettingsAppListAdapter;
import nodomain.freeyourgadget.gadgetbridge.database.AppSpecificNotificationSettingsRepository;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.entities.AppSpecificNotificationSetting;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.AbstractNotificationPattern;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
public class AppSpecificNotificationSettingsDetailActivity extends AbstractGBActivity {
private AppSpecificNotificationSettingsRepository repository = null;
private String bundleId = null;
private GBDevice mDevice;
private DeviceCoordinator mCoordinator;
private List<String> mLedPatternValues = new ArrayList<>();
private List<String> mVibrationPatternValues = new ArrayList<>();
private List<String> mVibrationCountValues = new ArrayList<>();
private Spinner mSpinnerLedPattern;
private Spinner mSpinnerVibrationPattern;
private Spinner mSpinnerVibrationCount;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_notification_per_app_setting_detail);
Button mButtonSave = findViewById(R.id.buttonSaveSettings);
Button mButtonDelete = findViewById(R.id.buttonDeleteSettings);
mSpinnerLedPattern = findViewById(R.id.spinnerLedType);
mSpinnerVibrationPattern = findViewById(R.id.spinnerVibraType);
mSpinnerVibrationCount = findViewById(R.id.spinnerVibraCount);
mDevice = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
mCoordinator = DeviceHelper.getInstance().getCoordinator(mDevice);
mLedPatternValues.clear();
for(AbstractNotificationPattern p: mCoordinator.getNotificationLedPatterns())
mLedPatternValues.add(p.getValue());
mVibrationPatternValues.clear();
for(AbstractNotificationPattern p: mCoordinator.getNotificationVibrationPatterns())
mVibrationPatternValues.add(p.getValue());
mVibrationCountValues.clear();
for(AbstractNotificationPattern p: mCoordinator.getNotificationVibrationRepetitionPatterns())
mVibrationCountValues.add(p.getValue());
if(!mCoordinator.supportsNotificationLedPatterns()) {
mSpinnerLedPattern.setEnabled(false);
} else {
mSpinnerLedPattern.setAdapter(
createAdapterFromArrayAddingDefault(mCoordinator.getNotificationLedPatterns())
);
}
if(!mCoordinator.supportsNotificationVibrationPatterns()) {
mSpinnerVibrationPattern.setEnabled(false);
} else {
mSpinnerVibrationPattern.setAdapter(
createAdapterFromArrayAddingDefault(mCoordinator.getNotificationVibrationPatterns())
);
}
if(!mCoordinator.supportsNotificationVibrationRepetitionPatterns()) {
mSpinnerVibrationCount.setEnabled(false);
} else {
mSpinnerVibrationCount.setAdapter(
createAdapterFromArrayAddingDefault(mCoordinator.getNotificationVibrationRepetitionPatterns())
);
}
String title = getIntent().getStringExtra(AppSpecificNotificationSettingsAppListAdapter.STRING_EXTRA_PACKAGE_TITLE);
setTitle(title);
bundleId = getIntent().getStringExtra(AppSpecificNotificationSettingsAppListAdapter.STRING_EXTRA_PACKAGE_NAME);
repository = new AppSpecificNotificationSettingsRepository(mDevice);
mButtonDelete.setOnClickListener(view -> {
repository.setSettingsForAppId(bundleId, null);
finish();
});
mButtonSave.setOnClickListener(view -> {
saveSettings();
finish();
});
AppSpecificNotificationSetting setting = repository.getSettingsForAppId(bundleId);
if(setting != null) {
if(setting.getLedPattern() != null) {
int idx = mLedPatternValues.indexOf(setting.getLedPattern());
if(idx >= 0) {
mSpinnerLedPattern.setSelection(idx + 1);
}
} else {
mSpinnerLedPattern.setSelection(0);
}
if(setting.getVibrationPattern() != null) {
int idx = mVibrationPatternValues.indexOf(setting.getVibrationPattern());
if(idx >= 0) {
mSpinnerVibrationPattern.setSelection(idx + 1);
}
} else {
mSpinnerVibrationPattern.setSelection(0);
}
if(setting.getVibrationRepetition() != null) {
int idx = mVibrationCountValues.indexOf(setting.getVibrationRepetition());
if(idx >= 0) {
mSpinnerVibrationCount.setSelection(idx + 1);
}
} else {
mSpinnerVibrationCount.setSelection(0);
}
}
}
private ArrayAdapter<String> createAdapterFromArrayAddingDefault(AbstractNotificationPattern[] array) {
List<String> allOptions = new ArrayList<>();
allOptions.add(getString(R.string.pref_default));
for(AbstractNotificationPattern s: array) allOptions.add(s.getUserReadableName(getApplicationContext()));
return new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, allOptions);
}
private void saveSettings() {
String led = null;
String vibra = null;
String vibraTimes = null;
if(mSpinnerLedPattern.getSelectedItemPosition() > 0) {
led = mLedPatternValues.get(mSpinnerLedPattern.getSelectedItemPosition() - 1);
}
if(mSpinnerVibrationPattern.getSelectedItemPosition() > 0) {
vibra = mVibrationPatternValues.get(mSpinnerVibrationPattern.getSelectedItemPosition() - 1);
}
if(mSpinnerVibrationCount.getSelectedItemPosition() > 0) {
vibraTimes = mVibrationCountValues.get(mSpinnerVibrationCount.getSelectedItemPosition() - 1);
}
AppSpecificNotificationSetting setting = new AppSpecificNotificationSetting(
bundleId,
0,
led,
vibra,
vibraTimes
);
repository.setSettingsForAppId(bundleId, setting);
}
}

View File

@ -342,6 +342,7 @@ public class DeviceSettingsPreferenceConst {
public static final String PREFS_ACTIVITY_IN_DEVICE_CARD_SLEEP = "prefs_activity_in_device_card_sleep";
public static final String PREFS_ACTIVITY_IN_DEVICE_CARD_DISTANCE = "prefs_activity_in_device_card_distance";
public static final String PREFS_DEVICE_CHARTS_TABS = "charts_tabs";
public static final String PREFS_PER_APP_NOTIFICATION_SETTINGS = "pref_per_app_notification_settings";
public static final String PREF_UM25_SHOW_THRESHOLD_NOTIFICATION = "um25_current_threshold_notify";
public static final String PREF_UM25_SHOW_THRESHOLD = "um25_current_threshold";

View File

@ -41,6 +41,7 @@ import nodomain.freeyourgadget.gadgetbridge.activities.CalBlacklistActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureContacts;
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureWorldClocks;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractPreferenceFragment;
import nodomain.freeyourgadget.gadgetbridge.activities.app_specific_notifications.AppSpecificNotificationSettingsActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.loyaltycards.LoyaltyCardsSettingsActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.loyaltycards.LoyaltyCardsSettingsConst;
import nodomain.freeyourgadget.gadgetbridge.capabilities.HeartRateCapability;
@ -49,6 +50,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3SettingKeys;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
@ -407,6 +409,8 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i
addPreferenceHandlerFor(PREF_AMPM_ENABLED);
addPreferenceHandlerFor(PREF_SOUNDS);
addPreferenceHandlerFor(PREF_CAMERA_REMOTE);
addPreferenceHandlerFor(PREF_SCREEN_LIFT_WRIST);
addPreferenceHandlerFor(PREF_SYNC_CALENDAR);
addPreferenceHandlerFor(PREF_BLUETOOTH_CALLS_ENABLED);
addPreferenceHandlerFor(PREF_DISPLAY_CALLER);
@ -900,6 +904,16 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i
});
}
final Preference notificationSettings = findPreference(PREFS_PER_APP_NOTIFICATION_SETTINGS);
if(notificationSettings != null) {
notificationSettings.setOnPreferenceClickListener(preference -> {
final Intent intent = new Intent(getContext(), AppSpecificNotificationSettingsActivity.class);
intent.putExtra(GBDevice.EXTRA_DEVICE, getDevice());
startActivity(intent);
return true;
});
}
if (deviceSpecificSettingsCustomizer != null) {
deviceSpecificSettingsCustomizer.customizeSettings(this, prefs);
}

View File

@ -0,0 +1,254 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.adapter;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import androidx.recyclerview.widget.RecyclerView;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.app_specific_notifications.AppSpecificNotificationSettingsDetailActivity;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import static nodomain.freeyourgadget.gadgetbridge.GBApplication.packageNameToPebbleMsgSender;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AppSpecificNotificationSettingsAppListAdapter extends RecyclerView.Adapter<AppSpecificNotificationSettingsAppListAdapter.AppNotificationSettingsViewHolder> implements Filterable {
protected static final Logger LOG = LoggerFactory.getLogger(AppSpecificNotificationSettingsAppListAdapter.class);
public static final String STRING_EXTRA_PACKAGE_NAME = "packageName";
public static final String STRING_EXTRA_PACKAGE_TITLE = "packageTitle";
private final List<ApplicationInfo> applicationInfoList;
private final int mLayoutId;
private final Context mContext;
private final PackageManager mPm;
private GBDevice mDevice;
private final IdentityHashMap<ApplicationInfo, String> mNameMap;
private ApplicationFilter applicationFilter;
public AppSpecificNotificationSettingsAppListAdapter(int layoutId, Context context, GBDevice device) {
mLayoutId = layoutId;
mContext = context;
mPm = context.getPackageManager();
mDevice = device;
applicationInfoList = getAllApplications();
// sort the package list by label and blacklist status
mNameMap = new IdentityHashMap<>(applicationInfoList.size());
for (ApplicationInfo ai : applicationInfoList) {
CharSequence name = mPm.getApplicationLabel(ai);
if (name == null) {
name = ai.packageName;
}
mNameMap.put(ai, name.toString());
}
Collections.sort(applicationInfoList, (ai1, ai2) -> {
final String s1 = mNameMap.get(ai1);
final String s2 = mNameMap.get(ai2);
return s1.compareToIgnoreCase(s2);
});
}
@Override
public AppNotificationSettingsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(mLayoutId, parent, false);
return new AppNotificationSettingsViewHolder(view);
}
@Override
public void onBindViewHolder(final AppNotificationSettingsViewHolder holder, int position) {
final ApplicationInfo appInfo = applicationInfoList.get(position);
holder.deviceAppVersionAuthorLabel.setText(appInfo.packageName);
holder.deviceAppNameLabel.setText(mNameMap.get(appInfo));
holder.deviceImageView.setImageDrawable(appInfo.loadIcon(mPm));
holder.itemView.setOnClickListener(v -> {
Intent intentStartNotificationFilterActivity = new Intent(mContext, AppSpecificNotificationSettingsDetailActivity.class);
intentStartNotificationFilterActivity.putExtra(STRING_EXTRA_PACKAGE_NAME, appInfo.packageName);
intentStartNotificationFilterActivity.putExtra(STRING_EXTRA_PACKAGE_TITLE, mNameMap.get(appInfo));
intentStartNotificationFilterActivity.putExtra(GBDevice.EXTRA_DEVICE, mDevice);
mContext.startActivity(intentStartNotificationFilterActivity);
});
holder.btnConfigureApp.setOnClickListener(view -> {
Intent intentStartNotificationFilterActivity = new Intent(mContext, AppSpecificNotificationSettingsDetailActivity.class);
intentStartNotificationFilterActivity.putExtra(STRING_EXTRA_PACKAGE_NAME, appInfo.packageName);
intentStartNotificationFilterActivity.putExtra(STRING_EXTRA_PACKAGE_TITLE, mNameMap.get(appInfo));
intentStartNotificationFilterActivity.putExtra(GBDevice.EXTRA_DEVICE, mDevice);
mContext.startActivity(intentStartNotificationFilterActivity);
});
}
/**
* Returns the applications for which the Gadgetbridge notifications are enabled.
*/
public List<ApplicationInfo> getAllApplications() {
final Set<String> allPackageNames = new HashSet<>();
final List<ApplicationInfo> ret = new LinkedList<>();
boolean filterInverted = !GBApplication.getPrefs().getString("notification_list_is_blacklist", "true").equals("true");
// Get apps for the current user
final List<ApplicationInfo> currentUserApps = mPm.getInstalledApplications(PackageManager.GET_META_DATA);
for (final ApplicationInfo app : currentUserApps) {
boolean blacklisted = GBApplication.appIsNotifBlacklisted(app.packageName) || GBApplication.appIsPebbleBlacklisted(packageNameToPebbleMsgSender(app.packageName));
if((!filterInverted && !blacklisted) || (filterInverted && blacklisted)) {
allPackageNames.add(app.packageName);
ret.add(app);
}
}
// Add all apps from other users (eg. manager profile)
try {
final UserHandle currentUser = Process.myUserHandle();
final LauncherApps launcher = (LauncherApps) mContext.getSystemService(Context.LAUNCHER_APPS_SERVICE);
final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
final List<UserHandle> userProfiles = um.getUserProfiles();
for (final UserHandle userProfile : userProfiles) {
if (userProfile.equals(currentUser)) {
continue;
}
final List<LauncherActivityInfo> userActivityList = launcher.getActivityList(null, userProfile);
for (final LauncherActivityInfo app : userActivityList) {
if (!allPackageNames.contains(app.getApplicationInfo().packageName)) {
boolean blacklisted = GBApplication.appIsNotifBlacklisted(app.getApplicationInfo().packageName) || GBApplication.appIsPebbleBlacklisted(packageNameToPebbleMsgSender(app.getApplicationInfo().packageName));
if((!filterInverted && !blacklisted) || (filterInverted && blacklisted)) {
allPackageNames.add(app.getApplicationInfo().packageName);
ret.add(app.getApplicationInfo());
}
}
}
}
} catch (final Exception e) {
LOG.error("Failed to get apps from other users", e);
}
return ret;
}
@Override
public int getItemCount() {
return applicationInfoList.size();
}
@Override
public Filter getFilter() {
if (applicationFilter == null)
applicationFilter = new ApplicationFilter(this, applicationInfoList);
return applicationFilter;
}
class AppNotificationSettingsViewHolder extends RecyclerView.ViewHolder {
final ImageView deviceImageView;
final TextView deviceAppVersionAuthorLabel;
final TextView deviceAppNameLabel;
final ImageView btnConfigureApp;
AppNotificationSettingsViewHolder(View itemView) {
super(itemView);
deviceImageView = itemView.findViewById(R.id.item_image);
deviceAppVersionAuthorLabel = itemView.findViewById(R.id.item_details);
deviceAppNameLabel = itemView.findViewById(R.id.item_name);
btnConfigureApp = itemView.findViewById(R.id.btn_configureApp);
}
}
private class ApplicationFilter extends Filter {
private final AppSpecificNotificationSettingsAppListAdapter adapter;
private final List<ApplicationInfo> originalList;
private final List<ApplicationInfo> filteredList;
private ApplicationFilter(AppSpecificNotificationSettingsAppListAdapter adapter, List<ApplicationInfo> originalList) {
super();
this.originalList = new ArrayList<>(originalList);
this.filteredList = new ArrayList<>();
this.adapter = adapter;
}
@Override
protected Filter.FilterResults performFiltering(CharSequence filter) {
filteredList.clear();
final Filter.FilterResults results = new Filter.FilterResults();
if (filter == null || filter.length() == 0)
filteredList.addAll(originalList);
else {
final String filterPattern = filter.toString().toLowerCase().trim();
for (ApplicationInfo ai : originalList) {
CharSequence name = mPm.getApplicationLabel(ai);
if (name.toString().toLowerCase().contains(filterPattern) ||
(ai.packageName.contains(filterPattern))) {
filteredList.add(ai);
}
}
}
results.values = filteredList;
results.count = filteredList.size();
return results;
}
@Override
protected void publishResults(CharSequence charSequence, Filter.FilterResults filterResults) {
adapter.applicationInfoList.clear();
adapter.applicationInfoList.addAll((List<ApplicationInfo>) filterResults.values);
adapter.notifyDataSetChanged();
}
}
}

View File

@ -0,0 +1,88 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.database;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.entities.AppSpecificNotificationSettingDao;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.AppSpecificNotificationSetting;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
// TODO: Distinguish by device ID as well to allow for different settings per device.
// I didn't know how to get a device ID from a settings activity, so I left it as is.
public class AppSpecificNotificationSettingsRepository {
private static final Logger LOG = LoggerFactory.getLogger(AppSpecificNotificationSettingsRepository.class);
private GBDevice mGbDevice;
public AppSpecificNotificationSettingsRepository(GBDevice gbDevice) {
mGbDevice = gbDevice;
}
@Nullable
public AppSpecificNotificationSetting getSettingsForAppId(String appId) {
try (DBHandler db = GBApplication.acquireDB()) {
DaoSession session = db.getDaoSession();
QueryBuilder<AppSpecificNotificationSetting> qb = session.getAppSpecificNotificationSettingDao().queryBuilder();
return qb.where(
qb.and(AppSpecificNotificationSettingDao.Properties.PackageId.eq(appId),
AppSpecificNotificationSettingDao.Properties.DeviceId.eq(DBHelper.findDevice(mGbDevice, session).getId())
)).build().unique();
} catch (Exception e) {
LOG.error("Failed to get DB handle", e);
}
return null;
}
private void deleteForAppId(@NonNull String appId) {
try (DBHandler db = GBApplication.acquireDB()) {
DaoSession session = db.getDaoSession();
QueryBuilder<AppSpecificNotificationSetting> qb = session.getAppSpecificNotificationSettingDao().queryBuilder();
qb.where(
qb.and(AppSpecificNotificationSettingDao.Properties.PackageId.eq(appId),
AppSpecificNotificationSettingDao.Properties.DeviceId.eq(DBHelper.findDevice(mGbDevice, session).getId())
)).buildDelete().executeDeleteWithoutDetachingEntities();
} catch (Exception e) {
LOG.error("Failed to get DB handle", e);
}
}
public void setSettingsForAppId(@NonNull String appId, @Nullable AppSpecificNotificationSetting settings) {
if (settings == null) {
deleteForAppId(appId);
} else {
settings.setPackageId(appId);
try (DBHandler db = GBApplication.acquireDB()) {
DaoSession session = db.getDaoSession();
Device dbDevice = DBHelper.findDevice(mGbDevice, session);
settings.setDeviceId(dbDevice.getId());
session.getAppSpecificNotificationSettingDao().insertOrReplace(settings);
} catch (Exception e) {
LOG.error("Failed to get DB handle", e);
}
}
}
}

View File

@ -57,6 +57,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.DeviceAttributesDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.AbstractNotificationPattern;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample;
@ -472,4 +473,28 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
public EnumSet<ServiceDeviceSupport.Flags> getInitialFlags() {
return EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING);
}
public boolean supportsNotificationVibrationPatterns() {
return false;
}
public boolean supportsNotificationVibrationRepetitionPatterns() {
return false;
}
public boolean supportsNotificationLedPatterns() {
return false;
}
public AbstractNotificationPattern[] getNotificationVibrationPatterns() {
return new AbstractNotificationPattern[0];
}
public AbstractNotificationPattern[] getNotificationVibrationRepetitionPatterns() {
return new AbstractNotificationPattern[0];
}
public AbstractNotificationPattern[] getNotificationLedPatterns() {
return new AbstractNotificationPattern[0];
}
}

View File

@ -38,6 +38,7 @@ import nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabi
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.AbstractNotificationPattern;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
@ -563,4 +564,32 @@ public interface DeviceCoordinator {
Class<? extends DeviceSupport> getDeviceSupportClass();
EnumSet<ServiceDeviceSupport.Flags> getInitialFlags();
/**
* Whether the device supports a variety of vibration patterns for notifications.
*/
boolean supportsNotificationVibrationPatterns();
/**
* Whether the device supports a variety of vibration pattern repetitions for notifications.
*/
boolean supportsNotificationVibrationRepetitionPatterns();
/**
* Whether the device supports a variety of LED patterns for notifications.
*/
boolean supportsNotificationLedPatterns();
/**
* What vibration pattern repetitions for notifications are supported by the device.
*/
AbstractNotificationPattern[] getNotificationVibrationPatterns();
/**
* What vibration pattern repetitions for notifications are supported by the device.
* Technote: this is not an int or a range because some devices (e.g. Wena 3) only allow
* a very specific set of value combinations here.
*/
AbstractNotificationPattern[] getNotificationVibrationRepetitionPatterns();
/**
* What LED patterns for notifications are supported by the device.
*/
AbstractNotificationPattern[] getNotificationLedPatterns();
}

View File

@ -0,0 +1,81 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3BehaviorSample;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3HeartRateSample;
import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity.BehaviorSample;
// The sole purpose of this part of code is to take the Behavior samples
// and drop them onto Steps samples (which are just your average activity data with the current
// architecture of Gadgetbridge)
public class SonyWena3ActivitySampleCombiner {
private static final Logger LOG = LoggerFactory.getLogger(SonyWena3ActivitySampleCombiner.class);
final SonyWena3ActivitySampleProvider activitySampleProvider;
public SonyWena3ActivitySampleCombiner(SonyWena3ActivitySampleProvider activitySampleProvider) {
this.activitySampleProvider = activitySampleProvider;
}
public void overlayBehaviorStartingAt(Date startDate, SonyWena3BehaviorSampleProvider behaviorSampleProvider) {
List<Wena3BehaviorSample> behaviorSamples = behaviorSampleProvider.getAllSamples(startDate.getTime(), Long.MAX_VALUE);
List<Wena3ActivitySample> alteredSamples = new ArrayList<>();
for(Wena3BehaviorSample behaviorSample: behaviorSamples) {
List<Wena3ActivitySample> activitySamplesForThisRange = activitySampleProvider.getAllActivitySamples((int)(behaviorSample.getTimestampFrom() / 1000L), (int)(behaviorSample.getTimestampTo() / 1000L));
LOG.debug("Changing " + activitySamplesForThisRange.size() + " samples to behavior type: " + BehaviorSample.Type.LUT[behaviorSample.getRawKind()].name());
for(Wena3ActivitySample activitySample: activitySamplesForThisRange) {
activitySample.setRawKind(behaviorSample.getRawKind());
alteredSamples.add(activitySample);
}
}
activitySampleProvider.addGBActivitySamples(alteredSamples.toArray(new Wena3ActivitySample[alteredSamples.size()]));
}
public void overlayHeartRateStartingAt(Date startDate, SonyWena3HeartRateSampleProvider heartRateSampleProvider) {
List<Wena3HeartRateSample> heartRateSamples = heartRateSampleProvider.getAllSamples(startDate.getTime(), Long.MAX_VALUE);
List<Wena3ActivitySample> alteredSamples = new ArrayList<>();
for(int i = 0; i < heartRateSamples.size(); i++) {
HeartRateSample currentSample = heartRateSamples.get(i);
HeartRateSample nextSample = (i == heartRateSamples.size() - 1) ? null : heartRateSamples.get(i + 1);
List<Wena3ActivitySample> activitySamplesInRange = activitySampleProvider.getAllActivitySamples(
(int) (currentSample.getTimestamp() / 1000L),
(nextSample == null ? Integer.MAX_VALUE : ((int) (nextSample.getTimestamp() / 1000L)))
);
LOG.debug("Changing " + activitySamplesInRange.size() + " samples to heart rate: " + currentSample.getHeartRate());
for(Wena3ActivitySample activitySample: activitySamplesInRange) {
activitySample.setHeartRate(currentSample.getHeartRate());
alteredSamples.add(activitySample);
}
}
activitySampleProvider.addGBActivitySamples(alteredSamples.toArray(new Wena3ActivitySample[alteredSamples.size()]));
}
}

View File

@ -0,0 +1,126 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.Property;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3ActivitySampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity.BehaviorSample;
public class SonyWena3ActivitySampleProvider extends AbstractSampleProvider<Wena3ActivitySample> {
public SonyWena3ActivitySampleProvider(GBDevice device, DaoSession session) {
super(device, session);
}
@Override
public AbstractDao<Wena3ActivitySample, ?> getSampleDao() {
return getSession().getWena3ActivitySampleDao();
}
@Nullable
@Override
protected Property getRawKindSampleProperty() {
return null;
}
@NonNull
@Override
protected Property getTimestampSampleProperty() {
return Wena3ActivitySampleDao.Properties.Timestamp;
}
@NonNull
@Override
protected Property getDeviceIdentifierSampleProperty() {
return Wena3ActivitySampleDao.Properties.DeviceId;
}
@Override
public int normalizeType(int rawType) {
if(rawType < 0 || rawType >= BehaviorSample.Type.LUT.length) return ActivityKind.TYPE_UNKNOWN;
BehaviorSample.Type internalType = BehaviorSample.Type.LUT[rawType];
switch(internalType) {
case NOT_WEARING:
return ActivityKind.TYPE_NOT_WORN;
case WALK:
return ActivityKind.TYPE_WALKING;
case RUN:
return ActivityKind.TYPE_RUNNING;
case EXERCISE:
return ActivityKind.TYPE_EXERCISE;
case SLEEP_LIGHT:
return ActivityKind.TYPE_LIGHT_SLEEP;
case SLEEP_REM:
return ActivityKind.TYPE_REM_SLEEP;
case SLEEP_DEEP:
return ActivityKind.TYPE_DEEP_SLEEP;
case STATIC:
case SLEEP_AWAKE:
case UNKNOWN:
default:
return ActivityKind.TYPE_UNKNOWN;
}
}
@Override
public int toRawActivityKind(int activityKind) {
switch(activityKind) {
case ActivityKind.TYPE_NOT_MEASURED:
case ActivityKind.TYPE_NOT_WORN:
return BehaviorSample.Type.NOT_WEARING.ordinal();
case ActivityKind.TYPE_WALKING:
return BehaviorSample.Type.WALK.ordinal();
case ActivityKind.TYPE_RUNNING:
return BehaviorSample.Type.RUN.ordinal();
case ActivityKind.TYPE_LIGHT_SLEEP:
return BehaviorSample.Type.SLEEP_LIGHT.ordinal();
case ActivityKind.TYPE_REM_SLEEP:
return BehaviorSample.Type.SLEEP_REM.ordinal();
case ActivityKind.TYPE_DEEP_SLEEP:
return BehaviorSample.Type.SLEEP_DEEP.ordinal();
case ActivityKind.TYPE_EXERCISE:
return BehaviorSample.Type.EXERCISE.ordinal();
default:
return BehaviorSample.Type.UNKNOWN.ordinal();
}
}
@Override
public float normalizeIntensity(int rawIntensity) {
return 0;
}
@Override
public Wena3ActivitySample createActivitySample() {
return new Wena3ActivitySample();
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3;
import androidx.annotation.NonNull;
import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.Property;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractTimeSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3BehaviorSample;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3BehaviorSampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class SonyWena3BehaviorSampleProvider extends AbstractTimeSampleProvider<Wena3BehaviorSample> {
public SonyWena3BehaviorSampleProvider(final GBDevice device, final DaoSession session) {
super(device, session);
}
@NonNull
@Override
public AbstractDao<Wena3BehaviorSample, ?> getSampleDao() {
return getSession().getWena3BehaviorSampleDao();
}
@NonNull
@Override
protected Property getTimestampSampleProperty() {
return Wena3BehaviorSampleDao.Properties.Timestamp;
}
@NonNull
@Override
protected Property getDeviceIdentifierSampleProperty() {
return Wena3BehaviorSampleDao.Properties.DeviceId;
}
@Override
public Wena3BehaviorSample createSample() {
return new Wena3BehaviorSample();
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3;
import androidx.annotation.NonNull;
import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.Property;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractTimeSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3CaloriesSample;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3CaloriesSampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class SonyWena3CaloriesSampleProvider extends AbstractTimeSampleProvider<Wena3CaloriesSample> {
public SonyWena3CaloriesSampleProvider(final GBDevice device, final DaoSession session) {
super(device, session);
}
@NonNull
@Override
public AbstractDao<Wena3CaloriesSample, ?> getSampleDao() {
return getSession().getWena3CaloriesSampleDao();
}
@NonNull
@Override
protected Property getTimestampSampleProperty() {
return Wena3CaloriesSampleDao.Properties.Timestamp;
}
@NonNull
@Override
protected Property getDeviceIdentifierSampleProperty() {
return Wena3CaloriesSampleDao.Properties.DeviceId;
}
@Override
public Wena3CaloriesSample createSample() {
return new Wena3CaloriesSample();
}
}

View File

@ -0,0 +1,64 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3;
import android.graphics.Color;
import java.util.UUID;
public class SonyWena3Constants {
public static final String BT_DEVICE_NAME = "WNW-21A";
private static final String uuidTemplate = "4EFD%s-A6C1-16F0-062F-F196CF496695";
public static final UUID COMMON_SERVICE_UUID = UUID.fromString(String.format(uuidTemplate, "1501"));
public static final UUID COMMON_SERVICE_CHARACTERISTIC_MODE_UUID = UUID.fromString(String.format(uuidTemplate, "1503"));
public static final UUID COMMON_SERVICE_CHARACTERISTIC_CONTROL_UUID = UUID.fromString(String.format(uuidTemplate, "1514"));
public static final UUID COMMON_SERVICE_CHARACTERISTIC_INFO_UUID = UUID.fromString(String.format(uuidTemplate, "1520"));
public static final UUID COMMON_SERVICE_CHARACTERISTIC_STATE_UUID = UUID.fromString(String.format(uuidTemplate, "1521"));
public static final UUID NOTIFICATION_SERVICE_UUID = UUID.fromString(String.format(uuidTemplate, "4001"));
public static final UUID NOTIFICATION_SERVICE_CHARACTERISTIC_UUID = UUID.fromString(String.format(uuidTemplate, "4002"));
public static final UUID ACTIVITY_LOG_SERVICE_UUID = UUID.fromString(String.format(uuidTemplate, "3001"));
public static final UUID ACTIVITY_LOG_CHARACTERISTIC_UUID = UUID.fromString(String.format(uuidTemplate, "3002"));
public static int[] LED_PRESETS = {
Color.rgb(255, 0, 0),
Color.rgb(255, 255, 0),
Color.rgb(0, 255, 0),
Color.rgb(0, 255, 255),
Color.rgb(0, 0, 255),
Color.rgb(255, 0, 255),
Color.rgb(255, 255, 255)
};
public static final long EPOCH_START = 1577836800000L;
public static final int ALARM_SLOTS = 9;
public static final int ALARM_DEFAULT_SMART_WAKEUP_MARGIN_MINUTES = 10;
public static final String DB_FIELD_VO2_DATAPOINT_NUMBER = "datapoint";
public static final String DB_FIELD_VO2_VALUE = "vo2";
public static final String DB_FIELD_STRESS = "stress";
public static final String DB_FIELD_STEPS = "steps";
public static final String DB_FIELD_HEART_RATE = "heartRate";
public static final String DB_FIELD_ENERGY = "energy";
public static final String DB_FIELD_CALORIES = "calories";
}

View File

@ -0,0 +1,302 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3;
import android.app.Activity;
import android.bluetooth.le.ScanFilter;
import android.content.Context;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3ActivitySampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3BehaviorSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3CaloriesSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3EnergySampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3HeartRateSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3StressSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3Vo2SampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.AbstractNotificationPattern;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample;
import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.SonyWena3DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines.LedColor;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines.VibrationCount;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines.VibrationKind;
public class SonyWena3Coordinator extends AbstractBLEDeviceCoordinator {
@Override
public DeviceType getDeviceType() {
return DeviceType.SONY_WENA_3;
}
@Nullable
@Override
public Class<? extends Activity> getPairingActivity() {
return null;
}
@Override
public String getManufacturer() {
return "Sony";
}
@Override
public boolean supportsAppsManagement(GBDevice device) {
return false;
}
@Override
public Class<? extends Activity> getAppsManagementActivity() {
return null;
}
@NonNull
@Override
public Class<? extends DeviceSupport> getDeviceSupportClass() {
return SonyWena3DeviceSupport.class;
}
@Override
public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(GBDevice device) {
return new SonyWena3SettingsCustomizer();
}
@NonNull
@Override
public Collection<? extends ScanFilter> createBLEScanFilters() {
ScanFilter.Builder builder = new ScanFilter.Builder();
builder.setDeviceName(SonyWena3Constants.BT_DEVICE_NAME);
ArrayList<ScanFilter> result = new ArrayList<>();
result.add(builder.build());
return result;
}
@Override
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) {
Long deviceId = device.getId();
QueryBuilder<?> qb;
qb = session.getWena3HeartRateSampleDao().queryBuilder();
qb.where(Wena3HeartRateSampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
qb = session.getWena3BehaviorSampleDao().queryBuilder();
qb.where(Wena3BehaviorSampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
qb = session.getWena3CaloriesSampleDao().queryBuilder();
qb.where(Wena3CaloriesSampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
qb = session.getWena3EnergySampleDao().queryBuilder();
qb.where(Wena3EnergySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
qb = session.getWena3ActivitySampleDao().queryBuilder();
qb.where(Wena3ActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
qb = session.getWena3Vo2SampleDao().queryBuilder();
qb.where(Wena3Vo2SampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
qb = session.getWena3StressSampleDao().queryBuilder();
qb.where(Wena3StressSampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
}
@NonNull
@Override
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
if(SonyWena3Constants.BT_DEVICE_NAME.equals(candidate.getName())) {
return DeviceType.SONY_WENA_3;
}
return DeviceType.UNKNOWN;
}
@Override
public int getBondingStyle() {
return BONDING_STYLE_BOND;
}
@Override
public boolean supportsCalendarEvents() {
return true;
}
@Override
public boolean supportsRealtimeData() {
return false;
}
@Override
public boolean supportsActivityDataFetching() {
return true;
}
@Override
public boolean supportsActivityTracking() {
return true;
}
@Override
public boolean supportsAppReordering() {
return false;
}
@Override
public boolean supportsStressMeasurement() {
return true;
}
@Override
public TimeSampleProvider<? extends StressSample> getStressSampleProvider(GBDevice device, DaoSession session) {
return new SonyWena3StressSampleProvider(device, session);
}
@Override
public boolean supportsSpo2() {
return false;
}
@Override
public boolean supportsHeartRateMeasurement(GBDevice device) {
return true;
}
@Override
public boolean supportsHeartRateStats() {
return true;
}
@Override
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
return new SonyWena3ActivitySampleProvider(device, session);
}
@Override
public TimeSampleProvider<? extends HeartRateSample> getHeartRateRestingSampleProvider(GBDevice device, DaoSession session) {
return new SonyWena3HeartRateSampleProvider(device, session);
}
@Override
public InstallHandler findInstallHandler(Uri uri, Context context) {
return null;
}
@Override
public boolean supportsScreenshots() {
return false;
}
@Override
public int getAlarmSlotCount(GBDevice device) {
return SonyWena3Constants.ALARM_SLOTS;
}
@Override
public boolean supportsSmartWakeup(GBDevice device) {
return true;
}
@Override
public boolean supportsMusicInfo() {
return true;
}
@Override
public boolean supportsFindDevice() {
return false;
}
@Override
public boolean supportsWeather() {
return true;
}
@Override
public boolean isExperimental() {
return false;
}
@Override
public String[] getSupportedLanguageSettings(GBDevice device) {
return new String[]{
"auto",
"en_US",
"ja_JP"
};
}
@Override
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
return new int[]{
R.xml.devicesettings_notifications_enable,
R.xml.devicesettings_sync_calendar,
R.xml.devicesettings_wearlocation,
R.xml.devicesettings_donotdisturb_no_auto,
R.xml.devicesettings_wena3_auto_power_off,
R.xml.devicesettings_goal_notification,
R.xml.devicesettings_wena3,
};
}
public boolean supportsNotificationVibrationPatterns() {
return true;
}
public boolean supportsNotificationVibrationRepetitionPatterns() {
return true;
}
public boolean supportsNotificationLedPatterns() {
return true;
}
public AbstractNotificationPattern[] getNotificationVibrationPatterns() {
return new AbstractNotificationPattern[] {
VibrationKind.NONE, VibrationKind.BASIC,
VibrationKind.CONTINUOUS, VibrationKind.RAPID,
VibrationKind.TRIPLE, VibrationKind.STEP_UP, VibrationKind.STEP_DOWN,
VibrationKind.WARNING, VibrationKind.SIREN, VibrationKind.SHORT
};
}
public AbstractNotificationPattern[] getNotificationVibrationRepetitionPatterns() {
return new AbstractNotificationPattern[] {
VibrationCount.ONCE, VibrationCount.TWICE, VibrationCount.THREE, VibrationCount.FOUR,
VibrationCount.INDEFINITE
};
}
public AbstractNotificationPattern[] getNotificationLedPatterns() {
return new AbstractNotificationPattern[] {
LedColor.NONE, LedColor.RED, LedColor.YELLOW, LedColor.GREEN,
LedColor.CYAN, LedColor.BLUE, LedColor.PURPLE, LedColor.WHITE
};
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3;
import androidx.annotation.NonNull;
import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.Property;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractTimeSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3EnergySample;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3EnergySampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class SonyWena3EnergySampleProvider extends AbstractTimeSampleProvider<Wena3EnergySample> {
public SonyWena3EnergySampleProvider(final GBDevice device, final DaoSession session) {
super(device, session);
}
@NonNull
@Override
public AbstractDao<Wena3EnergySample, ?> getSampleDao() {
return getSession().getWena3EnergySampleDao();
}
@NonNull
@Override
protected Property getTimestampSampleProperty() {
return Wena3EnergySampleDao.Properties.Timestamp;
}
@NonNull
@Override
protected Property getDeviceIdentifierSampleProperty() {
return Wena3EnergySampleDao.Properties.DeviceId;
}
@Override
public Wena3EnergySample createSample() {
return new Wena3EnergySample();
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3;
import androidx.annotation.NonNull;
import java.util.List;
import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.Property;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractTimeSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3HeartRateSample;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3HeartRateSampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class SonyWena3HeartRateSampleProvider extends AbstractTimeSampleProvider<Wena3HeartRateSample> {
public SonyWena3HeartRateSampleProvider(final GBDevice device, final DaoSession session) {
super(device, session);
}
@NonNull
@Override
public AbstractDao<Wena3HeartRateSample, ?> getSampleDao() {
return getSession().getWena3HeartRateSampleDao();
}
@NonNull
@Override
protected Property getTimestampSampleProperty() {
return Wena3HeartRateSampleDao.Properties.Timestamp;
}
@NonNull
@Override
protected Property getDeviceIdentifierSampleProperty() {
return Wena3HeartRateSampleDao.Properties.DeviceId;
}
@Override
public Wena3HeartRateSample createSample() {
return new Wena3HeartRateSample();
}
}

View File

@ -0,0 +1,52 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3;
public class SonyWena3SettingKeys {
// ------ Booleans
public static final String RICH_DESIGN_MODE = "pref_wena3_rich_design_mode";
public static final String LARGE_FONT_SIZE = "pref_wena3_large_font_size";
public static final String WEATHER_IN_STATUSBAR = "pref_wena3_weather_in_statusbar";
public static final String SMART_VIBRATION = "pref_wena3_vibration_smart";
public static final String RECEIVE_CALLS = "pref_wena3_receive_calls";
public static final String BACKGROUND_SYNC = "pref_wena3_background_sync";
public static final String AUTO_POWER_SCHEDULE_KIND = "pref_wena3_power_schedule_kind";
public static final String AUTO_POWER_SCHEDULE_START_HHMM = "pref_wena3_power_schedule_start";
public static final String AUTO_POWER_SCHEDULE_END_HHMM = "pref_wena3_power_schedule_end";
public static final String LEFT_HOME_ICON = "pref_wena3_home_icon_left";
public static final String CENTER_HOME_ICON = "pref_wena3_home_icon_center";
public static final String RIGHT_HOME_ICON = "pref_wena3_home_icon_right";
public static final String MENU_ICON_CSV_KEY = "pref_wena3_menu_icon_csv";
public static final String STATUS_PAGE_CSV_KEY = "pref_wena3_status_pages_csv";
public static final int MAX_STATUS_PAGES = 7;
public static final String DEFAULT_CALL_LED_COLOR = "pref_wena3_default_call_led_color";
public static final String DEFAULT_LED_COLOR = "pref_wena3_default_led_color";
public static final String DEFAULT_CALL_VIBRATION_PATTERN = "pref_wena3_call_default_vibration";
public static final String DEFAULT_VIBRATION_PATTERN = "pref_wena3_default_vibration";
public static final String BUTTON_LONG_PRESS_ACTION = "pref_wena3_button_long_action";
public static final String BUTTON_DOUBLE_PRESS_ACTION = "pref_wena3_button_double_action";
public static final String VIBRATION_STRENGTH = "pref_wena3_vibration_strength";
// ------ Ints
public static final String SMART_WAKEUP_MARGIN_MINUTES = "pref_wena3_smart_wakeup_margin";
public static final String DAY_START_HOUR = "pref_wena3_day_start_hour";
public static final String DEFAULT_VIBRATION_REPETITION = "pref_wena3_default_vibration_repetition";
public static final String DEFAULT_CALL_VIBRATION_REPETITION = "pref_wena3_default_call_vibration_repetition";
}

View File

@ -0,0 +1,83 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3;
import android.os.Parcel;
import androidx.preference.Preference;
import java.util.Collections;
import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class SonyWena3SettingsCustomizer implements DeviceSpecificSettingsCustomizer {
@Override
public void customizeSettings(DeviceSpecificSettingsHandler handler, Prefs prefs) {
handler.addPreferenceHandlerFor(SonyWena3SettingKeys.RICH_DESIGN_MODE);
handler.addPreferenceHandlerFor(SonyWena3SettingKeys.LARGE_FONT_SIZE);
handler.addPreferenceHandlerFor(SonyWena3SettingKeys.WEATHER_IN_STATUSBAR);
handler.addPreferenceHandlerFor(SonyWena3SettingKeys.SMART_VIBRATION);
handler.addPreferenceHandlerFor(SonyWena3SettingKeys.SMART_WAKEUP_MARGIN_MINUTES);
handler.addPreferenceHandlerFor(SonyWena3SettingKeys.VIBRATION_STRENGTH);
handler.addPreferenceHandlerFor(SonyWena3SettingKeys.LEFT_HOME_ICON);
handler.addPreferenceHandlerFor(SonyWena3SettingKeys.CENTER_HOME_ICON);
handler.addPreferenceHandlerFor(SonyWena3SettingKeys.RIGHT_HOME_ICON);
handler.addPreferenceHandlerFor(SonyWena3SettingKeys.DAY_START_HOUR);
handler.addPreferenceHandlerFor(SonyWena3SettingKeys.AUTO_POWER_SCHEDULE_KIND);
handler.addPreferenceHandlerFor(SonyWena3SettingKeys.AUTO_POWER_SCHEDULE_START_HHMM);
handler.addPreferenceHandlerFor(SonyWena3SettingKeys.AUTO_POWER_SCHEDULE_END_HHMM);
handler.addPreferenceHandlerFor(SonyWena3SettingKeys.BUTTON_DOUBLE_PRESS_ACTION);
handler.addPreferenceHandlerFor(SonyWena3SettingKeys.BUTTON_LONG_PRESS_ACTION);
handler.addPreferenceHandlerFor(SonyWena3SettingKeys.MENU_ICON_CSV_KEY);
handler.addPreferenceHandlerFor(SonyWena3SettingKeys.STATUS_PAGE_CSV_KEY);
}
@Override
public void onPreferenceChange(Preference preference, DeviceSpecificSettingsHandler handler) {
}
@Override
public Set<String> getPreferenceKeysWithSummary() {
return Collections.emptySet();
}
public static final Creator<SonyWena3SettingsCustomizer> CREATOR = new Creator<SonyWena3SettingsCustomizer>() {
@Override
public SonyWena3SettingsCustomizer createFromParcel(final Parcel in) {
return new SonyWena3SettingsCustomizer();
}
@Override
public SonyWena3SettingsCustomizer[] newArray(final int size) {
return new SonyWena3SettingsCustomizer[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(final Parcel dest, final int flags) {
}
}

View File

@ -0,0 +1,76 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3;
import androidx.annotation.NonNull;
import java.util.List;
import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.Property;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractTimeSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3StressSample;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3StressSampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class SonyWena3StressSampleProvider extends AbstractTimeSampleProvider<Wena3StressSample> {
public SonyWena3StressSampleProvider(final GBDevice device, final DaoSession session) {
super(device, session);
}
@NonNull
@Override
public AbstractDao<Wena3StressSample, ?> getSampleDao() {
return getSession().getWena3StressSampleDao();
}
@NonNull
@Override
protected Property getTimestampSampleProperty() {
return Wena3StressSampleDao.Properties.Timestamp;
}
@NonNull
@Override
protected Property getDeviceIdentifierSampleProperty() {
return Wena3StressSampleDao.Properties.DeviceId;
}
@NonNull
@Override
public List<Wena3StressSample> getAllSamples(long timestampFrom, long timestampTo) {
List<Wena3StressSample> samples = super.getAllSamples(timestampFrom, timestampTo);
for(Wena3StressSample sample: samples) {
if(sample.getStress() < -100 || sample.getStress() > 100) {
sample.setStress(-1);
} else {
// Move from the original -100 .. 100 range to 0 .. 100
sample.setStress(Math.round(((float)sample.getStress() + 100.0f) / 2.0f));
}
}
return samples;
}
@Override
public Wena3StressSample createSample() {
return new Wena3StressSample();
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3;
import androidx.annotation.NonNull;
import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.Property;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractTimeSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3Vo2Sample;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3Vo2SampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class SonyWena3Vo2SampleProvider extends AbstractTimeSampleProvider<Wena3Vo2Sample> {
public SonyWena3Vo2SampleProvider(final GBDevice device, final DaoSession session) {
super(device, session);
}
@NonNull
@Override
public AbstractDao<Wena3Vo2Sample, ?> getSampleDao() {
return getSession().getWena3Vo2SampleDao();
}
@NonNull
@Override
protected Property getTimestampSampleProperty() {
return Wena3Vo2SampleDao.Properties.Timestamp;
}
@NonNull
@Override
protected Property getDeviceIdentifierSampleProperty() {
return Wena3Vo2SampleDao.Properties.DeviceId;
}
@Override
public Wena3Vo2Sample createSample() {
return new Wena3Vo2Sample();
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.model;
import android.content.Context;
public interface AbstractNotificationPattern {
String getUserReadableName(Context context);
String getValue();
}

View File

@ -148,6 +148,7 @@ public enum DeviceType {
ASTEROIDOS(540, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_asteroidos),
SOFLOW_SO6(550, R.drawable.ic_device_vesc, R.drawable.ic_device_vesc_disabled, R.string.devicetype_soflow_s06),
WITHINGS_STEEL_HR(560, R.drawable.ic_device_watchxplus, R.drawable.ic_device_watchxplus_disabled, R.string.withings_steel_hr),
SONY_WENA_3(570, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_sony_wena3),
TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_test);
private final int key;

View File

@ -0,0 +1,21 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol;
public interface Wena3Packetable {
byte[] toByteArray();
}

View File

@ -0,0 +1,93 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic;
public class ActivityPacketCrc {
private static final int CRC_INITIAL_VALUE = 65535;
private static final int[] CRC_TABLE = new int[] {
0x0000, 0x8005, 0x800f, 0x000a, 0x801b,
0x001e, 0x0014, 0x8011, 0x8033, 0x0036,
0x003c, 0x8039, 0x0028, 0x802d, 0x8027,
0x0022, 0x8063, 0x0066, 0x006c, 0x8069,
0x0078, 0x807d, 0x8077, 0x0072, 0x0050,
0x8055, 0x805f, 0x005a, 0x804b, 0x004e,
0x0044, 0x8041, 0x80c3, 0x00c6, 0x00cc,
0x80c9, 0x00d8, 0x80dd, 0x80d7, 0x00d2,
0x00f0, 0x80f5, 0x80ff, 0x00fa, 0x80eb,
0x00ee, 0x00e4, 0x80e1, 0x00a0, 0x80a5,
0x80af, 0x00aa, 0x80bb, 0x00be, 0x00b4,
0x80b1, 0x8093, 0x0096, 0x009c, 0x8099,
0x0088, 0x808d, 0x8087, 0x0082, 0x8183,
0x0186, 0x018c, 0x8189, 0x0198, 0x819d,
0x8197, 0x0192, 0x01b0, 0x81b5, 0x81bf,
0x01ba, 0x81ab, 0x01ae, 0x01a4, 0x81a1,
0x01e0, 0x81e5, 0x81ef, 0x01ea, 0x81fb,
0x01fe, 0x01f4, 0x81f1, 0x81d3, 0x01d6,
0x01dc, 0x81d9, 0x01c8, 0x81cd, 0x81c7,
0x01c2, 0x0140, 0x8145, 0x814f, 0x014a,
0x815b, 0x015e, 0x0154, 0x8151, 0x8173,
0x0176, 0x017c, 0x8179, 0x0168, 0x816d,
0x8167, 0x0162, 0x8123, 0x0126, 0x012c,
0x8129, 0x0138, 0x813d, 0x8137, 0x0132,
0x0110, 0x8115, 0x811f, 0x011a, 0x810b,
0x010e, 0x0104, 0x8101, 0x8303, 0x0306,
0x030c, 0x8309, 0x0318, 0x831d, 0x8317,
0x0312, 0x0330, 0x8335, 0x833f, 0x033a,
0x832b, 0x032e, 0x0324, 0x8321, 0x0360,
0x8365, 0x836f, 0x036a, 0x837b, 0x037e,
0x0374, 0x8371, 0x8353, 0x0356, 0x035c,
0x8359, 0x0348, 0x834d, 0x8347, 0x0342,
0x03c0, 0x83c5, 0x83cf, 0x03ca, 0x83db,
0x03de, 0x03d4, 0x83d1, 0x83f3, 0x03f6,
0x03fc, 0x83f9, 0x03e8, 0x83ed, 0x83e7,
0x03e2, 0x83a3, 0x03a6, 0x03ac, 0x83a9,
0x03b8, 0x83bd, 0x83b7, 0x03b2, 0x0390,
0x8395, 0x839f, 0x039a, 0x838b, 0x038e,
0x0384, 0x8381, 0x0280, 0x8285, 0x828f,
0x028a, 0x829b, 0x029e, 0x0294, 0x8291,
0x82b3, 0x02b6, 0x02bc, 0x82b9, 0x02a8,
0x82ad, 0x82a7, 0x02a2, 0x82e3, 0x02e6,
0x02ec, 0x82e9, 0x02f8, 0x82fd, 0x82f7,
0x02f2, 0x02d0, 0x82d5, 0x82df, 0x02da,
0x82cb, 0x02ce, 0x02c4, 0x82c1, 0x8243,
0x0246, 0x024c, 0x8249, 0x0258, 0x825d,
0x8257, 0x0252, 0x0270, 0x8275, 0x827f,
0x027a, 0x826b, 0x026e, 0x0264, 0x8261,
0x0220, 0x8225, 0x822f, 0x022a, 0x823b,
0x023e, 0x0234, 0x8231, 0x8213, 0x0216,
0x021c, 0x8219, 0x0208, 0x820d, 0x8207,
0x0202
};
private int result = CRC_INITIAL_VALUE;
public void next(byte i) {
result = CRC_TABLE[((i & 255) ^ (result >>> 8))] ^ ((result << 8) & 65535);
}
public void next(byte[] b) {
for(byte v: b) next(v);
}
public int getResult() {
return result;
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity.ActivitySyncDataPacket;
public interface ActivityPacketParser {
/// Return `true` if further data packets should be accepted by this parser.
/// Make sure to configure internal state correctly to prepare for incoming data if returning `true`.
/// Otherwise return `false`.
boolean parseHeader(ActivitySyncDataPacket packet, GBDevice sourceDevice);
void parsePacket(ActivitySyncDataPacket packet, GBDevice sourceDevice);
void finishReceiving(GBDevice device);
void reset();
}

View File

@ -0,0 +1,118 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity.ActivitySyncDataPacket;
public class ActivitySyncPacketProcessor {
private static final int RESET_SEQ_NO = 0xFF;
private static final int MAX_SEQ_NO = 0x7F;
private static final Logger LOG = LoggerFactory.getLogger(ActivitySyncPacketProcessor.class);
private ActivityPacketParser currentParser = null;
private final List<ActivityPacketParser> parsers = new ArrayList<>();
private int currentSeqNo = RESET_SEQ_NO;
private boolean haveHeader = false;
public ActivitySyncPacketProcessor() {}
public void registerParser(ActivityPacketParser parser) {
parsers.add(parser);
}
public void receivePacket(ActivitySyncDataPacket packet, GBDevice device) {
if(packet.sequenceNo == RESET_SEQ_NO) {
LOG.info("Initial packet received, off we go with a sync!");
currentSeqNo = RESET_SEQ_NO;
resetAll();
// Do NOT return here: the initial packet is still a packet!
}
if(packet.sequenceNo != currentSeqNo) {
LOG.error("There was packet loss (skip "+currentSeqNo+" to "+packet.sequenceNo+")");
finalizeCurrentParserIfNeeded(device);
return;
} else {
if(currentSeqNo == MAX_SEQ_NO || currentSeqNo == RESET_SEQ_NO) {
currentSeqNo = 0;
} else {
currentSeqNo ++;
}
}
if(!packet.isCrcValid) {
LOG.error("Received packet has invalid CRC");
return;
}
switch(packet.type) {
case HEADER:
haveHeader = true;
finalizeCurrentParserIfNeeded(device);
for(ActivityPacketParser parser: parsers) {
if(parser.parseHeader(packet, device)) {
currentParser = parser;
break;
}
}
if(currentParser == null) {
LOG.warn("No parsers can understand " + packet);
}
break;
case DATA:
if(currentParser != null) {
currentParser.parsePacket(packet, device);
} else {
if(!haveHeader) {
LOG.warn("DATA arrived before HEADER: dropped " + packet);
} else {
LOG.warn("No parser known: dropped data packet " + packet);
}
}
break;
case FINISH:
LOG.info("End of transmission received");
finalizeCurrentParserIfNeeded(device);
break;
}
}
public void resetAll() {
currentParser = null;
for(ActivityPacketParser parser: parsers) parser.reset();
}
private void finalizeCurrentParserIfNeeded(GBDevice device) {
if(currentParser != null) {
currentParser.finishReceiving(device);
currentParser = null;
}
haveHeader = false;
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic;
import java.util.Date;
import java.util.List;
public class StepsSampleCollection {
public final Date startDate;
public final List<Integer> everyMinuteCount;
public StepsSampleCollection(Date startDate, List<Integer> everyMinuteCount) {
this.startDate = startDate;
this.everyMinuteCount = everyMinuteCount;
}
}

View File

@ -0,0 +1,107 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.parsers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3ActivitySampleCombiner;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3ActivitySampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3BehaviorSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3BehaviorSample;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity.BehaviorSample;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.util.TimeUtil;
public class BehaviorPacketParser extends SamplePacketParser<BehaviorSample> {
private static final Logger LOG = LoggerFactory.getLogger(BehaviorPacketParser.class);
public BehaviorPacketParser() {
super(0x02);
}
@Override
boolean tryExtractingMetadataFromHeaderBuffer(ByteBuffer buffer) {
return true;
}
@Override
BehaviorSample takeSampleFromBuffer(ByteBuffer buffer) {
// Entry structure:
// - 1b type
// - 4b padding?
// - 4b start date
// - 4b end date
int id = (buffer.get() & 0xFF);
if(id < BehaviorSample.Type.LUT.length) {
BehaviorSample.Type type = BehaviorSample.Type.LUT[id];
buffer.position(buffer.position()+4);
Date start = TimeUtil.wenaTimeToDate(buffer.getInt());
Date end = TimeUtil.wenaTimeToDate(buffer.getInt());
return new BehaviorSample(start, end, type);
}
return null;
}
@Override
boolean canTakeSampleFromBuffer(ByteBuffer buffer) {
return buffer.remaining() >= 13;
}
@Override
public void finishReceiving(GBDevice device) {
try (DBHandler db = GBApplication.acquireDB()) {
SonyWena3BehaviorSampleProvider sampleProvider = new SonyWena3BehaviorSampleProvider(device, db.getDaoSession());
Long userId = DBHelper.getUser(db.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(device, db.getDaoSession()).getId();
List<Wena3BehaviorSample> samples = new ArrayList<>();
for(BehaviorSample rawSample: accumulator) {
Wena3BehaviorSample gbSample = new Wena3BehaviorSample();
gbSample.setDeviceId(deviceId);
gbSample.setUserId(userId);
gbSample.setTimestamp(rawSample.start.getTime());
gbSample.setTimestampFrom(rawSample.start.getTime());
gbSample.setTimestampTo(rawSample.end.getTime());
gbSample.setRawKind(rawSample.type.ordinal());
samples.add(gbSample);
}
sampleProvider.addSamples(samples);
if(!accumulator.isEmpty()) {
SonyWena3ActivitySampleProvider activitySampleProvider = new SonyWena3ActivitySampleProvider(device, db.getDaoSession());
SonyWena3ActivitySampleCombiner combiner = new SonyWena3ActivitySampleCombiner(activitySampleProvider);
combiner.overlayBehaviorStartingAt(accumulator.get(0).start, sampleProvider);
}
} catch (Exception e) {
LOG.error("Error acquiring database for recording Behavior samples", e);
}
// Finally clean up the parser
super.finishReceiving(device);
}
}

View File

@ -0,0 +1,83 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.parsers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3CaloriesSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3CaloriesSample;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class CaloriesPacketParser extends LinearSamplePacketParser<Integer> {
private static final Logger LOG = LoggerFactory.getLogger(CaloriesPacketParser.class);
public CaloriesPacketParser() {
super(0x06, OneBytePerSamplePacketParser.ONE_MINUTE_IN_MS);
}
@Override
Integer takeSampleFromBuffer(ByteBuffer buffer) {
return (buffer.getShort() & 65535);
}
@Override
boolean canTakeSampleFromBuffer(ByteBuffer buffer) {
return buffer.remaining() >= 2;
}
@Override
public void finishReceiving(GBDevice device) {
try (DBHandler db = GBApplication.acquireDB()) {
SonyWena3CaloriesSampleProvider sampleProvider = new SonyWena3CaloriesSampleProvider(device, db.getDaoSession());
Long userId = DBHelper.getUser(db.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(device, db.getDaoSession()).getId();
int i = 0;
List<Wena3CaloriesSample> samples = new ArrayList<>();
for(int rawSample: accumulator) {
Date currentSampleDate = timestampOfSampleAtIndex(i);
Wena3CaloriesSample gbSample = new Wena3CaloriesSample();
gbSample.setDeviceId(deviceId);
gbSample.setUserId(userId);
gbSample.setTimestamp(currentSampleDate.getTime());
gbSample.setCalories(rawSample);
samples.add(gbSample);
i++;
}
sampleProvider.addSamples(samples);
} catch (Exception e) {
LOG.error("Error acquiring database for recording Calories samples", e);
}
// Finally clean up the parser
super.finishReceiving(device);
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.parsers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3EnergySampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3EnergySample;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class EnergyPacketParser extends OneBytePerSamplePacketParser {
private static final Logger LOG = LoggerFactory.getLogger(EnergyPacketParser.class);
private static final int ENERGY_PKT_MARKER = 0x05;
public EnergyPacketParser() {
super(ENERGY_PKT_MARKER, ONE_MINUTE_IN_MS);
}
@Override
public void finishReceiving(GBDevice device) {
try (DBHandler db = GBApplication.acquireDB()) {
SonyWena3EnergySampleProvider sampleProvider = new SonyWena3EnergySampleProvider(device, db.getDaoSession());
Long userId = DBHelper.getUser(db.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(device, db.getDaoSession()).getId();
List<Wena3EnergySample> samples = new ArrayList<>();
int i = 0;
for(int rawSample: accumulator) {
Date currentSampleDate = timestampOfSampleAtIndex(i);
Wena3EnergySample gbSample = new Wena3EnergySample();
gbSample.setDeviceId(deviceId);
gbSample.setUserId(userId);
gbSample.setTimestamp(currentSampleDate.getTime());
gbSample.setEnergy(rawSample);
samples.add(gbSample);
i++;
}
sampleProvider.addSamples(samples);
} catch (Exception e) {
LOG.error("Error acquiring database for recording Energy samples", e);
}
// Finally clean up the parser
super.finishReceiving(device);
}
}

View File

@ -0,0 +1,81 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.parsers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3ActivitySampleCombiner;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3ActivitySampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3HeartRateSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3HeartRateSample;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class HeartRatePacketParser extends OneBytePerSamplePacketParser {
private static final Logger LOG = LoggerFactory.getLogger(HeartRatePacketParser.class);
private static final int HEART_PKT_MARKER = 0x01;
public HeartRatePacketParser() {
super(HEART_PKT_MARKER, ONE_MINUTE_IN_MS);
}
@Override
public void finishReceiving(GBDevice device) {
try (DBHandler db = GBApplication.acquireDB()) {
SonyWena3HeartRateSampleProvider sampleProvider = new SonyWena3HeartRateSampleProvider(device, db.getDaoSession());
Long userId = DBHelper.getUser(db.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(device, db.getDaoSession()).getId();
List<Wena3HeartRateSample> samples = new ArrayList<>();
Date currentSampleDate = startDate;
int i = 0;
for(int rawSample: accumulator) {
Wena3HeartRateSample gbSample = new Wena3HeartRateSample();
gbSample.setDeviceId(deviceId);
gbSample.setUserId(userId);
gbSample.setTimestamp(currentSampleDate.getTime());
gbSample.setHeartRate(rawSample);
samples.add(gbSample);
i++;
currentSampleDate = timestampOfSampleAtIndex(i);
}
sampleProvider.addSamples(samples);
if(!accumulator.isEmpty()) {
SonyWena3ActivitySampleProvider activitySampleProvider = new SonyWena3ActivitySampleProvider(device, db.getDaoSession());
SonyWena3ActivitySampleCombiner combiner = new SonyWena3ActivitySampleCombiner(activitySampleProvider);
combiner.overlayHeartRateStartingAt(startDate, sampleProvider);
}
} catch (Exception e) {
LOG.error("Error acquiring database for recording heart rate samples", e);
}
// Finally clean up the parser
super.finishReceiving(device);
}
}

View File

@ -0,0 +1,68 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.parsers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.Date;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.util.TimeUtil;
abstract class LinearSamplePacketParser<Sample> extends SamplePacketParser<Sample> {
private static final Logger LOG = LoggerFactory.getLogger(LinearSamplePacketParser.class);
private int msBetweenSamples = 0;
public Date startDate = null;
public LinearSamplePacketParser(int headerMarker, int sampleDistanceInMs) {
super(headerMarker);
msBetweenSamples = sampleDistanceInMs;
reset();
}
@Override
public void finishReceiving(GBDevice device) {
Date estimatedEndDate = new Date(startDate.getTime() + ((long) accumulator.size() * msBetweenSamples));
LOG.info("Finished collecting "+accumulator.size()+" samples over "+startDate+" ~ "+estimatedEndDate);
super.finishReceiving(device);
}
@Override
boolean tryExtractingMetadataFromHeaderBuffer(ByteBuffer buffer) {
if(buffer.remaining() < 4) {
LOG.error("Received ASDP header is too short");
return false;
}
startDate = TimeUtil.wenaTimeToDate(buffer.getInt());
return true;
}
public Date timestampOfSampleAtIndex(int index) {
assert startDate != null;
if(index == 0) return startDate;
return new Date(startDate.getTime() + (long) msBetweenSamples * index);
}
abstract Sample takeSampleFromBuffer(ByteBuffer buffer);
abstract boolean canTakeSampleFromBuffer(ByteBuffer buffer);
}

View File

@ -0,0 +1,40 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.parsers;
import java.nio.ByteBuffer;
public class OneBytePerSamplePacketParser extends LinearSamplePacketParser<Integer> {
public static final int ONE_MINUTE_IN_MS = 60_000; // 1 sample per minute it seems
public OneBytePerSamplePacketParser(int headerMarker, int sampleDistanceInMs) {
super(headerMarker, sampleDistanceInMs);
}
@Override
Integer takeSampleFromBuffer(ByteBuffer buffer) {
return Integer.valueOf(buffer.get() & 0xFF);
}
@Override
boolean canTakeSampleFromBuffer(ByteBuffer buffer) {
return buffer.remaining() > 0;
}
}

View File

@ -0,0 +1,101 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.parsers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.ActivityPacketParser;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity.ActivitySyncDataPacket;
abstract class SamplePacketParser<Sample> implements ActivityPacketParser {
private static final Logger LOG = LoggerFactory.getLogger(SamplePacketParser.class);
private final int headerMarker;
public List<Sample> accumulator = new ArrayList<>();
private enum State {
READY, RECEIVING, FINISHED
}
private State currentState = State.READY;
public SamplePacketParser(int headerMarker) {
this.headerMarker = headerMarker;
reset();
}
@Override
public void reset() {
accumulator = new ArrayList<>();
currentState = State.READY;
}
@Override
public boolean parseHeader(ActivitySyncDataPacket packet, GBDevice sourceDevice) {
assert packet.isCrcValid;
assert packet.type == ActivitySyncDataPacket.PacketType.HEADER;
ByteBuffer buf = packet.dataBuffer();
int type = buf.get();
if(type != this.headerMarker) {
LOG.debug("Received ASDP with marker "+type+", not expected type");
return false;
}
if(currentState != State.READY)
return false;
if(!tryExtractingMetadataFromHeaderBuffer(buf)) {
return false;
}
currentState = State.RECEIVING;
LOG.info("Ready to receive packets");
return true;
}
@Override
public void parsePacket(ActivitySyncDataPacket packet, GBDevice sourceDevice) {
assert currentState == State.RECEIVING;
assert packet.isCrcValid;
assert packet.type == ActivitySyncDataPacket.PacketType.DATA;
ByteBuffer buf = packet.dataBuffer();
while(canTakeSampleFromBuffer(buf))
accumulator.add(takeSampleFromBuffer(buf));
LOG.info("Accumulated "+accumulator.size()+" samples");
}
@Override
public void finishReceiving(GBDevice device) {
assert currentState == State.RECEIVING;
currentState = State.FINISHED;
reset();
}
abstract Sample takeSampleFromBuffer(ByteBuffer buffer);
abstract boolean canTakeSampleFromBuffer(ByteBuffer buffer);
abstract boolean tryExtractingMetadataFromHeaderBuffer(ByteBuffer buffer);
}

View File

@ -0,0 +1,82 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.parsers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3ActivitySampleCombiner;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3ActivitySampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3BehaviorSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3HeartRateSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class StepsPacketParser extends OneBytePerSamplePacketParser {
private static final Logger LOG = LoggerFactory.getLogger(StepsPacketParser.class);
private static final int STEPS_PKT_MARKER = 0x00;
public StepsPacketParser() {
super(STEPS_PKT_MARKER, ONE_MINUTE_IN_MS);
}
@Override
public void finishReceiving(GBDevice device) {
try (DBHandler db = GBApplication.acquireDB()) {
SonyWena3ActivitySampleProvider sampleProvider = new SonyWena3ActivitySampleProvider(device, db.getDaoSession());
Long userId = DBHelper.getUser(db.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(device, db.getDaoSession()).getId();
List<Wena3ActivitySample> samples = new ArrayList<>();
Date currentSampleDate = startDate;
int i = 0;
for(int rawSample: accumulator) {
Wena3ActivitySample gbSample = new Wena3ActivitySample();
gbSample.setDeviceId(deviceId);
gbSample.setUserId(userId);
gbSample.setTimestamp((int)(currentSampleDate.getTime() / 1000L));
gbSample.setSteps(rawSample);
samples.add(gbSample);
i++;
currentSampleDate = timestampOfSampleAtIndex(i);
}
sampleProvider.addGBActivitySamples(samples.toArray(new Wena3ActivitySample[samples.size()]));
SonyWena3ActivitySampleCombiner combiner = new SonyWena3ActivitySampleCombiner(sampleProvider);
SonyWena3BehaviorSampleProvider behaviorSampleProvider = new SonyWena3BehaviorSampleProvider(device, db.getDaoSession());
combiner.overlayBehaviorStartingAt(startDate, behaviorSampleProvider);
SonyWena3HeartRateSampleProvider heartRateSampleProvider = new SonyWena3HeartRateSampleProvider(device, db.getDaoSession());
combiner.overlayHeartRateStartingAt(startDate, heartRateSampleProvider);
} catch (Exception e) {
LOG.error("Error acquiring database for recording steps samples", e);
}
// Finally clean up the parser
super.finishReceiving(device);
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.parsers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3StressSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3StressSample;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
public class StressPacketParser extends OneBytePerSamplePacketParser {
private static final Logger LOG = LoggerFactory.getLogger(StressPacketParser.class);
private static final int STRESS_PKT_MARKER = 0x04;
public StressPacketParser() {
super(STRESS_PKT_MARKER, ONE_MINUTE_IN_MS);
}
@Override
Integer takeSampleFromBuffer(ByteBuffer buffer) {
// In this one we actually need signed values, hence the override
return Integer.valueOf(buffer.get());
}
@Override
public void finishReceiving(GBDevice device) {
try (DBHandler db = GBApplication.acquireDB()) {
SonyWena3StressSampleProvider sampleProvider = new SonyWena3StressSampleProvider(device, db.getDaoSession());
Long userId = DBHelper.getUser(db.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(device, db.getDaoSession()).getId();
List<Wena3StressSample> samples = new ArrayList<>();
Date currentSampleDate = startDate;
int i = 0;
for(int rawSample: accumulator) {
if(rawSample >= -100 && rawSample <= 100) {
Wena3StressSample gbSample = new Wena3StressSample();
gbSample.setDeviceId(deviceId);
gbSample.setUserId(userId);
gbSample.setTimestamp(currentSampleDate.getTime());
gbSample.setStress(rawSample);
gbSample.setType(StressSample.Type.AUTOMATIC);
samples.add(gbSample);
} else {
LOG.debug("Discard stress value as out of range: " + rawSample);
}
i++;
currentSampleDate = timestampOfSampleAtIndex(i);
}
sampleProvider.addSamples(samples);
} catch (Exception e) {
LOG.error("Error acquiring database for recording stress samples", e);
}
// Finally clean up the parser
super.finishReceiving(device);
}
}

View File

@ -0,0 +1,96 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.parsers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3Vo2SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.Wena3Vo2Sample;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity.Vo2MaxSample;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.util.TimeUtil;
public class Vo2MaxPacketParser extends SamplePacketParser<Vo2MaxSample> {
private static final Logger LOG = LoggerFactory.getLogger(Vo2MaxPacketParser.class);
public Vo2MaxPacketParser() {
super(0x03);
}
@Override
Vo2MaxSample takeSampleFromBuffer(ByteBuffer buffer) {
Date ts = TimeUtil.wenaTimeToDate(buffer.getInt());
int value = buffer.get() & 0xFF;
return new Vo2MaxSample(ts, value);
}
@Override
boolean canTakeSampleFromBuffer(ByteBuffer buffer) {
return buffer.remaining() >= 5;
}
@Override
boolean tryExtractingMetadataFromHeaderBuffer(ByteBuffer buffer) {
return true;
}
@Override
public void finishReceiving(GBDevice device) {
try (DBHandler db = GBApplication.acquireDB()) {
SonyWena3Vo2SampleProvider sampleProvider = new SonyWena3Vo2SampleProvider(device, db.getDaoSession());
Long userId = DBHelper.getUser(db.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(device, db.getDaoSession()).getId();
List<Wena3Vo2Sample> samples = new ArrayList<>();
Date currentSampleDate = null;
int currentDateDatapoint = 0;
for(Vo2MaxSample rawSample: accumulator) {
if(currentSampleDate == null || currentSampleDate != rawSample.timestamp) {
currentDateDatapoint = 0;
currentSampleDate = rawSample.timestamp;
} else {
currentDateDatapoint ++;
}
Wena3Vo2Sample gbSample = new Wena3Vo2Sample();
gbSample.setDeviceId(deviceId);
gbSample.setUserId(userId);
gbSample.setTimestamp(currentSampleDate.getTime());
gbSample.setDatapoint(currentDateDatapoint);
gbSample.setVo2(rawSample.value);
samples.add(gbSample);
}
sampleProvider.addSamples(samples);
} catch (Exception e) {
LOG.error("Error acquiring database for recording Vo2 samples", e);
}
// Finally clean up the parser
super.finishReceiving(device);
}
}

View File

@ -0,0 +1,97 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity;
import androidx.annotation.NonNull;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.ActivityPacketCrc;
public class ActivitySyncDataPacket {
public enum PacketType {
HEADER,
DATA,
FINISH;
static final PacketType[] LUT = new PacketType[] { HEADER, DATA, FINISH };
}
public final int sequenceNo;
public final PacketType type;
public int crc;
public int expectedCrc;
public boolean isCrcValid;
public final byte[] data;
public ActivitySyncDataPacket(byte[] packet) {
/*
1byte [seqno OR FF=RESET]
2byte [crc16]
1byte [mark: 00=head, 01=data, 02=end]
IF HEAD
1byte [type: 00=Steps 01=Heart 02=Behavior 03=Vo2 04=Stress 05=BodyEnergy 06=Calories 07=???]
(usually) 4byte [WenaDate start ts]
IF DATA
(samples of appropriate [type]?)
IF END
(reception completed!)
*/
ByteBuffer buf = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN);
sequenceNo = Integer.valueOf(buf.get() & 0xFF);
crc = buf.getShort();
crc &= 0xFFFF;
type = PacketType.LUT[buf.get()];
data = new byte[buf.remaining()];
buf.get(data);
checkCrc();
}
private void checkCrc() {
ActivityPacketCrc calc = new ActivityPacketCrc();
ByteBuffer tmp = ByteBuffer.allocate(data.length + 1).put((byte)type.ordinal()).put(data);
calc.next(tmp.array());
expectedCrc = calc.getResult();
isCrcValid = (expectedCrc == crc);
}
@NonNull
public String toString() {
return String.format("<ASDP type %d, Seq.No: %d, CRC: %x, Valid: %s, Data: %s>", type.ordinal(), sequenceNo, crc, isCrcValid ? "yes" : String.format("NO [expected %x]", expectedCrc), toHexString(data));
}
private static String toHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
if (bytes != null)
for (byte b:bytes) {
final String hexString = Integer.toHexString(b & 0xff);
if(hexString.length()==1)
sb.append('0');
sb.append(hexString).append(' ');
}
return sb.toString().toUpperCase();
}
public ByteBuffer dataBuffer() {
return ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
public class ActivitySyncStartPacket implements Wena3Packetable {
public ActivitySyncStartPacket() {
}
@Override
public byte[] toByteArray() {
return new byte[] { (byte) 0xFF };
}
}

View File

@ -0,0 +1,76 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity;
import androidx.annotation.Nullable;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Date;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.util.TimeUtil;
abstract class ActivitySyncTimePacketTemplate implements Wena3Packetable {
public final byte header;
@Nullable
public final Date date1;
@Nullable
public final Date date2;
@Nullable
public final Date date3;
@Nullable
public final Date date4;
public ActivitySyncTimePacketTemplate(byte header, @Nullable Date date1, @Nullable Date date2, @Nullable Date date3, @Nullable Date date4) {
this.header = header;
this.date1 = date1;
this.date2 = date2;
this.date3 = date3;
this.date4 = date4;
}
@Override
public byte[] toByteArray() {
ByteBuffer buf = ByteBuffer.allocate(17)
.order(ByteOrder.LITTLE_ENDIAN)
.put(header);
if(date1 == null) {
buf.putInt(0);
} else {
buf.putInt(TimeUtil.dateToWenaTime(date1));
}
if(date2 == null) {
buf.putInt(0);
} else {
buf.putInt(TimeUtil.dateToWenaTime(date2));
}
if(date3 == null) {
buf.putInt(0);
} else {
buf.putInt(TimeUtil.dateToWenaTime(date3));
}
if(date4 == null) {
buf.putInt(0);
} else {
buf.putInt(TimeUtil.dateToWenaTime(date4));
}
return buf.array();
}
}

View File

@ -0,0 +1,27 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity;
import androidx.annotation.Nullable;
import java.util.Date;
public class ActivitySyncTimePacketTypeA extends ActivitySyncTimePacketTemplate {
public ActivitySyncTimePacketTypeA(@Nullable Date stepCountLastSyncTime, @Nullable Date heartRateLastSyncTime, @Nullable Date behaviorLastSyncTime, @Nullable Date vo2MaxLastSyncTime) {
super((byte) 0x1, stepCountLastSyncTime, heartRateLastSyncTime, behaviorLastSyncTime, vo2MaxLastSyncTime);
}
}

View File

@ -0,0 +1,27 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity;
import androidx.annotation.Nullable;
import java.util.Date;
public class ActivitySyncTimePacketTypeB extends ActivitySyncTimePacketTemplate {
public ActivitySyncTimePacketTypeB(@Nullable Date stressLastSyncTime, @Nullable Date bodyEnergyLastSyncTime, @Nullable Date caloriesLastSyncTime, @Nullable Date eventsLastSyncTime) {
super((byte) 0x2, stressLastSyncTime, bodyEnergyLastSyncTime, caloriesLastSyncTime, eventsLastSyncTime);
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity;
import java.util.Date;
public class BehaviorSample {
public enum Type {
UNKNOWN,
NOT_WEARING,
WALK,
RUN,
EXERCISE,
SLEEP_DEEP,
SLEEP_LIGHT,
SLEEP_REM,
SLEEP_AWAKE,
STATIC;
public final static Type[] LUT = new Type[] {
UNKNOWN,
NOT_WEARING,
WALK,
RUN,
EXERCISE,
SLEEP_DEEP,
SLEEP_LIGHT,
SLEEP_REM,
SLEEP_AWAKE,
STATIC
};
}
public final Date start;
public final Date end;
public final Type type;
public BehaviorSample(Date start, Date end, Type type) {
this.start = start;
this.end = end;
this.type = type;
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity;
import java.util.Date;
public class Vo2MaxSample {
public final Date timestamp;
public final int value;
public Vo2MaxSample(Date timestamp, int value) {
this.timestamp = timestamp;
this.value = value;
}
}

View File

@ -0,0 +1,81 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.calendar;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.util.TimeUtil;
public class CalendarEntry implements Wena3Packetable {
public final Date begin;
public final Date end;
public final boolean isAllDay;
public final String title;
public final String location;
/// 1-based, not index!
public final byte position;
public final byte totalItemCount;
public CalendarEntry(Date begin, Date end, boolean isAllDay, String title, String location, byte position, byte totalItemCount) {
this.begin = begin;
this.end = end;
this.isAllDay = isAllDay;
this.title = title;
this.location = location;
this.position = position;
this.totalItemCount = totalItemCount;
}
@Override
public byte[] toByteArray() {
byte[] cstrTitle = title.getBytes(StandardCharsets.UTF_8);
byte[] cstrLocation = location.getBytes(StandardCharsets.UTF_8);
return ByteBuffer.allocate(14 + cstrTitle.length + cstrLocation.length)
.order(ByteOrder.LITTLE_ENDIAN)
.put((byte) 0x02)
.put(position)
.put(totalItemCount)
.put((byte) cstrTitle.length)
.put((byte) cstrLocation.length)
.put((byte) (isAllDay ? 0x1 : 0x0))
.putInt(TimeUtil.dateToWenaTime(begin))
.putInt(TimeUtil.dateToWenaTime(end))
.put(cstrTitle)
.put(cstrLocation)
.array();
}
public static byte[] byteArrayForEmptyEvent(byte position, byte totalItemCount) {
return ByteBuffer.allocate(14)
.order(ByteOrder.LITTLE_ENDIAN)
.put((byte) 0x02)
.put(position)
.put(totalItemCount)
.put((byte) 0x0)
.put((byte) 0x0)
.put((byte) 0x0)
.putInt(0)
.putInt(0)
.array();
}
}

View File

@ -0,0 +1,30 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
// NB: This seems to not work on the Wena 3!
// For me, the vendor's app just sends an "APP" notification for incoming calls as well,
// just with a continuous vibration...
@Deprecated
public class EndOfIncomingCall implements Wena3Packetable {
@Override
public byte[] toByteArray() {
return new byte[] { 0x1 };
}
}

View File

@ -0,0 +1,106 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines.LedColor;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines.NotificationFlags;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines.NotificationKind;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines.VibrationOptions;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.util.TimeUtil;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
public class NotificationArrival implements Wena3Packetable {
public final NotificationKind kind;
public final int id;
public final String title;
public final String message;
public final String actionLabel;
public final Date dateTime;
public final VibrationOptions vibration;
public final LedColor ledColor;
public final NotificationFlags flags;
public NotificationArrival(
NotificationKind kind,
int id,
String title,
String message,
String actionLabel,
Date dateTime,
VibrationOptions vibration,
LedColor ledColor,
NotificationFlags flags
) {
this.kind = kind;
this.id = id;
this.title = title;
this.message = message;
this.actionLabel = actionLabel;
this.dateTime = dateTime;
this.vibration = vibration;
this.ledColor = ledColor;
this.flags = flags;
}
@Override
public byte[] toByteArray() {
assert vibration.count <= 255;
byte vibraFlags = 0;
byte vibraCount = (byte)vibration.count;
if(vibration.continuous) {
vibraFlags = 0x3;
vibraCount = 0;
} else {
if(vibraCount > 4) vibraCount = 4;
else if (vibraCount < 0) vibraCount = 1;
}
byte[] encodedTitle = StringUtils.truncate(title.trim(), 31).getBytes(StandardCharsets.UTF_8);
byte[] encodedMessage = StringUtils.truncate(message.trim(), 239).getBytes(StandardCharsets.UTF_8);
byte[] encodedAction = StringUtils.truncate(actionLabel.trim(), 15).getBytes(StandardCharsets.UTF_8);
ByteBuffer buf = ByteBuffer
.allocate(18 + encodedTitle.length + encodedMessage.length + encodedAction.length)
.order(ByteOrder.LITTLE_ENDIAN);
buf.put((byte) 0x00) // marker
.put((byte)ledColor.ordinal())
.put(vibraFlags)
.put((byte)vibration.kind.ordinal())
.put(vibraCount)
.put((byte)encodedTitle.length)
.put((byte)encodedMessage.length)
.put((byte)flags.value)
.put((byte)kind.ordinal())
.put((byte)encodedAction.length)
.putInt(id)
.putInt(TimeUtil.dateToWenaTime(dateTime))
.put(encodedTitle)
.put(encodedMessage)
.put(encodedAction);
return buf.array();
}
}

View File

@ -0,0 +1,44 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines.NotificationKind;
public class NotificationRemoval implements Wena3Packetable {
public final NotificationKind kind;
public final int id;
public NotificationRemoval(NotificationKind kind, int id) {
this.kind = kind;
this.id = id;
}
@Override
public byte[] toByteArray() {
return ByteBuffer
.allocate(7)
.order(ByteOrder.LITTLE_ENDIAN)
.put((byte) 0x01)
.put((byte)kind.ordinal())
.putInt(id)
.array();
}
}

View File

@ -0,0 +1,54 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines;
import android.content.Context;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.model.AbstractNotificationPattern;
public enum LedColor implements AbstractNotificationPattern {
NONE(0, R.string.prefs_wena3_led_none),
RED(1, R.string.red),
YELLOW(2, R.string.yellow),
GREEN(3, R.string.green),
CYAN(4, R.string.cyan),
BLUE(5, R.string.blue),
PURPLE(6, R.string.purple),
WHITE(7, R.string.white);
public final byte value;
private final int stringId;
LedColor(int value, int stringId) {
this.value = (byte) value;
this.stringId = stringId;
}
@Override
public String getUserReadableName(Context context) {
return context.getString(stringId);
}
@Override
public String getValue() {
return name();
}
}

View File

@ -0,0 +1,30 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines;
public enum NotificationFlags {
NONE(0),
IS_TEST(1),
IS_RETRANSMISSION(2),
HAS_ACTION(4);
public int value = 0;
NotificationFlags(int value) {
this.value = value;
}
}

View File

@ -0,0 +1,22 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines;
public enum NotificationKind {
APP,
CALL
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (C) 2023 akasaka / Genjitsu Labs
*
* This file is part of Gadgetbridge.
*
* Gadgetbridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gadgetbridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines;
import android.content.Context;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.model.AbstractNotificationPattern;
public enum VibrationCount implements AbstractNotificationPattern {
INDEFINITE(0, R.string.prefs_wena3_notification_vibration_repetition_0),
ONCE(1, R.string.prefs_wena3_notification_vibration_repetition_1),
TWICE(2, R.string.prefs_wena3_notification_vibration_repetition_2),
THREE(3, R.string.prefs_wena3_notification_vibration_repetition_3),
FOUR(4, R.string.prefs_wena3_notification_vibration_repetition_4),
;
public int value;
public int stringId;
VibrationCount(int value, int stringId) {
this.value = value;
this.stringId = stringId;
}
@Override
public String getUserReadableName(Context context) {
return context.getString(stringId);
}
@Override
public String getValue() {
return String.valueOf(value);
}
}

View File

@ -0,0 +1,56 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines;
import android.content.Context;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.model.AbstractNotificationPattern;
public enum VibrationKind implements AbstractNotificationPattern {
NONE(0, R.string.prefs_wena3_vibration_none),
CONTINUOUS(1, R.string.prefs_wena3_vibration_continuous),
BASIC(2, R.string.prefs_wena3_vibration_basic),
RAPID(3, R.string.prefs_wena3_vibration_rapid),
TRIPLE(4, R.string.prefs_wena3_vibration_triple),
STEP_UP(5, R.string.prefs_wena3_vibration_step_up),
STEP_DOWN(6, R.string.prefs_wena3_vibration_step_down),
WARNING(7, R.string.prefs_wena3_vibration_warning),
SIREN(8, R.string.prefs_wena3_vibration_siren),
SHORT(9, R.string.prefs_wena3_vibration_short);
public final byte value;
private final int stringId;
VibrationKind(int value, int stringId) {
this.value = (byte) value;
this.stringId = stringId;
}
@Override
public String getUserReadableName(Context context) {
return context.getString(stringId);
}
@Override
public String getValue() {
return name();
}
}

View File

@ -0,0 +1,31 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines;
public class VibrationOptions {
public final VibrationKind kind;
public final int count;
public final boolean continuous;
public VibrationOptions(VibrationKind kind, int count, boolean continuous) {
this.kind = kind;
this.count = count;
this.continuous = continuous;
}
}

View File

@ -0,0 +1,60 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
public class AlarmListSettings implements Wena3Packetable {
public static final int MAX_ALARMS_IN_PACKET = 3;
// While the Wena 3 has 9 alarms, the packet fits 3 at most
// So to update all alarms you need to send 3 packets
public final List<SingleAlarmSetting> alarms;
// Usually 0 for first packet, 3 for second, 6 for third
public final int alarmListOffset;
public AlarmListSettings(List<SingleAlarmSetting> alarms, int alarmListOffset) {
this.alarms = alarms;
this.alarmListOffset = alarmListOffset;
assert (this.alarmListOffset % 3) == 0;
assert this.alarms.size() <= MAX_ALARMS_IN_PACKET;
}
@Override
public byte[] toByteArray() {
ByteBuffer buf = ByteBuffer.allocate(19).order(ByteOrder.LITTLE_ENDIAN);
buf.put((byte) 0x06);
for(int i = 0; i < MAX_ALARMS_IN_PACKET; i++) {
buf.put((byte) (alarmListOffset + i));
if(i < alarms.size()) {
SingleAlarmSetting alarm = alarms.get(i);
buf.put(alarm.toByteArray());
} else {
buf.put(SingleAlarmSetting.emptyPacket());
}
}
return buf.array();
}
}

View File

@ -0,0 +1,40 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class AutoPowerOffSettings extends DoNotDisturbSettings {
public AutoPowerOffSettings(boolean enable, int startHour, int startMinute, int endHour, int endMinute) {
super(enable, startHour, startMinute, endHour, endMinute);
}
@Override
public byte[] toByteArray() {
return ByteBuffer.allocate(6)
.order(ByteOrder.LITTLE_ENDIAN)
.put((byte) 0x15)
.put((byte) (enable ? 0x1 : 0x0))
.put((byte) startHour)
.put((byte) startMinute)
.put((byte) endHour)
.put((byte) endMinute)
.array();
}
}

View File

@ -0,0 +1,56 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines.GenderSetting;
public class BodyPropertiesSetting implements Wena3Packetable {
public final GenderSetting gender;
public final short yearOfBirth;
public final short monthOfBirth;
public final short dayOfBirth;
public final short height;
public final short weight;
public BodyPropertiesSetting(GenderSetting gender, short yearOfBirth, short monthOfBirth, short dayOfBirth, short height, short weight) {
this.gender = gender;
this.yearOfBirth = yearOfBirth;
this.monthOfBirth = monthOfBirth;
this.dayOfBirth = dayOfBirth;
this.height = height;
this.weight = weight;
}
@Override
public byte[] toByteArray() {
return ByteBuffer.allocate(10)
.order(ByteOrder.LITTLE_ENDIAN)
.put((byte) 0x1D)
.put((byte) gender.ordinal())
.putShort(yearOfBirth)
.put((byte) (monthOfBirth + 1)) // Java uses 0-indexed months
.put((byte) dayOfBirth)
.putShort((short) (height * 10))
.putShort((short) (weight * 10))
.array();
}
}

View File

@ -0,0 +1,38 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
public class CalendarNotificationEnableSetting implements Wena3Packetable {
public final boolean enableCalendar;
public final boolean enableNotifications;
public CalendarNotificationEnableSetting(boolean enableCalendar, boolean enableNotifications) {
this.enableCalendar = enableCalendar;
this.enableNotifications = enableNotifications;
}
@Override
public byte[] toByteArray() {
return new byte[] {
0x12,
(byte) (enableNotifications ? 0x1 : 0x0),
(byte) (enableCalendar ? 0x1 : 0x0)
};
}
}

View File

@ -0,0 +1,52 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
import android.content.pm.PackageManager;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
public class CameraAppTypeSetting implements Wena3Packetable {
private static final String PHOTOPRO_APP_ID = "com.sonymobile.photopro";
public final boolean hasXperiaApp;
public CameraAppTypeSetting(boolean isXperia) {
this.hasXperiaApp = isXperia;
}
public static CameraAppTypeSetting findOut(PackageManager pm) {
try {
pm.getPackageInfo(PHOTOPRO_APP_ID, 0);
return new CameraAppTypeSetting(true);
} catch (PackageManager.NameNotFoundException e) {
return new CameraAppTypeSetting(false);
}
}
@Override
public byte[] toByteArray() {
return ByteBuffer
.allocate(2)
.order(ByteOrder.LITTLE_ENDIAN)
.put((byte)0x21)
.put((byte) (hasXperiaApp ? 0x1 : 0x0))
.array();
}
}

View File

@ -0,0 +1,34 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
public class DayStartHourSetting implements Wena3Packetable {
public final int dayStartHour;
public DayStartHourSetting(int dayStartHour) {
this.dayStartHour = dayStartHour;
}
@Override
public byte[] toByteArray() {
return new byte[] {
0x16, (byte) dayStartHour
};
}
}

View File

@ -0,0 +1,43 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines.DeviceButtonActionId;
public class DeviceButtonActionSetting implements Wena3Packetable {
public final DeviceButtonActionId longPress;
public final DeviceButtonActionId doubleClick;
public DeviceButtonActionSetting(DeviceButtonActionId longPress, DeviceButtonActionId doubleClick) {
this.longPress = longPress;
this.doubleClick = doubleClick;
}
@Override
public byte[] toByteArray() {
return ByteBuffer.allocate(5)
.order(ByteOrder.LITTLE_ENDIAN)
.put((byte)0x1C)
.putShort(longPress.value)
.putShort(doubleClick.value)
.array();
}
}

View File

@ -0,0 +1,63 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines.DisplayDesign;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines.DisplayOrientation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines.FontSize;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines.Language;
public class DisplaySetting implements Wena3Packetable {
public final boolean displayOnRaiseWrist;
public final Language language;
public final int displayDuration;
public final DisplayOrientation orientation;
public final DisplayDesign design;
public final FontSize fontSize;
public final boolean weatherInStatusBar;
public DisplaySetting(boolean displayOnRaiseWrist, Language language, int displayDuration, DisplayOrientation orientation, DisplayDesign design, FontSize fontSize, boolean weatherInStatusBar) {
this.displayOnRaiseWrist = displayOnRaiseWrist;
this.language = language;
this.displayDuration = displayDuration;
this.orientation = orientation;
this.design = design;
this.fontSize = fontSize;
this.weatherInStatusBar = weatherInStatusBar;
}
@Override
public byte[] toByteArray() {
return ByteBuffer
.allocate(8)
.order(ByteOrder.LITTLE_ENDIAN)
.put((byte)0x1A)
.put((byte) fontSize.ordinal())
.put((byte) orientation.ordinal())
.put((byte) language.ordinal())
.put((byte) (displayOnRaiseWrist ? 0x1 : 0x0))
.put((byte) design.ordinal())
.put((byte) (weatherInStatusBar ? 0x1 : 0x0))
.put((byte) displayDuration)
.array();
}
}

View File

@ -0,0 +1,51 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
public class DoNotDisturbSettings implements Wena3Packetable {
public final boolean enable;
public final int startHour;
public final int startMinute;
public final int endHour;
public final int endMinute;
public DoNotDisturbSettings(boolean enable, int startHour, int startMinute, int endHour, int endMinute) {
this.enable = enable;
this.startHour = startHour;
this.startMinute = startMinute;
this.endHour = endHour;
this.endMinute = endMinute;
}
@Override
public byte[] toByteArray() {
return ByteBuffer.allocate(6)
.order(ByteOrder.LITTLE_ENDIAN)
.put((byte) 0x14)
.put((byte) (enable ? 0x1 : 0x0))
.put((byte) startHour)
.put((byte) startMinute)
.put((byte) endHour)
.put((byte) endMinute)
.array();
}
}

View File

@ -0,0 +1,42 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
public class GoalStepsSetting implements Wena3Packetable {
public final boolean goalNotificationEnabled;
public final int goalSteps;
public GoalStepsSetting(boolean goalNotificationEnabled, int goalSteps) {
this.goalNotificationEnabled = goalNotificationEnabled;
this.goalSteps = goalSteps;
}
@Override
public byte[] toByteArray() {
return ByteBuffer.allocate(6)
.order(ByteOrder.LITTLE_ENDIAN)
.put((byte) 0x17)
.put((byte) (goalNotificationEnabled ? 0x1 : 0x0))
.putInt(goalSteps)
.array();
}
}

View File

@ -0,0 +1,47 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines.HomeIconId;
public class HomeIconOrderSetting implements Wena3Packetable {
public final HomeIconId left;
public final HomeIconId center;
public final HomeIconId right;
public HomeIconOrderSetting(HomeIconId left, HomeIconId center, HomeIconId right) {
this.left = left;
this.center = center;
this.right = right;
}
@Override
public byte[] toByteArray() {
return ByteBuffer.allocate(7)
.order(ByteOrder.LITTLE_ENDIAN)
.put((byte) 0x1B)
.putShort(left.value)
.putShort(center.value)
.putShort(right.value)
.array();
}
}

View File

@ -0,0 +1,42 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines.MenuIconId;
public class MenuIconSetting implements Wena3Packetable {
public final List<MenuIconId> iconList = new ArrayList<>();
public MenuIconSetting() {
}
@Override
public byte[] toByteArray() {
ByteBuffer buf = ByteBuffer.allocate(1 + iconList.size()).order(ByteOrder.LITTLE_ENDIAN);
buf.put((byte) 0x19);
for(MenuIconId id : iconList) {
buf.put(id.value);
}
return buf.array();
}
}

View File

@ -0,0 +1,64 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
public class SingleAlarmSetting implements Wena3Packetable {
public final boolean enable;
// Bitmask: See model.Alarm.ALARM_MON, TUE, ...
public final byte repetition;
public final int smartAlarmMargin;
public final int hour;
public final int minute;
public SingleAlarmSetting(boolean enable, byte repetition, int smartAlarmMargin, int hour, int minute) {
this.enable = enable;
this.repetition = repetition;
this.smartAlarmMargin = smartAlarmMargin;
this.hour = hour;
this.minute = minute;
}
// NB: normally this never occurs on the wire
// outside of an AlarmListSettings packet!
@Override
public byte[] toByteArray() {
// For some reason their bitmask starts on Sunday!
// So this brings it in line with what Gadgetbridge expects...
byte newRepetition = (byte) ((((repetition & ~Alarm.ALARM_SUN) << 1) | ((repetition & Alarm.ALARM_SUN) >> 6)) & 0xFF);
return ByteBuffer.allocate(5)
.order(ByteOrder.LITTLE_ENDIAN)
.put((byte) (enable ? 0x2 : 0x1)) // 0x0 means no object...
.put(newRepetition)
.put((byte) smartAlarmMargin)
.put((byte) hour)
.put((byte) minute)
.array();
}
public static byte[] emptyPacket() {
return ByteBuffer.allocate(5)
.order(ByteOrder.LITTLE_ENDIAN)
.put(new byte[] {0, 0, 0, 0, 0})
.array();
}
}

View File

@ -0,0 +1,41 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines.StatusPageId;
public class StatusPageOrderSetting implements Wena3Packetable {
public final List<StatusPageId> pages = new ArrayList<>();
public StatusPageOrderSetting() {}
@Override
public byte[] toByteArray() {
ByteBuffer buf = ByteBuffer.allocate(1 + pages.size()).order(ByteOrder.LITTLE_ENDIAN);
buf.put((byte) 0x18);
for(StatusPageId id : pages) {
buf.put(id.value);
}
return buf.array();
}
}

View File

@ -0,0 +1,41 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Date;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.util.TimeUtil;
public class TimeSetting implements Wena3Packetable {
public final Date currentTime;
public TimeSetting(Date dateTime) {
this.currentTime = dateTime;
}
@Override
public byte[] toByteArray() {
return ByteBuffer.allocate(5)
.order(ByteOrder.LITTLE_ENDIAN)
.put((byte) 0x10)
.putInt(TimeUtil.dateToWenaTime(currentTime))
.array();
}
}

View File

@ -0,0 +1,46 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Date;
import java.util.TimeZone;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
public class TimeZoneSetting implements Wena3Packetable {
public final TimeZone timeZone;
public final Date referenceDate;
public TimeZoneSetting(TimeZone tz, Date referenceDate) {
this.timeZone = tz;
this.referenceDate = referenceDate;
}
@Override
public byte[] toByteArray() {
int offset = timeZone.getOffset(referenceDate.getTime());
return ByteBuffer
.allocate(3)
.order(ByteOrder.LITTLE_ENDIAN)
.put((byte)0x11)
.put((byte) (offset / 3_600_000))
.put((byte) ((offset / 60_000) % 60))
.array();
}
}

View File

@ -0,0 +1,40 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines.VibrationStrength;
public class VibrationSetting implements Wena3Packetable {
public final boolean smartVibration;
public final VibrationStrength strength;
public VibrationSetting(boolean smartVibration, VibrationStrength strength) {
this.smartVibration = smartVibration;
this.strength = strength;
}
@Override
public byte[] toByteArray() {
return new byte[] {
0x08,
(byte) strength.ordinal(),
(byte) (smartVibration ? 0x01 : 0x00)
};
}
}

View File

@ -0,0 +1,50 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines;
public enum DeviceButtonActionId {
NONE(0),
ALEXA(1),
QRIO(2),
QRIO_UNLOCK(258),
QRIO_LOCK(514),
FIND_PHONE(3),
TOGGLE_NFC(4),
TOGGLE_SILENT(5),
START_TIMER(262),
TIMER(6),
WEATHER(7),
WENA_PAY(8),
SCHEDULE(9),
ACTIVITY_SCREEN(10),
NOTIFICATION_SCREEN(11),
SUICA(12),
EDY(13),
ALARM(14),
RIIIVER(15),
MUSIC(16),
TOGGLE_MUSIC(272),
MUSIC_NEXT(528),
MUSIC_PREV(784);
public final short value;
DeviceButtonActionId(int val) {
this.value = (short) val;
}
}

View File

@ -0,0 +1,22 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines;
public enum DisplayDesign {
NORMAL,
RICH
}

View File

@ -0,0 +1,22 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines;
public enum DisplayOrientation {
RIGHT_HAND,
LEFT_HAND
}

View File

@ -0,0 +1,22 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines;
public enum FontSize {
NORMAL,
LARGE
}

View File

@ -0,0 +1,22 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines;
public enum GenderSetting {
FEMALE, // == 0
MALE // == 1
}

View File

@ -0,0 +1,50 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines;
// This is done via a class to be able to init the value from an arbitrary int (e.g. from prefs)
// Whether listing all cases is needed is a good concern, they are essentially unused because
// the settings dialog uses a string-array instead...
public enum HomeIconId {
TIMER(256),
ALARM(512),
CLOCK(768),
ALEXA(1024),
WENA_PAY(1280),
QRIO_LOCK(1536),
EDY(1792),
NOTIFICATION_COUNT(2048),
SCHEDULE(2304),
PEDOMETER(2560),
SLEEP(2816),
HEART_RATE(3072),
VO2MAX(3328),
STRESS(3584),
ENERGY(3840),
SUICA(4096),
CALORIES(4352),
RIIIVER(4608),
MUSIC(4864),
CAMERA(5120);
public final short value;
HomeIconId(final int value) {
this.value = (short) value;
}
}

View File

@ -0,0 +1,22 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines;
public enum Language {
ENGLISH,
JAPANESE
}

View File

@ -0,0 +1,36 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines;
public enum MenuIconId {
NONE(0),
TIMER(1),
ALARM(2),
FIND_PHONE(3),
ALEXA(4),
PAYMENT(5),
QRIO(6),
WEATHER(7),
MUSIC(8),
CAMERA(9);
public final byte value;
MenuIconId(int value) {
this.value = (byte) value;
}
}

View File

@ -0,0 +1,34 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines;
public enum StatusPageId {
NONE(0),
PEDOMETER(1),
SLEEP(2),
HEART_RATE(3),
VO2MAX(4),
STRESS(5),
ENERGY(6),
CALORIES(7);
public final byte value;
StatusPageId(int val) {
this.value = (byte) val;
}
}

View File

@ -0,0 +1,29 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines;
public enum VibrationStrength {
NORMAL(0),
WEAK(1),
STRONG(2);
public final byte value;
VibrationStrength(int val) {
this.value = (byte) val;
}
}

View File

@ -0,0 +1,33 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.status;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
public class BatteryLevelInfo {
public final int batteryPercentage;
public BatteryLevelInfo(byte[] packet) {
this.batteryPercentage = Integer.valueOf(packet[0]);
}
public GBDeviceEventBatteryInfo toDeviceEvent() {
final GBDeviceEventBatteryInfo deviceEventBatteryInfo = new GBDeviceEventBatteryInfo();
deviceEventBatteryInfo.level = batteryPercentage;
return deviceEventBatteryInfo;
}
}

View File

@ -0,0 +1,43 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.status;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
public class DeviceInfo {
public final String firmwareName;
public final String serialNo;
public DeviceInfo(byte[] packet) {
ByteBuffer buf = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN);
byte[] fwNameStr = new byte[8];
buf.get(fwNameStr);
serialNo = Integer.toString(buf.getInt());
firmwareName = new String(fwNameStr, StandardCharsets.UTF_8);
}
public GBDeviceEventVersionInfo toDeviceEvent() {
final GBDeviceEventVersionInfo gbDeviceEventVersionInfo = new GBDeviceEventVersionInfo();
gbDeviceEventVersionInfo.fwVersion = firmwareName;
gbDeviceEventVersionInfo.fwVersion2 = serialNo;
return gbDeviceEventVersionInfo;
}
}

View File

@ -0,0 +1,44 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.status;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
public class MusicInfo implements Wena3Packetable {
public final String musicInfoText;
public MusicInfo(String musicInfoText) {
this.musicInfoText = musicInfoText;
}
@Override
public byte[] toByteArray() {
byte[] encodedString = StringUtils.truncate(musicInfoText.trim(), 99).getBytes(StandardCharsets.UTF_8);
return ByteBuffer
.allocate(2 + encodedString.length)
.order(ByteOrder.LITTLE_ENDIAN)
.put((byte)0x05)
.put((byte)encodedString.length)
.put(encodedString)
.array();
}
}

View File

@ -0,0 +1,26 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.status;
public class NotificationServiceStatusRequest {
public final int requestType;
public int unknown;
public NotificationServiceStatusRequest(byte[] packet) {
this.requestType = Integer.valueOf(packet[0]);
}
}

View File

@ -0,0 +1,34 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.status;
public enum StatusRequestType {
BACKGROUND_SYNC_REQUEST(0), // timer?
GET_CALENDAR(1),
LOCATE_PHONE(2),
GET_WEATHER(3),
RIIIVER_SIDE_BUTTON_ENGAGED(4), // -> 0x0 double click, 0x1 long click
RIIIVER_HOME_ICON_CLICKED(5), // -> 0x00 0x12, 0x01 0x12, 0x02 0x12 : button no.?
UNKNOWN1(6), // -> payment related?
NOTIFICATION_REMOVE_REQUEST(10), // -> followed by int notification id
MUSIC_INFO_FETCH(11);
public final int value;
StatusRequestType(int val) {
this.value = val;
}
}

View File

@ -0,0 +1,135 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.status;
import org.slf4j.LoggerFactory;
public class Weather {
private final WeatherType main;
private final WeatherType sub;
public Weather(WeatherType main, WeatherType sub) {
this.main = main;
this.sub = sub;
}
public byte packed() {
return (byte)((main.ordinal() << 3) | sub.ordinal());
}
public static Weather fromOpenWeatherMap(int conditionCode) {
WeatherType main = WeatherType.SUNNY;
WeatherType sub = WeatherType.SUNNY;
switch (conditionCode) {
// https://openweathermap.org/weather-conditions
case 200:
case 201:
case 210:
case 230:
case 231:
case 300:
case 301:
case 310:
case 311:
case 313:
case 500:
case 501:
case 520:
case 521:
main = WeatherType.RAIN;
sub = WeatherType.RAIN;
break;
case 202:
case 212:
case 221:
case 232:
case 302:
case 312:
case 314:
case 321:
case 502:
case 503:
case 504:
case 522:
case 531:
main = WeatherType.HEAVY_RAIN;
sub = WeatherType.HEAVY_RAIN;
break;
case 511:
case 615:
case 616:
main = WeatherType.RAIN;
sub = WeatherType.SNOW;
break;
case 600:
case 601:
case 602:
case 611:
case 612:
case 613:
case 620:
case 621:
case 622:
main = WeatherType.SNOW;
sub = WeatherType.SNOW;
break;
case 701:
case 711:
case 721:
case 731:
case 741:
case 751:
case 761:
case 762:
case 771:
case 781:
main = WeatherType.SUNNY;
sub = WeatherType.CLOUDY;
break;
case 800:
case 801:
main = WeatherType.SUNNY;
sub = WeatherType.SUNNY;
break;
case 802:
case 803:
main = WeatherType.SUNNY;
sub = WeatherType.CLOUDY;
break;
case 804:
main = WeatherType.CLOUDY;
sub = WeatherType.CLOUDY;
break;
default:
LoggerFactory.getLogger(Weather.class)
.warn("Unknown condition code " + conditionCode);
break;
}
return new Weather(main, sub);
}
}

View File

@ -0,0 +1,46 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.status;
import lineageos.weather.util.WeatherUtils;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
public class WeatherDay {
public final Weather day;
public final Weather night;
public final int temperatureMaximum;
public final int temperatureMinimum;
public WeatherDay(Weather day, Weather night, int temperatureMaximum, int temperatureMinimum) {
this.day = day;
this.night = night;
// For some reason, Wena uses Farenheit on the wire, but Celsius on display...
// Assume a middle ground input in Kelvin.
this.temperatureMaximum = (int) Math.round(WeatherUtils.celsiusToFahrenheit(temperatureMaximum - 273.15));
this.temperatureMinimum = (int) Math.round(WeatherUtils.celsiusToFahrenheit(temperatureMinimum - 273.15));
}
public static WeatherDay fromSpec(WeatherSpec.Daily daily) {
return new WeatherDay(
Weather.fromOpenWeatherMap(daily.conditionCode),
Weather.fromOpenWeatherMap(daily.conditionCode),
daily.maxTemp,
daily.minTemp
);
}
}

View File

@ -0,0 +1,51 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.status;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
public class WeatherReport implements Wena3Packetable {
public final List<WeatherDay> fiveDays;
public WeatherReport(List<WeatherDay> fiveDays) {
this.fiveDays = fiveDays;
assert this.fiveDays.size() == 5;
}
@Override
public byte[] toByteArray() {
ByteBuffer buf = ByteBuffer
.allocate(21)
.order(ByteOrder.LITTLE_ENDIAN)
.put((byte) 0x03);
for(int i = 0; i < 5; i++) {
WeatherDay current = this.fiveDays.get(i);
buf.put(current.day.packed());
buf.put(current.night.packed());
buf.put((byte) (current.temperatureMinimum + 100));
buf.put((byte) (current.temperatureMaximum + 100));
}
return buf.array();
}
}

View File

@ -0,0 +1,25 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.status;
public enum WeatherType {
SUNNY,
CLOUDY,
RAIN,
HEAVY_RAIN,
SNOW
}

View File

@ -0,0 +1,31 @@
/* Copyright (C) 2023 akasaka / Genjitsu Labs
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.util;
import java.util.Date;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3Constants;
public class TimeUtil {
public static int dateToWenaTime(Date date) {
return (int) ((date.getTime() - SonyWena3Constants.EPOCH_START) / (long)1000);
}
public static Date wenaTimeToDate(int wenaTime) {
return new Date(((long) wenaTime) * 1000L + SonyWena3Constants.EPOCH_START);
}
}

View File

@ -52,6 +52,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.lefun.BohemicSmartBraceletDe
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyLinkBudsSCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.soflow.SoFlowCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyWH1000XM5Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.supercars.SuperCarsCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.UnknownDeviceCoordinator;
@ -390,6 +391,7 @@ public class DeviceHelper {
result.add(new SonyWH1000XM2Coordinator());
result.add(new SonyWF1000XM4Coordinator());
result.add(new SonyWH1000XM5Coordinator());
result.add(new SonyWena3Coordinator());
result.add(new QC35Coordinator());
result.add(new BinarySensorCoordinator());
result.add(new FlipperZeroCoordinator());

View File

@ -0,0 +1,9 @@
<vector android:height="24dp" android:tint="#7E7E7E"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M16.9552,8.662l2.1207,-2.1222l1.4148,1.4138l-2.1207,2.1222z"/>
<path android:fillColor="@android:color/white" android:pathData="M2,18h20v2h-20z"/>
<path android:fillColor="@android:color/white" android:pathData="M11,4h2v3h-2z"/>
<path android:fillColor="@android:color/white" android:pathData="M3.5425,7.9248l1.4142,-1.4142l2.1213,2.1213l-1.4142,1.4142z"/>
<path android:fillColor="@android:color/white" android:pathData="M5,16h14c0,-3.87 -3.13,-7 -7,-7S5,12.13 5,16z"/>
</vector>

View File

@ -0,0 +1,24 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="12.7"
android:viewportHeight="12.7">
<path
android:pathData="M5.041,4.975h2.619v2.75h-2.619z"
android:strokeLineJoin="round"
android:strokeWidth="0.0891348"
android:fillColor="#616161"
android:strokeColor="#626262"/>
<path
android:pathData="M8.436,4.975h2.619v2.75h-2.619z"
android:strokeLineJoin="round"
android:strokeWidth="0.0891348"
android:fillColor="#00000000"
android:strokeColor="#626262"/>
<path
android:pathData="M1.645,4.975h2.619v2.75h-2.619z"
android:strokeLineJoin="round"
android:strokeWidth="0.0891348"
android:fillColor="#00000000"
android:strokeColor="#626262"/>
</vector>

View File

@ -0,0 +1,24 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="12.7"
android:viewportHeight="12.7">
<path
android:pathData="M5.041,4.975h2.619v2.75h-2.619z"
android:strokeLineJoin="round"
android:strokeWidth="0.0891348"
android:fillColor="#00000000"
android:strokeColor="#626262"/>
<path
android:pathData="M8.436,4.975h2.619v2.75h-2.619z"
android:strokeLineJoin="round"
android:strokeWidth="0.0891348"
android:fillColor="#00000000"
android:strokeColor="#626262"/>
<path
android:pathData="M1.645,4.975h2.619v2.75h-2.619z"
android:strokeLineJoin="round"
android:strokeWidth="0.0891348"
android:fillColor="#616161"
android:strokeColor="#626262"/>
</vector>

View File

@ -0,0 +1,24 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="12.7"
android:viewportHeight="12.7">
<path
android:pathData="M5.041,4.975h2.619v2.75h-2.619z"
android:strokeLineJoin="round"
android:strokeWidth="0.0891348"
android:fillColor="#00000000"
android:strokeColor="#626262"/>
<path
android:pathData="M8.436,4.975h2.619v2.75h-2.619z"
android:strokeLineJoin="round"
android:strokeWidth="0.0891348"
android:fillColor="#616161"
android:strokeColor="#626262"/>
<path
android:pathData="M1.645,4.975h2.619v2.75h-2.619z"
android:strokeLineJoin="round"
android:strokeWidth="0.0891348"
android:fillColor="#00000000"
android:strokeColor="#626262"/>
</vector>

Some files were not shown because too many files have changed in this diff Show More