Compre uma pulseira chinesa, fique desapontado com o software oficial, escreva o seu!
Esta história aguarda sua publicação há mais de seis meses. Durante esse período, muita coisa mudou, o firmware e o software foram atualizados e muitos dos meus desenvolvimentos já estão desatualizados.Prefácio
O trabalho ativo de um grande número de empresas no campo da tecnologia vestível e relógios inteligentes não deixou a paz para minha alma. Vi um grande potencial em dispositivos portáteis com uma tela. Não, não estou falando de contar etapas e outras coisas de condicionamento físico, elas certamente são legais, mas tão distantes do banal “Parabéns! Você andou 4 km, deu mais de 20k passos! ” e belos gráficos de progresso e regressão, eles não criaram nada de especial.Mas o fato de poder receber notificações diretamente na tela do meu pulso é conveniente. Se eu também puder interagir de alguma forma com ele ou com algo próximo, pressionando os botões 1-2-3 - isso é ainda mais legal.Mais uma vez explorando as extensões do aliexpress, me deparei com uma pulseira de fitness iWown i5. Ele imediatamente atraiu minha atenção a um preço incrivelmente baixo (na época, cerca de 800r com frete grátis) e a presença de uma tela OLED. Depois de ler atentamente a descrição do vendedor e as críticas dos clientes, decidi encomendar este milagre.Especificações declaradas (descrição da tradução do aliexpress):- Exposição: OLED
- Bateria: Li-Polymer
- Carregamento: carregamento USB padrão
- Tempo de espera: mais de 72 horas
- Dimensões: 69.1 * 15.8 * 11.2mm
- Peso: 18g
- Material: cinta ABS, fecho de aço
- Resistência à água: IP55
- Temperatura de operação: -20 ° C ~ + 45 ° C
- Temperatura de operação da mídia de flash: -40 ° C ~ + 45 ° C
Recursos:- Monitor esportivo: o tempo todo registra etapas e movimentos, distância percorrida e calorias queimadas, todos os números são calculados levando em consideração seu peso e altura.
- Monitorando a qualidade do sono: Enquanto você dorme, o rastreador registra as fases do sono, determinando o sono profundo e rápido, 8 grupos de alarmes silenciosos permitem que você o acorde sem incomodar outros membros da família
- Sincronização sem fio de baixa potência Bluetooth 4.0
- Suporte a sincronização do PC via USB
- Proteção IP55: protege o dispositivo sob chuva forte, mas não mais
e outras vantagens "absurdas" no estilo do marketing chinês.Eu estava muito interessado na oportunidade de rastrear um sonho e acordar na fase certa. Muitos de meus amigos compraram rastreadores de fitness baratos justamente por causa dessa função e ficaram satisfeitos com a mi band e similares. Sempre me faltava uma tela, mas aqui está a multifuncional.No meu trabalho, muitas vezes tenho que desenvolver aplicativos simples para Android; decidi que, se eu não tiver a funcionalidade do meu aplicativo nativo, escreverei o meu.O pacote chegou bem rápido e eu imediatamente corri para estudar uma pulseira maravilhosa. Após uma hora de jogo com o aplicativo Zeroner, que, de acordo com as instruções, deve ser instalado no meu dispositivo Android, percebi que a funcionalidade é bastante ruim e triste. O Zeroner, como todos os outros fabricantes, focado na contagem de etapas e calorias, exibindo belos gráficos, possui uma função de pesquisa por telefone (falarei sobre isso mais adiante), pode notificá-lo sobre uma chamada recebida, a chegada de uma mensagem no facebook e whatsapp e envia notificações de UM de qualquer aplicativo selecionado , que será considerado como um aplicativo para SMS.A vibração da pulseira é muito controversa, eles escrevem nos fóruns que ela é bastante fraca, alguns dizem que é normal. Para mim, poderia ter sido mais forte. A pulseira reage ao gesto "Assista ao relógio". Se você olhar para a pulseira como um relógio, levantando a mão e dobrando o cotovelo, a tela será ligada automaticamente e mostrará a hora ou a notificação perdida.
Em geral, sem hesitar, decidi escrever meu aplicativo, com notificações, vibração e sincronização. Vou em frente, demorou 4 dias e várias noites longas ...Para negócios
Dado que, com o Bluetooth, eu não sou um dedo do pé azul, decidi tentar interceptar os dados trocados entre o telefone e a pulseira com um tolo. Para fazer isso, entrei na guia Desenvolvedores e ative a caixa de seleção "Habilitar HCI Bluetooth Broadcast Log" . Depois de ativar esta opção, todo o despejo da comunicação do Android com qualquer dispositivo Bluetooth é adicionado ao arquivo /sdcard/Android/data/btsnoop_hci.log (o caminho pode mudar para diferentes dispositivos, o nome do arquivo parece ser sempre o mesmo).Depois de baixar o WireShark, comecei a estudar os logs de comunicação com a pulseira e vi algo semelhante a isso:
depois de passar quase duas horas estudando os logs, conduzindo vícios, protocolos do google na Internet, percebi que esse caminho não é para mim.Como meu telefone interpretou a pulseira como um dispositivo BLE comum e a mostrou na seção de dispositivos conectados, decidi usar exemplos de trabalho com o BLE no Android SDK.Depois de clonar o repositório https://github.com/googlesamples/android-BluetoothLeGatt , coloque o Android Studio no umbigo com as fontes, colete e inicie o aplicativo. ( Link para a descrição do SDK do Android com Bluetooth LE )Acabou como nas fotos do github:
Depois de executar a verificação, o aplicativo não viu o dispositivo. Aconteceu que o aplicativo nativo conectado à pulseira não permitiu que o BLE encontrasse o dispositivo. Tudo foi decidido pela simples remoção do Zeroner, era possível simplesmente desconectar, mas era mais confiável destruí-lo completamente.E assim, o Bluetooth LE é uma tecnologia que se baseia em dispositivos com baixo consumo de energia e é usada em sensores, etiquetas e muitos outros dispositivos com novos ventiladores. A base desta tecnologia é o perfil de atributo genérico (GATT) , este é um perfil Bluetooth que permite trocar pequenos pedaços de dados, "atributos" . Não vou escrever sobre como tudo funciona por um longo tempo, há muitas informações no hub e na internet, nas quais também tive que procurar soluções.Eu esperava que todos os dados necessários fossem armazenados nas características e descritores da pulseira, e eu pudesse receber e registrar dados sem problemas. Eu estava errado ... Oaplicativo BLE de teste me mostrou apenas 4 serviços:0000180f-0000-1000-8000-00805f9b34fb00001800-0000-1000-8000-00805f9b34fb0000ff20-0000-1000-8000-00805f9b34fb00001801-0000-1000-8000-00805f9b34fbtinham muito poucas características, aquelas que liam retornavam nulos ou zeros e a escrita era inútil. Mas fui encorajado por conseguir conectar e obter pelo menos alguns dados.Além disso, decidi que não era possível agir cegamente e resolvi dissecar o aplicativo Zeroner. Tendo acumulado alguns decompiladores de APK on-line na Internet, alimentei o zeroner.apk e obtive 2 arquivos zip na saída.O primeiro era uma versão JADX e o segundo continha o resultado do apktool.Revirando o código fonte Fiquei horrorizado com o código chinês(embora no meu trabalho eu o encontre muitas vezes na forma de back-end para sites e serviços, mas ele nunca deixa de me surpreender com sua tortuosidade e ingenuidade, mas de qualquer maneira, é terrivelmente difícil de ler).Depois de muita pesquisa, finalmente encontrei o arquivo WristBandDevice.java , que estava no caminho com.kunekt / bluetooth.Nesta aula, todo o trabalho com o dispositivo estava apenas escondido, mas novamente, uma emboscada estava me esperando.Como se viu mais tarde, no firmware anterior da pulseira, mais serviços foram usados nas características (como eu esperava anteriormente), mas mais tarde, os desenvolvedores deixaram apenas 2, um para leitura e o segundo para gravação. Todos os comandos são transmitidos em um pacote.Não era tão fácil entender como o pacote deveria ser, decidi determinar claramente o que quero da pulseira em primeiro lugar, para poder começar a rastrear as chamadas de função. E eu queria exibir mensagens personalizadas na pulseira.Sem hesitar, entrei no com.kunekt / receiver / CallReceiver.java , como as chamadas recebidas eram exibidas de maneira muito estável e mesmo com caracteres russos, decidi que era um ótimo começo, pois já havia encontrado um evento de chamada recebida no Android, a ideia de como isso pode funcionar já foi.Abrindo o arquivo, vi o seguinte:Um grande pedaço de código chinêspublic 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:
}
}
Aqui, vemos claramente que existem 2 opções para a API e os nomes que eles têm são newAPI muito lógico e a segunda, respectivamente, oldAPI. Em toda essa abundância de condições, eu estava interessado apenas em uma linha repetida:WristBandDevice.getInstance (context) .writeWristBandPhoneAlertNew (contexto, contact.getDisplayName .....)Era isso que eu estava procurando. Olhando para o futuro, direi que o iWown também possui os modelos i5 + e i6, eles têm uma tela maior e, portanto, mais caracteres são colocados, para isso todas essas verificações são necessárias. não está claro por que eles não escreveram uma classe ou algo assim, talvez sejam brincadeiras de descompilador, mas esse código é repetido em muitos lugares.Passando para a definição dessa função, vi o seguinte: public void writeWristBandPhoneAlertNew(Context context, String displayName) {
writeAlertNew(context, displayName, 1);
}
public void writeWristBandSmsAlertNew(Context context, String displayName) {
writeAlertNew(context, displayName, 2);
}
Ótimo, ele usa a mesma função para enviar texto, apenas com parâmetros diferentes. Todas as funções com a palavra New são apenas nossa opção, porque, como foi mostrado acima, eu tenho uma nova API.Contente, indo para a definição da função writeAlertNew , vi o seguinte: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));
}
}
Ficou claro que algumas funções usadas aqui me separam do lucro.writeWristBandDataByte - forma um pacote com uma mensagem para a pulseira, é interessante que exista uma função especial form_Header (3, 1) , que forma o cabeçalho do pacote, pelo qual a pulseira entende o que deseja dele. 3 é o número do grupo de equipes e 1 é o próprio timepublic static byte form_Header(int grp, int cmd) {
return (byte) (((((byte) grp) & 15) << 4) | (((byte) cmd) & 15));
}
A função é simples, copiada para o meu projeto sem alterações. O próximo foiNewAgreementBackgroundThreadManager.getInstance (). AddTask (new WriteOneDataTask (context, writeData));Como se viu, nada de incomum, o aplicativo cria um fluxo no qual a fila de pacotes para envio é constantemente verificada, se um pacote aparece na fila, o fluxo grava na característica do dispositivo, se houver mais de um pacote, ele os envia com um atraso de 240 milissegundos.A seguir, veio o mais incompreensível:PebbleBitmap.fromString (context, String.valueOf (e), 8, 1) .data)Por que a classe é chamada dessa maneira não é clara, porque esse dispositivo não tem nada a ver com o Pebble. Abrindo a fonte da classe, vi o seguinte:Origem da classe 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));
}
}
Após uma longa reflexão, cheguei à conclusão de que fromString cria uma imagem com uma letra usando uma fonte específica (incorporada no aplicativo) e depois converte os pixels em 0 ou 1, dependendo do preenchimento, para que a letra O tenha a seguinte aparência:0001110001100011011000110110001100011100Sem realmente entrar em detalhes, copiei tudo no meu projeto usando o exemplo BLE GATT do Google.E ... Oh, um milagre !!! A pulseira vibrou! Mas a mensagem não apareceu, uma linha vazia e um ícone de chamada recebida.Descobriu-se que um monte de verificações de tamanho não é casual, a pulseira ignora estupidamente mensagens muito longas e mensagens com comprimento de 11 caracteres, embora 12 sejam exibidas normalmente. Algumas horas de dança em torno dessas funções finalmente renderam resultados, aprendi a exibir texto em russo e em inglês e, ao mesmo tempo, aprendi que existem vários modos de operação no grupo de mensagens:- Chamada recebida. O telefone é exibido, o nome do chamador e a pulseira vibram
- Mensagem O ícone de texto e envelope é exibido. Quando vibra 2 vezes
- Nublado. O mesmo que 2, mas em vez do envelope, o ícone da nuvem
- O erro O mesmo que 2, que apenas um ícone com um ponto de exclamação.
Tendo ensinado meu aplicativo a me enviar notificações de diferentes aplicativos, whatsapp, vk, viber, telegrama e outros, decidi que era hora de ensinar a pulseira a responder às chamadas recebidas e, finalmente, usar um único botão para redefinir as recebidas.Não vou descrever esse processo, o post acabou inchado, só posso dizer que não foi difícil responder às mensagens recebidas, mas usar o botão não foi.Todas as mensagens recebidas do bracelete, Zeroner interceptou em uma classe especial. o pacote recebido tinha o cabeçalho do grupo de comandos e o número do comando, após uma longa depuração e testes, pesquei os grupos usados e, em seguida, encontrei uma descrição no código Zeroner.Grupos e equipes de pulseiras// 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
Graças a isso, pude realizar um trabalho completo com a pulseira. Eu posso obter dados sobre etapas, sobre sono. Eu posso gerenciar as configurações, definir alarmes. Consegui obter a designação de byte do pacote das classes que armazenam dados no banco de dados, implementei todos eles em casa.No final
Depois de pensar um pouco, decidi que tudo isso poderia ser útil não apenas para mim e escrevi um novo aplicativo que contém todos os dados e funções necessários para trabalhar com a pulseira, além de implementar uma interface simples para enviar notificações de qualquer aplicativo para a pulseira.WiliX iWown for GeekDesde então, muito tempo se passou e, muitos após a atualização para o Android 6, o aplicativo parou de funcionar. Também não funciona de forma estável com o firmware de pulseiras da 2ª versão. Mas espero encontrar tempo para revisão.O código fonte é postado no GitHub . Você pode se divertir e se divertir como quiser. Todas as solicitações de recebimento após a revisão serão aceitas e os testes serão enviados imediatamente para o Google Play.No momento, o aplicativo pode:- Exibir notificações de qualquer aplicativo
- BT
Uma conexão com o Google Fit foi implementada para salvar os dados de treinamento, mas, como não escolhi o SDK para o Fit, vasculhei vários links e fóruns, mas ainda não entendi como fazer o ajuste exibir dados de dispositivos personalizados. Não está claro, então, por que essa função está lá.Se alguém trabalhou com o Google Fit e sabe como fazê-lo usar os dados de um sensor personalizado para exibir gráficos, conte-me nos comentários ou escreva-me para os usuários e ficarei muito agradecido!Também foi uma ideia conectar a pulseira ao Sleep como Adnroid. Na verdade, para monitorar o sono, uma pulseira foi comprada. Mas, como se viu, o iWown pode retornar apenas a duração das fases do sono. Ou seja, dados já calculados a partir do acelerômetro.E o Sleep como Android requer dados simples do acelerômetro e com uma frequência desejada de 10 segundos.Em geral, o resultado. Convido desenvolvedores e proprietários a apoiar o projeto com seu código, dicas e tudo o mais. Deixe o pull-requist, problema no Github.O aplicativo acabou sendo muito popular no exterior; os estrangeiros costumam escrever para mim, pedindo para adicionar / corrigir / traduzir alguma coisa.A propósito, o iWown i5 possui vários clones, com firmware semelhante:Vidonn X5Harper BFB-301Excelvan i5Referências
Google Play - iWown para GeekRepository no GitHubDebate em w3bsit3-dns.comPS A partir da 5ª versão, uma categoria adicional apareceu nos andróides na cortina, que não aparece na tela de bloqueio.Alguém pode me dizer como transferir minha notificação para esta categoria? Obrigada