или как сделать неумный умным.
Привет, Хабр!
Скрытый текст

После года владения кондиционером Royal Clima fresh full inverter (RCI-RF40HN), обнаружил на пульте кнопку I FEEL, полез читать инструкцию (пультом не пользуюсь, кондиционер управляется умным домом по wifi) и нашел, что в пульт встроен датчик температуры и по этому датчику кондиционер может регулировать более точнее температуру в помещении. Это то что мне не хватало: кондиционер висит в центре квартиры, в гостиной, а на ночь я направлял воздушный поток (через открытую дверь) в спальню.
Встроенный в корпус кондиционера датчик работает довольно точно, но вот ночью, он охлаждал сам себя намного быстрее и уменьшал холодный поток воздуха и «холод» не доходил до спальной комнаты.
Вариант уносить с собой в спальню пульт на ночь, не подходил, так как нет прямой видимости пульта и кондиционера.
Анализ протокола пульта
Для анализа протокола пульта решил воспользоваться библиотекой IRremoteESP8266, быстро собрал простейшую схему из ESP2866 и IR сенсора KY-022 (удобно, что у него есть встроенные светодиод, который сигнализирует о получении сигнала) или любой другой сенсор IR.
Скрытый текст
Сначала залил прошивку анализатор IRrecvDumpV2.ino на ESP32 и начал анализировать сигналы от пульта, но получал лишь такие данные данные, в которых не определялся протокол:
E (1747402) gptimer: gptimer_start(348): timer is not enabled yet E (1747408) gptimer: gptimer_start(348): timer is not enabled yet E (1747414) gptimer: gptimer_start(348): timer is not enabled yet E (1747420) gptimer: gptimer_start(348): timer is not enabled yet E (1747426) gptimer: gptimer_start(348): timer is not enabled yet E (1747432) gptimer: gptimer_start(348): timer is not enabled yet E (1747437) gptimer: gptimer_start(348): timer is not enabled yet ; // UNKNOWN EBA17F Timestamp : 001747.491 Library : v2.8.6 Protocol : UNKNOWN Code : 0x5B39BFD5 (12 Bits) uint16_t rawData[23] = {3058, 1620, 468, 5504, 5816, 5812, 5818, 5816, 5816, 5816, 5814, 5818, 5816, 5816, 5816, 5812, 5818, 5814, 5816, 5816, 5816, 5814, 5818}; // UNKNOWN 5B39BFD5
Потратил несколько часов разбираясь какие байты за что могут отвечать, потом загуглил, что же за ошибка gptimer_start и нашел пост, в котором писалось, что эта библиотека иногда может некорректно с платами серии ESP32 и тогда решил запустить анализатор на esp8266 и все сработало, протокол начал определятся.

Добавляем библиотеку IRremoteESP8266 в Arduino IDE, открываем файл IRrecvDumpV2.ino из примеров, прописываем в коде порт к которому подключили сенсор и заливаем на esp8266. Включаем монитор COM порта, нажимаем кнопки на пульте и видим данные, которые передает пульт.
18:22:35.544 -> Library : v2.8.6 18:22:35.576 -> 18:22:35.576 -> Protocol : TCL112AC 18:22:35.576 -> Code : 0x23CB260200402000830000000008 (112 Bits) 18:22:35.576 -> Mesg Desc.: Model: 1 (TAC09CHSD), Type: 2, Quiet: Off 18:22:35.576 -> uint16_t rawData[227] = {3062, 1620, 468, 1118, 470, 1116, 468, 350, 468, 350, 468, 352, 468, 1140, 446, 350, 468, 352, 468, 1140, 444, 1120, 464, 352, 468, 1120, 466, 352, 468, 350, 468, 1118, 468, 1120, 464, 352, 468, 1118, 468, 1140, 446, 350, 468, 350, 468, 1142, 444, 350, 468, 350, 468, 350, 468, 1140, 444, 352, 468, 350, 468, 350, 468, 350, 468, 352, 468, 350, 468, 350, 468, 350, 468, 352, 468, 350, 468, 352, 468, 352, 468, 352, 466, 352, 468, 352, 468, 350, 468, 350, 468, 352, 468, 352, 466, 352, 466, 1120, 468, 352, 468, 352, 466, 352, 468, 352, 468, 350, 468, 350, 468, 1142, 444, 350, 468, 352, 468, 352, 468, 350, 468, 350, 468, 350, 468, 352, 466, 352, 468, 350, 468, 350, 468, 1118, 468, 1142, 444, 350, 468, 350, 468, 352, 468, 350, 468, 350, 468, 1142, 444, 352, 466, 352, 468, 350, 468, 350, 468, 350, 468, 350, 468, 350, 468, 350, 468, 350, 468, 350, 468, 352, 468, 350, 468, 350, 468, 350, 468, 352, 468, 352, 466, 352, 466, 352, 468, 350, 468, 350, 468, 352, 468, 350, 468, 350, 468, 352, 468, 350, 468, 350, 468, 350, 468, 350, 468, 352, 468, 350, 468, 350, 468, 350, 468, 350, 468, 350, 468, 352, 468, 1120, 466, 350, 468, 350, 468, 352, 468, 350, 468}; // TCL112AC 18:22:35.675 -> uint8_t state[14] = {0x23, 0xCB, 0x26, 0x02, 0x00, 0x40, 0x20, 0x00, 0x83, 0x00, 0x00, 0x00, 0x00, 0x08}; 18:22:35.707 -> 18:22:35.707 -> 18:22:35.740 -> Library : v2.8.6 18:22:35.740 -> 18:22:35.740 -> Protocol : TCL112AC 18:22:35.772 -> Code : 0x23CB26010024830500000019805A (112 Bits) 18:22:35.772 -> Mesg Desc.: Model: 1 (TAC09CHSD), Type: 1, Power: On, Mode: 3 (Cool), Temp: 26C, Fan: 0 (Auto), Swing(V): 0 (Auto), Swing(H): Off, Econo: Off, Health: Off, Turbo: Off, Light: On, On Timer: Off, Off Timer: Off 18:22:35.772 -> uint16_t rawData[227] = {3060, 1620, 468, 1142, 444, 1142, 444, 354, 466, 352, 468, 352, 468, 1118, 468, 350, 468, 352, 468, 1118, 468, 1142, 444, 352, 468, 1118, 468, 352, 468, 352, 468, 1142, 444, 1118, 468, 350, 468, 1142, 444, 1118, 466, 350, 468, 350, 468, 1118, 468, 350, 468, 352, 468, 1142, 444, 352, 466, 352, 468, 350, 468, 350, 468, 350, 468, 352, 468, 350, 468, 352, 468, 352, 468, 350, 468, 350, 468, 352, 468, 350, 468, 350, 468, 352, 468, 350, 468, 352, 468, 1118, 468, 350, 468, 352, 468, 1118, 468, 350, 468, 350, 468, 1116, 468, 1116, 470, 350, 468, 352, 468, 352, 468, 350, 468, 352, 468, 1142, 444, 1142, 444, 352, 468, 1142, 444, 350, 468, 350, 468, 352, 468, 352, 468, 350, 468, 350, 468, 352, 466, 352, 468, 350, 468, 350, 468, 350, 468, 350, 468, 352, 468, 350, 468, 350, 468, 350, 468, 350, 468, 350, 468, 350, 468, 350, 468, 352, 468, 352, 466, 352, 468, 350, 468, 350, 468, 352, 468, 352, 468, 350, 468, 352, 466, 1118, 468, 350, 468, 350, 468, 1118, 466, 1142, 444, 352, 468, 350, 468, 350, 468, 352, 468, 350, 466, 352, 468, 352, 468, 350, 468, 352, 468, 350, 468, 1142, 444, 352, 468, 1142, 444, 350, 468, 1142, 444, 1118, 468, 350, 468, 1142, 444, 352, 468}; // TCL112AC 18:22:35.905 -> uint8_t state[14] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x24, 0x83, 0x05, 0x00, 0x00, 0x00, 0x19, 0x80, 0x5A};
Кстати, как видно выше, при однократном нажатии на кнопку пульта моего кондиционера, отправляется последовательно два пакета данных, первый содержит лишь один статус — Тихий режим работы; непонятно зачем выделили целый пакет в 14 байт, ведь в основном пакете (во втором) полно «свободного места». И туже информацию с внешнего датчика (пульта) могли бы в отдельный пакет добавить.
Здесь мы узнаем, что пульт с кондиционером общаются по протоколу TCL112AC, но нажатие на кнопку IFEEL в логе не отображается, хотя сырые данные и меняются. Включил функцию IFEEL и начал ждать (здесь я все еще думал, что температура могла передаваться по Bluetooth) и ровно через 10 минут (и каждые 10 минут), в логе Arduiono появились новые пакеты данных.
Скрытый текст
На столе около кондиционера 20:08:14.829 -> Protocol : TCL112AC 20:08:14.829 -> Code : 0x23CB260100048305000000148035 (112 Bits) 20:08:14.829 -> Mesg Desc.: Model: 1 (TAC09CHSD), Type: 1, Power: On, Mode: 3 (Cool), Temp: 26C, Fan: 0 (Auto), Swing(V): 0 (Auto), Swing(H): Off, Econo: Off, Health: Off, Turbo: Off, Light: On, On Timer: Off, Off Timer: Off 20:08:14.861 -> uint16_t rawData[227] = {3056, 1620, 468, 1118, 466, 1120, 466, 352, 466, 352, 466, 352, 466, 1120, 464, 352, 466, 352, 466, 1120, 464, 1142, 442, 352, 466, 1118, 466, 350, 466, 352, 466, 1120, 466, 1120, 466, 352, 466, 1118, 466, 1144, 442, 352, 466, 352, 466, 1120, 466, 352, 466, 352, 466, 1142, 442, 352, 466, 352, 466, 350, 466, 352, 466, 350, 468, 352, 466, 352, 466, 352, 466, 350, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 350, 466, 352, 466, 350, 466, 1118, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 1118, 466, 1120, 464, 352, 466, 352, 466, 352, 466, 352, 466, 352, 468, 1142, 442, 1120, 466, 352, 466, 1118, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 1142, 444, 352, 466, 1142, 442, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 1142, 442, 1142, 442, 350, 466, 1118, 466, 352, 466, 1144, 442, 1118, 466, 352, 466, 352, 466}; // TCL112AC 20:08:14.961 -> uint8_t state[14] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x04, 0x83, 0x05, 0x00, 0x00, 0x00, 0x14, 0x80, 0x35}; Холодильник 20:18:15.241 -> Protocol : TCL112AC 20:18:15.241 -> Code : 0x23CB2601000483050000000C802D (112 Bits) 20:18:15.241 -> Mesg Desc.: Model: 1 (TAC09CHSD), Type: 1, Power: On, Mode: 3 (Cool), Temp: 26C, Fan: 0 (Auto), Swing(V): 0 (Auto), Swing(H): Off, Econo: Off, Health: Off, Turbo: Off, Light: On, On Timer: Off, Off Timer: Off 20:18:15.275 -> uint16_t rawData[227] = {3054, 1622, 464, 1142, 442, 1118, 466, 352, 466, 352, 466, 352, 464, 1120, 464, 352, 466, 352, 466, 1118, 466, 1142, 442, 352, 466, 1120, 464, 352, 466, 352, 466, 1116, 468, 1140, 442, 352, 464, 1116, 468, 1142, 442, 350, 468, 350, 466, 1140, 442, 352, 466, 350, 466, 1118, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 350, 466, 352, 466, 352, 464, 352, 466, 352, 466, 352, 466, 1118, 466, 350, 466, 352, 466, 352, 466, 352, 466, 350, 466, 1118, 466, 1142, 442, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 1118, 466, 1140, 444, 352, 466, 1118, 464, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 350, 466, 352, 466, 350, 466, 352, 466, 352, 466, 352, 466, 352, 464, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 350, 466, 352, 466, 352, 466, 350, 466, 352, 466, 1142, 442, 1118, 464, 352, 464, 352, 466, 352, 466, 350, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 464, 1118, 466, 1142, 442, 352, 466, 1116, 468, 1118, 464, 352, 466, 1116, 466, 352, 464, 352, 466}; // TCL112AC 20:18:15.410 -> uint8_t state[14] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x04, 0x83, 0x05, 0x00, 0x00, 0x00, 0x0C, 0x80, 0x2D}; Держал в руках 2 минуты 20:38:16.040 -> Protocol : TCL112AC 20:38:16.040 -> Code : 0x23CB2601000483050000001D803E (112 Bits) 20:38:16.040 -> Mesg Desc.: Model: 1 (TAC09CHSD), Type: 1, Power: On, Mode: 3 (Cool), Temp: 26C, Fan: 0 (Auto), Swing(V): 0 (Auto), Swing(H): Off, Econo: Off, Health: Off, Turbo: Off, Light: On, On Timer: Off, Off Timer: Off 20:38:16.072 -> uint16_t rawData[227] = {3060, 1622, 466, 1142, 444, 1144, 442, 352, 468, 352, 466, 354, 466, 1144, 444, 352, 466, 352, 468, 1120, 468, 1116, 468, 352, 468, 1120, 466, 352, 466, 352, 468, 1120, 466, 1120, 466, 352, 466, 1122, 464, 1120, 466, 352, 468, 352, 466, 1120, 466, 352, 466, 352, 468, 1120, 466, 352, 468, 352, 466, 352, 466, 354, 466, 352, 468, 352, 466, 352, 466, 352, 466, 354, 466, 352, 466, 352, 466, 352, 466, 352, 468, 352, 466, 354, 466, 352, 466, 352, 466, 1144, 444, 352, 466, 354, 466, 352, 468, 352, 468, 352, 466, 1120, 466, 1144, 442, 352, 468, 352, 468, 352, 468, 352, 466, 352, 466, 1142, 444, 1142, 444, 352, 466, 1122, 466, 352, 466, 352, 468, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 468, 352, 468, 352, 466, 352, 466, 354, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 468, 352, 466, 352, 466, 354, 466, 352, 466, 1144, 444, 352, 468, 1144, 442, 1122, 466, 1120, 466, 352, 468, 352, 468, 352, 466, 352, 466, 352, 466, 354, 466, 352, 468, 352, 466, 352, 466, 352, 466, 1142, 444, 352, 466, 1144, 444, 1120, 466, 1144, 444, 1120, 464, 1120, 466, 352, 468, 352, 466}; // TCL112AC 20:38:16.208 -> uint8_t state[14] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x04, 0x83, 0x05, 0x00, 0x00, 0x00, 0x1D, 0x80, 0x3E}; Лежит на столе после рук 20:48:16.327 -> Protocol : TCL112AC 20:48:16.327 -> Code : 0x23CB2601000483050000001A803B (112 Bits) 20:48:16.327 -> Mesg Desc.: Model: 1 (TAC09CHSD), Type: 1, Power: On, Mode: 3 (Cool), Temp: 26C, Fan: 0 (Auto), Swing(V): 0 (Auto), Swing(H): Off, Econo: Off, Health: Off, Turbo: Off, Light: On, On Timer: Off, Off Timer: Off 20:48:16.358 -> uint16_t rawData[227] = {3058, 1622, 466, 1142, 444, 1120, 466, 356, 464, 352, 466, 356, 462, 1144, 442, 352, 466, 352, 466, 1120, 466, 1144, 442, 352, 466, 1122, 466, 352, 466, 352, 468, 1118, 468, 1120, 464, 352, 468, 1120, 468, 1118, 466, 352, 466, 352, 466, 1122, 464, 352, 466, 352, 466, 1120, 466, 352, 468, 352, 468, 352, 466, 352, 466, 352, 466, 352, 468, 352, 468, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 354, 466, 352, 466, 352, 466, 1144, 442, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 1120, 466, 1120, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 468, 1142, 444, 1118, 468, 352, 466, 1120, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 468, 352, 468, 352, 468, 350, 468, 352, 466, 352, 466, 352, 468, 352, 466, 352, 466, 352, 466, 352, 468, 352, 466, 352, 466, 352, 468, 352, 466, 352, 466, 352, 466, 352, 466, 352, 468, 352, 466, 352, 466, 352, 466, 352, 468, 352, 466, 352, 466, 1144, 442, 354, 466, 1120, 468, 1120, 466, 352, 466, 352, 466, 352, 466, 352, 468, 352, 466, 352, 466, 352, 468, 352, 466, 352, 466, 352, 466, 1142, 444, 1142, 444, 1144, 444, 352, 466, 1120, 466, 1120, 466, 1120, 466, 352, 466, 352, 468}; // TCL112AC 20:48:16.466 -> uint8_t state[14] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x04, 0x83, 0x05, 0x00, 0x00, 0x00, 0x1A, 0x80, 0x3B}; Вынес на улицу 21:08:16.888 -> Protocol : TCL112AC 21:08:16.888 -> Code : 0x23CB260100048305000000178038 (112 Bits) 21:08:16.921 -> Mesg Desc.: Model: 1 (TAC09CHSD), Type: 1, Power: On, Mode: 3 (Cool), Temp: 26C, Fan: 0 (Auto), Swing(V): 0 (Auto), Swing(H): Off, Econo: Off, Health: Off, Turbo: Off, Light: On, On Timer: Off, Off Timer: Off 21:08:16.921 -> uint16_t rawData[227] = {3058, 1620, 468, 1120, 466, 1120, 466, 352, 468, 352, 466, 352, 466, 1120, 466, 352, 466, 352, 468, 1120, 466, 1118, 468, 352, 466, 1118, 466, 352, 466, 352, 466, 1120, 466, 1118, 468, 352, 466, 1144, 442, 1120, 466, 352, 468, 352, 466, 1120, 466, 354, 466, 352, 466, 1142, 444, 352, 466, 352, 468, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 354, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 356, 464, 1120, 466, 352, 466, 352, 466, 352, 466, 352, 466, 354, 466, 1142, 444, 1142, 442, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 1120, 466, 1120, 466, 352, 466, 1142, 442, 352, 466, 354, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 352, 466, 1142, 442, 1144, 442, 1142, 444, 352, 468, 1142, 444, 352, 466, 352, 466, 352, 466, 352, 468, 350, 468, 352, 466, 352, 466, 352, 466, 352, 466, 352, 468, 1118, 466, 352, 466, 352, 466, 352, 466, 1142, 444, 1120, 466, 1118, 466, 352, 466, 352, 466}; // TCL112AC 21:08:17.052 -> uint8_t state[14] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x04, 0x83, 0x05, 0x00, 0x00, 0x00, 0x17, 0x80, 0x38};
Прокрутите текст правее, все интересное там
Я нашел изменяющиеся байты нужные мне, но пока еще не обнаруживающимися анализатором библиотеки IRremoteESP8266.
{0x23, 0xCB, 0x26, 0x01, 0x00, 0x24, 0x03, 0x05, 0x00, 0x00, 0x00, 0x00, 0x80, 0xC1}
{0x23, 0xCB, 0x26, 0x01, 0x00, 0x04, 0x83, 0x05, 0x00, 0x00, 0x00, 0x14, 0x80, 0x35}
Меняющиеся байты это 11ый (считаем от нуля) байт 0x00 -> 0x14 и 6ой байт 0x03 -> 0x83.
11 байт подозрительно похож на температуру в 16ом исчисление и, действительно, если перевести все числа полученные в логе под спойлером выше, то получается температура окружающей среды (пульта).
Скрытый текст
На столе около кондиционера 20°, Холодильник 12°, Держал в руках 2 минуты 29°, Лежит на столе после рук 26°, Вынес на улицу 23°
Идем в исходной код библиотеки и код протокола TCL и видим что 11 байт не используется совсем, а 6ый не учитывает кнопку IFEEL.
6ой (считаем от нуля) байт 0x03 это 0000 0011, а 0x83 это 1000 0011 этот бит не использовался в библиотеке.
Быстренько дописываем нужные нам новые параметры и отправляем PR разработчику.
Скрытый текст
/// Native representation of a TCL 112 A/C message. union Tcl112Protocol{ uint8_t raw[kTcl112AcStateLength]; ///< The State in IR code form. struct { // Byte 0~2 uint8_t :8; uint8_t :8; uint8_t :8; // Byte 3 uint8_t MsgType :2; uint8_t :6; // Byte 4 uint8_t :8; // Byte 5 uint8_t :2; uint8_t Power :1; uint8_t OffTimerEnabled :1; uint8_t OnTimerEnabled :1; uint8_t Quiet :1; uint8_t Light :1; uint8_t Econo :1; // Byte 6 uint8_t Mode :4; uint8_t Health :1; uint8_t Turbo :1; uint8_t :1; uint8_t IFeel :1; // <---- изменил тут // Byte 7 uint8_t Temp :4; uint8_t :4; // Byte 8 uint8_t Fan :3; uint8_t SwingV :3; uint8_t TimerIndicator :1; uint8_t :1; // Byte 9 uint8_t :1; // 0 uint8_t OffTimer :6; uint8_t :1; // 0 // Byte 10 uint8_t :1; // 0 uint8_t OnTimer :6; uint8_t :1; // 0 // Byte 11 uint8_t CurrentTemp :8; // <---- изменил тут // Byte 12 uint8_t :3; uint8_t SwingH :1; uint8_t :1; uint8_t HalfDegree :1; uint8_t :1; uint8_t isTcl :1; // Byte 13 uint8_t Sum :8; }; };
Плюс написал методы для получение новых статусов

и вывод в лог

Теперь заливаем еще раз прошивку, но с измененными библиотекой и в логе видим правильное отображение нажатия кнопки IFEEL и температуры помещения где находится пульт — IFeel: On, Sensor Temp: 23C
23:01:52.168 -> Code : 0x23CB260100048306000000178039 (112 Bits) 23:01:52.168 -> Mesg Desc.: Model: 1 (TAC09CHSD), Type: 1, Power: On, Mode: 3 (Cool), Temp: 25C, Fan: 0 (Auto), Swing(V): 0 (Auto), Swing(H): Off, Econo: Off, Health: Off, Turbo: Off, Light: On, IFeel: On, Sensor Temp: 23C, On Timer: Off, Off Timer: Off 23:01:52.201 -> uint16_t rawData[227] = {3002, 1676, 412, 1176, 434, 1152, 408, 410, 408, 412, 410, 384, 434, 1174, 412, 408, 412, 408, 410, 1174, 412, 1178, 408, 408, 408, 1178, 410, 410, 412, 408, 410, 1174, 410, 1178, 408, 388, 432, 1176, 410, 1176, 410, 410, 410, 408, 410, 1176, 410, 408, 410, 410, 408, 1174, 412, 410, 410, 408, 410, 410, 410, 408, 410, 408, 412, 408, 410, 410, 408, 410, 408, 408, 410, 410, 408, 412, 410, 406, 412, 384, 436, 406, 412, 408, 408, 410, 410, 408, 412, 1174, 410, 408, 410, 434, 384, 412, 410, 406, 414, 406, 410, 1174, 412, 1176, 410, 408, 410, 408, 410, 410, 408, 410, 410, 408, 408, 1176, 410, 406, 412, 1176, 410, 1174, 414, 408, 410, 408, 410, 408, 410, 410, 410, 408, 412, 410, 408, 406, 412, 410, 410, 408, 410, 408, 410, 408, 412, 410, 408, 408, 410, 410, 408, 410, 408, 408, 410, 408, 410, 408, 410, 410, 410, 408, 384, 434, 412, 410, 410, 384, 434, 408, 410, 410, 410, 408, 412, 408, 410, 408, 412, 408, 410, 1176, 412, 1174, 410, 1176, 410, 410, 408, 1176, 412, 408, 410, 408, 410, 410, 410, 410, 406, 412, 408, 410, 408, 408, 412, 408, 412, 410, 410, 408, 410, 1176, 410, 1152, 436, 408, 410, 410, 410, 1176, 408, 1176, 410, 1174, 412, 408, 412, 408, 410}; // TEKNOPOINT 23:01:52.302 -> uint8_t state[14] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x04, 0x83, 0x06, 0x00, 0x00, 0x00, 0x17, 0x80, 0x39};
Вы наверно заметили, что менялся еще один байт 5ый (0x24 при нажатии и 0x04 при передачи температуры пультом), а конкретнее 3ий бит, в протоколе он отвечает за статус Quiet, и как можно догадаться, он отвечает за писк (подтверждение получения сигнала кондиционером) при нажатии на пульт и отсутствии звука при отправке температуры (иначе кондиционер пищал бы каждые 10 минут при включенной функции IFEEL).
Управление кондиционером и отправка температуры
После того как мы узнали протокол пульта (для управления кондиционером) и научились отправлять нужную температуру, необходимо сделать передатчик IR и настроить нужную автоматизацию для умного дома.
Схема опять таки очень простая: esp8266 и любой IR светодиод (я откусил от нонейм китайского пульта управления светодиодной ленты).

Умный дом у меня построен на Home Assistant, а DIY устройства используют прошивку и интеграцию ESPHome, но у него поддержка TCL протокола еще скуднее, а код намного сложнее чем у IRremoteESP8266, а управление происходит через сущность Climate для HA, а мне нужно было только отправлять температуру, а управление кондиционером, напомню, у меня уже настроено через интеграцию Tuya Local по wifi.
Если у вас другой кондиционер и не нужно отправлять спец данные (как у меня внешнюю температуре), то подойдет или эта интеграция IR Remote Climate, или эта Remote Transmitter, которая позволяет отправлять по IR даже сырые данные, которые вы получили на предыдущем шаге. Мне уж очень не хотелось собирать сырой пакет с нуля для отправки, тем более код в библиотеке IRremoteESP8266 уже был написан и работал.
Решил, написанный ранее код, подключить к ESPHome это позволяют сделать функционал Lambda — внедрение сишного кода прямо в yaml код прошивки.
Теперь перешиваем нашу платку esp8266 на прошивку «пустышку» ESPHome через web и добавляем его в HA.

Так как мой PR еще не приняли в основную ветку, придется залить модифицированную версию библиотеки IRremoteESP8266 в HA. Я скопировал его сюда в HomeAssistant /config/esphome/lib/IRremoteESP8266 и написал код для ESPHome, который берет данные с основной сущности Climate из HomeAssitant, устанавливает нужные флаги, температуру, режим работы и тд, обогащает данными с внешнего сенсора температуры (тоже из HA) и отправляет по IR с периодичностью (тут можно увеличить период обновления температуры с 10 минут, например, на 3).
Скрытый текст
esphome: name: friendly_name: AC IR temp sender libraries: - IRremoteESP8266 # подключаем внешнюю библиотеку для работы с IR includes: - lib/IRremoteESP8266/src/ir_Tcl.h # подменяем своей патченной локальной версией esp8266: board: d1_mini # Enable logging logger: # Enable Home Assistant API api: encryption: key: "HIDDEN" ota: - platform: esphome password: "HIDDEN" wifi: ssid: !secret wifi_ssid password: !secret wifi_password # Enable fallback hotspot (captive portal) in case wifi connection fails ap: ssid: "Test Fallback Hotspot" password: "HIDDEN" captive_portal: # Text sensor для получения режима работы text_sensor: - platform: homeassistant entity_id: climate.air_conditioner_none # сущность кондиционера id: ha_climate_mode # Режим работы (cool, heat, fan, dry) attribute: hvac_mode # Получаем режим работы # Text sensor для получения скорости вентилятора - platform: homeassistant entity_id: climate.air_conditioner_none # сущность кондиционера id: ha_climate_fan_mode # Режим вентилятора attribute: fan_mode # Получаем скорость вентилятора # Switch для управления UV-стерилизацией binary_sensor: - platform: homeassistant entity_id: switch.air_conditioner_uv_sterilization # сущность выключатель UV стериализации id: ha_uv_sterilization # Для управления функцией UV-стерилизации # Switch для управления экраном - platform: homeassistant entity_id: light.air_conditioner_display # сущность выключатель дисплея id: ha_conditioner_display # Для управления функцией UV-стерилизации # Получение данных с сенсора Home Assistant sensor: - platform: homeassistant name: "Средняя температура в гостиной" id: temp_sensor1 entity_id: sensor.sredniaia_temperatura_v_gostinnoi - platform: homeassistant name: "Средняя температура в спальне" id: temp_sensor2 entity_id: sensor.sredniaia_temperatura_v_spalne - platform: homeassistant entity_id: climate.air_conditioner_none # сущность кондиционера attribute: temperature # Атрибут целевой температуры id: ha_climate_target_temperature # Целевая температура # Select Интерфейс для выбора сенсора select: - platform: template icon: "mdi:snowflake-thermometer" name: "Внешний сенсор" id: select_temp_sensor options: - "Средняя температура в гостиной" - "Средняя температура в спальне" optimistic: True button: - platform: template name: "Send TCL AC Cool Command" on_press: - lambda: |- #include <IRremoteESP8266.h> #include <IRsend.h> #include <ir_Tcl.h> const uint8_t first_packet[14] = {0x23, 0xCB, 0x26, 0x02, 0x00, 0x40, 0x20, 0x00, 0x83, 0x00, 0x00, 0x00, 0x00, 0x08}; uint8_t default_state[14] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x04, 0x03, 0x07, 0x40, 0x00, 0x00, 0x00, 0x80, 0x03}; // Получаем значение температуры с выбранного сенсора float external_temperature = NAN; if (id(select_temp_sensor).state == "Средняя температура в гостиной") { external_temperature = id(temp_sensor1).state; } else if (id(select_temp_sensor).state == "Средняя температура в спальне") { external_temperature = id(temp_sensor2).state; } ESP_LOGD("main", "Selected temperature: %.2f", external_temperature); // Ограничиваем температуру от внешнего датчика 0 до 35 if (external_temperature < 1) { external_temperature = 0; } else if (external_temperature > 35) { external_temperature = 35; } // Получаем значение температуры целевой температуры с кондиционера float set_temperature = id(ha_climate_target_temperature).state; // Получаем состояние UV-стерилизации (health mode) ESP_LOGD("main", "Starting IR transmission"); bool uv_sterilization = id(ha_uv_sterilization).state; bool display = id(ha_conditioner_display).state; IRTcl112Ac ac(5); // GPIO5 ac.begin(); ac.setRaw(first_packet); // сначала устанавливаем первый пакет ac.send(0); // отправляем без повтора ac.setRaw(default_state); // устанавливаем дефалтовый режим кондиционера ac.setTemp(set_temperature); // Получаем режим работы std::string mode = id(ha_climate_mode).state; if (mode == "cool") { ac.setMode(kTcl112AcCool); } else if (mode == "heat") { ac.setMode(kTcl112AcHeat); } else if (mode == "fan_only") { ac.setMode(kTcl112AcFan); } else if (mode == "dry") { ac.setMode(kTcl112AcDry); } // Устанавливаем скорость вентилятора std::string fan_mode = id(ha_climate_fan_mode).state; if (fan_mode == "low") { ac.setFan(kTcl112AcFanLow); } else if (fan_mode == "medium") { ac.setFan(kTcl112AcFanMed); } else if (fan_mode == "high") { ac.setFan(kTcl112AcFanHigh); } else if (fan_mode == "auto") { ac.setFan(kTcl112AcFanAuto); } ac.setHealth(uv_sterilization); // Устанавливаем значение Health ac.setLight(display); // Устанавливаем значение включения экрана // ради этих двух строчек все и затевалось ac.setIFeel(true); // для передачи температы с внешнего датчика нужна нажатая кнопка IFEEL ac.setSensorTemp(static_cast<uint8_t>(external_temperature)); // температура с внешнего датчика, целое ac.send(0); // отправляем без повтора
Проверить работу IR светодиода можно через камеру телефона, при отправке сигнала, он будет еле светиться фиолетовым.
После заливки прошивки выше, получаем возможность управлять кондиционером отправляя его текущий статус плюс данные с внешнего температурного сенсора.

Автоматизация
Осталось написать автоматизацию для HomeAssistant, которая, в зависимости от времени суток, с заданным интервалом отправляла бы, температуру с внешнего датчика кондиционеру — днем среднюю температуру в гостиной, ночью среднюю температуру в спальне.
Скрытый текст
alias: Обновляем датчик температуры кондиционера description: "" trigger: - platform: time_pattern minutes: "/3" condition: - condition: device device_id: b6c541864ffbdbbc25da3b9dfd309b0e domain: climate entity_id: 6634d8677c24063798122ef1142ef016 type: is_hvac_mode hvac_mode: cool action: - if: - condition: time after: "00:00:00" before: "06:00:00" then: - action: select.select_option metadata: {} data: option: Средняя температура в спальне target: entity_id: select.test else: - action: select.select_option metadata: {} data: option: Средняя температура в гостиной target: entity_id: select.test - action: button.press metadata: {} data: {} target: entity_id: button.test_send_tcl_ac_cool_command mode: single
Скрытый текст

Я добавил IR трансмиттер в свою домашнюю станцию по определению качества воздуха.
Мира!
ссылка на оригинал статьи https://habr.com/ru/articles/840050/
Добавить комментарий