تاريخ الهندسة العكسية لسوار اللياقة البدنية الصيني

شراء سوار صيني ، خاب أملي في البرنامج الرسمي ، اكتب بنفسك!




هذه القصة تنتظر نشرها منذ أكثر من ستة أشهر ، وخلال هذا الوقت تغير الكثير ، وتم تحديث البرامج الثابتة والبرامج وأصبحت العديد من تطورياتي قديمة بالفعل.

مقدمة


إن العمل النشط لعدد كبير من الشركات في مجال التكنولوجيا القابلة للارتداء والساعات الذكية لم يترك السلام لروحي. رأيت إمكانات كبيرة في الأجهزة القابلة للارتداء مع شاشة. لا ، أنا لا أتحدث عن عد الخطوات وأشياء أخرى تتعلق باللياقة البدنية ، فهي بالتأكيد رائعة ، ولكنها بعيدة كل البعد عن "التهاني!" لقد مشيت 4 كيلومتر ، واتخذت 20 ألف خطوة! " والرسوم البيانية الجميلة للتقدم والانحدار ، لم يأتوا بأي شيء خاص.
لكن حقيقة أنني أستطيع تلقي إشعارات مباشرة على الشاشة على معصمي أمر مريح. إذا تمكنت أيضًا من التفاعل معه بطريقة أو بأخرى أو مع شيء قريب من خلال الضغط على أزرار 1-2-3 - فهذا أكثر برودة.

مرة أخرى حرثت مساحات aliexpress ، صادفت سوار iWown i5 للياقة البدنية. جذب انتباهي على الفور بسعر منخفض بشكل لا يصدق (في ذلك الوقت حوالي 800r مع الشحن المجاني) ووجود شاشة OLED. بعد قراءة وصف البائع ومراجعات العملاء بعناية ، قررت طلب هذه المعجزة.

المواصفات المعلنة (وصف الترجمة من aliexpress):
  • العرض: OLED
  • البطارية: ليثيوم بوليمر
  • الشحن: شحن USB قياسي
  • وقت الانتظار: أكثر من 72 ساعة
  • الأبعاد: 69.1 * 15.8 * 11.2 ملم
  • الوزن: 18 جرام
  • المواد: حزام ABS ، قفل فولاذي
  • مقاومة الماء: IP55
  • درجة حرارة التشغيل: -20 ° C ~ + 45 ° C
  • وسائط فلاش درجة حرارة التشغيل: -40 ° C ~ + 45 ° C


القدرات:
  • مراقب الرياضة: يسجل كل الوقت الخطوات والحركات ، والمسافة المقطوعة والسعرات الحرارية المحروقة ، ويتم حساب جميع الأرقام مع مراعاة وزنك وطولك.
  • مراقبة جودة النوم: أثناء النوم ، يسجل المتتبع مراحل النوم ، ويحدد النوم العميق والسريع ، 8 مجموعات من الإنذارات الصامتة تسمح لك بإيقاظك دون إزعاج أفراد العائلة الآخرين
  • مزامنة لاسلكية 4.0 بلوتوث منخفضة الطاقة
  • دعم مزامنة الكمبيوتر عبر USB
  • حماية IP55: يحمي الجهاز في الأمطار الغزيرة ، ولكن ليس أكثر

وغيرها من المزايا "بعيدة المنال" في أسلوب التسويق الصيني

كنت مهتمًا جدًا بفرصة تتبع الحلم والاستيقاظ في المرحلة الصحيحة. اشترى العديد من أصدقائي متتبعين للياقة البدنية غير مكلفة بسبب هذه الوظيفة على وجه التحديد وكانوا سعداء بفرقة Mi وما شابه. لطالما افتقرت إلى شاشة فيها ، ولكن هنا الكل في واحد.
في عملي ، غالبًا ما أضطر إلى تطوير تطبيقات بسيطة لنظام Android ، وقررت أنه إذا افتقرت إلى وظائف تطبيقي الأصلي ، فسأكتب تطبيقاتي الخاصة.

وصلت الحزمة بسرعة كبيرة وهرعت على الفور لدراسة سوار رائع. بعد ساعة من اللعب مع تطبيق Zeroner ، والذي وفقًا للتعليمات التي يجب تثبيتها على جهازي الذي يعمل بنظام Android ، أدركت أن الوظائف ضعيفة وحزينة. Zeroner ، مثل جميع الشركات المصنعة الأخرى ، التي تركز على حساب الخطوات والسعرات الحرارية ، وعرض الرسوم البيانية الجميلة ، ولها وظيفة بحث عن الهاتف (سأتحدث عن هذا لاحقًا) ، يمكنها إعلامك بمكالمة واردة ، ووصول رسالة على facebook و whatsapp ، وإرسال إشعارات من ONE لأي تطبيق محدد ، والذي سيتم اعتباره كتطبيق للرسائل القصيرة.
اهتزاز السوار مثير للجدل للغاية ، يكتبون في المنتديات أنه ضعيف إلى حد ما ، ويقول البعض أنه طبيعي. بالنسبة لي ، كان يمكن أن يكون أقوى. يكون للسوار رد فعل على إيماءة "مشاهدة الساعة" ، إذا نظرت إلى السوار مثل الساعة ، فإن رفع يدك والانحناء عند الكوع سيتم تشغيل الشاشة تلقائيًا وإظهار الوقت أو الإخطار الفائت.



بشكل عام ، دون تردد ، قررت كتابة طلبي ، مع الإخطارات والاهتزاز والمزامنة. سأمضي قدما ، استغرق الأمر 4 أيام راحة وعدة أمسيات طويلة ...

إلى الأعمال


نظرًا لأنني لست مع إصبع قدم أزرق ، فقد قررت محاولة اعتراض البيانات المتبادلة بين الهاتف والسوار مع أحمق. للقيام بذلك ، صعدت إلى علامة التبويب للمطورين ، وقمت بتشغيل مربع الاختيار "تمكين سجل بث Bluetooth HCI" . بعد تمكين هذا الخيار ، تتم إضافة تفريغ اتصال Android بالكامل مع أي أجهزة Bluetooth إلى ملف /sdcard/Android/data/btsnoop_hci.log (قد يتغير المسار للأجهزة المختلفة ، ويبدو أن اسم الملف هو نفسه دائمًا).
بعد تنزيل WireShark ، بدأت في دراسة سجلات التواصل مع السوار ورأيت شيئًا مشابهًا لهذا:



بعد قضاء ساعتين تقريبًا ، في دراسة السجلات ، وإجراء الإدمان ، وبروتوكولات google على الإنترنت ، أدركت أن هذا المسار ليس لي.

نظرًا لأن هاتفي يفسر السوار كجهاز BLE عادي ويظهره في قسم الأجهزة المتصلة ، فقد قررت استخدام أمثلة للعمل مع BLE من Android SDK.
بعد استنساخ المستودع https://github.com/googlesamples/android-BluetoothLeGatt ، قم بتعيين Android Studio في السرة مع المصادر ، وجمع التطبيق وتشغيله. ( رابط إلى وصف Android SDK مع Bluetooth LE )

اتضح كما في الصور من github:


بعد تشغيل الفحص ، لم ير التطبيق الجهاز. اتضح أن التطبيق الأصلي المتصل بالسوار لم يسمح لـ BLE بالعثور على الجهاز. تم تحديد كل شيء عن طريق إزالة Zeroner ببساطة ، يمكنك فقط إيقاف تشغيله ، ولكن من الأكثر موثوقية تدميره تمامًا.

وهكذا ، فإن تقنية Bluetooth LE هي تقنية مبنية على أجهزة ذات استهلاك منخفض للطاقة ، وتستخدم في أجهزة الاستشعار والعلامات والعديد من الأجهزة الأخرى. أساس هذه التقنية هو ملف تعريف السمات العام (GATT) ، وهو ملف تعريف Bluetooth يسمح لك بتبادل أجزاء صغيرة من البيانات ، "السمات" . لن أكتب حول كيفية عمل كل شيء لفترة طويلة. على الإنترنت وعلى الإنترنت ، هناك الكثير من المعلومات التي كان عليّ أيضًا البحث فيها عن حلول.

كنت أتمنى أن يتم تخزين جميع البيانات التي أحتاجها في خصائص واصف السوار ، ويمكنني تلقي البيانات وتسجيلها دون أي مشاكل. كنت مخطئا ...

أظهر لي اختبار BLE 4 خدمات فقط:
0000180f-0000-1000-8000-00805f9b34fb
00001800-0000-1000-8000-00805f9b34fb
0000ff20-0000-1000-8000-00805f9b34fb
00001801-0000-1000-8000-00805f9b34fb

لديهم القليل جدًا من الخصائص ، تلك التي تقرأ عادت باطلة أو أصفارًا ، وكانت الكتابة عديمة الفائدة. ولكن شجعتني أنني تمكنت من الاتصال والحصول على بعض البيانات على الأقل.

علاوة على ذلك ، قررت أنه لم يكن من الممكن التصرف بشكل أعمى وقررت تشريح تطبيق Zeroner. بعد أن جمعت اثنين من أجهزة فك APK APK على الإنترنت ، قمت بإطعامهم zeroner.apk وحصلت على أرشيفين مضغوطين على الإخراج.
الأول كان نسخة JADX ، والثاني يحتوي على نتيجة apktool.

تفشيت في شفرة المصدر ، لقد أصابني الرعب من الشفرة الصينية(على الرغم من أنني أواجهه في عملي في كثير من الأحيان على شكل خلفيات خلفية لمواقع الويب والخدمات ، لكنه لم يتوقف أبدًا عن دهشتي بسمعته وإبداعه ، ولكن على أي حال ، من الصعب جدًا قراءتها)
بعد الكثير من البحث ، صادفت أخيرًا ملف WristBandDevice.java ، الذي كان على المسار com.kunekt / bluetooth.
في هذا الفصل ، كان كل العمل مع الجهاز مختبئًا ، ولكن مرة أخرى ، كان هناك كمين ينتظرني.
كما اتضح لاحقًا ، في البرامج الثابتة السابقة للسوار ، تم استخدام المزيد من الخدمات في الخصائص (كما توقعت سابقًا) ، ولكن في وقت لاحق ، ترك المطورون فقط 2 ، واحدة للقراءة ، والثانية للكتابة. يتم إرسال جميع الأوامر في حزمة واحدة.

لم يكن من السهل فهم كيف يجب أن تبدو الحزمة ، فقررت أن أحدد بوضوح ما أريده من السوار في المقام الأول ، حتى أتمكن من البدء في تتبع مكالمات الوظيفة. وأردت عرض رسائل مخصصة على السوار.
دون تردد ، تسلقت إلى com.kunekt / receiver / CallReceiver.java ، حيث تم عرض المكالمات الواردة بشكل مستقر للغاية وحتى مع الأحرف الروسية ، قررت أن هذه كانت بداية رائعة ، نظرًا لأنني واجهت بالفعل حدث مكالمة واردة في Android ، فكرة كيف كان يعمل بالفعل.

عند فتح الملف رأيت هذا:
قطعة كبيرة من الكود الصيني
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:
        }
    }



هنا نرى بوضوح أن هناك خياران لواجهة برمجة التطبيقات والأسماء التي لديهم newAPI المنطقي للغاية ، والثاني ، على التوالي ، oldAPI. في جميع وفرة الظروف هذه ، كنت مهتمًا فقط بسطر واحد متكرر:
WristBandDevice.getInstance (السياق) .writeWristBandPhoneAlertNew (السياق ، contact.getDisplayName .....)

هذا ما كنت أبحث عنه. بالنظر إلى المستقبل ، سأقول أن iWown يحتوي أيضًا على طرازين i5 + و i6 ، ولديهما شاشة أكبر ، وبالتالي يتم وضع المزيد من الشخصيات ، لذلك هناك حاجة إلى كل هذه الفحوصات. ليس من الواضح لماذا لم يكتبوا صفًا أو شيء من هذا القبيل ، ربما يكون مزحًا مترجمًا ، ولكن هذا الرمز يتكرر في العديد من الأماكن.
بالانتقال إلى تعريف هذه الوظيفة ، رأيت هذا:

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

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


رائع ، يستخدم نفس الوظيفة لإرسال النص ، فقط مع معلمات مختلفة. جميع الوظائف بكلمة New هي خيارنا فقط ، لأنه كما اتضح أعلاه ، لدي واجهة برمجة تطبيقات جديدة.

انتقل بكل سرور إلى تعريف دالة writeAlertNew ، رأيت ما يلي:
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));
        }
    }


كان من الواضح أن هناك وظيفتان تستخدمان هنا تفصلني عن الربح.
writeWristBandDataByte - تشكل حزمة تحتوي على رسالة للسوار ، من المثير للاهتمام وجود وظيفة خاصة form_Header (3 ، 1) ، والتي تشكل رأس الحزمة ، والتي يفهم من خلالها السوار ما يريدون منه. 3 هو رقم مجموعة الفريق ، و 1 هو الفريق نفسه
public static byte form_Header(int grp, int cmd) {
        return (byte) (((((byte) grp) & 15) << 4) | (((byte) cmd) & 15));
    }


الوظيفة بسيطة ، تم نسخها إلى مشروعي بدون تغييرات. التالي كان

NewAgreementBackgroundThreadManager.getInstance (). AddTask (new WriteOneDataTask (context، writeData)) ؛

كما اتضح ، لا شيء غير عادي ، يقوم التطبيق بإنشاء دفق يتم فيه التحقق من قائمة انتظار الحزم للإرسال باستمرار ، إذا ظهرت الحزمة في قائمة الانتظار ، يكتب الدفق إلى خاصية الجهاز المعينة ، إذا كان هناك أكثر من حزمة واحدة ، يرسلها مع تأخير 240 مللي ثانية.
جاء بعد ذلك أكثرها غير مفهومة:

PebbleBitmap.fromString (السياق ، String.valueOf (e) ، 8 ، 1) .data)

لماذا يسمى الفصل بهذه الطريقة غير واضح ، لأن هذا الجهاز لا علاقة له بـ Pebble. عند فتح مصدر الفصل ، رأيت ما يلي:

مصدر فئة 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));
    }
}



بعد تفكير طويل ، توصلت إلى استنتاج مفاده أن fromString ينشئ صورة بحرف باستخدام خط معين (مضمن في التطبيق) ، ثم يحول وحدات البكسل إلى 0 أو 1 اعتمادًا على التعبئة ، لذلك سيبدو الحرف O على
النحو التالي : 00011100
01100011
01100011
01100011
00011100

دون الخوض في التفاصيل حقًا ، قمت بنسخ كل شيء في مشروعي باستخدام مثال BLE GATT Google.
و ... أوه ، معجزة !!! اهتزت سوار! لكن الرسالة لم تظهر ، سطر فارغ ورمز مكالمة واردة.
اتضح أن مجموعة من عمليات التحقق من الحجم ليست عارضة ، ويتجاهل بغباء الرسائل والرسائل الطويلة جدًا التي يبلغ طولها 11 حرفًا ، على الرغم من أن 12 يعرض بشكل طبيعي. أدت ساعتان من الرقص حول هذه الوظائف في النهاية إلى نتائج ، تعلمت عرض كل من النص الروسي والإنجليزي ، وفي نفس الوقت علمت أن هناك العديد من أوضاع التشغيل في مجموعة الرسائل:
  1. مكالمة واردة. يتم عرض الهاتف ، ويهتز اسم المتصل والسوار
  2. الرسالة يتم عرض رمز النص والمغلف. عندما يهتز مرتين
  3. غائم. مثل 2 ، ولكن بدلاً من المغلف ، رمز السحابة
  4. الخطأ. مثل 2 ، هذا الرمز فقط بعلامة تعجب.




بعد أن علمت طلبي لإرسال إخطارات من تطبيقات مختلفة ، واتس اب ، vk ، فايبر ، برقية وغيرها ، قررت أن الوقت قد حان لتعليم السوار للرد على المكالمات الواردة ، وأخيرا ، استخدام زر واحد لإعادة تعيين تلك الواردة.

لن أصف هذه العملية ، فقد تبين أن المنشور منتفخ ، ولا يمكنني إلا أن أقول أنه لم يكن من الصعب الرد على الرسائل الواردة ، ولكن استخدام الزر لم يكن كذلك.

جميع الرسائل الواردة من السوار ، تم اعتراض Zeroner في فصل خاص. تحتوي الحزمة الواردة على عنوان مجموعة الأوامر ورقم الفريق ، بعد تصحيح أخطاء طويل واختبارات قمت بصيد المجموعات المستخدمة ، ثم وجدت وصفاً في رمز Zeroner.

مجموعات سوار وفرق
// 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



بفضل هذا ، تمكنت من تحقيق عمل كامل مع السوار. يمكنني تلقي بيانات حول الخطوات ، حول النوم. يمكنني إدارة الإعدادات وضبط المنبهات. تمكنت من الحصول على تعيين البايت للحزمة نفسها من الفئات التي تخزن البيانات في قاعدة البيانات ، وقمت بتنفيذها جميعًا في المنزل.

في النهاية


بعد قليل من التفكير ، قررت أن كل هذا يمكن أن يكون مفيدًا ليس لي فقط وكتبت تطبيقًا جديدًا يحتوي على جميع البيانات والوظائف اللازمة للعمل مع السوار ، بالإضافة إلى تنفيذ واجهة بسيطة لإرسال إشعارات من أي تطبيق إلى السوار.

WiliX iWown for Geek

منذ ذلك الحين ، مر الكثير من الوقت ، والكثير بعد التحديث إلى Android 6 ، توقف التطبيق عن العمل. كما أنه لا يعمل بثبات مع البرامج الثابتة للأساور من الإصدار الثاني. ولكن آمل أن أجد الوقت للمراجعة.

يتم نشر كود المصدر على جيثب . يمكنك الشوكة والمرح كما تريد. سيتم قبول جميع طلبات السحب بعد المراجعة ، وبعد ذلك سيتم تحميل الاختبارات على الفور إلى Google Play.

في الوقت الحالي ، يمكن للتطبيق:
  • عرض الإخطارات من أي تطبيق
  • BT


تم تنفيذ اتصال بـ Google Fit لحفظ بيانات التدريب ، ولكن نظرًا لأنني لم أختار SDK to Fit ، فقد بحثت عن طريق مجموعة من الروابط والمنتديات ، لكنني لم أفهم حتى الآن كيفية عمل بيانات العرض المناسبة من الأجهزة المخصصة. من غير الواضح إذن سبب وجود هذه الوظيفة.
إذا عمل شخص ما مع Google Fit ، ويعرف كيفية جعله يستخدم البيانات من مستشعر مخصص لعرض الرسوم البيانية ، أخبرني في التعليقات أو اكتب لي ، وسأكون ممتنًا جدًا للمستخدمين!

كانت أيضًا فكرة ربط السوار بـ Sleep as Adnroid. في الواقع ، لمراقبة النوم ، تم شراء سوار. ولكن ، كما اتضح ، يمكن لـ iWown إرجاع مدة مراحل النوم فقط. أي ، البيانات المحسوبة بالفعل من مقياس التسارع.
ويتطلب تطبيق Sleep as Android بيانات عارية من مقياس التسارع ، وبتردد مطلوب يبلغ 10 ثوانٍ.

بشكل عام ، النتيجة. أدعو المطورين والمالكين لدعم المشروع برموزهم ونصائحهم وأي شيء. اترك طلب السحب ، مشكلة في Github.
تبين أن التطبيق يحظى بشعبية كبيرة في الخارج ، وكثيرا ما يكتب لي الأجانب ، يطلبون مني إضافة / إصلاح / ترجمة شيء.

بالمناسبة ، يحتوي iWown i5 على عدة نسخ ، مع برامج ثابتة مماثلة:
Vidonn X5
Harper BFB-301
Excelvan i5

المراجع

Google Play - iWown for Geek
Repository على
مناقشة GitHub على w3bsit3-dns.com

PS بدءًا من الإصدار الخامس ، ظهرت فئة إضافية في androids في الستارة ، والتي لا تظهر على شاشة القفل.
هل يستطيع أحد أن يخبرني عن كيفية نقل إشعاري إلى هذه الفئة؟ شكرا لك!

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


All Articles