1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-25 11:26:47 +01:00

Merge branch 'master' of https://codeberg.org/Freeyourgadget/Gadgetbridge into fossil-q-hr

This commit is contained in:
dakhnod 2019-12-30 21:46:41 +01:00
commit 6f012c2109
40 changed files with 388 additions and 104 deletions

View File

@ -1,6 +1,12 @@
### Changelog
#### Version 0.40.0 (WIP)
#### Version 0.40.1
* Mi Band/Amazfit: Recogize changes when toggling alarm on device (immediately when connected, else when connecting)
* Mi Band/Amazfit: Fix some bugs with stuck connection when re-connecting
* Mi Band 4: Support higher MTU for multiple times faster firmware transfer (probably also Amazfit GTR/GTS)
* Amazfit Cor: Fix setting language to Chinese manually
#### Version 0.40.0
* Fossil Q Hybrid: Initial support
* Bangle.js: Initial support
* Reserve Alarm for Calendar feature restricted to Mi Band 1/2 and moved to per-device settings

View File

@ -15,8 +15,6 @@
*/
package nodomain.freeyourgadget.gadgetbridge.daogen;
import java.util.Date;
import de.greenrobot.daogenerator.DaoGenerator;
import de.greenrobot.daogenerator.Entity;
import de.greenrobot.daogenerator.Index;
@ -45,7 +43,7 @@ public class GBDaoGenerator {
public static void main(String[] args) throws Exception {
Schema schema = new Schema(21, MAIN_PACKAGE + ".entities");
Schema schema = new Schema(22, MAIN_PACKAGE + ".entities");
Entity userAttributes = addUserAttributes(schema);
Entity user = addUserInfo(schema, userAttributes);
@ -378,6 +376,7 @@ public class GBDaoGenerator {
);
alarm.addIntProperty("hour").notNull();
alarm.addIntProperty("minute").notNull();
alarm.addBooleanProperty("unused").notNull();
alarm.addToOne(user, userId);
alarm.addToOne(device, deviceId);
}

View File

@ -80,6 +80,7 @@ Please see [FEATURES.md](https://codeberg.org/Freeyourgadget/Gadgetbridge/src/ma
* Johannes Schmitt (BFH-16)
* Lukas Schwichtenberg (Makibes HR3)
* Daniel Dakhno (Fossil Q Hybrid)
* Gordon Williams (Bangle.js)
## Contribute

View File

@ -25,8 +25,8 @@ android {
targetSdkVersion 27
// Note: always bump BOTH versionCode and versionName!
versionName "0.40.0"
versionCode 163
versionName "0.40.1"
versionCode 164
vectorDrawables.useSupportLibrary = true
}
buildTypes {

View File

@ -17,18 +17,23 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.MenuItem;
import androidx.annotation.NonNull;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.adapter.GBAlarmListAdapter;
@ -40,6 +45,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.User;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
@ -59,6 +65,10 @@ public class ConfigureAlarms extends AbstractGBActivity {
setContentView(R.layout.activity_configure_alarms);
IntentFilter filterLocal = new IntentFilter();
filterLocal.addAction(DeviceService.ACTION_SAVE_ALARMS);
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
gbDevice = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
mGBAlarmListAdapter = new GBAlarmListAdapter(this);
@ -136,7 +146,7 @@ public class ConfigureAlarms extends AbstractGBActivity {
}
private Alarm createDefaultAlarm(@NonNull Device device, @NonNull User user, int position) {
return new Alarm(device.getId(), user.getId(), position, false, false,0, 6, 30);
return new Alarm(device.getId(), user.getId(), position, false, false, 0, 6, 30, false);
}
@Override
@ -165,4 +175,25 @@ public class ConfigureAlarms extends AbstractGBActivity {
private void sendAlarmsToDevice() {
GBApplication.deviceService().onSetAlarms(mGBAlarmListAdapter.getAlarmList());
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
switch (action) {
case DeviceService.ACTION_SAVE_ALARMS: {
updateAlarmsFromDB();
break;
}
}
}
};
@Override
protected void onDestroy() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
super.onDestroy();
}
}

View File

@ -27,12 +27,13 @@ import android.widget.CompoundButton;
import android.widget.Switch;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
@ -71,7 +72,7 @@ public class GBAlarmListAdapter extends RecyclerView.Adapter<GBAlarmListAdapter.
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
public void onBindViewHolder(@NonNull final ViewHolder holder, final int position) {
final Alarm alarm = alarmList.get(position);
@ -82,7 +83,7 @@ public class GBAlarmListAdapter extends RecyclerView.Adapter<GBAlarmListAdapter.
holder.alarmDayFriday.setChecked(alarm.getRepetition(Alarm.ALARM_FRI));
holder.alarmDaySaturday.setChecked(alarm.getRepetition(Alarm.ALARM_SAT));
holder.alarmDaySunday.setChecked(alarm.getRepetition(Alarm.ALARM_SUN));
holder.container.setAlpha(alarm.getUnused() ? 0.5f : 1.0f);
holder.isEnabled.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
@ -97,6 +98,16 @@ public class GBAlarmListAdapter extends RecyclerView.Adapter<GBAlarmListAdapter.
((ConfigureAlarms) mContext).configureAlarm(alarm);
}
});
holder.container.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
alarm.setUnused(!alarm.getUnused());
holder.container.setAlpha(alarm.getUnused() ? 0.5f : 1.0f);
updateInDB(alarm);
return true;
}
});
holder.alarmTime.setText(DateTimeUtils.formatTime(alarm.getHour(), alarm.getMinute()));
holder.isEnabled.setChecked(alarm.getEnabled());
if (alarm.getSmartWakeup()) {

View File

@ -0,0 +1,39 @@
/* Copyright (C) 2019 Andreas Shimokawa
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.database.schema;
import android.database.sqlite.SQLiteDatabase;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
import nodomain.freeyourgadget.gadgetbridge.entities.AlarmDao;
import nodomain.freeyourgadget.gadgetbridge.entities.No1F1ActivitySampleDao;
public class GadgetbridgeUpdate_22 implements DBUpdateScript {
@Override
public void upgradeSchema(SQLiteDatabase db) {
if (!DBHelper.existsColumn(AlarmDao.TABLENAME, AlarmDao.Properties.Unused.columnName, db)) {
String ADD_COLUMN_UNUSED = "ALTER TABLE " + AlarmDao.TABLENAME + " ADD COLUMN "
+ AlarmDao.Properties.Unused.columnName + " INTEGER NOT NULL DEFAULT 0;";
db.execSQL(ADD_COLUMN_UNUSED);
}
}
@Override
public void downgradeSchema(SQLiteDatabase db) {
}
}

View File

@ -1,3 +1,19 @@
/* Copyright (C) 2019 Gordon Williams
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.banglejs;
import java.util.UUID;

View File

@ -1,3 +1,20 @@
/* Copyright (C) 2016-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Gordon Williams, José Rebelo
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.banglejs;
import android.annotation.TargetApi;

View File

@ -59,6 +59,7 @@ public class HuamiService {
public static final int ALERT_LEVEL_PHONE_CALL = 2;
public static final int ALERT_LEVEL_VIBRATE_ONLY = 3;
// set metric distance
// set 12 hour time mode
@ -158,6 +159,9 @@ public class HuamiService {
public static final byte[] COMMAND_ENABLE_DISCONNECT_NOTIFCATION = new byte[]{ENDPOINT_DISPLAY, 0x0c, 0x00, 0x01, 0, 0, 0, 0};
public static final byte[] COMMAND_DISABLE_DISCONNECT_NOTIFCATION = new byte[]{ENDPOINT_DISPLAY, 0x0c, 0x00, 0x00, 0, 0, 0, 0};
public static final byte[] COMMAND_REQUEST_ALARMS = new byte[]{0x0d};
public static final byte[] COMMAND_REQUEST_GPS_VERSION = new byte[]{0x0e};
// The third byte controls the threshold, in minutes
// The last 8 bytes represent 2 separate time intervals for the inactivity warnings
// If there is no do not disturb interval, the last 4 bytes (the second interval) are 0

View File

@ -26,9 +26,6 @@ import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.EN
public class AmazfitBipService {
public static final UUID UUID_CHARACTERISTIC_WEATHER = UUID.fromString("0000000e-0000-3512-2118-0009af100700");
// goes to UUID_CHARACTERISTIC_3_CONFIGURATION, TODO: validate this for Mi Band 2, it maybe triggers more than only GPS version...
public static final byte[] COMMAND_REQUEST_GPS_VERSION = new byte[]{0x0e};
public static final byte COMMAND_ACTIVITY_DATA_TYPE_DEBUGLOGS = 0x07;
public static final byte[] COMMAND_SET_LANGUAGE_SIMPLIFIED_CHINESE = new byte[]{ENDPOINT_DISPLAY, 0x13, 0x00, 0x00};

View File

@ -37,6 +37,8 @@ public interface Alarm extends Serializable {
boolean getEnabled();
boolean getUnused();
boolean getSmartWakeup();
int getRepetition();

View File

@ -53,6 +53,7 @@ public interface DeviceService extends EventHandler {
String ACTION_FIND_DEVICE = PREFIX + ".action.find_device";
String ACTION_SET_CONSTANT_VIBRATION = PREFIX + ".action.set_constant_vibration";
String ACTION_SET_ALARMS = PREFIX + ".action.set_alarms";
String ACTION_SAVE_ALARMS = PREFIX + ".action.save_alarms";
String ACTION_ENABLE_REALTIME_STEPS = PREFIX + ".action.enable_realtime_steps";
String ACTION_REALTIME_SAMPLES = PREFIX + ".action.realtime_samples";
String ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT = PREFIX + ".action.realtime_hr_measurement";

View File

@ -1,8 +1,8 @@
/* Copyright (C) 2015-2019 Andreas Böhler, Andreas Shimokawa, Carsten
Pfeiffer, Cre3per, Daniel Dakhno, Daniele Gobbetti, Jean-François Greffier,
João Paulo Barraca, José Rebelo, Kranz, ladbsoft, Manuel Ruß, maxirnilian,
protomors, Quallenauge, Sami Alaoui, Sebastian Kranz, Sophanimus, tiparega,
Vadim Kaushan
Pfeiffer, Cre3per, Daniel Dakhno, Daniele Gobbetti, Gordon Williams,
Jean-François Greffier, João Paulo Barraca, José Rebelo, Kranz, ladbsoft,
Manuel Ruß, maxirnilian, protomors, Quallenauge, Sami Alaoui, Sebastian
Kranz, Sophanimus, tiparega, Vadim Kaushan
This file is part of Gadgetbridge.

View File

@ -287,7 +287,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
return START_NOT_STICKY;
}
if (mDeviceSupport == null || (!isInitialized() && !mDeviceSupport.useAutoConnect())) {
if (mDeviceSupport == null || (!isInitialized() && !action.equals(ACTION_DISCONNECT) && (!mDeviceSupport.useAutoConnect() || isConnected()))) {
// trying to send notification without valid Bluetooth connection
if (mGBDevice != null) {
// at least send back the current device state
@ -814,6 +814,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
}
if (mAutoConnectInvervalReceiver != null) {
unregisterReceiver(mAutoConnectInvervalReceiver);
mAutoConnectInvervalReceiver.destroy();
mAutoConnectInvervalReceiver = null;
}
}

View File

@ -1,8 +1,8 @@
/* Copyright (C) 2015-2019 0nse, Andreas Böhler, Andreas Shimokawa, Carsten
Pfeiffer, Cre3per, criogenic, Daniel Dakhno, Daniele Gobbetti, Jean-François
Greffier, João Paulo Barraca, José Rebelo, Kranz, ladbsoft, Manuel Ruß,
maxirnilian, protomors, Quallenauge, Sami Alaoui, Sergey Trofimov, Sophanimus,
tiparega, Vadim Kaushan
Pfeiffer, Cre3per, criogenic, Daniel Dakhno, Daniele Gobbetti, Gordon Williams,
Jean-François Greffier, João Paulo Barraca, José Rebelo, Kranz, ladbsoft,
Manuel Ruß, maxirnilian, protomors, Quallenauge, Sami Alaoui, Sergey Trofimov,
Sophanimus, tiparega, Vadim Kaushan
This file is part of Gadgetbridge.

View File

@ -265,9 +265,6 @@ public final class BtLEQueue {
mGbDevice.setState(newState);
mGbDevice.sendDeviceUpdateIntent(mContext);
if (mConnectionLatch != null && newState == State.CONNECTED) {
mConnectionLatch.countDown();
}
}
public void disconnect() {
@ -516,6 +513,9 @@ public final class BtLEQueue {
// only propagate the successful event
getCallbackToUse().onServicesDiscovered(gatt);
}
if (mConnectionLatch != null) {
mConnectionLatch.countDown();
}
} else {
LOG.warn("onServicesDiscovered received: " + status);
}

View File

@ -1,3 +1,19 @@
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniel Dakhno
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
/*
* Copyright (C) 2013 The Android Open Source Project
*

View File

@ -1,3 +1,19 @@
/* Copyright (C) 2019 Andreas Shimokawa, Gordon Williams
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.banglejs;
import android.bluetooth.BluetoothGatt;

View File

@ -30,5 +30,6 @@ public class HuamiDeviceEvent {
public static final byte BUTTON_PRESSED_LONG = 0x0b;
public static final byte TICK_30MIN = 0x0e; // unsure
public static final byte FIND_PHONE_STOP = 0x0f;
public static final byte MTU_REQUEST = 0x16;
public static final byte MUSIC_CONTROL = (byte) 0xfe;
}

View File

@ -143,6 +143,7 @@ import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ge
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.getNotificationPrefStringValue;
import static nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic.UUID_CHARACTERISTIC_ALERT_LEVEL;
public class HuamiSupport extends AbstractBTLEDeviceSupport {
// We introduce key press counter for notification purposes
@ -164,7 +165,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
};
private BluetoothGattCharacteristic characteristicHRControlPoint;
protected BluetoothGattCharacteristic characteristicChunked;
private BluetoothGattCharacteristic characteristicChunked;
private boolean needsAuth;
private volatile boolean telephoneRinging;
@ -181,6 +182,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
private MusicSpec bufferMusicSpec = null;
private MusicStateSpec bufferMusicStateSpec = null;
private boolean heartRateNotifyEnabled;
private int mMTU = 23;
public HuamiSupport() {
this(LOG);
@ -293,15 +295,16 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
builder.notify(getCharacteristic(GattService.UUID_SERVICE_CURRENT_TIME), enable);
// Notify CHARACTERISTIC9 to receive random auth code
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_AUTH), enable);
return this;
}
public HuamiSupport enableFurtherNotifications(TransactionBuilder builder, boolean enable) {
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), enable);
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_6_BATTERY_INFO), enable);
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_DEVICEEVENT), enable);
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_AUDIO), enable);
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_AUDIODATA), enable);
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_DEVICEEVENT), enable);
return this;
}
@ -1150,6 +1153,9 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
break;
case HuamiDeviceEvent.ALARM_TOGGLED:
LOG.info("An alarm was toggled");
TransactionBuilder builder = new TransactionBuilder("requestAlarms");
requestAlarms(builder);
builder.queue(getQueue());
break;
case HuamiDeviceEvent.FELL_ASLEEP:
LOG.info("Fell asleep");
@ -1212,11 +1218,34 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
}
evaluateGBDeviceEvent(deviceEventMusicControl);
break;
case HuamiDeviceEvent.MTU_REQUEST:
int mtu = (value[2] & 0xff) << 8 | value[1] & 0xff;
LOG.info("device announced MTU of " + mtu);
mMTU = mtu;
/*
* not really sure if this would make sense, is this event already a proof of a successful MTU
* negotiation initiated by the Huami device, and acknowledged by the phone? do we really have to
* requestMTU() from our side after receiving this?
* /
if (mMTU != mtu) {
requestMTU(mtu);
}
*/
break;
default:
LOG.warn("unhandled event " + value[0]);
}
}
private void requestMTU(int mtu) {
if (GBApplication.isRunningLollipopOrLater()) {
new TransactionBuilder("requestMtu")
.requestMtu(mtu)
.queue(getQueue());
mMTU = mtu;
}
}
private void acknowledgeFindPhone() {
try {
TransactionBuilder builder = performInitialized("acknowledge find phone");
@ -1300,6 +1329,9 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
} else if (HuamiService.UUID_CHARACTERISTIC_7_REALTIME_STEPS.equals(characteristicUUID)) {
handleRealtimeSteps(characteristic.getValue());
return true;
} else if (HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION.equals(characteristicUUID)) {
handleConfigurationInfo(characteristic.getValue());
return true;
} else {
LOG.info("Unhandled characteristic changed: " + characteristicUUID);
logMessageContent(characteristic.getValue());
@ -1394,6 +1426,53 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
}
}
private void handleConfigurationInfo(byte[] value) {
if (value == null || value.length < 4) {
return;
}
if (value[0] == 0x10 && value[2] == 0x01) {
if (value[1] == 0x0e) {
String gpsVersion = new String(value, 3, value.length - 3);
LOG.info("got gps version = " + gpsVersion);
gbDevice.setFirmwareVersion2(gpsVersion);
} else if (value[1] == 0x0d) {
LOG.info("got alarms from watch");
decodeAndUpdateAlarmStatus(value);
} else {
LOG.warn("got configuration info we do not handle yet " + GB.hexdump(value, 3, -1));
}
} else {
LOG.warn("error received from configuration request " + GB.hexdump(value, 0, -1));
}
}
private void decodeAndUpdateAlarmStatus(byte[] response) {
List<nodomain.freeyourgadget.gadgetbridge.entities.Alarm> alarms = DBHelper.getAlarms(gbDevice);
boolean[] alarmsInUse = new boolean[10];
boolean[] alarmsEnabled = new boolean[10];
int nr_alarms = response[8];
for (int i = 0; i < nr_alarms; i++) {
byte alarm_data = response[9 + i];
int index = alarm_data & 0xf;
alarmsInUse[index] = true;
boolean enabled = (alarm_data & 0x10) == 0x10;
alarmsEnabled[index] = enabled;
LOG.info("alarm " + index + " is enabled:" + enabled);
}
for (nodomain.freeyourgadget.gadgetbridge.entities.Alarm alarm : alarms) {
boolean enabled = alarmsEnabled[alarm.getPosition()];
boolean unused = !alarmsInUse[alarm.getPosition()];
if (alarm.getEnabled() != enabled || alarm.getUnused() != unused) {
LOG.info("updating alarm index " + alarm.getPosition() + " unused=" + unused + ", enabled=" + enabled);
alarm.setEnabled(enabled);
alarm.setUnused(unused);
DBHelper.store(alarm);
Intent intent = new Intent(DeviceService.ACTION_SAVE_ALARMS);
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
}
}
}
private void enableRealtimeSamplesTimer(boolean enable) {
if (enable) {
getRealtimeSamplesSupport().start();
@ -1487,13 +1566,17 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
}
int base = 0;
if (alarm.getEnabled()) {
int daysMask = 0;
if (alarm.getEnabled() && !alarm.getUnused()) {
base = 128;
}
int daysMask = alarm.getRepetition();
if (!alarm.isRepetitive()) {
daysMask = 128;
if (!alarm.getUnused()) {
daysMask = alarm.getRepetition();
if (!alarm.isRepetitive()) {
daysMask = 128;
}
}
byte[] alarmMessage = new byte[] {
(byte) 0x2, // TODO what is this?
(byte) (base + alarm.getPosition()), // 128 is the base, alarm slot is added
@ -2170,8 +2253,8 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
return this;
}
protected void writeToChunked(TransactionBuilder builder, int type, byte[] data) {
final int MAX_CHUNKLENGTH = 17;
private void writeToChunked(TransactionBuilder builder, int type, byte[] data) {
final int MAX_CHUNKLENGTH = mMTU - 6;
int remaining = data.length;
byte count = 0;
while (remaining > 0) {
@ -2198,6 +2281,19 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
}
}
protected HuamiSupport requestGPSVersion(TransactionBuilder builder) {
LOG.info("Requesting GPS version");
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), HuamiService.COMMAND_REQUEST_GPS_VERSION);
return this;
}
private HuamiSupport requestAlarms(TransactionBuilder builder) {
LOG.info("Requesting alarms");
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), HuamiService.COMMAND_REQUEST_ALARMS);
return this;
}
@Override
public String customStringFilter(String inputString) {
if (HuamiCoordinator.getUseCustomFont(gbDevice.getAddress())) {
@ -2258,6 +2354,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
setDisconnectNotification(builder);
setExposeHRThridParty(builder);
setHeartrateMeasurementInterval(builder, getHeartRateMeasurementInterval());
requestAlarms(builder);
}
private int getHeartRateMeasurementInterval() {
@ -2271,4 +2368,8 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
public UpdateFirmwareOperation createUpdateFirmwareOperation(Uri uri) {
return new UpdateFirmwareOperation(uri, this);
}
public int getMTU() {
return mMTU;
}
}

View File

@ -159,9 +159,11 @@ public class AmazfitBipFirmwareInfo extends HuamiFirmwareInfo {
// BipOS FW
crcToVersion.put(28373, "1.1.2.05 (BipOS 0.5)");
crcToVersion.put(62977, "1.1.2.05 (BipOS 0.5.1)");
// BipOS RES
crcToVersion.put(16303, "1.1.2.05 (BipOS 0.5)");
crcToVersion.put(61135, "1.1.2.05 (BipOS 0.5.1)");
}
public AmazfitBipFirmwareInfo(byte[] bytes) {

View File

@ -17,11 +17,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import org.slf4j.Logger;
@ -29,7 +25,6 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Set;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
@ -37,21 +32,14 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipFWHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipService;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertCategory;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertNotificationProfile;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.NewAlert;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiIcon;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchActivityOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchSportsSummaryOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.HuamiFetchDebugLogsOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.NotificationStrategy;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
public class AmazfitBipSupport extends HuamiSupport {
@ -162,39 +150,6 @@ public class AmazfitBipSupport extends HuamiSupport {
}
}
@Override
public boolean onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
boolean handled = super.onCharacteristicChanged(gatt, characteristic);
if (!handled) {
UUID characteristicUUID = characteristic.getUuid();
if (HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION.equals(characteristicUUID)) {
return handleConfigurationInfo(characteristic.getValue());
}
}
return false;
}
private boolean handleConfigurationInfo(byte[] value) {
if (value == null || value.length < 4) {
return false;
}
if (value[0] == 0x10 && value[1] == 0x0e && value[2] == 0x01) {
String gpsVersion = new String(value, 3, value.length - 3);
LOG.info("got gps version = " + gpsVersion);
gbDevice.setFirmwareVersion2(gpsVersion);
return true;
}
return false;
}
// this probably does more than only getting the GPS version...
private AmazfitBipSupport requestGPSVersion(TransactionBuilder builder) {
LOG.info("Requesting GPS version");
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), AmazfitBipService.COMMAND_REQUEST_GPS_VERSION);
return this;
}
@Override
public void phase2Initialize(TransactionBuilder builder) {
super.phase2Initialize(builder);

View File

@ -44,6 +44,7 @@ public class AmazfitCorFirmwareInfo extends HuamiFirmwareInfo {
crcToVersion.put(51575, "1.0.7.88");
crcToVersion.put(6346, "1.2.5.00");
crcToVersion.put(24277, "1.2.7.20");
crcToVersion.put(10078, "1.2.7.32");
// resources
crcToVersion.put(46341, "RES 1.0.5.60");
@ -53,6 +54,7 @@ public class AmazfitCorFirmwareInfo extends HuamiFirmwareInfo {
crcToVersion.put(31263, "RES 1.0.7.77-91");
crcToVersion.put(20920, "RES 1.2.5.00-69");
crcToVersion.put(25397, "RES 1.2.7.20");
crcToVersion.put(54167, "RES 1.2.7.32");
// font
crcToVersion.put(61054, "8");

View File

@ -61,6 +61,7 @@ public class MiBand3FirmwareInfo extends HuamiFirmwareInfo {
crcToVersion.put(40949, "2.3.0.28");
crcToVersion.put(59213, "2.4.0.12");
crcToVersion.put(10810, "2.4.0.20");
crcToVersion.put(18271, "2.4.0.32");
// firmware (Mi Band 3 NFC)
crcToVersion.put(46724, "1.7.0.4");
@ -74,7 +75,7 @@ public class MiBand3FirmwareInfo extends HuamiFirmwareInfo {
crcToVersion.put(1815, "2.0.0.4");
crcToVersion.put(7225, "2.2.0.12-2.3.0.6");
crcToVersion.put(52754, "2.3.0.28");
crcToVersion.put(17930, "2.4.0.12-20");
crcToVersion.put(17930, "2.4.0.12-32");
// font
crcToVersion.put(19775, "1");

View File

@ -41,12 +41,14 @@ public class MiBand4FirmwareInfo extends HuamiFirmwareInfo {
crcToVersion.put(43437, "1.0.5.66");
crcToVersion.put(31632, "1.0.6.00");
crcToVersion.put(6856, "1.0.7.14");
crcToVersion.put(50145, "1.0.7.60");
// resources
crcToVersion.put(27412, "1.0.5.22");
crcToVersion.put(5466, "1.0.5.66");
crcToVersion.put(20047, "1.0.6.00");
crcToVersion.put(62914, "1.0.7.14");
crcToVersion.put(17303, "1.0.7.60");
// font
crcToVersion.put(31978, "1");

View File

@ -139,8 +139,8 @@ public class InitOperation extends AbstractBTLEOperation<HuamiSupport> {
value[2] == HuamiService.AUTH_SUCCESS) {
TransactionBuilder builder = createTransactionBuilder("Authenticated, now initialize phase 2");
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
huamiSupport.requestDeviceInfo(builder);
huamiSupport.enableFurtherNotifications(builder, true);
huamiSupport.requestDeviceInfo(builder);
huamiSupport.phase2Initialize(builder);
huamiSupport.phase3Initialize(builder);
huamiSupport.setInitialized(builder);

View File

@ -232,7 +232,7 @@ public class UpdateFirmwareOperation extends AbstractHuamiOperation {
private boolean sendFirmwareData(HuamiFirmwareInfo info) {
byte[] fwbytes = info.getBytes();
int len = fwbytes.length;
final int packetLength = 20;
final int packetLength = getSupport().getMTU() - 3;
int packets = len / packetLength;
try {

View File

@ -103,4 +103,8 @@ public class AutoConnectIntervalReceiver extends BroadcastReceiver {
}
}
public void destroy() {
LocalBroadcastManager.getInstance(service).unregisterReceiver(this);
}
}

View File

@ -48,7 +48,7 @@ public class AlarmUtils {
* @return
*/
public static nodomain.freeyourgadget.gadgetbridge.model.Alarm createSingleShot(int index, boolean smartWakeup, Calendar calendar) {
return new Alarm(-1, -1, index, true, smartWakeup, Alarm.ALARM_ONCE, calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE));
return new Alarm(-1, -1, index, true, smartWakeup, Alarm.ALARM_ONCE, calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), false);
}
/**
@ -127,7 +127,7 @@ public class AlarmUtils {
int hour = Integer.parseInt(tokens[4]);
int minute = Integer.parseInt(tokens[5]);
return new Alarm(device.getId(), user.getId(), index, enabled, smartWakeup, repetition, hour, minute);
return new Alarm(device.getId(), user.getId(), index, enabled, smartWakeup, repetition, hour, minute, false);
}
private static Comparator<Alarm> createComparator() {

View File

@ -1,7 +1,8 @@
/* Copyright (C) 2015-2019 0nse, Andreas Böhler, Andreas Shimokawa, Carsten
Pfeiffer, Cre3per, Daniel Dakhno, Daniele Gobbetti, Jean-François Greffier,
João Paulo Barraca, José Rebelo, Kranz, ladbsoft, Manuel Ruß, maxirnilian,
protomors, Quallenauge, Sami Alaoui, Sophanimus, tiparega, Vadim Kaushan
/* Copyright (C) 2015-2019 0nse, Andreas Böhler, Andreas Shimokawa,
Carsten Pfeiffer, Cre3per, Daniel Dakhno, Daniele Gobbetti, Gordon Williams,
Jean-François Greffier, João Paulo Barraca, José Rebelo, Kranz, ladbsoft,
Manuel Ruß, maxirnilian, protomors, Quallenauge, Sami Alaoui, Sophanimus,
tiparega, Vadim Kaushan
This file is part of Gadgetbridge.

View File

@ -307,7 +307,7 @@
<string name="activity_prefs_weight_kg">Váha v kg</string>
<string name="authenticating">Ověřování</string>
<string name="authentication_required">Ověřování vyžadováno</string>
<string name="appwidget_text">Spí</string>
<string name="appwidget_text">Spí</string>
<string name="add_widget">Přidat widget</string>
<string name="activity_prefs_sleep_duration">Preferovaná doba spánku v hodinách</string>
<string name="appwidget_setting_alarm">Budík nastaven na %1$02d:%2$02d</string>
@ -356,7 +356,7 @@
<string name="unit_metric">Metrické</string>
<string name="unit_imperial">Imperiální</string>
<string name="timeformat_24h">24h</string>
<string name="timeformat_am_pm">dop./odp.</string>
<string name="timeformat_am_pm">Dop./Odp.</string>
<string name="pref_screen_notification_profile_alarm_clock">Budík</string>
<string name="StringUtils_sender">(%1$s)</string>
<string name="find_device_you_found_it">Nalezeno!</string>
@ -576,7 +576,7 @@
<string name="ok">Ok</string>
<string name="mi3_night_mode_sunset">Při západu slunce</string>
<string name="controlcenter_start_activity_tracks">Záznamy aktivit</string>
<string name="activity_type_treadmill">h na pásu</string>
<string name="activity_type_treadmill">žecký pás</string>
<string name="devicetype_miband3">Mi Band 3</string>
<string name="devicetype_q8">Q8</string>
<string name="devicetype_mykronoz_zetime">MyKronoz ZeTime</string>
@ -725,7 +725,7 @@
<string name="pref_title_use_custom_font">Využít vlastní font</string>
<string name="pref_summary_use_custom_font">Vaše zařízení má vlastní font s podporou emoji</string>
<string name="activity_db_management_autoexport_explanation">Cesta autoexportu databáze je nastaveno na:</string>
<string name="activity_db_management_autoexport_label">AutoExport</string>
<string name="activity_db_management_autoexport_label">Automatický export</string>
<string name="activity_DB_ExportButton">Exportovat Databázi</string>
<string name="activity_DB_import_button">Importovat Databázi</string>
<string name="activity_DB_test_export_button">Spustit AutoExport Teď</string>
@ -792,4 +792,22 @@
\n
\nPOKRAČUJTE NA VLASTNÍ NEBEZPEČÍ!</string>
<string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
<string name="devicetype_qhybrid">Fossil Q Hybrid</string>
<string name="preferences_qhybrid_settings">Q hybrid nastavení</string>
<string name="watch_not_connected">Hodinky nejsou připojeny</string>
<string name="qhybrid_vibration_strength">síla vibrace:</string>
<string name="qhybrid_goal_in_steps">Cíl v krocích</string>
<string name="qhybrid_time_shift">časový posun</string>
<string name="qhybrid_second_timezone_offset_relative_to_utc">posun dalšího časového pásma vzhledem k UTC</string>
<string name="qhybrid_overwrite_buttons">přepsat tlačítka</string>
<string name="qhybrid_use_activity_hand_as_notification_counter">použít čítač aktivity jako čítač oznámení</string>
<string name="qhybrid_prompt_million_steps">Chcete-li aktivovat počítadlo, nastavte počet kroků na milion.</string>
<string name="qhybrid_buttons_overwrite_success">Tlačítka přepsána</string>
<string name="qhybrid_buttons_overwrite_error">Chyba při přepisování tlačítek</string>
<string name="qhybrid_offset_timezone">posun časového pásma podle</string>
<string name="qhybrid_changes_delay_prompt">změna může trvat několik sekund…</string>
<string name="qhybrid_offset_time_by">posun času o</string>
<string name="pref_disable_new_ble_scanning">Zakázat nové BLE skenování</string>
<string name="pref_summary_disable_new_ble_scanning">Tuto možnost zaškrtněte pokud Vaše zařízení nelze najít během vyhledávání</string>
<string name="devicetype_banglejs">Bangle.js</string>
</resources>

View File

@ -44,7 +44,7 @@
<string name="title_activity_calblacklist">Kalender auf Blacklist</string>
<!--Strings related to FwAppInstaller-->
<string name="title_activity_fw_app_insaller">FW/App-Installer</string>
<string name="fw_upgrade_notice">Du installierst jetzt %s.</string>
<string name="fw_upgrade_notice">Du bist dabei, die %s zu installieren.</string>
<string name="fw_multi_upgrade_notice">Du bist dabei, die %1$s und %2$s Firmware zu installieren, anstatt die, die sich derzeit auf deinem Mi Band befinden.</string>
<string name="miband_firmware_known">Diese Firmware wurde getestet und ist bekanntlich mit Gadgetbridge kompatibel.</string>
<string name="miband_firmware_unknown_warning">Diese Firmware ist nicht getestet und ist möglicherweise nicht mit Gadgetbridge kompatibel.

View File

@ -795,4 +795,9 @@
<string name="watch_not_connected">Klokke ikke tilkoblet</string>
<string name="qhybrid_vibration_strength">vibrasjonsstyrke:</string>
<string name="devicetype_banglejs">Bangle.js</string>
<string name="devicetype_qhybrid">Fossil Q Hybrid</string>
<string name="preferences_qhybrid_settings">Q Hybrid-innstillinger</string>
<string name="qhybrid_overwrite_buttons">overskriv taster</string>
<string name="qhybrid_buttons_overwrite_success">Knapper overskrevet</string>
<string name="qhybrid_buttons_overwrite_error">Kunne ikke overskrive knapper</string>
</resources>

View File

@ -346,7 +346,7 @@
<string name="activity_type_running">Bieganie</string>
<string name="activity_type_swimming">Pływanie</string>
<string name="activity_type_treadmill">Bieżnia</string>
<string name="controlcenter_connect">Połącz</string>
<string name="controlcenter_connect">Połącz</string>
<string name="controlcenter_navigation_drawer_open">Otwórz okno nawigacji</string>
<string name="controlcenter_navigation_drawer_close">Zamknij okno nawigacji</string>
<string name="controlcenter_calibrate_device">Kalibracja urządzenia</string>

View File

@ -26,4 +26,19 @@
<string name="appmananger_app_delete_cache">刪除並清除緩存</string>
<string name="appmananger_app_reinstall">重新安裝</string>
<string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
<string name="app_name">Gadgetbridge</string>
<string name="title_activity_controlcenter">Gadgetbridge</string>
<string name="controlcenter_snackbar_connecting">連接中…</string>
<string name="controlcenter_snackbar_requested_screenshot">截取設備的屏幕截圖</string>
<string name="controlcenter_calibrate_device">校正設備</string>
<string name="title_activity_appmanager">應用管理器</string>
<string name="appmanager_cached_watchapps_watchfaces">緩存的應用程式</string>
<string name="appmanager_installed_watchfaces">已安裝的錶盤</string>
<string name="appmanager_app_openinstore">在Pebble應用商店中搜索</string>
<string name="appmanager_health_activate">啟動</string>
<string name="appmanager_health_deactivate">停用</string>
<string name="appmanager_hrm_activate">激活HRM</string>
<string name="appmanager_hrm_deactivate">停用HRM</string>
<string name="appmanager_weather_activate">啟動系統天氣應用</string>
<string name="appmanager_weather_deactivate">停用系统天气应用</string>
</resources>

View File

@ -390,7 +390,7 @@
<string-array name="pref_amazfitbip_language">
<item name="auto">@string/automatic</item>
<item name="zh_CH">@string/simplified_chinese</item>
<item name="zh_CN">@string/simplified_chinese</item>
<item name="zh_TW">@string/traditional_chinese</item>
<item name="en_US">@string/english</item>
<item name="es_ES">@string/spanish</item>
@ -404,7 +404,7 @@
<string-array name="pref_amazfitbip_language_values">
<item>auto</item>
<item>zh_CH</item>
<item>zh_CN</item>
<item>zh_TW</item>
<item>en_US</item>
<item>es_ES</item>
@ -418,7 +418,7 @@
<string-array name="pref_amazfitcor_language">
<item name="auto">@string/automatic</item>
<item name="zh_CH">@string/simplified_chinese</item>
<item name="zh_CN">@string/simplified_chinese</item>
<item name="zh_TW">@string/traditional_chinese</item>
<item name="en_US">@string/english</item>
<item name="es_ES">@string/spanish</item>
@ -427,7 +427,7 @@
<string-array name="pref_amazfitcor_language_values">
<item>auto</item>
<item>zh_CH</item>
<item>zh_CN</item>
<item>zh_TW</item>
<item>en_US</item>
<item>es_ES</item>
@ -436,7 +436,7 @@
<string-array name="pref_miband3_language">
<item name="auto">@string/automatic</item>
<item name="zh_CH">@string/simplified_chinese</item>
<item name="zh_CN">@string/simplified_chinese</item>
<item name="zh_TW">@string/traditional_chinese</item>
<item name="en_US">@string/english</item>
<item name="es_ES">@string/spanish</item>
@ -459,7 +459,7 @@
<string-array name="pref_miband3_language_values">
<item>auto</item>
<item>zh_CH</item>
<item>zh_CN</item>
<item>zh_TW</item>
<item>en_US</item>
<item>es_ES</item>

View File

@ -1,5 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<changelog>
<release version="0.40.1" versioncode="164">
<change>Mi Band/Amazfit: Recogize changes when toggling alarm on device (immediately when connected, else when connecting)</change>
<change>Mi Band/Amazfit: Fix some bugs with stuck connection when re-connecting</change>
<change>Mi Band 4: Support higher MTU for multiple times faster firmware transfer (probably also Amazfit GTR/GTS)</change>
<change>Amazfit Cor: Fix setting language to Chinese manually</change>
</release>
<release version="0.40.0" versioncode="163">
<change>Fossil Q Hybrid: Initial support</change>
<change>Bangle.js: Initial support</change>
<change>Reserve Alarm for Calendar feature restricted to Mi Band 1/2 and moved to per-device settings</change>
<change>New icon for App Manager</change>
</release>
<release version="0.39.1" versioncode="162">
<change>Try to actively re-connect when a connection gets interrupted (interval grows up to 64 seconds)</change>
<change>Mi Band2/Amazfip Bip: Make button action settings per-device and enable for Amazfit Bip</change>

View File

@ -0,0 +1,4 @@
* Fossil Q Hybrid: Initial support
* Bangle.js: Initial support
* Reserve Alarm for Calendar feature restricted to Mi Band 1/2 and moved to per-device settings
* New icon for App Manager

View File

@ -0,0 +1,4 @@
* Mi Band/Amazfit: Recogize changes when toggling alarm on device (immediately when connected, else when connecting)
* Mi Band/Amazfit: Fix some bugs with stuck connection when re-connecting
* Mi Band 4: Support higher MTU for multiple times faster firmware transfer (probably also Amazfit GTR/GTS)
* Amazfit Cor: Fix setting language to Chinese manually