نقوم بتفكيك بروتوكول غلاية Redmond G200S وتوصيله بـ HomeAssistant

الدخول


كان هناك بالفعل مقال عن Gicktime مكرس لتحليل بروتوكول غلاية Redmond SkyKettle. ومع ذلك ، تحدثوا هناك عن طراز RK-M171S ، وهنا سنتحدث عن G200S أكثر وظيفية. في هذا النموذج ، تم تغيير بروتوكول التفاعل ، ونتيجة لذلك لم يعد نهج المؤلف للمقالة السابقة يعمل بعد الآن ، وظهرت وظائف الإضاءة الليلية الإضافية وشاشات عرض درجة حرارة اللون الحالية.

في هذه المقالة ، سأقدم نتائج تحليل بروتوكول مع أمثلة كود بيثون (إذا كان أي شخص يريد تطوير الوحدة النمطية / التطبيق الخاص بهم للتحكم في إبريق الشاي). أيضًا في نهاية المقالة يوجد رابط إلى وحدة جاهزة لتوصيل إبريق الشاي بـ HomeAssistant (هذه هي تجربتي الأولى في الكتابة في python بعد أخذ دورة عبر الإنترنت ، لذلك يمكن لهذه الوحدة بل تحتاج إلى تحسين).

كل من يهمه الأمر ، مرحبا بكم في القطط.

المشاكل والمهام


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

الأدوات


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

نظرًا لأن HomeAssistant مكتوب بلغة python ، سنكتب جميع الأوامر الإضافية عليه. لاستخدام وضع التشغيل التفاعلي gatttool في python ، ستساعدنا مكتبة pexpect ، مما يسمح لك بتوليد جوهر تطبيقات الطرف الثالث ومراقبة مخرجاتها (مشهورة).

تدرب


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

  1. التثبيت والانفصال

    إنشاء اتصال:

    child = pexpect.spawn("gatttool -I -t random -b " + mac, ignore_sighup=False) child.expect(r'\[LE\]>', timeout=3) child.sendline("connect") child.expect(r'Connection successful.*\[LE\]>', timeout=3) 

    هنا ماك هو عنوان الخشخاش لإبريق الشاي.

    نقطع الاتصال:

     child.sendline("exit") 
  2. اشترك في الإخطارات

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

     child.sendline("char-write-cmd 0x000c 0100") child.expect(r'\[LE\]>') 
  3. تسجيل الدخول

     child.sendline("char-write-req 0x000e 55" + iter + "ff" + key + "aa") child.expect("value: ") child.expect("\r\n") connectedStr = child.before[0:].decode("utf-8") answer = connectedStr.split()[3] # parse: 00 - no 01 - yes child.expect(r'\[LE\]>') 

    فيما يلي ، iter هو متغير سداسي متكرر صحيح من 0 إلى 64 (من 0 إلى 100 في النظام العشري). بعد كل أمر (ناجح وغير ناجح) ، يجب زيادة هذا المتغير بمقدار 1 ؛ عندما يصل إلى 64 ، يتم إعادة تعيينه مرة أخرى إلى 0 ؛ مفتاح - مفتاح تخويل سداسي 8 بايت (على سبيل المثال: ffffffffffffffff).

    مثال على الإجابة:
    القيمة: 55 00 ff 01 aa
    البايت الرابع (01) يعني أن الغلاية قد أذنت لك ، وإلا ستكون الإجابة 00.
  4. بعض سحر الشارع
    بعد التفويض ، يتم إرسال طلب "السحر" دائمًا ، ولا يتضح جوهره بالنسبة لي. هناك نظرية مفادها أنه من الضروري "الاحتفاظ" بالحالة المتصلة. يزعم ، إذا لم ترسله ، يحدث انقطاع في غضون ثانية ، وتحتاج إلى البدء من جديد. إذا قمت بإرساله ، فإن المهلة تزداد بشكل ملحوظ لتصل إلى حوالي 12 ثانية. أكد هذا بشكل موثوق ، لم أستطع.

     child.sendline("char-write-req 0x000e 55" + iter + "01aa") child.expect("value: ") child.expect("\r\n") child.expect(r'\[LE\]>') 

    مثال على الإجابة:
    القيمة: 55 01 01 02 1d aa

    في جميع تجاربي ، كان الجواب دائمًا هو هذا.

    UPD: في التعليقات ، اقترحوا أنه لم يكن سحرًا على الإطلاق ، ولكن ببساطة طلب إصدار البرنامج ؛ وبناءً على ذلك ، تم تضمين هذا الإصدار في الرد. وبالتالي ، يمكن إزالة هذا الطلب بشكل عام باعتباره غير ضروري.
  5. تزامن
    أمر يقوم بمزامنة الوقت في إبريق الشاي مع ساعة خادم. لديها تأثير آخر. في الغلاية ، من الممكن إظهار درجة الحرارة الحالية في وضع السكون عن طريق وميض مؤشر LED بلون معين. تعمل هذه الوظيفة فقط بعد التزامن. للحصول على وصف الوظيفة نفسها ، انظر الفقرة 11.

     child.sendline("char-write-req 0x000e 55" + iter + "6e" + timeNow + tmz + "0000aa") child.expect("value: ") child.expect("\r\n") child.expect(r'\[LE\]>') 

    هنا tmz هي المنطقة الزمنية بتنسيق سداسي عكسي (على سبيل المثال ، ترجم المنطقة الزمنية +3 إلى ثوانٍ ، ثم إلى تنسيق سداسي عشري واحصل على سداسي عشري (3 * 60 * 60) = 2a30 ، مقسمًا إلى أزواج وإخراج 302a بترتيب عكسي). لا أعرف ماذا أفعل في المناطق الزمنية السلبية ، لم أختبرها ، ولكن هناك شك في أن البايت tmz التالي هو المسؤول عن ذلك. هنا timeNow هو وقت unixtime الحالي بتنسيق سداسي عكسي. الخوارزمية هي نفسها: نحصل على الوقت الحالي بالثواني ، ونترجمه إلى HEX ، ونقسمه إلى أزواج ونخرجه في صف بالترتيب العكسي.

    مثال على الإجابة:
    القيمة: 55 02 6e 00 aa
    في جميع تجاربي ، كان الجواب دائمًا هو هذا.
  6. الإحصائيات
    تحتوي الغلاية على متر من الكهرباء المستهلكة ، ووقت التشغيل الإجمالي وعدد مرات البدء. إذا كان شخص ما لا يحتاج إلى هذه البيانات ، يمكنك تخطي هذا العنصر بأمان.

     child.sendline("char-write-req 0x000e 55" + iter + "4700aa") child.expect("value: ") child.expect("\r\n") statusStr = child.before[0:].decode("utf-8") Watts = hexToDec(str(statusStr.split()[11] + statusStr.split()[10] + statusStr.split()[9])) alltime = round(self._Watts/2200, 1) child.expect(r'\[LE\]>') child.sendline("char-write-req 0x000e 55" + iter + "5000aa") child.expect("value: ") child.expect("\r\n") statusStr = child.before[0:].decode("utf-8") times = hexToDec(str(statusStr.split()[7] + statusStr.split()[6])) child.expect(r'\[LE\]>') 

    الواط - يعيد الطاقة المستهلكة بالساعة * h ، طوال الوقت - ساعات تشغيل الغلاية ، الأوقات - عدد بدايات الغلاية. hexToDec - دالة للتحويل إلى تنسيق عشري.
  7. اقرأ الوضع الحالي للعمل

     child.sendline("char-write-req 0x000e 55" + iter + "06aa") child.expect("value: ") child.expect("\r\n") statusStr = child.before[0:].decode("utf-8") answer = statusStr.split() status = str(answer[11]) temp = hexToDec(str(answer[8])) mode = str(answer[3]) 

    مثال على الإجابة:
    القيمة: 55 04 06 00 00 00 00 01 2a 1e 00 00 00 00 00 00 80 80 00 aa
    البايت الرابع هو وضع التشغيل (الوضع): 00 - الغليان ، 01 - التسخين لدرجة الحرارة ، 03 - ضوء الليل. البايت السادس هو درجة الحرارة السداسية التي يجب تسخينها في وضع التسخين ، في وضع الغليان هو 00. البايت التاسع هو درجة حرارة التيار السداسي للماء (2a = 42 درجة مئوية). البايت الثاني عشر هو حالة إبريق الشاي: 00 - إيقاف ، 02 - في. البايت السابع عشر هو مدة الغلاية بعد الوصول إلى درجة الحرارة المرغوبة ، افتراضيًا هو 80 في ست عشري (على ما يبدو ، هذه نوع من الوحدات النسبية ، وبالتأكيد ليست ثوان).
  8. تسجيل الوضع الحالي للعمل

     child.sendline("char-write-req 0x000e 55" + iter + "05" + mode + "00" + temp + "00000000000000000000" + howMuchBoil + "0000aa") child.expect("value: ") child.expect("\r\n") statusStr = child.before[0:].decode("utf-8") answer = statusStr.split()[3] child.expect(r'\[LE\]>') 

    وضع المعلمة: 00 - الغليان ، 01 - التسخين لدرجة الحرارة ، 03 - ضوء الليل. المعلمة المؤقتة هي درجة الحرارة السداسية التي من الضروري تسخينها في وضع "التسخين" ، في وضع الغليان تكون 00. معلمة howMuchBoil هي مدة الغلاية بعد الوصول إلى درجة الحرارة المطلوبة ، الافتراضي هو 80 بالست عشري (على ما يبدو ، هذه بعض الوحدات النسبية ، بالتأكيد ليس ثواني).

    مثال على الإجابة:
    القيمة: 55 05 05 01 أأ
    يشير البايت الرابع من الاستجابة إلى نجاح الإعدادات: 01 - ناجح ، 00 - غير ناجح.
  9. تشغيل الوضع الحالي للعمل

     child.sendline("char-write-req 0x000e 55" + iter + "03aa") child.expect("value: ") child.expect("\r\n") statusStr = self.child.before[0:].decode("utf-8") answer = statusStr.split()[3] child.expect(r'\[LE\]>') 

    مثال على الإجابة:
    القيمة: 55 06 03 01 أأ
    يشير البايت الرابع من الاستجابة إلى نجاح التضمين: 01 - ناجح ، 00 - غير ناجح.
  10. إيقاف وضع التشغيل الحالي

     child.sendline("char-write-req 0x000e 55" + iter + "04aa") child.expect("value: ") child.expect("\r\n") statusStr = self.child.before[0:].decode("utf-8") answer = statusStr.split()[3] child.expect(r'\[LE\]>') 

    مثال على الإجابة:
    القيمة: 55 07 04 01 aa
    يشير البايت الرابع من الاستجابة إلى نجاح الإغلاق: 01 - بنجاح ، 00 - دون نجاح.
  11. عرض درجة الحرارة الحالية بالألوان في وضع الخمول

     child.sendline("char-write-req 0x000e 55" + iter + "37c8c8" + onoff + "aa") # 00 - off, 01 - on child.expect("value: ") child.expect("\r\n") child.expect(r'\[LE\]>') 

    المعلمة onoff هي إما 01 لتمكين الوظيفة ، أو 00 لتعطيل الوظيفة.

    مثال على الإجابة:
    القيمة: 55 08 37 00 أأ
    في جميع تجاربي ، كان الجواب دائمًا هو هذا.
  12. قم بتسجيل لوحة ألوان لمختلف أوضاع التشغيل
    يتم تعيين لوحة من المراسلات بين لون LED ودرجة الحرارة في وضع عرض درجة الحرارة الحالية وأوضاع التدفئة والغليان ، بالإضافة إلى لوحة الألوان في وضع الإضاءة الليلية.

     child.sendline("char-write-req 0x000e 55" + iter + "32" + boilOrLight + scale_from + rand + rgb1 + scale_mid + rand + rgb_mid + scale_to + rand + rgb2 + "aa") child.expect("value: ") child.expect("\r\n") child.expect(r'\[LE\]>') 

    المعلمة boilOrLight هي 00 إذا قمنا بضبط وضع العرض لدرجة الحرارة الحالية أو 01 إذا قمنا بضبط الوضع الليلي. تشير المعلمة scale_from إلى بداية نطاق تغير اللون وهي تساوي 00 في وضع الإضاءة الليلية و 28 في وضع عرض درجة الحرارة الحالية (28 هي 40 بتنسيق عشري ومن هذه الدرجة سيبدأ تغيير اللون السلس). المعلمة scale_mid هي منتصف النطاق وهي 32 في وضع ضوء الليل و 46 في وضع عرض درجة الحرارة الحالية. تشير المعلمة scale_to إلى نهاية نطاق الألوان وهي 64 في كلا الوضعين. المعلمة rgb1 هي اللون السداسي لبداية اللوحة. المعلمة rgb_mid هي اللون السداسي لمنتصف اللوحة (أحسبها كمنتصف بين النهايتين اليسرى واليمنى ، ولكن نظريًا يمكنك تحديد أي لون ، وهذا سيؤثر فقط على جمالية وسلاسة تغيير اللون). المعلمة rgb2 هي اللون السداسي لنهاية اللوحة. إن معلمة rand هي معلمة معينة ، لم أفهم قيمتها بالضبط ، ربما تتعلق بطريقة أو بأخرى بسطوع اللون (أمثلة للقيم: e5 ، cc).

    مثال على الإجابة:
    القيمة: 55 09 32 00 أأ
    في جميع تجاربي ، كان الجواب دائمًا هو هذا.
  13. اقرأ لوحة الألوان لمختلف أوضاع التشغيل

     child.sendline("char-write-req 0x000e 55" + iter + "33" + boilOrLight + "aa") child.expect("value: ") child.expect("\r\n") statusStr = self.child.before[0:].decode("utf-8") child.expect(r'\[LE\]>') 

    يمكن أن تكون المعلمة boilOrLight 00 - إذا قمنا بضبط وضع العرض لدرجة الحرارة الحالية أو 01 - إذا قمنا بضبط الوضع الليلي.

    مثال على الإجابة:
    القيمة: 55 10 33 01 00 7f 00 00 ff 32 7f 00 ff 00 64 7f ff 00 00 aa
    هنا ، البايتة السادسة والحادية عشرة والسادسة عشرة (7f) هي معلمة راند من البند 12. البايت الخامس هو scale_from ، البايت العاشر scale_mid ، البايت الخامس عشر هو scale_to. البايت السابعة + الثامنة + التاسعة هي rgb_from. البايت الثاني عشر + الثالث عشر + الرابع عشر هي rgb_mid. السابع عشر + الثامن عشر + التاسع عشر بايت - rgb_to.

الخلاصة


إذا كان gatttool لا يرغب في الاتصال بإبريق الشاي (هذا ممكن في المرة الأولى التي تتصل فيها بأجهزة غير معروفة) ، فحاول البحث عن إبريق الشاي باستخدام نظام التشغيل قبل توصيل الوحدة:

 sudo hciconfig device reset sudo timeout 1 hcitool lescan 


device - معرف جهاز البلوتوث الخاص بك (على سبيل المثال ، hci0). تأكد من أن عنوان الخشخاش للغلاية موجود في قائمة الأجهزة التي تم العثور عليها. بعد ذلك:

 sudo hcitool lewladd mac sudo hcitool lerladd mac 


ماك - عنوان الخشخاش لإبريق الشاي الخاص بك

UPD6 : تحسن بشكل ملحوظ وحدة الغلاية:
1. نقل الوحدة من النظام الأساسي إلى وضع التكامل
2. بعد الإضافة ، سيكون لديك تلقائيًا 3 عناصر: سخان مياه (درجة الحرارة الحالية ، درجة الحرارة المستهدفة ، الغلي والتدفئة) ، جهاز استشعار (وقت المزامنة ، الطاقة المستهلكة ، ساعات التشغيل ، عدد مرات البدء) والضوء (يمكن استخدامه كمصباح ليلي واختيار أي لون الإضاءة الخلفية)
3. الوحدة متاحة الآن على جيثب .
4. تدعم الوحدة التثبيت من خلال HACS
5. مثال التكوين:
 r4s_kettler: device: 'hci0' mac: 'FF:FF:FF:FF:FF:FF' password: 'ffffffffffffffff' 


لقطات شاشة للنسخة الجديدة
الصورة
الصورة
الصورة
الصورة


UPD7 : معلومات محذوفة غير ذات صلة

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


All Articles