Compre un brazalete chino, se decepcionará con el software oficial, ¡escriba el suyo!
Esta historia ha estado esperando su publicación durante más de seis meses, durante este tiempo ha cambiado mucho, el firmware y el software se han actualizado y muchos de mis desarrollos ya han quedado obsoletos.Prólogo
El trabajo activo de un gran número de empresas en el campo de la tecnología portátil y los relojes inteligentes no dejó la paz en mi alma. Vi un gran potencial en dispositivos portátiles con pantalla. No, no estoy hablando de contar pasos y otras cosas relacionadas con el ejercicio físico, sin duda son geniales, pero tan lejos de lo banal “¡Felicitaciones! ¡Caminaste 4 km, tomaste más de 20 km! ” y hermosos gráficos de progreso y regresión, no se les ocurrió nada especial.Pero el hecho de que pueda recibir notificaciones directamente en la pantalla de mi muñeca es conveniente. Si también puedo interactuar de alguna manera con él o con algo cercano presionando los botones 1-2-3, esto es aún mejor.Una vez más, arando las extensiones de aliexpress, me encontré con una pulsera de fitness iWown i5. Inmediatamente atrajo mi atención a un precio increíblemente bajo (en ese momento alrededor de 800r con envío gratis) y la presencia de una pantalla OLED. Después de leer detenidamente la descripción del vendedor y las opiniones de los clientes, decidí ordenar este milagro.Especificaciones declaradas (descripción de la traducción de aliexpress):- Pantalla: OLED
- Batería: polímero de litio
- Carga: carga USB estándar
- Tiempo en espera: más de 72 horas
- Dimensiones: 69.1 * 15.8 * 11.2 mm
- Peso: 18g
- Material: correa de ABS, cierre de acero
- Resistencia al agua: IP55
- Temperatura de funcionamiento: -20 ° C ~ + 45 ° C
- Temperatura de funcionamiento del medio flash: -40 ° C ~ + 45 ° C
Capacidades:- Monitor deportivo: todo el tiempo registra los pasos y movimientos, la distancia recorrida y las calorías quemadas, todos los números se calculan teniendo en cuenta su peso y altura.
- Monitoreo de la calidad del sueño: mientras duerme, el rastreador registra las fases del sueño, determinando el sueño profundo y rápido, 8 grupos de alarmas silenciosas le permiten despertarlo sin molestar a otros miembros de la familia.
- Sincronización inalámbrica Bluetooth 4.0 de baja potencia
- Soporte de sincronización de PC vía USB
- Protección IP55: protege el dispositivo bajo fuertes lluvias, pero no más
y otras ventajas "exageradas" en el estilo del marketing chino.Estaba muy interesado en la oportunidad de seguir un sueño y despertar en la fase correcta. Muchos de mis amigos compraron rastreadores de ejercicios económicos precisamente por esta función y estaban satisfechos con mi banda y similares. Siempre me faltó una pantalla, pero aquí está todo en uno.En mi trabajo, a menudo tengo que desarrollar aplicaciones simples para Android, decidí que si no tengo suficiente funcionalidad de mi aplicación nativa, escribiré la mía.El paquete llegó bastante rápido e inmediatamente me apresuré a estudiar un maravilloso brazalete. Después de una hora de jugar con la aplicación Zeroner, que, de acuerdo con las instrucciones, debe instalarse en mi dispositivo Android, me di cuenta de que la funcionalidad es bastante pobre y triste. Zeroner, como todos los demás fabricantes, enfocado en contar pasos y calorías, mostrar gráficos hermosos, tiene una función de búsqueda telefónica (hablaré de esto más adelante), puede notificarle sobre una llamada entrante, la llegada de un mensaje en Facebook y WhatsApp, y envía notificaciones desde UNA de cualquier aplicación seleccionada , que se considerará como una aplicación para SMS.La vibración del brazalete es muy controvertida, escriben en los foros que es bastante débil, algunos dicen que es normal. Para mí, podría haber sido más fuerte. El brazalete reacciona al gesto de "Mira el reloj". Si miras el brazalete como un reloj, levantando la mano y doblando el codo, la pantalla se encenderá automáticamente y mostrará la hora o la notificación perdida.
En general, sin dudarlo, decidí escribir mi solicitud, con notificaciones, vibración y sincronización. Seguiré adelante, me tomó 4 días de descanso y varias tardes largas ...A los negocios
Dado que con Bluetooth no soy un dedo del pie azul, decidí intentar interceptar los datos intercambiados entre el teléfono y el brazalete con un tonto. Para hacer esto, subí a la pestaña de desarrolladores y activé la casilla de verificación "Habilitar registro de transmisión de Bluetooth HCI" . Después de habilitar esta opción, el volcado completo de la comunicación de Android con cualquier dispositivo Bluetooth se agrega al archivo /sdcard/Android/data/btsnoop_hci.log (la ruta puede cambiar para diferentes dispositivos, el nombre del archivo parece ser siempre el mismo).Después de descargar WireShark, comencé a estudiar los registros de comunicación con el brazalete y vi algo similar a esto:
después de pasar casi dos horas, estudiar los registros, realizar adicciones, protocolos de google en Internet, me di cuenta de que ese camino no es para mí.Sin embargo, dado que mi teléfono interpretó el brazalete como un dispositivo BLE normal y lo mostró en la sección de dispositivos conectados, decidí usar ejemplos de trabajo con BLE desde el SDK de Android.Después de clonar el repositorio https://github.com/googlesamples/android-BluetoothLeGatt , configure Android Studio en el ombligo con el código fuente, construyó y lanzó la aplicación. ( Enlace a la descripción del SDK de Android con Bluetooth LE )Resultó como en las imágenes del github:
después de ejecutar el escaneo, la aplicación no vio el dispositivo. Resultó que la aplicación nativa al conectarse al brazalete no permitió que BLE encontrara el dispositivo. Todo se decidió mediante una simple eliminación de Zeroner, podría apagarlo, pero es más confiable demolerlo por completo.Y así, Bluetooth LE es una tecnología que se basa en dispositivos con bajo consumo de energía, se utiliza en sensores nuevos, etiquetas y muchos otros dispositivos. La base de esta tecnología es el perfil de atributo genérico (GATT) , este es un perfil de Bluetooth que le permite intercambiar pequeños datos, "atributos" . No voy a escribir sobre cómo funciona todo durante mucho tiempo, hay mucha información en el centro y en Internet, que también tuve que buscar soluciones.Esperaba que todos los datos que necesitaba se almacenaran en las características y descriptores del brazalete, y pudiera recibir y registrar datos sin ningún problema. Me equivoqué ... Laaplicación de prueba BLE me mostró solo 4 servicios:0000180f -icsoft1000-8000-00805f9b34fb00001800-0000-1000-8000-00805f9b34fb0000ff20-0000-1000-8000-00805f9b34fb00001801-0000-1000-8000-00805f9b34fbtenían muy pocas características, las que leían devuelven nulos o ceros, y la escritura era inútil. Pero me alentó que pude conectarme y obtener al menos algunos datos.Además, decidí que no era posible actuar a ciegas y decidí diseccionar la aplicación Zeroner. Después de haber acumulado un par de descompiladores APK en línea en Internet, les di de comer zeroner.apk y obtuve 2 archivos zip en la salida.La primera era una versión JADX, y la segunda contenía el resultado de apktool.Hurgando en el código fuente me horrorizó el código chino(aunque en mi trabajo a menudo lo encuentro en forma de backends para sitios y servicios, pero nunca deja de sorprenderme con su tortuosidad e ingenio, pero de todos modos, es terriblemente difícil de leer)Después de mucha investigación, finalmente me topé con el archivo WristBandDevice.java , que estaba en el camino com.kunekt / bluetooth.En esta clase, todo el trabajo con el dispositivo estaba escondido, pero nuevamente, una emboscada me estaba esperando.Como resultó más tarde, en el firmware anterior del brazalete, se usaron más servicios en las características (como esperaba anteriormente), pero más tarde, los desarrolladores dejaron solo 2, uno para leer, el segundo para escribir. Todos los comandos se transmiten en un paquete.No fue tan fácil entender cómo debería verse el paquete, decidí determinar claramente lo que quiero del brazalete en primer lugar, para poder comenzar a rastrear las llamadas a funciones. Y quería mostrar mensajes personalizados en la pulsera.Sin dudarlo, subí a com.kunekt / receptor / CallReceiver.java , ya que las llamadas entrantes se mostraban de manera muy estable e incluso con caracteres rusos, decidí que este era un gran comienzo, dado que ya había encontrado un evento de llamada entrante en Android, la idea de cómo podría funcionar ya ha sido.Al abrir el archivo, vi esto:Un gran trozo de código chinopublic void onReceive(Context context, Intent intent) {
Log.e(this.TAG, "+++ ON RECEIVE +++");
switch (((TelephonyManager) context.getSystemService("phone")).getCallState()) {
case C08571.POSITION_OPEN :
if (ZeronerApplication.newAPI) {
BackgroundThreadManager.getInstance().addTask(new WriteOneDataTask(context, WristBandDevice.getInstance(context).setPhoneStatue()));
}
case BitmapCacheManagementTask.MESSAGE_INIT_DISK_CACHE :
incomingNumber = intent.getStringExtra("incoming_number");
Contact contact = getContact(context, incomingNumber);
if (!WristBandDevice.getInstance(context).isConnected() || !ZeronerApplication.phoneAlert) {
return;
}
if (ZeronerApplication.newAPI) {
this.fMdeviceInfo = jsonToFMdeviceInfo(UserConfig.getInstance(context).getDevicesInfo());
if (this.fMdeviceInfo.getModel().indexOf("5+") != -1) {
if (UserConfig.getInstance(context).getFont_lib() == 1 || UserConfig.getInstance(context).getFont_lib() == 2 || UserConfig.getInstance(context).getSysFont().equalsIgnoreCase("en") || UserConfig.getInstance(context).getSysFont().equalsIgnoreCase("es")) {
if (contact.getDisplayName().length() > 11) {
WristBandDevice.getInstance(context).writeWristBandFontLibrary(context, 1, contact.getDisplayName().substring(0, 11));
} else if (contact.getDisplayName().length() <= 6 || contact.getDisplayName().length() > 11) {
WristBandDevice.getInstance(context).writeWristBandFontLibrary(context, 1, contact.getDisplayName());
} else {
WristBandDevice.getInstance(context).writeWristBandFontLibrary(context, 1, contact.getDisplayName().substring(0, contact.getDisplayName().length()));
}
} else if (contact.getDisplayName().length() > 11) {
WristBandDevice.getInstance(context).writeWristBandPhoneAlertNew(context, contact.getDisplayName().substring(0, 11));
} else if (contact.getDisplayName().length() <= 6 || contact.getDisplayName().length() > 11) {
WristBandDevice.getInstance(context).writeWristBandPhoneAlertNew(context, contact.getDisplayName());
} else {
WristBandDevice.getInstance(context).writeWristBandPhoneAlertNew(context, contact.getDisplayName().substring(0, contact.getDisplayName().length()));
}
} else if (contact.getDisplayName().length() > 11) {
WristBandDevice.getInstance(context).writeWristBandPhoneAlertNew(context, contact.getDisplayName().substring(0, 11));
} else if (contact.getDisplayName().length() <= 6 || contact.getDisplayName().length() > 11) {
WristBandDevice.getInstance(context).writeWristBandPhoneAlertNew(context, contact.getDisplayName());
} else {
WristBandDevice.getInstance(context).writeWristBandPhoneAlertNew(context, contact.getDisplayName().substring(0, contact.getDisplayName().length()));
}
} else if (contact.getDisplayName().length() > 11) {
WristBandDevice.getInstance(context).writeWristBandPhoneAlert(context, contact.getDisplayName().substring(0, 11));
} else if (contact.getDisplayName().length() <= 6 || contact.getDisplayName().length() > 11) {
WristBandDevice.getInstance(context).writeWristBandPhoneAlert(context, contact.getDisplayName());
} else {
WristBandDevice.getInstance(context).writeWristBandPhoneAlert(context, contact.getDisplayName().substring(0, contact.getDisplayName().length()));
}
case BitmapCacheManagementTask.MESSAGE_FLUSH :
if (ZeronerApplication.newAPI) {
BackgroundThreadManager.getInstance().addTask(new WriteOneDataTask(context, WristBandDevice.getInstance(context).setPhoneStatue()));
}
default:
}
}
Aquí vemos claramente que hay 2 opciones para la API y los nombres que tienen son newAPI muy lógicos y el segundo, respectivamente, oldAPI. En toda esta abundancia de condiciones, solo me interesaba una línea repetitiva:WristBandDevice.getInstance (context) .writeWristBandPhoneAlertNew (context, contact.getDisplayName .....)Esto era lo que estaba buscando. Mirando hacia el futuro, diré que iWown también tiene modelos i5 + e i6, tienen una pantalla más grande y, en consecuencia, se colocan más caracteres, para esto se necesitan todas estas comprobaciones. no está claro por qué no escribieron una clase o algo así, tal vez son bromas del descompilador, pero este código se repite en muchos lugares.Volviendo a la definición de esta función, vi esto: public void writeWristBandPhoneAlertNew(Context context, String displayName) {
writeAlertNew(context, displayName, 1);
}
public void writeWristBandSmsAlertNew(Context context, String displayName) {
writeAlertNew(context, displayName, 2);
}
Genial, utiliza la misma función para enviar texto, solo que con diferentes parámetros. Todas las funciones con la palabra Nuevo son solo nuestra opción, porque como resultó anteriormente, tengo una nueva API.Con mucho gusto yendo a la definición de la función writeAlertNew , vi lo siguiente:private void writeAlertNew(Context context, String displayName, int type) {
ArrayList<Byte> datas = new ArrayList();
datas.add(Byte.valueOf((byte) type));
int i = 0;
while (i < displayName.length()) {
if (displayName.charAt(i) < '@' || (displayName.charAt(i) < '\u0080' && displayName.charAt(i) > '`')) {
char e = displayName.charAt(i);
datas.add(Byte.valueOf((byte) 0));
for (byte valueOf : PebbleBitmap.fromString(context, String.valueOf(e), 8, 1).data) {
datas.add(Byte.valueOf(valueOf));
}
} else {
char c = displayName.charAt(i);
datas.add(Byte.valueOf((byte) 1));
for (byte valueOf2 : PebbleBitmap.fromString(context, String.valueOf(c), 16, 1).data) {
datas.add(Byte.valueOf(valueOf2));
}
}
i++;
}
byte[] data = writeWristBandDataByte(true, form_Header(3, 1), datas);
for (i = 0; i < data.length; i += 20) {
byte[] writeData;
if (i + 20 > data.length) {
writeData = Arrays.copyOfRange(data, i, data.length);
} else {
writeData = Arrays.copyOfRange(data, i, i + 20);
}
NewAgreementBackgroundThreadManager.getInstance().addTask(new WriteOneDataTask(context, writeData));
}
}
Estaba claro que un par de funciones que se usan aquí me separan de las ganancias.writeWristBandDataByte: forma un paquete con un mensaje para el brazalete, es interesante que haya una función especial form_Header (3, 1) , que forma el encabezado del paquete, por el cual el brazalete entiende lo que quiere de él. 3 es el número del grupo del equipo y 1 es el equipo mismopublic static byte form_Header(int grp, int cmd) {
return (byte) (((((byte) grp) & 15) << 4) | (((byte) cmd) & 15));
}
La función es simple, copiada a mi proyecto sin cambios. El siguiente fueNewAgreementBackgroundThreadManager.getInstance (). AddTask (new WriteOneDataTask (context, writeData));Al final resultó que, nada inusual, la aplicación crea una secuencia en la que se verifica constantemente la cola de paquetes para enviar, si aparece un paquete en la cola, la secuencia escribe en la característica del dispositivo dado, si hay más de un paquete, los envía con un retraso de 240 milisegundos.Luego vino lo más incomprensible:PebbleBitmap.fromString (context, String.valueOf (e), 8, 1) .data)Por qué la clase se llama de esa manera no está claro, porque este dispositivo no tiene nada que ver con Pebble. Al abrir la fuente de la clase, vi lo siguiente:Fuente de la clase PebbleBitmappublic class PebbleBitmap {
public static boolean f1285D;
public final byte[] data;
public final UnsignedInteger flags;
public final short height;
public int index;
public int offset;
public final UnsignedInteger rowLengthBytes;
public final short width;
public final short f1286x;
public final short f1287y;
static {
f1285D = true;
}
private PebbleBitmap(UnsignedInteger _rowLengthBytes, UnsignedInteger _flags, short _x, short _y, short _width, short _height, byte[] _data) {
this.offset = 0;
this.index = 0;
this.rowLengthBytes = _rowLengthBytes;
this.flags = _flags;
this.f1286x = _x;
this.f1287y = _y;
this.width = _width;
this.height = _height;
this.data = _data;
}
public static PebbleBitmap fromString(Context context, String text, int w, int l) {
TextPaint textPaint = new TextPaint();
textPaint.setAntiAlias(true);
textPaint.setTextSize(16.5f);
if (w == 32) {
textPaint.setTextAlign(Align.CENTER);
}
textPaint.setTypeface(ZeronerApplication.unifont);
StaticLayout sl = new StaticLayout(text, textPaint, w, Alignment.ALIGN_NORMAL, 1.0f, 0.49f, false);
int h = sl.getHeight();
if (h > l * 16) {
h = l * 16;
}
Bitmap newBitmap = Bitmap.createBitmap(w, h, Config.ARGB_8888);
sl.draw(new Canvas(newBitmap));
return fromAndroidBitmap(newBitmap);
}
public static PebbleBitmap fromAndroidBitmap(Bitmap bitmap) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int rowLengthBytes = width / 8;
ByteBuffer data = ByteBuffer.allocate(rowLengthBytes * height);
data.order(ByteOrder.LITTLE_ENDIAN);
StringBuffer stringBuffer = new StringBuffer(StatConstants.MTA_COOPERATION_TAG);
for (int y = 0; y < height; y++) {
int[] pixels = new int[width];
bitmap.getPixels(pixels, 0, width * 2, 0, y, width, 1);
stringBuffer = new StringBuffer(StatConstants.MTA_COOPERATION_TAG);
for (int x = 0; x < width; x++) {
if (pixels[x] == 0) {
stringBuffer.append(Constants.VIA_RESULT_SUCCESS);
if (f1285D) {
stringBuffer.append("-");
}
} else {
stringBuffer.append(Constants.VIA_TO_TYPE_QQ_GROUP);
if (f1285D) {
stringBuffer.append("#");
}
}
}
for (int k = 0; k < rowLengthBytes * 8; k += 8) {
ByteBuffer byteBuffer = data;
byteBuffer.put(Byte.valueOf((byte) new BigInteger(stringBuffer.substring(k, k + 8), 2).intValue()).byteValue());
}
if (f1285D) {
stringBuffer.append("\n");
}
Log.i("info", stringBuffer.toString());
}
if (f1285D) {
System.out.println(stringBuffer.toString());
}
if (!(bitmap == null || bitmap.isRecycled())) {
bitmap.recycle();
}
System.gc();
return new PebbleBitmap(UnsignedInteger.fromIntBits(rowLengthBytes), UnsignedInteger.fromIntBits(DfuSettingsConstants.SETTINGS_DEFAULT_MBR_SIZE), (short) 0, (short) 0, (short) width, (short) height, data.array());
}
public static PebbleBitmap fromPng(InputStream paramInputStream) throws IOException {
return fromAndroidBitmap(BitmapFactory.decodeStream(paramInputStream));
}
}
Después de una larga reflexión, llegué a la conclusión de que fromString crea una imagen con una letra usando una determinada fuente (que está incrustada en la aplicación), y luego convierte los píxeles a 0 o 1 dependiendo del relleno, por lo que la letra O se verá así:0001110001100011011000110110001100011100Sin entrar realmente en los detalles, copié todo en mi proyecto utilizando el ejemplo BLE GATT de Google.Y ... ¡Oh, un milagro! ¡La pulsera vibró! Pero el mensaje no apareció, una línea vacía y un icono de llamada entrante.Resultó que un montón de comprobaciones de tamaño no son casuales, el brazalete ignora estúpidamente mensajes demasiado largos y mensajes cuya longitud es de 11 caracteres, aunque 12 se muestra normalmente. Un par de horas bailando alrededor de estas funciones finalmente arrojaron resultados, aprendí a mostrar textos en ruso e inglés, y al mismo tiempo aprendí que hay varios modos de funcionamiento en el grupo de mensajes:- Llamada entrante Se muestra el auricular, el nombre de la persona que llama y la pulsera vibra
- Mensaje Se muestran el texto y el icono del sobre. Cuando vibra 2 veces
- Nublado Lo mismo que 2, pero en lugar del sobre, el ícono de la nube
- El error Igual que 2, que solo es un icono con un signo de exclamación.
Después de haber enseñado a mi aplicación a enviarme notificaciones de diferentes aplicaciones, whatsapp, vk, viber, telegram y otras, decidí que era hora de enseñarle al brazalete a responder las llamadas entrantes y, finalmente, usar un solo botón para restablecer las entrantes.No describiré este proceso, la publicación resultó estar hinchada, solo diré que no fue difícil responder a los mensajes entrantes, pero usar el botón no.Todos los mensajes entrantes de la pulsera, Zeroner interceptado en una clase especial. el paquete entrante tenía el encabezado del grupo de comando y el número del equipo, después de una larga depuración y pruebas, busqué los grupos usados y luego encontré una descripción en el código Zeroner.Pulsera grupos y equipos// HEADER GROUPS //
DEVICE = 0
CONFIG = 1
DATALOG = 2
MSG = 3
PHONE_MSG = 4
// CONFIG = 1 ///
CMD_ID_CONFIG_GET_AC = 5
CMD_ID_CONFIG_GET_BLE = 3
CMD_ID_CONFIG_GET_HW_OPTION = 9
CMD_ID_CONFIG_GET_NMA = 7
CMD_ID_CONFIG_GET_TIME = 1
CMD_ID_CONFIG_SET_AC = 4
CMD_ID_CONFIG_SET_BLE = 2
CMD_ID_CONFIG_SET_HW_OPTION = 8
CMD_ID_CONFIG_SET_NMA = 6
CMD_ID_CONFIG_SET_TIME = 0
// DATALOG = 2 //
CMD_ID_DATALOG_CLEAR_ALL = 2
CMD_ID_DATALOG_GET_BODY_PARAM = 1
CMD_ID_DATALOG_SET_BODY_PARAM = 0
CMD_ID_DATALOG_GET_CUR_DAY_DATA = 7
CMD_ID_DATALOG_START_GET_DAY_DATA = 3
CMD_ID_DATALOG_START_GET_MINUTE_DATA = 5
CMD_ID_DATALOG_STOP_GET_DAY_DATA = 4
CMD_ID_DATALOG_STOP_GET_MINUTE_DATA = 6
// DEVICE = 0 //
CMD_ID_DEVICE_GET_BATTERY = 1
CMD_ID_DEVICE_GET_INFORMATION = 0
CMD_ID_DEVICE_RESE = 2
CMD_ID_DEVICE_UPDATE = 3
// MSG = 3 //
CMD_ID_MSG_DOWNLOAD = 1
CMD_ID_MSG_MULTI_DOWNLOAD_CONTINUE = 3
CMD_ID_MSG_MULTI_DOWNLOAD_END = 4
CMD_ID_MSG_MULTI_DOWNLOAD_START = 2
CMD_ID_MSG_UPLOAD = 0
// PHONE_MSG = 4 //
CMD_ID_PHONE_ALERT = 1
CMD_ID_PHONE_PRESSKEY = 0
Gracias a esto, pude realizar un trabajo completo con el brazalete. Puedo recibir datos sobre pasos, sobre el sueño. Puedo administrar la configuración, configurar alarmas. Logré obtener la designación de bytes del paquete de las clases que almacenan datos en la base de datos, los implementé en casa.Al final
Después de pensar un poco, decidí que todo esto podría ser útil no solo para mí y escribí una nueva aplicación que contiene todos los datos y funciones necesarios para trabajar con el brazalete, así como también implementa una interfaz simple para enviar notificaciones desde cualquier aplicación al brazalete.WiliX iWown para GeekDesde entonces, ha pasado mucho tiempo, y muchos después de actualizar a Android 6, la aplicación dejó de funcionar. Tampoco funciona de manera estable con el firmware de las pulseras de la segunda versión. Pero espero encontrar tiempo para la revisión.El código fuente se publica en GitHub . Puedes bifurcarte y divertirte como quieras. Se aceptarán todas las solicitudes de extracción después de la revisión, y después de que las pruebas se carguen inmediatamente en Google Play.Por el momento, la aplicación puede:- Mostrar notificaciones desde cualquier aplicación
- BT
Se implementó una conexión con Google Fit para guardar los datos de entrenamiento, pero, como no elegí el SDK para Fit, busqué en un montón de enlaces y foros, pero aún no entendía cómo hacer que los datos de visualización de ajuste de dispositivos personalizados. No está claro entonces por qué esta función está ahí.Si alguien trabajó con Google Fit y sabe cómo hacer que use datos de un sensor personalizado para mostrar gráficos, dígame en comentarios o escríbame, ¡los usuarios y yo estaremos muy agradecidos!También fue una idea conectar el brazalete a Sleep como Adnroid. En realidad, para controlar el sueño, se compró un brazalete. Pero, como resultó, iWown solo puede devolver la duración de las fases de sueño. Es decir, datos ya calculados del acelerómetro.Y Sleep as Android requiere datos desnudos del acelerómetro, y con una frecuencia deseada de 10 segundos.En general, el resultado. Invito a los desarrolladores y propietarios a apoyar el proyecto con su código, consejos y cualquier cosa. Deje pull-requist, problema en Github.La aplicación resultó ser muy popular en el extranjero, los extranjeros a menudo me escriben, pidiéndome que agregue / arregle / traduzca algo.Por cierto, iWown i5 tiene varios clones con firmware similar:Vidonn X5Harper BFB-301Excelvan i5Referencias
Google Play: iWown for GeekRepository en GitHubDiscussion en w3bsit3-dns.comPS A partir de la quinta versión, apareció una categoría adicional en los androides en la cortina, que no aparece en la pantalla de bloqueo.¿Alguien puede decirme cómo transferir mi notificación a esta categoría? Gracias