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