diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java
index b3d312391..fb36779fa 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java
@@ -23,9 +23,52 @@ import java.util.UUID;
import static nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport.BASE_UUID;
public class LefunConstants {
+ // BLE UUIDs
public static final UUID UUID_SERVICE_LEFUN = UUID.fromString(String.format(BASE_UUID, "18D0"));
+ // Coordinator constants
public static final String ADVERTISEMENT_NAME = "Lefun";
public static final String MANUFACTURER_NAME = "Teng Jin Da";
public static int NUM_ALARM_SLOTS = 5;
+
+ // Commands
+ public static final byte CMD_REQUEST_ID = (byte)0xab;
+ public static final byte CMD_RESPONSE_ID = 0x55;
+
+ public static final int CMD_MAX_LENGTH = 20;
+ // 3 header bytes plus checksum
+ public static final int CMD_HEADER_LENGTH = 4;
+
+ public static final byte CMD_FIRMWARE_INFO = 0x00;
+ public static final byte CMD_BONDING_REQUEST = 0x01;
+ public static final byte CMD_SETTINGS = 0x02;
+ public static final byte CMD_BATTERY_LEVEL = 0x03;
+ public static final byte CMD_TIME = 0x04;
+ public static final byte CMD_ALARM = 0x05;
+ public static final byte CMD_PROFILE = 0x06;
+ public static final byte CMD_UI_PAGES = 0x07;
+ public static final byte CMD_FEATURES = 0x08;
+ public static final byte CMD_FIND_DEVICE = 0x09;
+ public static final byte CMD_FIND_PHONE = 0x0a;
+ public static final byte CMD_SEDENTARY_REMINDER_INTERVAL = 0x0b;
+ public static final byte CMD_HYDRATION_REMINDER_INTERVAL = 0x0c;
+ public static final byte CMD_REMOTE_CAMERA = 0x0d;
+ public static final byte CMD_REMOTE_CAMERA_TRIGGERED = 0x0e;
+ public static final byte CMD_PPG_START = 0x0f;
+ public static final byte CMD_PPG_RESULT = 0x10;
+ public static final byte CMD_PPG_DATA = 0x11;
+ public static final byte CMD_STEPS_DATA = 0x12;
+ public static final byte CMD_ACTIVITY_DATA = 0x13;
+ public static final byte CMD_SLEEP_TIME_DATA = 0x14;
+ public static final byte CMD_SLEEP_DATA = 0x15;
+ public static final byte CMD_NOTIFICATION = 0x17;
+ public static final byte CMD_LANGUAGE = 0x21;
+ public static final byte CMD_UNKNOWN_22 = 0x22;
+ public static final byte CMD_UNKNOWN_25 = 0x25;
+ public static final byte CMD_UNKNOWN_80 = (byte)0x80;
+
+ public static final int PPG_TYPE_INVALID = -1;
+ public static final int PPG_TYPE_HEART_RATE = 0;
+ public static final int PPG_TYPE_BLOOD_PRESSURE = 1;
+ public static final int PPG_TYPE_BLOOD_OXYGEN = 2;
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunFeatureSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunFeatureSupport.java
new file mode 100644
index 000000000..ba76fb046
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunFeatureSupport.java
@@ -0,0 +1,41 @@
+/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+ Gobbetti
+ Copyright (C) 2020 Yukai Li
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun;
+
+public class LefunFeatureSupport {
+ public static final int SUPPORT_HEART_RATE = 1 << 2;
+ public static final int SUPPORT_BLOOD_PRESSURE = 1 << 3;
+ public static final int SUPPORT_FAKE_ECG = 1 << 10;
+ public static final int SUPPORT_ECG = 1 << 11;
+ public static final int SUPPORT_WALLPAPER_UPLOAD = 1 << 12;
+
+ public static final int RESERVE_BLOOD_OXYGEN = 1 << 0;
+ public static final int RESERVE_CLOCK_FACE_UPLOAD = 1 << 3;
+ public static final int RESERVE_CONTACTS = 1 << 5;
+ public static final int RESERVE_WALLPAPER = 1 << 6;
+ public static final int RESERVE_REMOTE_CAMERA = 1 << 7;
+
+ public static boolean checkSupported(short deviceSupport, int featureSupport) {
+ return (deviceSupport & featureSupport) == featureSupport;
+ }
+
+ public static boolean checkNotReserved(short deviceReserve, int featureReserve) {
+ return !((deviceReserve & featureReserve) == featureReserve);
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/AlarmCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/AlarmCommand.java
new file mode 100644
index 000000000..a351920b4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/AlarmCommand.java
@@ -0,0 +1,174 @@
+/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+ Gobbetti
+ Copyright (C) 2020 Yukai Li
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class AlarmCommand extends BaseCommand {
+ public static final int DOW_SUNDAY = 0;
+ public static final int DOW_MONDAY = 1;
+ public static final int DOW_TUESDAY = 2;
+ public static final int DOW_WEDNESDAY = 3;
+ public static final int DOW_THURSDAY = 4;
+ public static final int DOW_FRIDAY = 5;
+ public static final int DOW_SATURDAY = 6;
+
+ private byte op;
+ private byte index;
+ private boolean enabled;
+ // Snooze is not implemented how you think it would be
+ // Number of snoozes is decremented every time the alarm triggers, and the alarm time
+ // is moved forward by number of minutes in snooze time. It never gets reset to the
+ // original time.
+ private byte numOfSnoozes;
+ private byte snoozeTime;
+ private byte dayOfWeek;
+ private byte hour;
+ private byte minute;
+
+ private boolean setSuccess;
+
+ public byte getOp() {
+ return op;
+ }
+
+ public void setOp(byte op) {
+ if (op != OP_GET && op != OP_SET)
+ throw new IllegalArgumentException("Operation must be get or set");
+ this.op = op;
+ }
+
+ public byte getIndex() {
+ return index;
+ }
+
+ public void setIndex(byte index) {
+ if (index < 0 || index >= LefunConstants.NUM_ALARM_SLOTS)
+ throw new IllegalArgumentException("Index must be between 0 and "
+ + (LefunConstants.NUM_ALARM_SLOTS - 1) + " inclusive");
+ this.index = index;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public byte getNumOfSnoozes() {
+ return numOfSnoozes;
+ }
+
+ public void setNumOfSnoozes(byte numOfSnoozes) {
+ this.numOfSnoozes = numOfSnoozes;
+ }
+
+ public byte getSnoozeTime() {
+ return snoozeTime;
+ }
+
+ public void setSnoozeTime(byte snoozeTime) {
+ this.snoozeTime = snoozeTime;
+ }
+
+ public boolean getDayOfWeek(int day) {
+ if (day < 0 || day > 6)
+ throw new IllegalArgumentException("Invalid day of week");
+ return getBit(dayOfWeek, 1 << day);
+ }
+
+ public void setDayOfWeek(int day, boolean enabled) {
+ if (day < 0 || day > 6)
+ throw new IllegalArgumentException("Invalid day of week");
+ dayOfWeek = setBit(dayOfWeek, 1 << day, enabled);
+ }
+
+ public byte getHour() {
+ return hour;
+ }
+
+ public void setHour(byte hour) {
+ if (hour < 0 || hour > 23)
+ throw new IllegalArgumentException("Hour must be between 0 and 23 inclusive");
+ this.hour = hour;
+ }
+
+ public byte getMinute() {
+ return minute;
+ }
+
+ public void setMinute(byte minute) {
+ if (minute < 0 || minute > 59)
+ throw new IllegalArgumentException("Minute must be between 0 and 59 inclusive");
+ this.minute = minute;
+ }
+
+ public boolean isSetSuccess() {
+ return setSuccess;
+ }
+
+ @Override
+ protected void deserializeParams(byte id, ByteBuffer params) {
+ validateId(id, LefunConstants.CMD_ALARM);
+
+ if (params.limit() < 2)
+ throwUnexpectedLength();
+
+ op = params.get();
+ index = params.get();
+ if (op == OP_GET) {
+ if (params.limit() != 8)
+ throwUnexpectedLength();
+
+ enabled = params.get() == 1;
+ numOfSnoozes = params.get();
+ snoozeTime = params.get();
+ dayOfWeek = params.get();
+ hour = params.get();
+ minute = params.get();
+ } else if (op == OP_SET) {
+ if (params.limit() != 3)
+ throwUnexpectedLength();
+
+ setSuccess = params.get() == 1;
+ } else {
+ throw new IllegalArgumentException("Invalid operation type received");
+ }
+ }
+
+ @Override
+ protected byte serializeParams(ByteBuffer params) {
+ params.put(op);
+ // No alarm ID for get; all of them are returned
+ if (op == OP_SET) {
+ params.put(index);
+ params.put((byte)(enabled ? 1: 0));
+ params.put(numOfSnoozes);
+ params.put(snoozeTime);
+ params.put(dayOfWeek);
+ params.put(hour);
+ params.put(minute);
+ }
+ return LefunConstants.CMD_ALARM;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/BaseCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/BaseCommand.java
new file mode 100644
index 000000000..7a8ac77d3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/BaseCommand.java
@@ -0,0 +1,148 @@
+/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+ Gobbetti
+ Copyright (C) 2020 Yukai Li
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public abstract class BaseCommand {
+ // Common constants
+ public static final byte OP_GET = 0;
+ public static final byte OP_SET = 1;
+
+ abstract protected void deserializeParams(byte id, ByteBuffer params);
+ abstract protected byte serializeParams(ByteBuffer params);
+
+ public void deserialize(byte[] response) {
+ if (response.length < LefunConstants.CMD_HEADER_LENGTH)
+ throw new IllegalArgumentException("Response is too short");
+ if (calculateChecksum(response, 0, response.length - 1) != response[response.length - 1])
+ throw new IllegalArgumentException("Incorrect message checksum");
+ ByteBuffer buffer = ByteBuffer.wrap(response, LefunConstants.CMD_HEADER_LENGTH - 1,
+ response.length - LefunConstants.CMD_HEADER_LENGTH);
+ buffer.order(ByteOrder.BIG_ENDIAN);
+ deserializeParams(response[2], buffer);
+ }
+
+ public byte[] serialize() {
+ ByteBuffer buffer = ByteBuffer.allocate(LefunConstants.CMD_MAX_LENGTH - LefunConstants.CMD_HEADER_LENGTH);
+ buffer.order(ByteOrder.BIG_ENDIAN);
+ byte id = serializeParams(buffer);
+ return makeCommand(id, buffer);
+ }
+
+ static protected byte calculateChecksum(byte[] data, int offset, int length) {
+ byte checksum = 0;
+ for (int i = offset; i < offset + length; ++i) {
+ byte b = data[i];
+ for (int j = 0; j < 8; ++j) {
+ if (((b ^ checksum) & 1) == 0) {
+ checksum >>= 1;
+ } else {
+ checksum = (byte) ((checksum ^ 0x18) >> 1 | 0x80);
+ }
+ b >>= 1;
+ }
+ }
+ return checksum;
+ }
+
+ /**
+ * Builds a command given ID and parameters buffer.
+ *
+ * @param id the command ID
+ * @param params the parameters buffer
+ * @return the assembled command buffer
+ */
+ protected byte[] makeCommand(byte id, ByteBuffer params) {
+ if (params.position() > LefunConstants.CMD_MAX_LENGTH - LefunConstants.CMD_HEADER_LENGTH)
+ throw new IllegalArgumentException("params is too long to fit");
+
+ int paramsLength = params.position();
+ byte[] request = new byte[paramsLength + LefunConstants.CMD_HEADER_LENGTH];
+ request[0] = LefunConstants.CMD_REQUEST_ID;
+ request[1] = (byte) request.length;
+ request[2] = id;
+ params.flip();
+ params.get(request, LefunConstants.CMD_HEADER_LENGTH - 1, paramsLength);
+ request[request.length - 1] = calculateChecksum(request, 0, request.length - 1);
+ return request;
+ }
+
+ protected void throwUnexpectedLength() {
+ throw new IllegalArgumentException("Unexpected parameters length");
+ }
+
+ protected void validateId(byte id, byte expectedId) {
+ if (id != expectedId)
+ throw new IllegalArgumentException("Wrong command ID");
+ }
+
+ protected void validateIdAndLength(byte id, ByteBuffer params, byte expectedId, int expectedLength) {
+ validateId(id, expectedId);
+ if (params.limit() != expectedLength)
+ throwUnexpectedLength();
+ }
+
+ protected boolean getBit(int value, int mask) {
+ return (value & mask) != 0;
+ }
+
+ protected int setBit(int value, int mask, boolean set) {
+ if (set) {
+ return value | mask;
+ } else {
+ return value & ~mask;
+ }
+ }
+
+ protected short setBit(short value, int mask, boolean set) {
+ if (set) {
+ return (short)(value | mask);
+ } else {
+ return (short)(value & ~mask);
+ }
+ }
+
+ protected byte setBit(byte value, int mask, boolean set) {
+ if (set) {
+ return (byte)(value | mask);
+ } else {
+ return (byte)(value & ~mask);
+ }
+ }
+
+ /**
+ * Find index of first bit that is set
+ * @param value
+ * @return
+ */
+ protected int getLowestSetBitIndex(int value) {
+ if (value == 0) return -1;
+
+ int i = 0;
+ while ((value & 1) == 0) {
+ ++i;
+ value >>= 1;
+ }
+ return i;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd22Command.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd22Command.java
new file mode 100644
index 000000000..c40a92fc3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd22Command.java
@@ -0,0 +1,84 @@
+/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+ Gobbetti
+ Copyright (C) 2020 Yukai Li
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class Cmd22Command extends BaseCommand {
+ private byte op;
+ private short unknown;
+
+ private boolean setSuccess;
+
+ public byte getOp() {
+ return op;
+ }
+
+ public void setOp(byte op) {
+ if (op != OP_GET && op != OP_SET)
+ throw new IllegalArgumentException("Operation must be get or set");
+ this.op = op;
+ }
+
+ public short getUnknown() {
+ return unknown;
+ }
+
+ public void setUnknown(short unknown) {
+ this.unknown = unknown;
+ }
+
+ public boolean isSetSuccess() {
+ return setSuccess;
+ }
+
+ @Override
+ protected void deserializeParams(byte id, ByteBuffer params) {
+ validateId(id, LefunConstants.CMD_UNKNOWN_22);
+
+ if (params.limit() < 1)
+ throwUnexpectedLength();
+
+ op = params.get();
+ if (op == OP_GET) {
+ if (params.limit() != 3)
+ throwUnexpectedLength();
+
+ unknown = params.getShort();
+ } else if (op == OP_SET) {
+ if (params.limit() != 2)
+ throwUnexpectedLength();
+
+ setSuccess = params.get() == 1;
+ } else {
+ throw new IllegalArgumentException("Invalid operation type received");
+ }
+ }
+
+ @Override
+ protected byte serializeParams(ByteBuffer params) {
+ params.put(op);
+ if (op == OP_SET) {
+ params.putShort(unknown);
+ }
+ return LefunConstants.CMD_UNKNOWN_22;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd25Command.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd25Command.java
new file mode 100644
index 000000000..9bc49813d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd25Command.java
@@ -0,0 +1,86 @@
+/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+ Gobbetti
+ Copyright (C) 2020 Yukai Li
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class Cmd25Command extends BaseCommand {
+ private byte op;
+ private int unknown;
+
+ private boolean setSuccess;
+
+ public byte getOp() {
+ return op;
+ }
+
+ public void setOp(byte op) {
+ if (op != OP_GET && op != OP_SET)
+ throw new IllegalArgumentException("Operation must be get or set");
+ this.op = op;
+ }
+
+ public int getUnknown() {
+ return unknown;
+ }
+
+ public void setUnknown(int unknown) {
+ if (unknown < 0 || unknown > 65000)
+ throw new IllegalArgumentException("Value must be between 0 and 65000 inclusive");
+ this.unknown = unknown;
+ }
+
+ public boolean isSetSuccess() {
+ return setSuccess;
+ }
+
+ @Override
+ protected void deserializeParams(byte id, ByteBuffer params) {
+ validateId(id, LefunConstants.CMD_UNKNOWN_25);
+
+ if (params.limit() < 1)
+ throwUnexpectedLength();
+
+ op = params.get();
+ if (op == OP_GET) {
+ if (params.limit() != 5)
+ throwUnexpectedLength();
+
+ unknown = params.getInt();
+ } else if (op == OP_SET) {
+ if (params.limit() != 2)
+ throwUnexpectedLength();
+
+ setSuccess = params.get() == 1;
+ } else {
+ throw new IllegalArgumentException("Invalid operation type received");
+ }
+ }
+
+ @Override
+ protected byte serializeParams(ByteBuffer params) {
+ params.put(op);
+ if (op == OP_SET) {
+ params.putInt(unknown);
+ }
+ return LefunConstants.CMD_UNKNOWN_25;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FeaturesCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FeaturesCommand.java
new file mode 100644
index 000000000..b0006d4ed
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FeaturesCommand.java
@@ -0,0 +1,95 @@
+/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+ Gobbetti
+ Copyright (C) 2020 Yukai Li
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class FeaturesCommand extends BaseCommand {
+ public static final int FEATURE_RAISE_TO_WAKE = 0;
+ public static final int FEATURE_SEDENTARY_REMINDER = 1;
+ public static final int FEATURE_HYDRATION_REMINDER = 2;
+ public static final int FEATURE_REMOTE_CAMERA = 3;
+ public static final int FEATURE_UNKNOWN_4 = 4;
+ public static final int FEATURE_ANTI_LOST = 5;
+
+ private byte op;
+ private short features;
+
+ private boolean setSuccess;
+
+ public byte getOp() {
+ return op;
+ }
+
+ public void setOp(byte op) {
+ if (op != OP_GET && op != OP_SET)
+ throw new IllegalArgumentException("Operation must be get or set");
+ this.op = op;
+ }
+
+ public boolean getFeature(int index) {
+ if (index < 0 || index > 5)
+ throw new IllegalArgumentException("Index must be between 0 and 5 inclusive");
+ return getBit(features, 1 << index);
+ }
+
+ public void setFeature(int index, boolean enabled) {
+ if (index < 0 || index > 5)
+ throw new IllegalArgumentException("Index must be between 0 and 5 inclusive");
+ features = setBit(features, 1 << index, enabled);
+ }
+
+ public boolean isSetSuccess() {
+ return setSuccess;
+ }
+
+ @Override
+ protected void deserializeParams(byte id, ByteBuffer params) {
+ validateId(id, LefunConstants.CMD_FEATURES);
+
+ if (params.limit() < 1)
+ throwUnexpectedLength();
+
+ op = params.get();
+ if (op == OP_GET) {
+ if (params.limit() != 3)
+ throwUnexpectedLength();
+
+ features = params.getShort();
+ } else if (op == OP_SET) {
+ if (params.limit() != 2)
+ throwUnexpectedLength();
+
+ setSuccess = params.get() == 1;
+ } else {
+ throw new IllegalArgumentException("Invalid operation type received");
+ }
+ }
+
+ @Override
+ protected byte serializeParams(ByteBuffer params) {
+ params.put(op);
+ if (op == OP_SET) {
+ params.putShort(features);
+ }
+ return LefunConstants.CMD_FEATURES;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FindDeviceCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FindDeviceCommand.java
new file mode 100644
index 000000000..433bb9faa
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FindDeviceCommand.java
@@ -0,0 +1,43 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+ Gobbetti
+ Copyright (C) 2020 Yukai Li
+
+ 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 . */
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class FindDeviceCommand extends BaseCommand {
+ private boolean success;
+
+ public boolean isSuccess() {
+ return success;
+ }
+
+ @Override
+ protected void deserializeParams(byte id, ByteBuffer params) {
+ validateIdAndLength(id, params, LefunConstants.CMD_FIND_DEVICE, 1);
+
+ success = params.get() == 1;
+ }
+
+ @Override
+ protected byte serializeParams(ByteBuffer params) {
+ return LefunConstants.CMD_FIND_DEVICE;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FindPhoneCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FindPhoneCommand.java
new file mode 100644
index 000000000..4190929a6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FindPhoneCommand.java
@@ -0,0 +1,35 @@
+/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+ Gobbetti
+ Copyright (C) 2020 Yukai Li
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class FindPhoneCommand extends BaseCommand {
+ @Override
+ protected void deserializeParams(byte id, ByteBuffer params) {
+ validateIdAndLength(id, params, LefunConstants.CMD_FIND_PHONE, 0);
+ }
+
+ @Override
+ protected byte serializeParams(ByteBuffer params) {
+ return LefunConstants.CMD_FIND_PHONE;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetActivityDataCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetActivityDataCommand.java
new file mode 100644
index 000000000..92d2a8ac8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetActivityDataCommand.java
@@ -0,0 +1,110 @@
+/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+ Gobbetti
+ Copyright (C) 2020 Yukai Li
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class GetActivityDataCommand extends BaseCommand {
+ private byte daysAgo;
+ private byte totalRecords;
+ private byte currentRecord;
+ private byte year;
+ private byte month;
+ private byte day;
+ private byte hour;
+ private byte minute;
+ private short steps;
+ private short distance; // m
+ private short calories; // calories
+
+ public byte getDaysAgo() {
+ return daysAgo;
+ }
+
+ public void setDaysAgo(byte daysAgo) {
+ if (daysAgo < 0 || daysAgo > 6)
+ throw new IllegalArgumentException("Days ago must be between 0 and 6 inclusive");
+ this.daysAgo = daysAgo;
+ }
+
+ public byte getTotalRecords() {
+ return totalRecords;
+ }
+
+ public byte getCurrentRecord() {
+ return currentRecord;
+ }
+
+ public byte getYear() {
+ return year;
+ }
+
+ public byte getMonth() {
+ return month;
+ }
+
+ public byte getDay() {
+ return day;
+ }
+
+ public byte getHour() {
+ return hour;
+ }
+
+ public byte getMinute() {
+ return minute;
+ }
+
+ public short getSteps() {
+ return steps;
+ }
+
+ public short getDistance() {
+ return distance;
+ }
+
+ public short getCalories() {
+ return calories;
+ }
+
+ @Override
+ protected void deserializeParams(byte id, ByteBuffer params) {
+ validateIdAndLength(id, params, LefunConstants.CMD_ACTIVITY_DATA, 14);
+
+ daysAgo = params.get();
+ totalRecords = params.get();
+ currentRecord = params.get();
+ year = params.get();
+ month = params.get();
+ day = params.get();
+ hour = params.get();
+ minute = params.get();
+ steps = params.getShort();
+ distance = params.getShort();
+ calories = params.getShort();
+ }
+
+ @Override
+ protected byte serializeParams(ByteBuffer params) {
+ params.put(daysAgo);
+ return LefunConstants.CMD_ACTIVITY_DATA;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetBatteryLevelCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetBatteryLevelCommand.java
new file mode 100644
index 000000000..d0ff51d29
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetBatteryLevelCommand.java
@@ -0,0 +1,43 @@
+/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+ Gobbetti
+ Copyright (C) 2020 Yukai Li
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class GetBatteryLevelCommand extends BaseCommand {
+ private byte batteryLevel;
+
+ public byte getBatteryLevel() {
+ return batteryLevel;
+ }
+
+ @Override
+ protected void deserializeParams(byte id, ByteBuffer params) {
+ validateIdAndLength(id, params, LefunConstants.CMD_BATTERY_LEVEL, 1);
+
+ batteryLevel = params.get();
+ }
+
+ @Override
+ protected byte serializeParams(ByteBuffer params) {
+ return LefunConstants.CMD_BATTERY_LEVEL;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetFirmwareInfoCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetFirmwareInfoCommand.java
new file mode 100644
index 000000000..301efdff9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetFirmwareInfoCommand.java
@@ -0,0 +1,78 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+ Gobbetti
+ Copyright (C) 2020 Yukai Li
+
+ 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 . */
+import java.nio.charset.StandardCharsets;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class GetFirmwareInfoCommand extends BaseCommand {
+ private short supportCode;
+ private short devTypeReserveCode;
+ private String typeCode;
+ private short hardwareVersion;
+ private short softwareVersion;
+ private String vendorCode;
+
+ public short getSupportCode() {
+ return supportCode;
+ }
+
+ public short getDevTypeReserveCode() {
+ return devTypeReserveCode;
+ }
+
+ public String getTypeCode() {
+ return typeCode;
+ }
+
+ public short getHardwareVersion() {
+ return hardwareVersion;
+ }
+
+ public short getSoftwareVersion() {
+ return softwareVersion;
+ }
+
+ public String getVendorCode() {
+ return vendorCode;
+ }
+
+ @Override
+ protected void deserializeParams(byte id, ByteBuffer params) {
+ validateIdAndLength(id, params, LefunConstants.CMD_FIRMWARE_INFO, 16);
+
+ supportCode = (short)(params.get() | (params.get() << 8));
+ devTypeReserveCode = params.getShort();
+ byte[] typeCodeBytes = new byte[4];
+ params.get(typeCodeBytes);
+ typeCode = new String(typeCodeBytes, StandardCharsets.US_ASCII);
+ hardwareVersion = params.getShort();
+ softwareVersion = params.getShort();
+ byte[] vendorCodeBytes = new byte[4];
+ params.get(vendorCodeBytes);
+ vendorCode = new String(vendorCodeBytes, StandardCharsets.US_ASCII);
+ }
+
+ @Override
+ protected byte serializeParams(ByteBuffer params) {
+ return LefunConstants.CMD_FIRMWARE_INFO;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetPpgDataCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetPpgDataCommand.java
new file mode 100644
index 000000000..25a812944
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetPpgDataCommand.java
@@ -0,0 +1,118 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class GetPpgDataCommand extends BaseCommand {
+ private byte ppgType;
+ private short totalRecords;
+ private short currentRecord;
+ private byte year;
+ private byte month;
+ private byte day;
+ private byte hour;
+ private byte minute;
+ private byte second;
+ private byte[] ppgData;
+
+ public int getPpgType() {
+ return getLowestSetBitIndex(ppgType);
+ }
+
+ public void setPpgType(int type) {
+ if (type < 0 || type > 2)
+ throw new IllegalArgumentException("Invalid PPG type");
+ this.ppgType = (byte)(1 << type);
+ }
+
+ public short getTotalRecords() {
+ return totalRecords;
+ }
+
+ public short getCurrentRecord() {
+ return currentRecord;
+ }
+
+ public byte getYear() {
+ return year;
+ }
+
+ public byte getMonth() {
+ return month;
+ }
+
+ public byte getDay() {
+ return day;
+ }
+
+ public byte getHour() {
+ return hour;
+ }
+
+ public byte getMinute() {
+ return minute;
+ }
+
+ public byte getSecond() {
+ return second;
+ }
+
+ public byte[] getPpgData() {
+ return ppgData;
+ }
+
+ @Override
+ protected void deserializeParams(byte id, ByteBuffer params) {
+ validateId(id, LefunConstants.CMD_PPG_DATA);
+
+ if (params.limit() < 9)
+ throwUnexpectedLength();
+
+ ppgType = params.get();
+ totalRecords = params.get();
+ currentRecord = params.get();
+ year = params.get();
+ month = params.get();
+ day = params.get();
+ hour = params.get();
+ minute = params.get();
+ second = params.get();
+
+ int typeIndex = getPpgType();
+ int dataLength;
+ switch (typeIndex) {
+ case LefunConstants.PPG_TYPE_HEART_RATE:
+ case LefunConstants.PPG_TYPE_BLOOD_OXYGEN:
+ dataLength = 1;
+ break;
+ case LefunConstants.PPG_TYPE_BLOOD_PRESSURE:
+ dataLength = 2;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown PPG type");
+ }
+
+ if (params.limit() < dataLength + 9)
+ throwUnexpectedLength();
+
+ ppgData = new byte[dataLength];
+ params.get(ppgData);
+
+ // Extended count/index
+ if (params.limit() == dataLength + 11)
+ {
+ totalRecords |= params.get() << 8;
+ currentRecord |= params.get() << 8;
+ }
+ else if (params.limit() > dataLength + 11) {
+ throwUnexpectedLength();
+ }
+ }
+
+ @Override
+ protected byte serializeParams(ByteBuffer params) {
+ params.put(ppgType);
+ return LefunConstants.CMD_PPG_DATA;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetSleepDataCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetSleepDataCommand.java
new file mode 100644
index 000000000..c0ebc6381
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetSleepDataCommand.java
@@ -0,0 +1,102 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+ Gobbetti
+ Copyright (C) 2020 Yukai Li
+
+ 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 . */
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class GetSleepDataCommand extends BaseCommand {
+ public static final int SLEEP_TYPE_AWAKE = 1;
+ public static final int SLEEP_TYPE_LIGHT_SLEEP = 2;
+ public static final int SLEEP_TYPE_DEEP_SLEEP = 3;
+
+ private byte daysAgo;
+ private short totalRecords;
+ private short currentRecord;
+ private byte year;
+ private byte month;
+ private byte day;
+ private byte hour;
+ private byte minute;
+ private byte sleepType;
+
+ public byte getDaysAgo() {
+ return daysAgo;
+ }
+
+ public void setDaysAgo(byte daysAgo) {
+ if (daysAgo < 0 || daysAgo > 6)
+ throw new IllegalArgumentException("Days ago must be between 0 and 6 inclusive");
+ this.daysAgo = daysAgo;
+ }
+
+ public short getTotalRecords() {
+ return totalRecords;
+ }
+
+ public short getCurrentRecord() {
+ return currentRecord;
+ }
+
+ public byte getYear() {
+ return year;
+ }
+
+ public byte getMonth() {
+ return month;
+ }
+
+ public byte getDay() {
+ return day;
+ }
+
+ public byte getHour() {
+ return hour;
+ }
+
+ public byte getMinute() {
+ return minute;
+ }
+
+ public byte getSleepType() {
+ return sleepType;
+ }
+
+ @Override
+ protected void deserializeParams(byte id, ByteBuffer params) {
+ validateIdAndLength(id, params, LefunConstants.CMD_SLEEP_DATA, 11);
+
+ daysAgo = params.get();
+ totalRecords = params.getShort();
+ currentRecord = params.getShort();
+ year = params.get();
+ month = params.get();
+ day = params.get();
+ hour = params.get();
+ minute = params.get();
+ sleepType = params.get();
+ }
+
+ @Override
+ protected byte serializeParams(ByteBuffer params) {
+ params.put(daysAgo);
+ return LefunConstants.CMD_SLEEP_DATA;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetSleepTimeCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetSleepTimeCommand.java
new file mode 100644
index 000000000..d0f7d722e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetSleepTimeCommand.java
@@ -0,0 +1,75 @@
+/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+ Gobbetti
+ Copyright (C) 2020 Yukai Li
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class GetSleepTimeCommand extends BaseCommand {
+ private byte daysAgo;
+ private byte year;
+ private byte month;
+ private byte day;
+ private short minutes;
+
+ public byte getDaysAgo() {
+ return daysAgo;
+ }
+
+ public void setDaysAgo(byte daysAgo) {
+ if (daysAgo < 0 || daysAgo > 6)
+ throw new IllegalArgumentException("Days ago must be between 0 and 6 inclusive");
+ this.daysAgo = daysAgo;
+ }
+
+ public byte getYear() {
+ return year;
+ }
+
+ public byte getMonth() {
+ return month;
+ }
+
+ public byte getDay() {
+ return day;
+ }
+
+ public short getMinutes() {
+ return minutes;
+ }
+
+
+ @Override
+ protected void deserializeParams(byte id, ByteBuffer params) {
+ validateIdAndLength(id, params, LefunConstants.CMD_SLEEP_TIME_DATA, 6);
+
+ daysAgo = params.get();
+ year = params.get();
+ month = params.get();
+ day = params.get();
+ minutes = params.getShort();
+ }
+
+ @Override
+ protected byte serializeParams(ByteBuffer params) {
+ params.put(daysAgo);
+ return LefunConstants.CMD_SLEEP_TIME_DATA;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetStepsDataCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetStepsDataCommand.java
new file mode 100644
index 000000000..f365c94d6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetStepsDataCommand.java
@@ -0,0 +1,86 @@
+/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+ Gobbetti
+ Copyright (C) 2020 Yukai Li
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class GetStepsDataCommand extends BaseCommand {
+ private byte daysAgo;
+ private byte year;
+ private byte month;
+ private byte day;
+ private int steps;
+ private int distance; // m
+ private int calories; // calories
+
+ public byte getDaysAgo() {
+ return daysAgo;
+ }
+
+ public void setDaysAgo(byte daysAgo) {
+ if (daysAgo < 0 || daysAgo > 6)
+ throw new IllegalArgumentException("Days ago must be between 0 and 6 inclusive");
+ this.daysAgo = daysAgo;
+ }
+
+ public byte getYear() {
+ return year;
+ }
+
+ public byte getMonth() {
+ return month;
+ }
+
+ public byte getDay() {
+ return day;
+ }
+
+ public int getSteps() {
+ return steps;
+ }
+
+ public int getDistance() {
+ return distance;
+ }
+
+ public int getCalories() {
+ return calories;
+ }
+
+ @Override
+ protected void deserializeParams(byte id, ByteBuffer params) {
+ validateIdAndLength(id, params, LefunConstants.CMD_STEPS_DATA, 16);
+
+ daysAgo = params.get();
+ year = params.get();
+ month = params.get();
+ day = params.get();
+ steps = params.getInt();
+ distance = params.getInt();
+ calories = params.getInt();
+ }
+
+ @Override
+ protected byte serializeParams(ByteBuffer params) {
+ params.put(daysAgo);
+ return LefunConstants.CMD_STEPS_DATA;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/HydrationReminderIntervalCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/HydrationReminderIntervalCommand.java
new file mode 100644
index 000000000..e92fa8aee
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/HydrationReminderIntervalCommand.java
@@ -0,0 +1,86 @@
+/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+ Gobbetti
+ Copyright (C) 2020 Yukai Li
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class HydrationReminderIntervalCommand extends BaseCommand {
+ private byte op;
+ private byte hydrationReminderInterval;
+
+ private boolean setSuccess;
+
+ public byte getOp() {
+ return op;
+ }
+
+ public void setOp(byte op) {
+ if (op != OP_GET && op != OP_SET)
+ throw new IllegalArgumentException("Operation must be get or set");
+ this.op = op;
+ }
+
+ public byte getHydrationReminderInterval() {
+ return hydrationReminderInterval;
+ }
+
+ public void setHydrationReminderInterval(byte hydrationReminderInterval) {
+ if (hydrationReminderInterval == 0)
+ throw new IllegalArgumentException("Interval must be non-zero");
+ this.hydrationReminderInterval = hydrationReminderInterval;
+ }
+
+ public boolean isSetSuccess() {
+ return setSuccess;
+ }
+
+ @Override
+ protected void deserializeParams(byte id, ByteBuffer params) {
+ validateId(id, LefunConstants.CMD_HYDRATION_REMINDER_INTERVAL);
+
+ if (params.limit() < 1)
+ throwUnexpectedLength();
+
+ op = params.get();
+ if (op == OP_GET) {
+ if (params.limit() != 2)
+ throwUnexpectedLength();
+
+ hydrationReminderInterval = params.get();
+ } else if (op == OP_SET) {
+ if (params.limit() != 2)
+ throwUnexpectedLength();
+
+ setSuccess = params.get() == 1;
+ } else {
+ throw new IllegalArgumentException("Invalid operation type received");
+ }
+ }
+
+ @Override
+ protected byte serializeParams(ByteBuffer params) {
+ params.put(op);
+ if (op == OP_SET) {
+ params.put(hydrationReminderInterval);
+ }
+ return LefunConstants.CMD_HYDRATION_REMINDER_INTERVAL;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/NotificationCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/NotificationCommand.java
new file mode 100644
index 000000000..6335c8f40
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/NotificationCommand.java
@@ -0,0 +1,121 @@
+/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+ Gobbetti
+ Copyright (C) 2020 Yukai Li
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class NotificationCommand extends BaseCommand {
+ public static final byte SERVICE_TYPE_CALL = 0;
+ public static final byte SERVICE_TYPE_TEXT = 1;
+ public static final byte SERVICE_TYPE_QQ = 2;
+ public static final byte SERVICE_TYPE_WECHAT = 3;
+ public static final byte SERVICE_TYPE_EXTENDED = 4;
+
+ public static final byte EXTENDED_SERVICE_TYPE_FACEBOOK = 1;
+ public static final byte EXTENDED_SERVICE_TYPE_TWITTER = 2;
+ public static final byte EXTENDED_SERVICE_TYPE_LINKEDIN = 3;
+ public static final byte EXTENDED_SERVICE_TYPE_WHATSAPP = 4;
+ public static final byte EXTENDED_SERVICE_TYPE_LINE = 5;
+ public static final byte EXTENDED_SERVICE_TYPE_KAKAOTALK = 6;
+
+ private byte serviceType;
+ private byte totalPieces;
+ private byte currentPiece;
+ private byte extendedServiceType;
+ private byte[] payload;
+
+ public int getServiceType() {
+ return getLowestSetBitIndex(serviceType);
+ }
+
+ public void setServiceType(int type) {
+ if (type < 0 || type > 4)
+ throw new IllegalArgumentException("Invalid service type");
+ this.serviceType = (byte)(1 << type);
+ }
+
+ public byte getTotalPieces() {
+ return totalPieces;
+ }
+
+ public void setTotalPieces(byte totalPieces) {
+ // This check isn't on device, but should probably be added
+ if (totalPieces == 0)
+ throw new IllegalArgumentException("Total pieces must not be 0");
+ this.totalPieces = totalPieces;
+ }
+
+ public byte getCurrentPiece() {
+ return currentPiece;
+ }
+
+ public void setCurrentPiece(byte currentPiece) {
+ // This check isn't on device, but should probably be added
+ if (currentPiece == 0)
+ throw new IllegalArgumentException("Current piece must not be 0");
+ this.currentPiece = currentPiece;
+ }
+
+ public byte getExtendedServiceType() {
+ return extendedServiceType;
+ }
+
+ public void setExtendedServiceType(byte extendedServiceType) {
+ this.extendedServiceType = extendedServiceType;
+ }
+
+ public byte[] getPayload() {
+ return payload;
+ }
+
+ public void setPayload(byte[] payload) {
+ if (payload == null)
+ throw new IllegalArgumentException("Payload must not be null");
+ if (payload.length > 13)
+ throw new IllegalArgumentException("Payload is too long");
+ this.payload = payload;
+ }
+
+ @Override
+ protected void deserializeParams(byte id, ByteBuffer params) {
+ // We should not receive a response for this
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected byte serializeParams(ByteBuffer params) {
+ boolean hasExtendedServiceType = (serviceType & (1 << SERVICE_TYPE_EXTENDED)) != 0
+ && (extendedServiceType & 0x0f) != 0 ;
+ int maxPayloadLength = hasExtendedServiceType ? 12 : 13;
+
+ if (payload.length > maxPayloadLength)
+ throw new IllegalStateException("Payload is too long");
+
+ params.put(serviceType);
+ params.put(totalPieces);
+ params.put(currentPiece);
+ if (hasExtendedServiceType)
+ params.put(extendedServiceType);
+ params.put(payload);
+
+ return LefunConstants.CMD_NOTIFICATION;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/PpgResultCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/PpgResultCommand.java
new file mode 100644
index 000000000..0a55a54a8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/PpgResultCommand.java
@@ -0,0 +1,72 @@
+/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+ Gobbetti
+ Copyright (C) 2020 Yukai Li
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class PpgResultCommand extends BaseCommand {
+ private byte ppgType;
+ private byte[] ppgData;
+
+ public int getPpgType() {
+ return getLowestSetBitIndex(ppgType);
+ }
+
+ public byte[] getPpgData() {
+ return ppgData;
+ }
+
+ @Override
+ protected void deserializeParams(byte id, ByteBuffer params) {
+ validateId(id, LefunConstants.CMD_PPG_RESULT);
+
+ if (params.limit() < 1)
+ throwUnexpectedLength();
+
+ ppgType = params.get();
+
+ int typeIndex = getPpgType();
+ int dataLength;
+ switch (typeIndex) {
+ case LefunConstants.PPG_TYPE_HEART_RATE:
+ case LefunConstants.PPG_TYPE_BLOOD_OXYGEN:
+ dataLength = 1;
+ break;
+ case LefunConstants.PPG_TYPE_BLOOD_PRESSURE:
+ dataLength = 2;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown PPG type");
+ }
+
+ if (params.limit() != dataLength + 1)
+ throwUnexpectedLength();
+
+ ppgData = new byte[dataLength];
+ params.get(ppgData);
+ }
+
+ @Override
+ protected byte serializeParams(ByteBuffer params) {
+ // No handler on device side
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/ProfileCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/ProfileCommand.java
new file mode 100644
index 000000000..e4b806108
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/ProfileCommand.java
@@ -0,0 +1,130 @@
+/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+ Gobbetti
+ Copyright (C) 2020 Yukai Li
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class ProfileCommand extends BaseCommand {
+ public static final byte GENDER_FEMALE = 0;
+ public static final byte GENDER_MALE = 1;
+
+ private byte op;
+ private byte gender;
+ private byte height; // cm
+ private byte weight; // kg
+ private byte age; // years
+
+ private boolean setSuccess;
+
+ public byte getOp() {
+ return op;
+ }
+
+ public void setOp(byte op) {
+ if (op != OP_GET && op != OP_SET)
+ throw new IllegalArgumentException("Operation must be get or set");
+ this.op = op;
+ }
+
+ public byte getGender() {
+ return gender;
+ }
+
+ public void setGender(byte gender) {
+ if (gender != GENDER_FEMALE && gender != GENDER_MALE)
+ throw new IllegalArgumentException("Invalid gender");
+ this.gender = gender;
+ }
+
+ public byte getHeight() {
+ return height;
+ }
+
+ public void setHeight(byte height) {
+ int intHeight = (int)height & 0xff;
+ if (intHeight < 40 || intHeight > 210)
+ throw new IllegalArgumentException("Height must be between 40 and 210 cm inclusive");
+ this.height = height;
+ }
+
+ public byte getWeight() {
+ return weight;
+ }
+
+ public void setWeight(byte weight) {
+ int intWeight = (int)weight & 0xff;
+ if (intWeight < 5 || intWeight > 200)
+ throw new IllegalArgumentException("Weight must be between 5 and 200 kg inclusive");
+ this.weight = weight;
+ }
+
+ public byte getAge() {
+ return age;
+ }
+
+ public void setAge(byte age) {
+ if (age < 0 || age > 110)
+ throw new IllegalArgumentException("Age must be between 0 and 110 years inclusive");
+ this.age = age;
+ }
+
+ public boolean isSetSuccess() {
+ return setSuccess;
+ }
+
+ @Override
+ protected void deserializeParams(byte id, ByteBuffer params) {
+ validateId(id, LefunConstants.CMD_PROFILE);
+
+ if (params.limit() < 1)
+ throwUnexpectedLength();
+
+ op = params.get();
+ if (op == OP_GET) {
+ if (params.limit() != 5)
+ throwUnexpectedLength();
+
+ gender = params.get();
+ height = params.get();
+ weight = params.get();
+ age = params.get();
+ } else if (op == OP_SET) {
+ if (params.limit() != 2)
+ throwUnexpectedLength();
+
+ setSuccess = params.get() == 1;
+ } else {
+ throw new IllegalArgumentException("Invalid operation type received");
+ }
+ }
+
+ @Override
+ protected byte serializeParams(ByteBuffer params) {
+ params.put(op);
+ if (op == OP_SET) {
+ params.put(gender);
+ params.put(height);
+ params.put(weight);
+ params.put(age);
+ }
+ return LefunConstants.CMD_PROFILE;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/RemoteCameraTriggeredCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/RemoteCameraTriggeredCommand.java
new file mode 100644
index 000000000..f385e3fb1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/RemoteCameraTriggeredCommand.java
@@ -0,0 +1,36 @@
+/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+ Gobbetti
+ Copyright (C) 2020 Yukai Li
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class RemoteCameraTriggeredCommand extends BaseCommand {
+ @Override
+ protected void deserializeParams(byte id, ByteBuffer params) {
+ validateIdAndLength(id, params, LefunConstants.CMD_REMOTE_CAMERA_TRIGGERED, 0);
+ }
+
+ @Override
+ protected byte serializeParams(ByteBuffer params) {
+ // No handler on device side
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/RequestBondingCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/RequestBondingCommand.java
new file mode 100644
index 000000000..b4b925538
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/RequestBondingCommand.java
@@ -0,0 +1,46 @@
+/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+ Gobbetti
+ Copyright (C) 2020 Yukai Li
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class RequestBondingCommand extends BaseCommand {
+ public static final byte STATUS_ALREADY_BONDED = 0;
+ public static final byte STATUS_BONDING_SUCCESSFUL = 1;
+
+ private byte status;
+
+ public byte getStatus() {
+ return status;
+ }
+
+ @Override
+ protected void deserializeParams(byte id, ByteBuffer params) {
+ validateIdAndLength(id, params, LefunConstants.CMD_BONDING_REQUEST, 1);
+
+ status = params.get();
+ }
+
+ @Override
+ protected byte serializeParams(ByteBuffer params) {
+ return LefunConstants.CMD_BONDING_REQUEST;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SedentaryReminderIntervalCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SedentaryReminderIntervalCommand.java
new file mode 100644
index 000000000..fa116134d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SedentaryReminderIntervalCommand.java
@@ -0,0 +1,86 @@
+/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+ Gobbetti
+ Copyright (C) 2020 Yukai Li
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class SedentaryReminderIntervalCommand extends BaseCommand {
+ private byte op;
+ private byte sedentaryReminderInterval;
+
+ private boolean setSuccess;
+
+ public byte getOp() {
+ return op;
+ }
+
+ public void setOp(byte op) {
+ if (op != OP_GET && op != OP_SET)
+ throw new IllegalArgumentException("Operation must be get or set");
+ this.op = op;
+ }
+
+ public byte getSedentaryReminderInterval() {
+ return sedentaryReminderInterval;
+ }
+
+ public void setSedentaryReminderInterval(byte sedentaryReminderInterval) {
+ if (sedentaryReminderInterval == 0)
+ throw new IllegalArgumentException("Interval must be non-zero");
+ this.sedentaryReminderInterval = sedentaryReminderInterval;
+ }
+
+ public boolean isSetSuccess() {
+ return setSuccess;
+ }
+
+ @Override
+ protected void deserializeParams(byte id, ByteBuffer params) {
+ validateId(id, LefunConstants.CMD_SEDENTARY_REMINDER_INTERVAL);
+
+ if (params.limit() < 1)
+ throwUnexpectedLength();
+
+ op = params.get();
+ if (op == OP_GET) {
+ if (params.limit() != 2)
+ throwUnexpectedLength();
+
+ sedentaryReminderInterval = params.get();
+ } else if (op == OP_SET) {
+ if (params.limit() != 2)
+ throwUnexpectedLength();
+
+ setSuccess = params.get() == 1;
+ } else {
+ throw new IllegalArgumentException("Invalid operation type received");
+ }
+ }
+
+ @Override
+ protected byte serializeParams(ByteBuffer params) {
+ params.put(op);
+ if (op == OP_SET) {
+ params.put(sedentaryReminderInterval);
+ }
+ return LefunConstants.CMD_SEDENTARY_REMINDER_INTERVAL;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SetLanguageCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SetLanguageCommand.java
new file mode 100644
index 000000000..63455fac2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SetLanguageCommand.java
@@ -0,0 +1,54 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+ Gobbetti
+ Copyright (C) 2020 Yukai Li
+
+ 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 . */
+public class SetLanguageCommand extends BaseCommand {
+ private byte language;
+
+ private boolean setSuccess;
+
+ public byte getLanguage() {
+ return language;
+ }
+
+ public void setLanguage(byte language) {
+ this.language = language;
+ }
+
+ public boolean isSetSuccess() {
+ return setSuccess;
+ }
+
+ @Override
+ protected void deserializeParams(byte id, ByteBuffer params) {
+ validateIdAndLength(id, params, LefunConstants.CMD_LANGUAGE, 1);
+
+ setSuccess = params.get() == 1;
+ }
+
+ @Override
+ protected byte serializeParams(ByteBuffer params) {
+ params.put(language);
+ return LefunConstants.CMD_LANGUAGE;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SetRemoteCameraCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SetRemoteCameraCommand.java
new file mode 100644
index 000000000..c4dc1ae73
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SetRemoteCameraCommand.java
@@ -0,0 +1,54 @@
+/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+ Gobbetti
+ Copyright (C) 2020 Yukai Li
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class SetRemoteCameraCommand extends BaseCommand {
+ private boolean remoteCameraEnabled;
+
+ private boolean setSuccess;
+
+ public boolean getRemoteCameraEnabled() {
+ return remoteCameraEnabled;
+ }
+
+ public void setRemoteCameraEnabled(boolean remoteCameraEnabled) {
+ this.remoteCameraEnabled = remoteCameraEnabled;
+ }
+
+ public boolean isSetSuccess() {
+ return setSuccess;
+ }
+
+ @Override
+ protected void deserializeParams(byte id, ByteBuffer params) {
+ validateIdAndLength(id, params, LefunConstants.CMD_REMOTE_CAMERA, 1);
+
+ setSuccess = params.get() == 1;
+ }
+
+ @Override
+ protected byte serializeParams(ByteBuffer params) {
+ params.put((byte)(remoteCameraEnabled ? 1 : 0));
+ return LefunConstants.CMD_REMOTE_CAMERA;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SettingsCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SettingsCommand.java
new file mode 100644
index 000000000..1c76a8711
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SettingsCommand.java
@@ -0,0 +1,117 @@
+/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+ Gobbetti
+ Copyright (C) 2020 Yukai Li
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class SettingsCommand extends BaseCommand {
+ public static final byte AM_PM_24_HOUR = 0;
+ public static final byte AM_PM_12_HOUR = 1;
+ public static final byte MEASUREMENT_UNIT_METRIC = 0;
+ public static final byte MEASUREMENT_UNIT_IMPERIAL = 1;
+
+ private byte op;
+ private byte option1;
+ private byte amPmIndicator;
+ private byte measurementUnit;
+
+ private boolean setSuccess;
+
+ public byte getOp() {
+ return op;
+ }
+
+ public void setOp(byte op) {
+ if (op != OP_GET && op != OP_SET)
+ throw new IllegalArgumentException("Operation must be get or set");
+ this.op = op;
+ }
+
+ public byte getOption1() {
+ return option1;
+ }
+
+ public void setOption1(byte option1) {
+ if (option1 < 0 || option1 > 24)
+ throw new IllegalArgumentException("option1 must be between 0 and 24 inclusive");
+ this.option1 = option1;
+ }
+
+ public byte getAmPmIndicator() {
+ return amPmIndicator;
+ }
+
+ public void setAmPmIndicator(byte amPmIndicator) {
+ if (amPmIndicator != AM_PM_12_HOUR && amPmIndicator != AM_PM_24_HOUR)
+ throw new IllegalArgumentException("Indicator must be 12 or 24 hours");
+ this.amPmIndicator = amPmIndicator;
+ }
+
+ public byte getMeasurementUnit() {
+ return measurementUnit;
+ }
+
+ public void setMeasurementUnit(byte measurementUnit) {
+ if (measurementUnit != MEASUREMENT_UNIT_METRIC && measurementUnit != MEASUREMENT_UNIT_IMPERIAL)
+ throw new IllegalArgumentException(("Unit must be metric or imperial"));
+ this.measurementUnit = measurementUnit;
+ }
+
+ public boolean isSetSuccess() {
+ return setSuccess;
+ }
+
+ @Override
+ protected void deserializeParams(byte id, ByteBuffer params) {
+ validateId(id, LefunConstants.CMD_SETTINGS);
+
+ if (params.limit() < 1)
+ throwUnexpectedLength();
+
+ op = params.get();
+ if (op == OP_GET) {
+ if (params.limit() != 4)
+ throwUnexpectedLength();
+
+ option1 = params.get();
+ amPmIndicator = params.get();
+ measurementUnit = params.get();
+ } else if (op == OP_SET) {
+ if (params.limit() != 2)
+ throwUnexpectedLength();
+
+ setSuccess = params.get() == 1;
+ } else {
+ throw new IllegalArgumentException("Invalid operation type received");
+ }
+ }
+
+ @Override
+ protected byte serializeParams(ByteBuffer params) {
+ params.put(op);
+ if (op == OP_SET) {
+ params.put(option1);
+ params.put(amPmIndicator);
+ params.put(measurementUnit);
+ }
+ return LefunConstants.CMD_SETTINGS;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/StartPpgSensingCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/StartPpgSensingCommand.java
new file mode 100644
index 000000000..f03403251
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/StartPpgSensingCommand.java
@@ -0,0 +1,57 @@
+/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+ Gobbetti
+ Copyright (C) 2020 Yukai Li
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class StartPpgSensingCommand extends BaseCommand {
+ private byte ppgType;
+
+ private boolean setSuccess;
+
+ public int getPpgType() {
+ return getLowestSetBitIndex(ppgType);
+ }
+
+ public void setPpgType(int type) {
+ if (type < 0 || type > 2)
+ throw new IllegalArgumentException("Invalid PPG type");
+ this.ppgType = (byte)(1 << type);
+ }
+
+ public boolean isSetSuccess() {
+ return setSuccess;
+ }
+
+ @Override
+ protected void deserializeParams(byte id, ByteBuffer params) {
+ validateIdAndLength(id, params, LefunConstants.CMD_PPG_START, 2);
+
+ ppgType = params.get();
+ setSuccess = params.get() == 1;
+ }
+
+ @Override
+ protected byte serializeParams(ByteBuffer params) {
+ params.put(ppgType);
+ return LefunConstants.CMD_PPG_START;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/TimeCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/TimeCommand.java
new file mode 100644
index 000000000..f25a6c27f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/TimeCommand.java
@@ -0,0 +1,149 @@
+/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+ Gobbetti
+ Copyright (C) 2020 Yukai Li
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class TimeCommand extends BaseCommand {
+ private byte op;
+ private byte year;
+ private byte month;
+ private byte day;
+ private byte hour;
+ private byte minute;
+ private byte second;
+
+ private boolean setSuccess;
+
+ public byte getOp() {
+ if (op != OP_GET && op != OP_SET)
+ throw new IllegalArgumentException("Operation must be get or set");
+ return op;
+ }
+
+ public void setOp(byte op) {
+ this.op = op;
+ }
+
+ public byte getYear() {
+ return year;
+ }
+
+ public void setYear(byte year) {
+ this.year = year;
+ }
+
+ public byte getMonth() {
+ return month;
+ }
+
+ public void setMonth(byte month) {
+ if (month < 1 || month > 12)
+ throw new IllegalArgumentException("Month must be between 1 and 12 inclusive");
+ this.month = month;
+ }
+
+ public byte getDay() {
+ return day;
+ }
+
+ public void setDay(byte day) {
+ if (day < 1 || day > 31)
+ throw new IllegalArgumentException("Day must be between 1 and 31 inclusive");
+ this.day = day;
+ }
+
+ public byte getHour() {
+ return hour;
+ }
+
+ public void setHour(byte hour) {
+ if (hour < 0 || hour > 23)
+ throw new IllegalArgumentException("Hour must be between 0 and 23 inclusive");
+ this.hour = hour;
+ }
+
+ public byte getMinute() {
+ return minute;
+ }
+
+ public void setMinute(byte minute) {
+ if (minute < 0 || minute > 59)
+ throw new IllegalArgumentException("Minute must be between 0 and 59 inclusive");
+ this.minute = minute;
+ }
+
+ public byte getSecond() {
+ return second;
+ }
+
+ public void setSecond(byte second) {
+ if (second < 0 || second > 59)
+ throw new IllegalArgumentException("Second must be between 0 and 59 inclusive");
+ this.second = second;
+ }
+
+ public boolean isSetSuccess() {
+ return setSuccess;
+ }
+
+ @Override
+ protected void deserializeParams(byte id, ByteBuffer params) {
+ validateId(id, LefunConstants.CMD_TIME);
+
+ if (params.limit() < 1)
+ throwUnexpectedLength();
+
+ op = params.get();
+ if (op == OP_GET) {
+ if (params.limit() != 7)
+ throwUnexpectedLength();
+
+ year = params.get();
+ month = params.get();
+ day = params.get();
+ hour = params.get();
+ minute = params.get();
+ second = params.get();
+ } else if (op == OP_SET) {
+ if (params.limit() != 2)
+ throwUnexpectedLength();
+
+ setSuccess = params.get() == 1;
+ } else {
+ throw new IllegalArgumentException("Invalid operation type received");
+ }
+ }
+
+ @Override
+ protected byte serializeParams(ByteBuffer params) {
+ params.put(op);
+ if (op == OP_SET) {
+ params.put(year);
+ params.put(month);
+ params.put(day);
+ params.put(hour);
+ params.put(minute);
+ params.put(second);
+ }
+ return LefunConstants.CMD_TIME;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/UiPagesCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/UiPagesCommand.java
new file mode 100644
index 000000000..77deae3cc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/UiPagesCommand.java
@@ -0,0 +1,91 @@
+/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+ Gobbetti
+ Copyright (C) 2020 Yukai Li
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class UiPagesCommand extends BaseCommand {
+ // I don't know which pages since they're not implemented in my watch, so no
+ // constants here for now
+
+ private byte op;
+ private short pages;
+
+ private boolean setSuccess;
+
+ public byte getOp() {
+ return op;
+ }
+
+ public void setOp(byte op) {
+ if (op != OP_GET && op != OP_SET)
+ throw new IllegalArgumentException("Operation must be get or set");
+ this.op = op;
+ }
+
+ public boolean getPage(int index) {
+ if (index < 0 || index >= 16)
+ throw new IllegalArgumentException("Index must be between 0 and 15 inclusive");
+ return getBit(pages, 1 << index);
+ }
+
+ public void setPage(int index, boolean enabled) {
+ if (index < 0 || index >= 16)
+ throw new IllegalArgumentException("Index must be between 0 and 15 inclusive");
+ pages = setBit(pages, 1 << index, enabled);
+ }
+
+ public boolean isSetSuccess() {
+ return setSuccess;
+ }
+
+ @Override
+ protected void deserializeParams(byte id, ByteBuffer params) {
+ validateId(id, LefunConstants.CMD_UI_PAGES);
+
+ if (params.limit() < 1)
+ throwUnexpectedLength();
+
+ op = params.get();
+ if (op == OP_GET) {
+ if (params.limit() != 3)
+ throwUnexpectedLength();
+
+ pages = params.getShort();
+ } else if (op == OP_SET) {
+ if (params.limit() != 2)
+ throwUnexpectedLength();
+
+ setSuccess = params.get() == 1;
+ } else {
+ throw new IllegalArgumentException("Invalid operation type received");
+ }
+ }
+
+ @Override
+ protected byte serializeParams(ByteBuffer params) {
+ params.put(op);
+ if (op == OP_SET) {
+ params.putShort(pages);
+ }
+ return LefunConstants.CMD_UI_PAGES;
+ }
+}