Histoire de l'ingénierie inverse d'un bracelet de fitness chinois

Achetez un bracelet chinois, soyez déçu par le logiciel officiel, écrivez le vôtre!




Cette histoire attend sa publication depuis plus de six mois, pendant ce temps, beaucoup de choses ont changé, le firmware et les logiciels ont été mis à jour et beaucoup de mes développements sont déjà devenus obsolètes.

Préface


Le travail actif d'un grand nombre d'entreprises dans le domaine de la technologie portable et des montres intelligentes n'a pas laissé la paix à mon âme. J'ai vu un grand potentiel dans les appareils portables avec un écran. Non, je ne parle pas de compter les pas et d'autres choses de fitness, ils sont certainement cool, mais si loin du banal «Félicitations! Vous avez marché 4 km, fait 20 km + de pas! » et de beaux graphiques de progrès et de régression, ils n’ont rien trouvé de spécial.
Mais le fait que je puisse recevoir des notifications directement sur l'écran de mon poignet est pratique. Si je peux également interagir avec lui ou quelque chose à proximité en appuyant sur les boutons 1-2-3 - c'est encore plus cool.

Une fois de plus, labourant les étendues d'aliexpress, je suis tombé sur un bracelet de fitness iWown i5. Il a immédiatement attiré mon attention à un prix incroyablement bas (à l'époque environ 800r avec la livraison gratuite) et la présence d'un écran OLED. Après avoir lu attentivement la description du vendeur et les avis clients, j'ai décidé de commander ce miracle.

Spécifications déclarées (description de la traduction d'aliexpress):
  • Affichage: OLED
  • Batterie: Li-Polymer
  • Chargement: chargement USB standard
  • Autonomie en veille: plus de 72 heures
  • Dimensions: 69,1 * 15,8 * 11,2 mm
  • Poids: 18g
  • Matériel: bracelet ABS, fermoir en acier
  • Résistance à l'eau: IP55
  • Température de fonctionnement: -20 ° C ~ + 45 ° C
  • Température de fonctionnement du support flash: -40 ° C ~ + 45 ° C


Capacités:
  • Moniteur de sport: tout le temps enregistre les pas et les mouvements, la distance parcourue et les calories brûlées, tous les chiffres sont calculés en tenant compte de votre poids et de votre taille.
  • Surveillance de la qualité du sommeil: pendant que vous dormez, le tracker enregistre les phases du sommeil, déterminant le sommeil profond et rapide, 8 groupes d'alarmes silencieuses vous permettent de vous réveiller sans déranger les autres membres de la famille
  • Synchronisation sans fil Bluetooth 4.0 à faible puissance
  • Prise en charge de la synchronisation PC via USB
  • Protection IP55: protège l'appareil sous de fortes pluies, mais pas plus

et d'autres avantages "farfelus" dans le style du marketing chinois

J'ai été très intéressé par l'opportunité de suivre un rêve et de me réveiller dans la bonne phase. Beaucoup de mes amis ont acheté des trackers de fitness bon marché précisément à cause de cette fonction et étaient satisfaits de mi band et autres. Il me manquait toujours un écran, mais ici, c'est tout-en-un.
Dans mon travail, je dois souvent développer des applications simples pour Android, j'ai décidé que si je n'ai pas assez de fonctionnalités de mon application native, j'écrirai la mienne.

Le colis est arrivé assez rapidement et je me suis immédiatement précipité pour étudier un magnifique bracelet. Après une heure de jeu avec l'application Zeroner, qui selon les instructions doit être installée sur mon appareil Android, j'ai réalisé que la fonctionnalité est plutôt pauvre et triste. Zeroner, comme tous les autres fabricants, se concentre sur le comptage des étapes et des calories, affiche de beaux graphiques, dispose d'une fonction de recherche par téléphone (j'en parlerai plus tard), peut vous avertir d'un appel entrant, de l'arrivée d'un message sur Facebook et WhatsApp, et envoie des notifications à partir de l'une des applications sélectionnées , qui sera considérée comme une application SMS.
La vibration du bracelet est très controversée, ils écrivent sur les forums qu'il est plutôt faible, certains disent que c'est normal. Pour moi, cela aurait pu être plus fort. Le bracelet réagit au geste «Regarder la montre», si vous regardez le bracelet comme une montre, en levant la main et en vous pliant au coude, l'écran s'allumera automatiquement et affichera l'heure ou la notification manquée.



En général, sans hésitation, j'ai décidé d'écrire mon application, avec notifications, vibration et synchronisation. Je vais courir, il a fallu 4 jours de congé et plusieurs longues soirées ...

Aux entreprises


Étant donné qu'avec Bluetooth, je ne suis pas un orteil bleu, j'ai décidé d'essayer d'intercepter les données échangées entre le téléphone et le bracelet avec un imbécile. Pour ce faire, je suis monté dans l'onglet pour les développeurs et j'ai activé la case à cocher "Activer le journal de diffusion Bluetooth HCI" . Après avoir activé cette option, le vidage complet de la communication de l'androïde avec tous les appareils Bluetooth est ajouté au fichier /sdcard/Android/data/btsnoop_hci.log (le chemin peut changer pour différents appareils, le nom du fichier semble être toujours le même).
Après avoir téléchargé WireShark, j'ai commencé à étudier les journaux de communication avec le bracelet et j'ai vu quelque chose de similaire à cela:



après avoir passé près de deux heures, à étudier les journaux, à mener des addictions, des protocoles Google sur Internet, j'ai réalisé qu'un tel chemin n'était pas pour moi.

Étant donné que mon téléphone a néanmoins interprété le bracelet comme un appareil BLE normal et l'a montré dans la section des appareils connectés, j'ai décidé d'utiliser des exemples de travail avec BLE à partir du SDK Android.
Après avoir cloné le référentiel https://github.com/googlesamples/android-BluetoothLeGatt , mis Android Studio sur le nombril avec les sources, construit et lancé l'application. ( Lien vers la description du SDK Android avec Bluetooth LE )

Il s'est avéré que dans les images du github:


Après avoir exécuté le scan, l'application n'a pas vu l'appareil. Il s'est avéré que l'application native se connectant au bracelet ne permettait pas à BLE de trouver l'appareil. Tout a été décidé par la simple suppression de Zeroner, il était possible de se déconnecter simplement, mais il était plus fiable de le démolir complètement.

Et donc, Bluetooth LE est une technologie qui s'appuie sur des appareils à faible consommation d'énergie, est utilisée dans les nouveaux capteurs, balises et de nombreux autres appareils. La base de cette technologie est le profil d'attribut générique (GATT) , c'est un profil Bluetooth qui vous permet d'échanger de petites données, des «attributs» . Je n'écrirai pas sur le fonctionnement de tout cela pendant longtemps. Sur Internet et sur Internet, il y a beaucoup d'informations sur lesquelles j'ai dû chercher des solutions.

J'espérais que toutes les données dont j'avais besoin étaient stockées dans les caractéristiques et les descripteurs du bracelet, et je pourrais recevoir et enregistrer des données sans aucun problème. J'avais tort ... L'

application de test BLE ne m'a montré que 4 services:
0000180f-0000-1000-8000-00805f9b34fb
00001800-0000-1000-8000-00805f9b34fb
0000ff20-0000-1000-8000-00805f9b34fb
00001801-0000-1000-8000-00805f9b34fb

elles avaient très peu de caractéristiques, celles qui lisaient retournées nulles ou nulles, et écrire était inutile. Mais j'ai été encouragé d'avoir pu me connecter et obtenir au moins quelques données.

De plus, j’ai décidé qu’il n’était pas possible d’agir à l’aveugle et j'ai décidé de disséquer l’application Zeroner. Après avoir accumulé quelques décompilateurs APK en ligne sur Internet, je les ai alimentés zeroner.apk et obtenu 2 archives zip sur la sortie.
La première était une version JADX, et la seconde contenait le résultat d'apktool.

En fouillant dans le code source, j'ai été horrifié par le code chinois(bien que dans mon travail, je le rencontre souvent sous la forme de backends pour des sites et des services, mais il ne cesse de m'étonner par sa tortuosité et son ingéniosité, mais de toute façon, c'est terriblement difficile à lire)
Après de nombreuses recherches, j'ai finalement trébuché sur le fichier WristBandDevice.java , qui se trouvait sur le chemin com.kunekt / bluetooth.
Dans cette classe, tout le travail avec l'appareil se cachait, mais encore une fois, une embuscade m'attendait.
Comme il s'est avéré plus tard, dans le firmware précédent du bracelet, plus de services étaient utilisés dans les caractéristiques (comme je m'y attendais précédemment), mais plus tard, les développeurs n'en ont laissé que 2, un pour la lecture, le second pour l'écriture. Toutes les commandes sont transmises dans un seul paquet.

Ce n'était pas si facile de comprendre à quoi le paquet devrait ressembler, j'ai décidé de déterminer clairement ce que je voulais du bracelet en premier lieu, afin de pouvoir commencer à tracer les appels de fonction. Et je voulais afficher des messages personnalisés sur le bracelet.
Sans hésitation, j'ai grimpé dans com.kunekt / receiver / CallReceiver.java , car les appels entrants étaient affichés de manière très stable et même avec des caractères russes, j'ai décidé que c'était un bon début, étant donné que j'avais déjà rencontré un événement d'appel entrant dans Android, l'idée de comment cela pourrait fonctionner a déjà été.

En ouvrant le dossier, j'ai vu ceci:
Un gros morceau de code chinois
public void onReceive(Context context, Intent intent) {
        Log.e(this.TAG, "+++ ON RECEIVE +++");
        switch (((TelephonyManager) context.getSystemService("phone")).getCallState()) {
            case C08571.POSITION_OPEN /*0*/:
                if (ZeronerApplication.newAPI) {
                    BackgroundThreadManager.getInstance().addTask(new WriteOneDataTask(context, WristBandDevice.getInstance(context).setPhoneStatue()));
                }
            case BitmapCacheManagementTask.MESSAGE_INIT_DISK_CACHE /*1*/:
                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 /*2*/:
                if (ZeronerApplication.newAPI) {
                    BackgroundThreadManager.getInstance().addTask(new WriteOneDataTask(context, WristBandDevice.getInstance(context).setPhoneStatue()));
                }
            default:
        }
    }



Ici, nous voyons clairement qu'il existe 2 variantes de l'API et les noms qu'elles portent sont newAPI très logiques et le second, respectivement, oldAPI. Dans toute cette abondance de conditions, je ne m'intéressais qu'à une seule ligne répétitive:
WristBandDevice.getInstance (context) .writeWristBandPhoneAlertNew (context, contact.getDisplayName .....)

C'était ce que je cherchais. Pour l'avenir, je dirai que iWown a également des modèles i5 + et i6, ils ont un écran plus grand et en conséquence plus de caractères sont placés, pour cela toutes ces vérifications sont nécessaires. ce n'est pas clair pourquoi ils n'ont pas écrit une classe ou quelque chose comme ça, peut-être que ce sont des farces de décompilation, mais ce code est répété à de nombreux endroits.
Passant à la définition de cette fonction, j'ai vu ceci:

    public void writeWristBandPhoneAlertNew(Context context, String displayName) {
        writeAlertNew(context, displayName, 1);
    }

    public void writeWristBandSmsAlertNew(Context context, String displayName) {
        writeAlertNew(context, displayName, 2);
    }


Génial, il utilise la même fonction pour envoyer du texte, juste avec des paramètres différents. Toutes les fonctions avec le mot Nouveau sont juste notre option, car comme il s'est avéré ci-dessus, j'ai une nouvelle API.

En passant volontiers à la définition de la fonction writeAlertNew , j'ai vu ce qui suit:
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));
        }
    }


Il était clair que quelques fonctions utilisées ici me séparent du profit.
writeWristBandDataByte - forme un package avec un message pour le bracelet, il est intéressant de noter qu'il existe une fonction spéciale form_Header (3, 1) , qui forme l'en-tête du package, par laquelle le bracelet comprend ce qu'ils en veulent. 3 est le numéro du groupe d'équipe et 1 est l'équipe elle-même
public static byte form_Header(int grp, int cmd) {
        return (byte) (((((byte) grp) & 15) << 4) | (((byte) cmd) & 15));
    }


La fonction est simple, copiée dans mon projet sans modifications. Vient ensuite

NewAgreementBackgroundThreadManager.getInstance (). AddTask (new WriteOneDataTask (context, writeData));

Comme il s'est avéré, rien d'inhabituel, l'application crée un flux dans lequel la file d'attente des paquets à envoyer est constamment vérifiée, si un paquet apparaît dans la file d'attente, le flux écrit dans la caractéristique de périphérique donnée, s'il y a plus d'un paquet, il les envoie avec un retard de 240 millisecondes.
Vient ensuite le plus incompréhensible:

PebbleBitmap.fromString (context, String.valueOf (e), 8, 1) .data) La raison

pour laquelle la classe est appelée de cette façon n'est pas claire, car cet appareil n'a rien à voir avec Pebble. En ouvrant la source du cours, j'ai vu ce qui suit:

Source de classe PebbleBitmap
public 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));
    }
}



Après une longue réflexion, je suis arrivé à la conclusion que fromString crée une image avec une lettre en utilisant une certaine police (qui est intégrée dans l'application), puis convertit les pixels en 0 ou 1 en fonction du remplissage, de sorte que la lettre O ressemblera à ceci:
00011100
01100011
01100011
01100011
00011100

Sans entrer dans les détails, j'ai tout copié dans mon projet en utilisant l'exemple BLE GATT de Google.
Et ... Oh, un miracle !!! Le bracelet a vibré! Mais le message n'est pas apparu, une ligne vide et une icône d'appel entrant.
Il s'est avéré qu'un tas de vérifications de taille ne sont pas occasionnelles, le bracelet ignore bêtement les messages trop longs et les messages dont la longueur est de 11 caractères, bien que 12 s'affichent normalement. Quelques heures de danse autour de ces fonctions ont finalement donné des résultats, j'ai appris à afficher le texte russe et anglais, et en même temps, j'ai appris qu'il y a plusieurs modes de fonctionnement dans le groupe de messages:
  1. Appel entrant. Le combiné s'affiche, le nom de l'appelant et le bracelet vibrent
  2. Message L'icône de texte et d'enveloppe s'affiche. Quand il vibre 2 fois
  3. Nuageux. Identique à 2, mais au lieu de l'enveloppe, l'icône du nuage
  4. L'erreur. Identique à 2, que seule une icône avec un point d'exclamation.




Après avoir appris à ma demande à m'envoyer des notifications à partir de différentes applications, WhatsApp, vk, viber, télégramme et autres, j'ai décidé qu'il était temps d'apprendre au bracelet à répondre aux appels entrants et, enfin, à utiliser un seul bouton pour réinitialiser les appels entrants.

Je ne décrirai pas ce processus, le message s’est avéré gonflé, je dirai simplement qu’il n’était pas difficile de répondre aux messages entrants, mais que l’utilisation du bouton ne l’était pas.

Tous les messages entrants du bracelet, Zeroner intercepté dans une classe spéciale. le paquet entrant avait l'en-tête du groupe de commandes et le numéro de commande, après un long débogage et des tests, j'ai repéré les groupes utilisés, puis j'ai trouvé une description dans le code Zeroner.

Groupes et équipes de bracelets
// 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



Grâce à cela, j'ai pu réaliser un travail à part entière avec le bracelet. Je peux recevoir des données sur les étapes, sur le sommeil. Je peux gérer les paramètres, définir des alarmes. J'ai réussi à obtenir la désignation d'octet du package à partir des classes qui stockent les données dans la base de données, je les ai toutes implémentées à la maison.

En fin de compte


Après un peu de réflexion, j'ai décidé que tout cela pourrait être utile non seulement pour moi et j'ai écrit une nouvelle application qui contient toutes les données et fonctions nécessaires pour travailler avec le bracelet, ainsi que met en œuvre une interface simple pour envoyer des notifications depuis n'importe quelle application au bracelet.

WiliX iWown for Geek

Depuis lors, beaucoup de temps s'est écoulé, et beaucoup après la mise à jour vers Android 6, l'application a cessé de fonctionner. Il ne fonctionne pas non plus de manière stable avec le firmware des bracelets de la 2ème version. Mais j'espère trouver du temps pour la révision.

Le code source est publié sur GitHub . Vous pouvez bifurquer et vous amuser comme vous le souhaitez. Toutes les demandes d'extraction après examen seront acceptées et après que les tests seront immédiatement téléchargés sur Google Play.

À l'heure actuelle, l'application peut:
  • Afficher les notifications de n'importe quelle application
  • BT


Une connexion à Google Fit a été mise en place pour enregistrer les données de formation, mais, comme je n'ai pas choisi le SDK pour Fit, j'ai fouillé un tas de liens et de forums, mais je ne comprenais toujours pas comment faire l'ajustement des données à partir d'appareils personnalisés. Il n'est pas clair alors pourquoi cette fonction est même là.
Si quelqu'un a travaillé avec Google Fit et sait comment l'amener à utiliser les données d'un capteur personnalisé pour afficher des graphiques, dites-le-moi dans les commentaires ou écrivez-moi, les utilisateurs et je serai très reconnaissant!

C'était aussi une idée de connecter le bracelet à Sleep as Adnroid. En fait, pour surveiller le sommeil, un bracelet a été acheté. Mais, il s'est avéré que iWown ne peut renvoyer que la durée des phases de sommeil. Autrement dit, les données déjà calculées de l'accéléromètre.
Et Sleep as Android nécessite des données nues de l'accéléromètre, et avec une fréquence souhaitée de 10 secondes.

En général, le résultat. J'invite les développeurs et les propriétaires à soutenir le projet avec leur code, leurs conseils et tout. Laissez pull-requist, émettez sur Github.
L'application s'est avérée très populaire à l'étranger, les étrangers m'écrivent souvent pour me demander d'ajouter / corriger / traduire quelque chose.

Soit dit en passant, iWown i5 possède plusieurs clones, avec un firmware similaire:
Vidonn X5
Harper BFB-301
Excelvan i5

Les références

Google Play - iWown pour Geek
Repository sur GitHub
Discussion sur w3bsit3-dns.com

PS À partir de la 5e version, une catégorie supplémentaire est apparue dans les androïdes dans le rideau, qui n'apparaît pas sur l'écran de verrouillage.
Quelqu'un peut-il me dire comment transférer ma notification dans cette catégorie? Je vous remercie!

Source: https://habr.com/ru/post/fr391109/


All Articles