packet = DaFitPacketIn.parsePacket(packetIn.getPacket());
+ packetIn = new DaFitPacketIn();
+ if (packet != null) {
+ byte packetType = packet.first;
+ byte[] payload = packet.second;
+
+ Log.i("AAAAAAAAAAAAAAAA", "Response for: " + packetType);
+
+ if (handlePacket(packetType, payload))
+ return true;
+ }
+ }
+ }
+
+ return super.onCharacteristicChanged(gatt, characteristic);
+ }
+
+ private boolean handlePacket(byte packetType, byte[] payload)
+ {
+ LOG.warn("Got packet " + packetType + ": " + Logging.formatBytes(payload));
+ return false;
+ }
+
+ private void handleDeviceInfo(DeviceInfo info) {
+ LOG.warn("Device info: " + info);
+ versionCmd.hwVersion = info.getHardwareRevision();
+ versionCmd.fwVersion = info.getSoftwareRevision();
+ handleGBDeviceEvent(versionCmd);
+ }
+
+ private void handleBatteryInfo(BatteryInfo info) {
+ LOG.warn("Battery info: " + info);
+ batteryCmd.level = (short) info.getPercentCharged();
+ handleGBDeviceEvent(batteryCmd);
+ }
+
+ @Override
+ public boolean useAutoConnect() {
+ return true;
+ }
+
+ @Override
+ public void onNotification(NotificationSpec notificationSpec) {
+ // TODO
+ }
+
+ @Override
+ public void onDeleteNotification(int id) {
+ // not supported :(
+ }
+
+ @Override
+ public void onSetCallState(CallSpec callSpec) {
+ // TODO
+ }
+
+ @Override
+ public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
+ // not supported :(
+ }
+
+ @Override
+ public void onSetTime() {
+ // TODO
+ }
+
+ @Override
+ public void onSetAlarms(ArrayList extends Alarm> alarms) {
+ // TODO: set alarms
+ }
+
+ @Override
+ public void onSetMusicState(MusicStateSpec stateSpec) {
+ // not supported :(
+ }
+
+ @Override
+ public void onSetMusicInfo(MusicSpec musicSpec) {
+ // not supported :(
+ }
+
+ @Override
+ public void onInstallApp(Uri uri) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void onAppInfoReq() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void onAppStart(UUID uuid, boolean start) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void onAppDelete(UUID uuid) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void onAppConfiguration(UUID appUuid, String config, Integer id) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void onAppReorder(UUID[] uuids) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void onFetchRecordedData(int dataTypes) {
+ // TODO
+ }
+
+ @Override
+ public void onReset(int flags) {
+ // TODO: this shuts down the watch, rather than rebooting it - perhaps add a new operation type?
+ // (reboot is not supported, btw)
+
+ try {
+ TransactionBuilder builder = performInitialized("shutdown");
+ sendPacket(builder, DaFitPacketOut.buildPacket(DaFitConstants.CMD_SHUTDOWN, new byte[] { -1 }));
+ builder.queue(getQueue());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void onHeartRateTest() {
+ // TODO
+ }
+
+ // TODO: starting other tests
+
+ @Override
+ public void onEnableRealtimeSteps(boolean enable) {
+ // TODO
+ }
+
+ @Override
+ public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
+ // TODO
+ }
+
+ @Override
+ public void onSetHeartRateMeasurementInterval(int seconds) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void onEnableHeartRateSleepSupport(boolean enable) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void onFindDevice(boolean start) {
+ if (start)
+ {
+ try {
+ TransactionBuilder builder = performInitialized("onFindDevice");
+ sendPacket(builder, DaFitPacketOut.buildPacket(DaFitConstants.CMD_FIND_MY_WATCH, new byte[0]));
+ builder.queue(getQueue());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ else
+ {
+ // Not supported - the device vibrates three times and then stops automatically
+ }
+ }
+
+ @Override
+ public void onSetConstantVibration(int integer) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void onScreenshotReq() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void onDeleteCalendarEvent(byte type, long id) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void onSendConfiguration(String config) {
+ // TODO
+ }
+
+ @Override
+ public void onReadConfiguration(String config) {
+ // TODO
+ }
+
+ @Override
+ public void onTestNewFunction() {
+ }
+
+ @Override
+ public void onSendWeather(WeatherSpec weatherSpec) {
+ // TODO
+ }
+
+ @Override
+ public void onSetFmFrequency(float frequency) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void onSetLedColor(int color) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/dafit/DaFitPacket.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/dafit/DaFitPacket.java
new file mode 100644
index 000000000..3550968b2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/dafit/DaFitPacket.java
@@ -0,0 +1,22 @@
+/* Copyright (C) 2019 krzys_h
+
+ 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.service.devices.dafit;
+
+public class DaFitPacket {
+ protected byte[] packet;
+ protected int position = 0;
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/dafit/DaFitPacketIn.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/dafit/DaFitPacketIn.java
new file mode 100644
index 000000000..9c60e6d1e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/dafit/DaFitPacketIn.java
@@ -0,0 +1,136 @@
+/* Copyright (C) 2019 krzys_h
+
+ 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.service.devices.dafit;
+
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.Logging;
+
+/**
+ * A class for handling fragmentation of incoming packets
+ *
+ * Usage:
+ *
+ * {@code
+ * if(packetIn.putFragment(fragment)) {
+ * Pair packet = DaFitPacketIn.parsePacket(packetIn.getPacket());
+ * packetIn = new DaFitPacketIn();
+ * if (packet != null) {
+ * byte packetType = packet.first;
+ * byte[] payload = packet.second;
+ * // ...
+ * }
+ * }
+ *
+ */
+public class DaFitPacketIn extends DaFitPacket {
+ private static final Logger LOG = LoggerFactory.getLogger(DaFitPacketIn.class);
+
+ public DaFitPacketIn()
+ {
+
+ }
+
+ /**
+ * Store the incoming fragment and try to reconstruct packet
+ *
+ * @param fragment The incoming fragment
+ * @return true if the packet is complete
+ */
+ public boolean putFragment(byte[] fragment)
+ {
+ if (packet == null)
+ {
+ int len = parsePacketLength(fragment);
+ if (len < 0)
+ return false; // corrupted packet
+ packet = new byte[len];
+ }
+
+ int toCopy = Math.min(fragment.length, packet.length - position);
+ if (fragment.length > toCopy)
+ {
+ LOG.warn("Got fragment with more data than expected!");
+ }
+
+ System.arraycopy(fragment, 0, packet, position, toCopy);
+ position += fragment.length;
+ return position >= packet.length;
+ }
+
+ public byte[] getPacket()
+ {
+ if (packet == null || position < packet.length)
+ throw new IllegalStateException("Packet is not complete yet");
+ return packet;
+ }
+
+ /**
+ * Parse the packet header and return the length
+ * @param packetOrFragment The entire packet or it's first fragment
+ * @return The packet length, or -1 if packet is corrupted
+ */
+ private static int parsePacketLength(@NonNull byte[] packetOrFragment)
+ {
+ if (packetOrFragment[0] != (byte)0xFE || packetOrFragment[1] != (byte)0xEA)
+ {
+ LOG.warn("Invalid packet header, ignoring! Fragment: " + Logging.formatBytes(packetOrFragment));
+ return -1;
+ }
+
+ int len_h = 0;
+ if (packetOrFragment[2] != 16)
+ {
+ if ((packetOrFragment[2] & 0xFF) < 32)
+ {
+ LOG.warn("Corrupted packet, unable to parse length");
+ return -1;
+ }
+ len_h = (packetOrFragment[2] & 0xFF) - 32;
+ }
+ int len_l = (packetOrFragment[3] & 0xFF);
+
+ return (len_h << 8) | len_l;
+ }
+
+ /**
+ * Parse the packet
+ * @param packet The complete packet
+ * @return A pair containing the packet type and payload
+ */
+ public static Pair parsePacket(@NonNull byte[] packet)
+ {
+ int len = parsePacketLength(packet);
+ if (len < 0)
+ return null;
+ if (len != packet.length)
+ {
+ LOG.warn("Invalid packet length!");
+ return null;
+ }
+ byte packetType = packet[4];
+ byte[] payload = new byte[packet.length - 5];
+ System.arraycopy(packet, 5, payload, 0, payload.length);
+ return Pair.create(packetType, payload);
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/dafit/DaFitPacketOut.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/dafit/DaFitPacketOut.java
new file mode 100644
index 000000000..a1c9b8bb2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/dafit/DaFitPacketOut.java
@@ -0,0 +1,81 @@
+/* Copyright (C) 2019 krzys_h
+
+ 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.service.devices.dafit;
+
+import androidx.annotation.NonNull;
+
+/**
+ * A class for handling fragmentation of outgoing packets
+ *
+ * Usage:
+ *
+ * {@code
+ * DaFitPacketOut packetOut = new DaFitPacketOut(DaFitPacketOut.buildPacket(type, payload));
+ * byte[] fragment = new byte[MTU];
+ * while(packetOut.getFragment(fragment))
+ * send(fragment);
+ * }
+ *
+ */
+public class DaFitPacketOut extends DaFitPacket {
+ public DaFitPacketOut(byte[] packet)
+ {
+ this.packet = packet;
+ }
+
+ /**
+ * Get the next fragment of this packet to be sent
+ *
+ * @param fragmentBuffer The buffer to store the output in, of desired size (i.e. == MTU)
+ * @return true if there is more data to be sent, false otherwise
+ */
+ public boolean getFragment(byte[] fragmentBuffer)
+ {
+ if (position >= packet.length)
+ return false;
+ int remainingToTransfer = Math.min(fragmentBuffer.length, packet.length - position);
+ System.arraycopy(packet, position, fragmentBuffer, 0, remainingToTransfer);
+ position += remainingToTransfer;
+ return true;
+ }
+
+ /**
+ * Encode the packet
+ * @param packetType The packet type
+ * @param payload The packet payload
+ * @return The encoded packet
+ */
+ public static byte[] buildPacket(byte packetType, @NonNull byte[] payload)
+ {
+ byte[] packet = new byte[payload.length + 5];
+ packet[0] = (byte)0xFE;
+ packet[1] = (byte)0xEA;
+ if (DaFitDeviceSupport.MTU == 20)
+ {
+ packet[2] = 16;
+ packet[3] = (byte)(packet.length & 0xFF);
+ }
+ else
+ {
+ packet[2] = (byte)(32 + (packet.length >> 8) & 0xFF);
+ packet[3] = (byte)(packet.length & 0xFF);
+ }
+ packet[4] = packetType;
+ System.arraycopy(payload, 0, packet, 5, payload.length);
+ return packet;
+ }
+}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 5156b8e6e..daf5a1720 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1829,6 +1829,7 @@
Mijia Temperature and Humidity Sensor 2
Mijia Temperature and Humidity Sensor 2 (E-ink)
Mijia MHO-C303
+ Da Fit
Makibes HR3
Bangle.js
TLW64