From c7053747cd6d157a620c6980282e4429d7f1bb72 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Tue, 19 Nov 2019 14:40:20 +0100 Subject: [PATCH 1/3] Add active reconnection Closes #1724 Closes #1632 Closes #1452 Closes #1271 Closes #564 (Probably more) --- .../service/DeviceCommunicationService.java | 13 ++- .../gadgetbridge/service/btle/BtLEQueue.java | 6 +- .../btle/actions/SetDeviceStateAction.java | 3 + .../AutoConnectIntervalReceiver.java | 101 ++++++++++++++++++ 4 files changed, 117 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/AutoConnectIntervalReceiver.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java index 34dd7f66b..1fd696893 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java @@ -44,14 +44,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; -import java.util.Random; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; -import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator; import nodomain.freeyourgadget.gadgetbridge.externalevents.AlarmClockReceiver; import nodomain.freeyourgadget.gadgetbridge.externalevents.AlarmReceiver; import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothConnectReceiver; @@ -75,13 +73,13 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; +import nodomain.freeyourgadget.gadgetbridge.service.receivers.AutoConnectIntervalReceiver; import nodomain.freeyourgadget.gadgetbridge.service.receivers.GBAutoFetchReceiver; import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; import nodomain.freeyourgadget.gadgetbridge.util.EmojiConverter; import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; -import nodomain.freeyourgadget.gadgetbridge.util.StringUtils; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ADD_CALENDAREVENT; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_APP_CONFIGURE; @@ -193,6 +191,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere private BluetoothPairingRequestReceiver mBlueToothPairingRequestReceiver = null; private AlarmClockReceiver mAlarmClockReceiver = null; private GBAutoFetchReceiver mGBAutoFetchReceiver = null; + private AutoConnectIntervalReceiver mAutoConnectInvervalReceiver= null; private AlarmReceiver mAlarmReceiver = null; private CalendarReceiver mCalendarReceiver = null; @@ -760,6 +759,10 @@ public class DeviceCommunicationService extends Service implements SharedPrefere mGBAutoFetchReceiver = new GBAutoFetchReceiver(); registerReceiver(mGBAutoFetchReceiver, new IntentFilter("android.intent.action.USER_PRESENT")); } + if (mAutoConnectInvervalReceiver == null) { + mAutoConnectInvervalReceiver= new AutoConnectIntervalReceiver(this); + registerReceiver(mAutoConnectInvervalReceiver, new IntentFilter("GB_RECONNECT")); + } } else { if (mPhoneCallReceiver != null) { unregisterReceiver(mPhoneCallReceiver); @@ -809,6 +812,10 @@ public class DeviceCommunicationService extends Service implements SharedPrefere unregisterReceiver(mGBAutoFetchReceiver); mGBAutoFetchReceiver = null; } + if (mAutoConnectInvervalReceiver != null) { + unregisterReceiver(mAutoConnectInvervalReceiver); + mAutoConnectInvervalReceiver = null; + } } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java index 92b56bb33..a79d8a907 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java @@ -49,6 +49,7 @@ import nodomain.freeyourgadget.gadgetbridge.Logging; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State; import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.receivers.AutoConnectIntervalReceiver; /** * One queue/thread per connectable device. @@ -301,8 +302,6 @@ public final class BtLEQueue { mWaitForServerActionResultLatch.countDown(); } - boolean wasInitialized = mGbDevice.isInitialized(); - setDeviceConnectionState(State.NOT_CONNECTED); // either we've been disconnected because the device is out of range @@ -312,7 +311,7 @@ public final class BtLEQueue { // reconnecting automatically, so we try to fix this by re-creating mBluetoothGatt. // Not sure if this actually works without re-initializing the device... if (mBluetoothGatt != null) { - if (!wasInitialized || !maybeReconnect()) { + if (!maybeReconnect()) { disconnect(); // ensure that we start over cleanly next time } } @@ -329,6 +328,7 @@ public final class BtLEQueue { boolean result = mBluetoothGatt.connect(); if (result) { setDeviceConnectionState(State.WAITING_FOR_RECONNECT); + AutoConnectIntervalReceiver.scheduleReconnect(); } return result; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/SetDeviceStateAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/SetDeviceStateAction.java index bac898926..334e7432f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/SetDeviceStateAction.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/SetDeviceStateAction.java @@ -19,6 +19,8 @@ package nodomain.freeyourgadget.gadgetbridge.service.btle.actions; import android.bluetooth.BluetoothGatt; import android.content.Context; +import androidx.annotation.NonNull; + import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; public class SetDeviceStateAction extends PlainAction { @@ -43,6 +45,7 @@ public class SetDeviceStateAction extends PlainAction { return context; } + @NonNull @Override public String toString() { return super.toString() + " to " + deviceState; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/AutoConnectIntervalReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/AutoConnectIntervalReceiver.java new file mode 100644 index 000000000..9de1e78e2 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/AutoConnectIntervalReceiver.java @@ -0,0 +1,101 @@ +/* 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 . */ +package nodomain.freeyourgadget.gadgetbridge.service.receivers; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Build; + +import androidx.localbroadcastmanager.content.LocalBroadcastManager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Calendar; + +import nodomain.freeyourgadget.gadgetbridge.BuildConfig; +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; + +public class AutoConnectIntervalReceiver extends BroadcastReceiver { + + final DeviceCommunicationService service; + static int mDelay = 4; + private static final Logger LOG = LoggerFactory.getLogger(AutoConnectIntervalReceiver.class); + + public AutoConnectIntervalReceiver(DeviceCommunicationService service) { + this.service = service; + IntentFilter filterLocal = new IntentFilter(); + filterLocal.addAction(DeviceManager.ACTION_DEVICES_CHANGED); + LocalBroadcastManager.getInstance(service).registerReceiver(this, filterLocal); + } + + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action == null) { + return; + } + GBDevice gbDevice = service.getGBDevice(); + + if (action.equals(DeviceManager.ACTION_DEVICES_CHANGED)) { + if (gbDevice.isInitialized()) { + LOG.info("will reset connection delay, device is initialized!"); + mDelay = 4; + } + } + else if (action.equals("GB_RECONNECT")) { + if (gbDevice != null) { + if (gbDevice.getState() == GBDevice.State.WAITING_FOR_RECONNECT) { + LOG.info("Will re-connect to " + gbDevice.getAddress() + "(" + gbDevice.getName() + ")"); + GBApplication.deviceService().connect(); + } + } + } + } + + public static void scheduleReconnect() { + mDelay*=2; + if (mDelay > 128) { + mDelay = 128; + } + scheduleReconnect(mDelay); + } + + public static void scheduleReconnect(int delay) { + LOG.info("schduling reconnect in " + delay + " seconds"); + AlarmManager am = (AlarmManager) (GBApplication.getContext().getSystemService(Context.ALARM_SERVICE)); + Intent intent = new Intent("GB_RECONNECT"); + intent.setPackage(BuildConfig.APPLICATION_ID); + PendingIntent pendingIntent = PendingIntent.getBroadcast(GBApplication.getContext(), 0, intent, 0); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + am.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, Calendar.getInstance(). + getTimeInMillis() + delay * 1000, pendingIntent); + } else { + am.set(AlarmManager.RTC_WAKEUP, Calendar.getInstance(). + getTimeInMillis() + delay * 1000, pendingIntent); + } + } + +} From c2db30274f582172655715f307bf7f6efa9398b7 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Wed, 20 Nov 2019 10:53:25 +0100 Subject: [PATCH 2/3] Make reconnect logic more device agnostic and remove pebble reconnect logic --- .../gadgetbridge/service/btle/BtLEQueue.java | 1 - .../devices/pebble/PebbleIoThread.java | 39 +++++-------------- .../AutoConnectIntervalReceiver.java | 25 +++++++----- 3 files changed, 24 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java index a79d8a907..03d0d7488 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java @@ -328,7 +328,6 @@ public final class BtLEQueue { boolean result = mBluetoothGatt.connect(); if (result) { setDeviceConnectionState(State.WAITING_FOR_RECONNECT); - AutoConnectIntervalReceiver.scheduleReconnect(); } return result; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java index 7c1ba63c0..ec6fc8052 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java @@ -28,6 +28,8 @@ import android.webkit.ValueCallback; import android.webkit.WebView; import androidx.annotation.NonNull; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,7 +46,6 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.UUID; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.ExternalPebbleJSActivity; @@ -110,7 +111,7 @@ class PebbleIoThread extends GBDeviceIoThread { } } - public static void sendAppMessage(GBDeviceEventAppMessage message) { + private static void sendAppMessage(GBDeviceEventAppMessage message) { final String jsEvent; try { WebViewSingleton.getInstance().checkAppRunning(message.appUUID); @@ -190,7 +191,7 @@ class PebbleIoThread extends GBDeviceIoThread { mOutStream = new PipedOutputStream(); mPebbleLESupport = new PebbleLESupport(this.getContext(), btDevice, (PipedInputStream) mInStream, (PipedOutputStream) mOutStream); } else { - ParcelUuid uuids[] = btDevice.getUuids(); + ParcelUuid[] uuids = btDevice.getUuids(); if (uuids == null) { return false; } @@ -364,7 +365,7 @@ class PebbleIoThread extends GBDeviceIoThread { mInStream.skip(2); } - GBDeviceEvent deviceEvents[] = mPebbleProtocol.decodeResponse(buffer); + GBDeviceEvent[] deviceEvents = mPebbleProtocol.decodeResponse(buffer); if (deviceEvents == null) { LOG.info("unhandled message to endpoint " + endpoint + " (" + length + " bytes)"); } else { @@ -386,31 +387,9 @@ class PebbleIoThread extends GBDeviceIoThread { if (e.getMessage() != null && (e.getMessage().equals("broken pipe") || e.getMessage().contains("socket closed"))) { //FIXME: this does not feel right LOG.info(e.getMessage()); mIsConnected = false; - int reconnectAttempts = prefs.getInt("pebble_reconnect_attempts", 10); - if (!mQuit && GBApplication.getGBPrefs().getAutoReconnect() && reconnectAttempts > 0) { - gbDevice.setState(GBDevice.State.WAITING_FOR_RECONNECT); - gbDevice.sendDeviceUpdateIntent(getContext()); - - long delaySeconds = 1; - while (reconnectAttempts-- > 0 && !mQuit && !mIsConnected) { - LOG.info("Trying to reconnect (attempts left " + reconnectAttempts + ")"); - mIsConnected = connect(); - if (!mIsConnected) { - try { - Thread.sleep(delaySeconds * 1000); - } catch (InterruptedException ignored) { - } - if (delaySeconds < 64) { - delaySeconds *= 2; - } - } - } - } - if (!mIsConnected) { - mBtSocket = null; - LOG.info("Bluetooth socket closed, will quit IO Thread"); - break; - } + mBtSocket = null; + LOG.info("Bluetooth socket closed, will quit IO Thread"); + break; } } } @@ -426,7 +405,7 @@ class PebbleIoThread extends GBDeviceIoThread { enablePebbleKitSupport(false); - if (mQuit) { + if (mQuit || !GBApplication.getGBPrefs().getAutoReconnect()) { gbDevice.setState(GBDevice.State.NOT_CONNECTED); } else { gbDevice.setState(GBDevice.State.WAITING_FOR_RECONNECT); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/AutoConnectIntervalReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/AutoConnectIntervalReceiver.java index 9de1e78e2..dad443888 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/AutoConnectIntervalReceiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/AutoConnectIntervalReceiver.java @@ -57,34 +57,39 @@ public class AutoConnectIntervalReceiver extends BroadcastReceiver { if (action == null) { return; } + GBDevice gbDevice = service.getGBDevice(); + if (gbDevice == null) { + return; + } if (action.equals(DeviceManager.ACTION_DEVICES_CHANGED)) { if (gbDevice.isInitialized()) { LOG.info("will reset connection delay, device is initialized!"); mDelay = 4; } + else if (gbDevice.getState() == GBDevice.State.WAITING_FOR_RECONNECT) { + scheduleReconnect(); + } } else if (action.equals("GB_RECONNECT")) { - if (gbDevice != null) { - if (gbDevice.getState() == GBDevice.State.WAITING_FOR_RECONNECT) { - LOG.info("Will re-connect to " + gbDevice.getAddress() + "(" + gbDevice.getName() + ")"); - GBApplication.deviceService().connect(); - } + if (gbDevice.getState() == GBDevice.State.WAITING_FOR_RECONNECT) { + LOG.info("Will re-connect to " + gbDevice.getAddress() + "(" + gbDevice.getName() + ")"); + GBApplication.deviceService().connect(); } } } - public static void scheduleReconnect() { + public void scheduleReconnect() { mDelay*=2; - if (mDelay > 128) { - mDelay = 128; + if (mDelay > 64) { + mDelay = 64; } scheduleReconnect(mDelay); } - public static void scheduleReconnect(int delay) { - LOG.info("schduling reconnect in " + delay + " seconds"); + public void scheduleReconnect(int delay) { + LOG.info("scheduling reconnect in " + delay + " seconds"); AlarmManager am = (AlarmManager) (GBApplication.getContext().getSystemService(Context.ALARM_SERVICE)); Intent intent = new Intent("GB_RECONNECT"); intent.setPackage(BuildConfig.APPLICATION_ID); From e38f50cb0329b262cf940031d53842627db0dadc Mon Sep 17 00:00:00 2001 From: GabO Date: Fri, 15 Nov 2019 13:44:46 +0000 Subject: [PATCH 3/3] Translated using Weblate (Spanish) Currently translated at 100.0% (724 of 724 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/es/ --- app/src/main/res/values-es/strings.xml | 44 +++++++++++++++++--------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 3b5005954..b57b657b8 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -108,7 +108,7 @@ Soporte para llamadas salientes Desactivar esto evitará que el Pebble 2/LE vibre en llamadas salientes Permitir el acceso a aplicaciones Android de terceros - habilitar el soporte experimental para aplicaciones Android que usan PebbleKit + Habilitar el soporte experimental para aplicaciones Android que usan PebbleKit Timeline Pebble Salida y puesta de Sol Enviar las horas de salida y puesta de Sol basándose en la localización a la timeline del Pebble @@ -231,7 +231,7 @@ Pasos por minuto Encuentra un dispositivo perdido Cancelar para detener la vibración. - Tu actividad + Actividad y sueño Configurar alarmas Configurar alarmas Detalles de alarma @@ -259,11 +259,11 @@ A %1$s le queda: %2$s%% batería Última carga: %s \n Número de cargas: %s - Tu sueño + Sueño Sueño por semana Sueño hoy, objetivo: %1$s Pasos por semana - Tu actividad y sueño + Actividad Instalando firmware… El archivo no puede ser instalado, el dispositivo no está listo. %1$s: %2$s %3$s @@ -291,7 +291,7 @@ Si los datos no son marcados como descargados, no serán borrados de tu Mi Band. Útil si Gadgetbridge se usa conjuntamente con otras apps. Mantendrá los datos de actividad en la Mi Band incluso después de la sincronización. Útil si GB se usa junto con otras apps. Usa el modo de baja latencia para las instalaciones de firmware - Esto podría ayudar en dispositivos donde las instalaciones de firmware fallan + Esto podría ayudar en dispositivos donde las instalaciones de firmware fallan. Historial de pasos Pasos/min actuales Pasos totales @@ -348,7 +348,7 @@ Ritmo cardíaco Ritmo cardíaco Almacenar datos en bruto en la base de datos - Una vez seleccionado, los datos archivados se guardan en bruto y están disponibles para ser interpretados más tarde. Nota: ¡La base de datos será más grande! + Guarda los datos en bruto para poder ser interpretados más tarde, esto incrementa el tamaño de la base de datos. Administración de bases de datos Administración de bases de datos La base de datos usa la siguiente ubicación en su dispositivo.\nEsta ubicación está accesible para otras aplicaciones Android y para su ordenador.\nEncontrará sus bases de datos exportadas (o la que quiere importar) aquí: @@ -377,7 +377,7 @@ Vibración Emparejando con Pebble - En su dispositivo Android aparecerá un mensaje para emparejar. Si no aparece, mira en el cajón de notificaciones y acepta la propuesta de emparejamiento. Después acepta también en tu Pebble + En su dispositivo Android aparecerá un mensaje para emparejar. Si no aparece, mira en el cajón de notificaciones y acepta la propuesta de emparejamiento. Después acepta también en tu Pebble. Asegúrate de que este tema esté activado en la aplicación de notificación del tiempo para obtener la información en tu Pebble.\n\nNo se requiere configuración.\n\nPuedes activar la aplicación del tiempo del sistema desde la configuración de la app.\n\nLas watchfaces soportadas mostrarán la información del tiempo automáticamente. Activar el emparejamiento Bluetooth Desactiva esto si tienes problemas de conexión @@ -663,15 +663,15 @@ Configuraciones específicas del dispositivo Clave de autenticación Cambie la clave de autenticación a una clave común en todos sus dispositivos Android desde los que desea conectarse. La clave predeterminada anterior para todos los dispositivos es 0123456789@ABCDE - Está a punto de instalar el «firmware» %s en su Amazfit Cor 2. -\n -\nAsegúrese de instalar el archivo .fw y, a continuación, el archivo .res. Se reiniciará la pulsera tras instalar el archivo .fw. -\n + Está a punto de instalar el firmware %s en su Amazfit Cor 2. +\n +\nAsegúrese de instalar el archivo .fw y, a continuación, el archivo .res. La pulsera se reiniciará despues de instalar el archivo .fw. +\n \nNota: no es necesario instalar el archivo .res si es idéntico al instalado previamente. -\n -\nPROCEDA BAJO SU PROPIA CUENTA Y RIESGO. -\n -\nNO SE HA REALIZADO NINGUNA PRUEBA. QUIZÁ NECESITE INSTALAR UN «FIRMWARE» BEATS_W SI EL NOMBRE DE SU DISPOSITIVO ES «Amazfit Band 2» +\n +\nPROCEDA BAJO SU PROPIO RIESGO. +\n +\nNO SE HA REALIZADO NINGUNA PRUEBA. QUIZÁ NECESITE INSTALAR UN FIRMWARE BEATS_W SI EL NOMBRE DE SU DISPOSITIVO ES \"Amazfit Band 2\" Está a punto de instalar el «firmware» %s en su Mi Band 4. \n \nAsegúrese de instalar el archivo .fw y, a continuación, el archivo .res. Se reiniciará la pulsera tras instalar el archivo .fw. @@ -774,4 +774,18 @@ Intervalo de sueño Últimas 24 horas De mediodía a mediodía + Alarma de sueño + Sueño: %1$s + Botón de añadir nuevo dispositivo + Makibes HR3 + Amazfit Bip Lite + Amazfit GTR + Amazfit GTS + Está a punto de instalar el firmware %s en su Amazfit GTS. +\n +\nPor favor, asegúrese de instalar el archivo .fw primero, el archivo .res a continuación y por ultimo el archivo .gps. Su reloj se reiniciara después de instalar el archivo .fw. +\n +\nNota: No hace falta instalar los archivos .res y .gps si los archivos son idénticos a los previamente instalados. +\n +\n¡PROCEDE BAJO TU PROPIO RIESGO! \ No newline at end of file