или как сделать неумный умным.
Привет, Хабр!
Мой домашний кондиционер
![](https://habrastorage.org/getpro/habr/upload_files/b5a/637/551/b5a637551a5550957bd2288717bc397d.png)
После года владения кондиционером Royal Clima fresh full inverter (RCI-RF40HN), обнаружил на пульте кнопку I FEEL, полез читать инструкцию (пультом не пользуюсь, кондиционер управляется умным домом по wifi) и нашел, что в пульт встроен датчик температуры и по этому датчику кондиционер может регулировать более точнее температуру в помещении. Это то что мне не хватало: кондиционер висит в центре квартиры, в гостиной, а на ночь я направлял воздушный поток (через открытую дверь) в спальню.
![ночью температура в спальне повышалась ночью температура в спальне повышалась](https://habrastorage.org/getpro/habr/upload_files/8e8/72c/9a2/8e872c9a213020bd5aa818afc615f005.png)
Встроенный в корпус кондиционера датчик работает довольно точно, но вот ночью, он охлаждал сам себя намного быстрее и уменьшал холодный поток воздуха и "холод" не доходил до спальной комнаты.
Вариант уносить с собой в спальню пульт на ночь, не подходил, так как нет прямой видимости пульта и кондиционера.
Анализ протокола пульта
Для анализа протокола пульта решил воспользоваться библиотекой IRremoteESP8266, быстро собрал простейшую схему из ESP2866 и IR сенсора KY-022 (удобно, что у него есть встроенные светодиод, который сигнализирует о получении сигнала) или любой другой сенсор IR.
Здесь история как я сначала выбрал неудачную платку ESP32WROOM
Сначала залил прошивку анализатор 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 и все сработало, протокол начал определятся.
![](https://habrastorage.org/getpro/habr/upload_files/077/d08/d6f/077d08d6f4cf4ab1fc48f9a7a241d543.png)
Добавляем библиотеку 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;
};
};
Плюс написал методы для получение новых статусов
![](https://habrastorage.org/getpro/habr/upload_files/003/17f/afc/00317fafc0ea4a70da8c362f8ac73a6a.png)
и вывод в лог
![](https://habrastorage.org/getpro/habr/upload_files/066/536/7b9/0665367b9d017b385642986d21a55696.png)
Теперь заливаем еще раз прошивку, но с измененными библиотекой и в логе видим правильное отображение нажатия кнопки 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 светодиод (я откусил от нонейм китайского пульта управления светодиодной ленты).
![](https://habrastorage.org/getpro/habr/upload_files/c03/83d/edb/c0383dedbc4b0b7d109d04441748d54b.png)
Умный дом у меня построен на 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.
![](https://habrastorage.org/getpro/habr/upload_files/622/328/6e0/6223286e00b19348cac05f80dea0e54d.png)
Так как мой PR еще не приняли в основную ветку, придется залить модифицированную версию библиотеки IRremoteESP8266 в HA. Я скопировал его сюда в HomeAssistant /config/esphome/lib/IRremoteESP8266
и написал код для ESPHome, который берет данные с основной сущности Climate из HomeAssitant, устанавливает нужные флаги, температуру, режим работы и тд, обогащает данными с внешнего сенсора температуры (тоже из HA) и отправляет по IR с периодичностью (тут можно увеличить период обновления температуры с 10 минут, например, на 3).
Основной код прошивки отправки IR сигналов
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 светодиода можно через камеру телефона, при отправке сигнала, он будет еле светиться фиолетовым.
После заливки прошивки выше, получаем возможность управлять кондиционером отправляя его текущий статус плюс данные с внешнего температурного сенсора.
![](https://habrastorage.org/getpro/habr/upload_files/4ab/b7c/685/4abb7c685b1ae9e42391fc5d86b21553.png)
Автоматизация
Осталось написать автоматизацию для 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
Спрятал в коробочку с домашней "погодной" станцией
![](https://habrastorage.org/getpro/habr/upload_files/86c/829/5b3/86c8295b30c9ecfe80f7d6e073ddafe4.png)
Я добавил IR трансмиттер в свою домашнюю станцию по определению качества воздуха.
Мира!
Комментарии (23)
StarkIII
02.09.2024 09:36У меня точно такой же кондиционер, и мне кажется что или он чересчур умный, или я глупый. Он у меня каждую ночь самопроизвольно включает функцию GENTLE WIND (мягкий обдув), при этом внутренние шторки поворачиваются на 90гр. и практически полностью перекрывают поток воздуха (в шторках есть отверстия, через них воздух все таки немного (мягко) просачивается). В таком режиме комната очень плохо охлаждается. И я не могу никак это поведение изменить. На пульте эта функция естественно выключена, к тому же если она и включена, то она режим включается моментально. Кондиционер подключен к интернету, пробовал и в смартфоне всё включать и выключать... Ничего не помогает. Может у кого есть какие идеи? Может его можно как-то сбросить к заводским настройкам?
foxyrus Автор
02.09.2024 09:36Да это умный режим, после того как в комнате темнеет (и вечернее время), включается этот режим. Нужно писать разработчикам.
StarkIII
02.09.2024 09:36И как вы с этим живете? Свет не выключаете? Или через HA можно этим управлять?
foxyrus Автор
02.09.2024 09:36дергаю повторно установку температуры и "повторно" включаю включенный кондиционер
Скрытый текст
alias: Включение кондиционера ночью description: "" trigger: - type: temperature platform: device device_id: b718c0f6b262ea8f33c5a4bf8d01b89c entity_id: 752121ecc078653fd46f100ba736c743 domain: sensor above: 24 condition: - condition: and conditions: - condition: time after: "00:00:00" before: "05:55:00" - condition: state entity_id: binary_sensor.presence_sensor state: "on" action: - action: light.turn_off metadata: {} data: {} target: entity_id: light.air_conditioner_display - device_id: b6c541864ffbdbbc25da3b9dfd309b0e domain: climate entity_id: 6634d8677c24063798122ef1142ef016 type: set_hvac_mode hvac_mode: cool - type: turn_off device_id: b6c541864ffbdbbc25da3b9dfd309b0e entity_id: f58fb4bcb4a0b21fbdb8786c9037b1f2 domain: switch - device_id: b6c541864ffbdbbc25da3b9dfd309b0e domain: select entity_id: 3642e78e16504801bc167f2ced6a902d type: select_option option: Leftmost - metadata: {} data: temperature: 25 hvac_mode: cool target: device_id: b6c541864ffbdbbc25da3b9dfd309b0e action: climate.set_temperature mode: single
StarkIII
02.09.2024 09:36Похоже, что тоже придется НА настраивать. А то вручную приходится всю ночь дергать (
И разработчикам надо написать!
foxyrus Автор
02.09.2024 09:36короче, убирает gentle wind выключение Adaptive display - китайцы к этой функции привязали спящий режим для кондиционера.
StarkIII
02.09.2024 09:36А где эта функция, я не могу её найти?
foxyrus Автор
02.09.2024 09:36в приложении tuya есть
StarkIII
02.09.2024 09:36А что это за приложение? Есть Smart Life и SmartLife-SmartHome...
foxyrus Автор
02.09.2024 09:36Всегда ею пользовался https://play.google.com/store/apps/details?id=com.tuya.smart&hl=ru
Это основное приложение умных устройств на китайской платформе Tuya
Скрытый текст
StarkIII
02.09.2024 09:36Спасибо. Это приложение не доступно для моего устройства, но такую же кнопку я нашел в своём приложении, и даже вчера выключил функцию, но так крепко спал, что не смог проверить, сработало или нет)
Sergey_12345
02.09.2024 09:36+1Подсветить датчик освещенности
-
Вынуть одну шторку.
Ну а как иначе с умными девайсами?
foxyrus Автор
02.09.2024 09:36А через HA вы через что управляете кондиционером этим?
StarkIII
02.09.2024 09:36Через пуль ДУ и через SmartLife-SmartHome на телефоне.
foxyrus Автор
02.09.2024 09:36Вот думаю, сделать эксперимент: отвязать от приложения, сбросить, чтобы доступа к сети не было, не будет установлено время (внутренние часы), не сможет ночью включать этот режим.
StarkIII
02.09.2024 09:36Я его отлучал от сети, но часы не сбрасывал (они сбросятся если его обесточить?), не помогло.
foxyrus Автор
02.09.2024 09:36Тут наверно нужно делать полный сброс. чтоб как с завода и не подключать к приложению.
Если просто отключить от wifi, внутренние часы скорее всего продолжат идти.
helloworld0
Для этого кондиционера возможно реализовать управление через аналог брендового Wi-Fi модуля.
Родной модуль работает только с китайскими облаками. Альтернативной прошивкой можно добиться управления через HA/MQTT.
foxyrus Автор
Я ж многократно написал в статье, мой кондиционер отлично управлялся(ется) через встроенный в него модуль wifi и интеграцию Tuya Local (без китайских облаков), обертку для него я тоже сам написал. Но API не предполагает отправку данных с внешнего сенсора температуры, только по IR можно.
Скрытый текст
helloworld0
Почему бы в таком случае не управлять кондиционером по API на основе какого-либо внешнего датчика температуры?
Есть ли преимущество у использования iFeel по сравнению с внешней автоматизацией?
foxyrus Автор
Нужно будет угадывать или собирать статистику, как на основе внешнего датчика и моего управления, кондиционер будет охлаждать, а тут уже все готово.