From f3b4f0ed266700ffb3d0230641e2efd018a2f96f Mon Sep 17 00:00:00 2001 From: Linos Giannopoulos Date: Sat, 12 Oct 2024 21:35:18 +0300 Subject: [PATCH] Add device support for the IKEA desk controller All the required functionality is included: - Move the desk up and down manually - See the current height and speed - Store three desk positions - Move to any of those desk positions --- app/src/main/AndroidManifest.xml | 6 +- .../DeviceSettingsPreferenceConst.java | 4 + .../devices/idasen/IdasenConstants.java | 30 +++ .../devices/idasen/IdasenControlActivity.java | 105 +++++++++ .../devices/idasen/IdasenCoordinator.java | 70 ++++++ .../idasen/IdasenSettingsCustomizer.java | 83 +++++++ .../gadgetbridge/model/DeviceType.java | 2 + .../devices/idasen/IdasenDeviceSupport.java | 208 ++++++++++++++++++ .../res/drawable/arrow_autofit_height.xml | 41 ++++ app/src/main/res/drawable/brand_speedtest.xml | 20 ++ .../res/layout/activity_idasen_control.xml | 122 ++++++++++ app/src/main/res/values/strings.xml | 8 + .../main/res/xml/devicesettings_idasen.xml | 27 +++ 13 files changed, 725 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenConstants.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenControlActivity.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenCoordinator.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenSettingsCustomizer.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/idasen/IdasenDeviceSupport.java create mode 100644 app/src/main/res/drawable/arrow_autofit_height.xml create mode 100644 app/src/main/res/drawable/brand_speedtest.xml create mode 100644 app/src/main/res/layout/activity_idasen_control.xml create mode 100644 app/src/main/res/xml/devicesettings_idasen.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9de4385ec..ffbee889b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -211,6 +211,10 @@ android:name=".devices.vesc.VescControlActivity" android:label="@string/devicetype_vesc" android:parentActivityName=".activities.ControlCenterv2" /> + - \ No newline at end of file + diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java index 141768f8b..2e7c2619c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java @@ -482,6 +482,10 @@ public class DeviceSettingsPreferenceConst { public static final String PREF_VESC_MINIMUM_VOLTAGE = "vesc_minimum_battery_voltage"; public static final String PREF_VESC_MAXIMUM_VOLTAGE = "vesc_maximum_battery_voltage"; + public static final String PREF_IDASEN_SIT_HEIGHT = "idasen_sit_height"; + public static final String PREF_IDASEN_MID_HEIGHT = "idasen_mid_height"; + public static final String PREF_IDASEN_STAND_HEIGHT = "idasen_stand_height"; + public static final String PREF_SOUNDS = "sounds"; public static final String PREF_AUTH_KEY = "authkey"; public static final String PREF_USER_FITNESS_GOAL = "fitness_goal"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenConstants.java new file mode 100644 index 000000000..08402ac3f --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenConstants.java @@ -0,0 +1,30 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.idasen; + +import java.util.UUID; + +public class IdasenConstants { + public static final UUID CHARACTERISTIC_SVC_HEIGHT = UUID.fromString("99fa0020-338a-1024-8a49-009c0215f78a"); + public static final UUID CHARACTERISTIC_SVC_COMMAND = UUID.fromString("99fa0001-338a-1024-8a49-009c0215f78a"); + public static final UUID CHARACTERISTIC_COMMAND = UUID.fromString("99fa0002-338a-1024-8a49-009c0215f78a"); + public static final UUID CHARACTERISTIC_SVC_REF_HEIGHT = UUID.fromString("99fa0030-338a-1024-8a49-009c0215f78a"); + public static final UUID CHARACTERISTIC_SVC_DPG = UUID.fromString("99fa0010-338a-1024-8a49-009c0215f78a"); + public static final UUID CHARACTERISTIC_REF_HEIGHT = UUID.fromString("99fa0031-338a-1024-8a49-009c0215f78a"); + public static final UUID CHARACTERISTIC_HEIGHT = UUID.fromString("99fa0021-338a-1024-8a49-009c0215f78a"); + public static final UUID CHARACTERISTIC_DPG = UUID.fromString("99fa0011-338a-1024-8a49-009c0215f78a"); + public static final String ACTION_REALTIME_DESK_VALUES = ".action.realtime_desk_values"; + public static final String EXTRA_DESK_HEIGHT = "EXTRA_DESK_HEIGHT"; + public static final String EXTRA_DESK_SPEED = "EXTRA_DESK_SPEED"; + + public static final double MIN_HEIGHT = 0.62; + public static final double MAX_HEIGHT = 1.27; + + public static final byte[] CMD_UP = new byte[]{0x47, 0x00}; + public static final byte[] CMD_DOWN = new byte[]{0x46, 0x00}; + public static final byte[] CMD_STOP = new byte[]{(byte)0xFF, 0x00}; + public static final byte[] CMD_WAKEUP = new byte[]{(byte)0xFE, 0x00}; + public static final byte[] CMD_REF_INPUT_STOP = new byte[]{0x01, (byte)0x80}; + public static final byte[] CMD_DPG_WAKEUP_PREP = new byte[]{0x7F, (byte)0x86, 0x00}; + public static final byte[] CMD_DPG_WAKEUP = new byte[]{0x7F, (byte)0x86, (byte)0x80, 0x01, + 0x02, 0x03, 0x04, 0x05, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0x0E, 0x0F, 0x10, 0x11}; +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenControlActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenControlActivity.java new file mode 100644 index 000000000..cb08e6470 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenControlActivity.java @@ -0,0 +1,105 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.idasen; + +import androidx.localbroadcastmanager.content.LocalBroadcastManager; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.view.MotionEvent; +import android.view.View; +import android.widget.TextView; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.text.DecimalFormat; +import java.util.Objects; + + +import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity; +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.activities.workouts.WorkoutValueFormatter; +import nodomain.freeyourgadget.gadgetbridge.service.devices.idasen.IdasenDeviceSupport; + +public class IdasenControlActivity extends AbstractGBActivity { + private final Logger logger = LoggerFactory.getLogger(getClass()); + public TextView mDeskHeight, mDeskSpeed; + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Objects.requireNonNull(action).equals(IdasenConstants.ACTION_REALTIME_DESK_VALUES)){ + final WorkoutValueFormatter formatter = new WorkoutValueFormatter(); + float height = (float) intent.getSerializableExtra(IdasenConstants.EXTRA_DESK_HEIGHT); + float speed = (float) intent.getSerializableExtra(IdasenConstants.EXTRA_DESK_SPEED); + logger.debug("Received desk values: {} {}", speed, height); + mDeskHeight.setText(formatter.formatValue(height * 100F, "cm")); + mDeskSpeed.setText(formatter.formatValue(speed * 1000F, "mm/s")); + } + } + }; + + LocalBroadcastManager localBroadcastManager; + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_idasen_control); + + localBroadcastManager = LocalBroadcastManager.getInstance(this); + + mDeskHeight = findViewById(R.id.idasen_desk_height); + mDeskSpeed = findViewById(R.id.idasen_desk_speed); + + IntentFilter filterLocal = new IntentFilter(); + filterLocal.addAction(IdasenConstants.ACTION_REALTIME_DESK_VALUES); + LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(mReceiver, filterLocal); + Intent intent = new Intent(IdasenDeviceSupport.COMMAND_GET_DESK_VALUES); + sendLocalBroadcast(intent); + + View.OnTouchListener controlTouchListener = new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + Intent intent = null; + + if (event.getAction() == MotionEvent.ACTION_DOWN) { + if (v.getId() == R.id.idasen_control_button_up) { + intent = new Intent(IdasenDeviceSupport.COMMAND_UP); + } else if (v.getId() == R.id.idasen_control_button_down) { + intent = new Intent(IdasenDeviceSupport.COMMAND_DOWN); + } else if (v.getId() == R.id.idasen_control_button_sit) { + intent = new Intent(IdasenDeviceSupport.COMMAND_SET_HEIGHT); + intent.putExtra(IdasenDeviceSupport.TARGET_HEIGHT, IdasenDeviceSupport.TARGET_POS_SIT); + } else if (v.getId() == R.id.idasen_control_button_stand) { + intent = new Intent(IdasenDeviceSupport.COMMAND_SET_HEIGHT); + intent.putExtra(IdasenDeviceSupport.TARGET_HEIGHT, IdasenDeviceSupport.TARGET_POS_STAND); + } else if (v.getId() == R.id.idasen_control_button_mid) { + intent = new Intent(IdasenDeviceSupport.COMMAND_SET_HEIGHT); + intent.putExtra(IdasenDeviceSupport.TARGET_HEIGHT, IdasenDeviceSupport.TARGET_POS_MID); + } + sendLocalBroadcast(intent); + } else { + return false; + } + return true; + } + }; + findViewById(R.id.idasen_control_button_up).setOnTouchListener(controlTouchListener); + findViewById(R.id.idasen_control_button_down).setOnTouchListener(controlTouchListener); + findViewById(R.id.idasen_control_button_sit).setOnTouchListener(controlTouchListener); + findViewById(R.id.idasen_control_button_mid).setOnTouchListener(controlTouchListener); + findViewById(R.id.idasen_control_button_stand).setOnTouchListener(controlTouchListener); + } + + private void sendLocalBroadcast(Intent intent) { + localBroadcastManager.sendBroadcast(intent); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + localBroadcastManager.unregisterReceiver(mReceiver); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenCoordinator.java new file mode 100644 index 000000000..7523dc0d3 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenCoordinator.java @@ -0,0 +1,70 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.idasen; + +import android.app.Activity; + +import androidx.annotation.NonNull; + +import java.util.regex.Pattern; + +import nodomain.freeyourgadget.gadgetbridge.GBException; +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer; +import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator; +import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; +import nodomain.freeyourgadget.gadgetbridge.entities.Device; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.idasen.IdasenDeviceSupport; + +public class IdasenCoordinator extends AbstractBLEDeviceCoordinator { + @Override + protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException { + + } + @Override + public String getManufacturer() { + return "IKEA"; + } + + @Override + protected Pattern getSupportedDeviceName() { + return Pattern.compile("Desk 1000", Pattern.CASE_INSENSITIVE); + } + @Override + public int getDeviceNameResource() { + return R.string.devicetype_idasen; + } + + @NonNull + @Override + public Class getDeviceSupportClass() { + return IdasenDeviceSupport.class; + } + + @Override + public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) { + return new IdasenSettingsCustomizer(); + } + + @Override + public int getBondingStyle() { + return BONDING_STYLE_BOND; + } + + @Override + public boolean supportsAppsManagement(final GBDevice device) { + return true; + } + + @Override + public Class getAppsManagementActivity() { + return IdasenControlActivity.class; + } + + @Override + public int[] getSupportedDeviceSpecificSettings(GBDevice device) { + return new int[]{ + R.xml.devicesettings_idasen + }; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenSettingsCustomizer.java new file mode 100644 index 000000000..c36faa037 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenSettingsCustomizer.java @@ -0,0 +1,83 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.idasen; + +import android.os.Parcel; +import android.text.InputType; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.preference.EditTextPreference; +import androidx.preference.Preference; + + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; +import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer; +import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler; +import nodomain.freeyourgadget.gadgetbridge.util.Prefs; +import nodomain.freeyourgadget.gadgetbridge.R; + +import static nodomain.freeyourgadget.gadgetbridge.util.GB.toast; + +public class IdasenSettingsCustomizer implements DeviceSpecificSettingsCustomizer { + public static final Creator CREATOR = new Creator() { + @Override + public IdasenSettingsCustomizer createFromParcel(final Parcel in) { + return new IdasenSettingsCustomizer(); + } + + @Override + public IdasenSettingsCustomizer[] newArray(final int size) { + return new IdasenSettingsCustomizer[size]; + } + }; + private static final Logger LOG = LoggerFactory.getLogger(IdasenSettingsCustomizer.class); + + @Override + public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) { + } + + @Override + public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String rootKey) { + final EditTextPreference prefMidHeight = handler.findPreference(DeviceSettingsPreferenceConst.PREF_IDASEN_MID_HEIGHT); + final EditTextPreference prefSitHeight = handler.findPreference(DeviceSettingsPreferenceConst.PREF_IDASEN_SIT_HEIGHT); + final EditTextPreference prefStandHeight = handler.findPreference(DeviceSettingsPreferenceConst.PREF_IDASEN_STAND_HEIGHT); + if (prefSitHeight == null || prefMidHeight == null || prefStandHeight == null) { + return; + } + Preference.OnPreferenceChangeListener prefListener = (preference, newValue) -> { + final double val = Double.parseDouble(newValue.toString()); + if (val > IdasenConstants.MAX_HEIGHT * 100F || val < IdasenConstants.MIN_HEIGHT * 100F) { + toast(handler.getContext(), R.string.idasen_pref_value_warning, Toast.LENGTH_SHORT, 0); + return false; + } + return true; + }; + for (EditTextPreference pref: List.of(prefMidHeight, prefSitHeight, prefStandHeight)){ + pref.setOnBindEditTextListener(p -> { + p.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL); + p.setSelection(p.getText().length()); + }); + pref.setOnPreferenceChangeListener(prefListener); + } + } + + @Override + public Set getPreferenceKeysWithSummary() { + return Collections.emptySet(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java index b6be12da2..f92b9dc1b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java @@ -195,6 +195,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatchgtrunner.H import nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatchultimate.HuaweiWatchUltimateCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.id115.ID115Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.itag.ITagCoordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.idasen.IdasenCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.jyou.BFH16DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.jyou.TeclastH30.TeclastH30Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.jyou.y5.Y5Coordinator; @@ -406,6 +407,7 @@ public enum DeviceType { COLACAO21(ColaCao21Coordinator.class), COLACAO23(ColaCao23Coordinator.class), ITAG(ITagCoordinator.class), + IKEA_IDASEN(IdasenCoordinator.class), NUTMINI(NutCoordinator.class), VIVOMOVE_HR(GarminVivomoveHrCoordinator.class), GARMIN_ENDURO_3(GarminEnduro3Coordinator.class), diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/idasen/IdasenDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/idasen/IdasenDeviceSupport.java new file mode 100644 index 000000000..47ead980b --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/idasen/IdasenDeviceSupport.java @@ -0,0 +1,208 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.idasen; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; + +import androidx.localbroadcastmanager.content.LocalBroadcastManager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.HashMap; +import java.util.Objects; +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; +import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.util.GB; +import nodomain.freeyourgadget.gadgetbridge.devices.idasen.IdasenConstants; + +public class IdasenDeviceSupport extends AbstractBTLEDeviceSupport { + private static final Logger LOG = LoggerFactory.getLogger(IdasenDeviceSupport.class); + + public static final String COMMAND_UP = "nodomain.freeyourgadget.gadgetbridge.idasen.command.UP"; + public static final String COMMAND_DOWN = "nodomain.freeyourgadget.gadgetbridge.idasen.command.DOWN"; + public static final String COMMAND_SET_HEIGHT = "nodomain.freeyourgadget.gadgetbridge.idasen.command.SET_HEIGHT"; + public static final String COMMAND_GET_DESK_VALUES = "nodomain.freeyourgadget.gadgetbridge.idasen.command.GET_DESK_VALUES"; + public static final String TARGET_HEIGHT = "TARGET_HEIGHT"; + public static final String TARGET_POS_SIT = "TARGET_POS_SIT"; + public static final String TARGET_POS_STAND = "TARGET_POS_STAND"; + public static final String TARGET_POS_MID = "TARGET_POS_MID"; + public float sit_height, stand_height, mid_height; + public float deskSpeed, deskHeight; + + public IdasenDeviceSupport() { + super(LOG); + addSupportedService(IdasenConstants.CHARACTERISTIC_SVC_HEIGHT); + addSupportedService(IdasenConstants.CHARACTERISTIC_SVC_COMMAND); + addSupportedService(IdasenConstants.CHARACTERISTIC_SVC_REF_HEIGHT); + addSupportedService(IdasenConstants.CHARACTERISTIC_SVC_DPG); + } + + BroadcastReceiver commandReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String act = intent.getAction(); + + switch(Objects.requireNonNull(act)){ + case COMMAND_UP: + sendCommand("cmd", IdasenConstants.CHARACTERISTIC_COMMAND, IdasenConstants.CMD_UP); + break; + case COMMAND_DOWN: + sendCommand("cmd", IdasenConstants.CHARACTERISTIC_COMMAND, IdasenConstants.CMD_DOWN); + break; + case COMMAND_GET_DESK_VALUES: + readCharacteristic("IdasenGetHeight", IdasenConstants.CHARACTERISTIC_HEIGHT); + break; + case COMMAND_SET_HEIGHT: + HashMap mPositionsMap = new HashMap<>(); + + SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()); + stand_height = Float.parseFloat(prefs.getString(DeviceSettingsPreferenceConst.PREF_IDASEN_STAND_HEIGHT, "0.0")) / 100F; + mid_height = Float.parseFloat(prefs.getString(DeviceSettingsPreferenceConst.PREF_IDASEN_MID_HEIGHT, "0.0")) / 100F; + sit_height = Float.parseFloat(prefs.getString(DeviceSettingsPreferenceConst.PREF_IDASEN_SIT_HEIGHT, "0.0")) / 100F; + mPositionsMap.put(TARGET_POS_SIT, sit_height); + mPositionsMap.put(TARGET_POS_STAND, stand_height); + mPositionsMap.put(TARGET_POS_MID, mid_height); + + sendCommand("cmd", IdasenConstants.CHARACTERISTIC_COMMAND, IdasenConstants.CMD_WAKEUP); + sendCommand("cmd", IdasenConstants.CHARACTERISTIC_COMMAND, IdasenConstants.CMD_STOP); + + BluetoothGattCharacteristic characteristic = getCharacteristic(IdasenConstants.CHARACTERISTIC_REF_HEIGHT); + String targetHeightKey = intent.getStringExtra(TARGET_HEIGHT); + float targetHeight = mPositionsMap.get(targetHeightKey); + + ByteBuffer setHeightBB = ByteBuffer.allocate(2); + setHeightBB.order(ByteOrder.LITTLE_ENDIAN); + setHeightBB.putShort(0, (short) ((targetHeight - IdasenConstants.MIN_HEIGHT) * 10000F)); + byte[] setHeightRequest = setHeightBB.array(); + new Thread(() -> { + // This acts as a fail-safe, in case deskSpeed never goes to 0 + // for whatever reason. It's based on the time the desk controller + // needs to get from the lowest to the highest point. + int cutOff = 100; + do { + TransactionBuilder builder = new TransactionBuilder("height"); + + builder.write(characteristic, setHeightRequest); + builder.queue(getQueue()); + try { + Thread.sleep(300); + } catch (InterruptedException e) { + GB.log("error", GB.ERROR, e); + } + cutOff--; + } while (deskSpeed != 0F && cutOff != 0); + + if (cutOff == 0) { + LOG.warn("desk controller did not reach the desired height in time"); + } + }).start(); + break; + } + } + }; + + @Override + public boolean useAutoConnect() { + return true; + } + + @Override + public void dispose() { + super.dispose(); + LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(commandReceiver); + } + + @Override + protected TransactionBuilder initializeDevice(TransactionBuilder builder) { + builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext())); + builder.notify(getCharacteristic(IdasenConstants.CHARACTERISTIC_HEIGHT), true); + sendCommand("dpg", IdasenConstants.CHARACTERISTIC_DPG, IdasenConstants.CMD_DPG_WAKEUP_PREP); + sendCommand("dpg", IdasenConstants.CHARACTERISTIC_DPG, IdasenConstants.CMD_DPG_WAKEUP); + sendCommand("dpg", IdasenConstants.CHARACTERISTIC_COMMAND, IdasenConstants.CMD_WAKEUP); + initBroadcast(); + builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext())); + return builder; + } + + @Override + public boolean onCharacteristicRead(BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic, + int status) { + + if (characteristic.getUuid().equals(IdasenConstants.CHARACTERISTIC_HEIGHT)) { + getDeskValues(characteristic); + announceDeskValues(); + return true; + } + return super.onCharacteristicRead(gatt, characteristic, status); + } + + @Override + public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + + if (characteristic.getUuid().equals(IdasenConstants.CHARACTERISTIC_HEIGHT)) { + getDeskValues(characteristic); + announceDeskValues(); + return true; + } + return super.onCharacteristicChanged(gatt, characteristic); + } + + private void readCharacteristic(String taskName, UUID charac) { + BluetoothGattCharacteristic characteristic = getCharacteristic(charac); + + TransactionBuilder builder = new TransactionBuilder(taskName); + builder.read(characteristic); + builder.queue(getQueue()); + } + + private void initBroadcast() { + LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext()); + + IntentFilter filter = new IntentFilter(); + filter.addAction(COMMAND_UP); + filter.addAction(COMMAND_DOWN); + filter.addAction(COMMAND_SET_HEIGHT); + filter.addAction(COMMAND_GET_DESK_VALUES); + + broadcastManager.registerReceiver(commandReceiver, filter); + } + + private void getDeskValues(BluetoothGattCharacteristic characteristic) { + byte[] value = characteristic.getValue(); + final ByteBuffer buf = ByteBuffer.wrap(value).order(ByteOrder.LITTLE_ENDIAN); + int hh = BLETypeConversions.toUnsigned(buf.getShort()); + deskHeight = (float) IdasenConstants.MIN_HEIGHT + hh / 10000F; + deskSpeed = buf.getShort() / 10000F; + } + + private void announceDeskValues() { + final Intent intent = new Intent(IdasenConstants.ACTION_REALTIME_DESK_VALUES) + .putExtra(IdasenConstants.EXTRA_DESK_HEIGHT, deskHeight) + .putExtra(IdasenConstants.EXTRA_DESK_SPEED, deskSpeed); + LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent); + } + + private void sendCommand(String taskName, UUID charac, byte[] contents) { + TransactionBuilder builder = new TransactionBuilder(taskName); + BluetoothGattCharacteristic characteristic = getCharacteristic(charac); + if (characteristic != null) { + builder.write(characteristic, contents); + builder.queue(getQueue()); + } + } +} diff --git a/app/src/main/res/drawable/arrow_autofit_height.xml b/app/src/main/res/drawable/arrow_autofit_height.xml new file mode 100644 index 000000000..aa096431c --- /dev/null +++ b/app/src/main/res/drawable/arrow_autofit_height.xml @@ -0,0 +1,41 @@ + + + + + + + diff --git a/app/src/main/res/drawable/brand_speedtest.xml b/app/src/main/res/drawable/brand_speedtest.xml new file mode 100644 index 000000000..e18f1e762 --- /dev/null +++ b/app/src/main/res/drawable/brand_speedtest.xml @@ -0,0 +1,20 @@ + + + + diff --git a/app/src/main/res/layout/activity_idasen_control.xml b/app/src/main/res/layout/activity_idasen_control.xml new file mode 100644 index 000000000..7fa59991f --- /dev/null +++ b/app/src/main/res/layout/activity_idasen_control.xml @@ -0,0 +1,122 @@ + + + + + + +