Приветствую, уважаемые хабрачитатели! В этой, первой для меня, статье я хотел поделиться своими исследованиями и наработками. Имея в своей квартире кондиционер я ощущал необходимость управлять им когда меня нет дома. Возвращаясь с дачи или просто с работы и, включив кондиционер, приходится ждать некоторое время пока не спадет жара. А хочется приходить в уже легкую прохладу.
«Вот бы можно было удаленно включить кондишн...» — подумал я.
Итак, у меня имеется сплит-система Panasonic CS-XE9DKE. Идея управлять кондиционером сводилась к созданию «своего» ИК-пульта. Назовем его «ИК-контроллер». А уж управлять самим контроллером — это задача десятая. И это зависит от конкретного места применения. Мне, например, было бы удобнее управлять с домашнего сервера через WizFi200(только вот достать его за приемлемые деньги проблематично). Моим родителям — с помощью отправки SMS(на SIM900) на номер контроллера. Возможны и другие варианты. Но это не главное, о чем я хотел рассказать.
Так как же родной пульт передает команду? Имея в наличии ИК-приемник TSOP17XX я начал анализировать поток данных от пульта. Выяснилось, что пульт шлет 2 посылки с небольшой паузой. Первая — заголовок, вторая — команда. Заголовок был всегда одинаковый.
Ознакомившись с существующими системами кодирования ИК-сигналов(RC5, RC6, NEC, JVC и другие) стало ясно, что здесь применяется что-то другое, хотя принцип схож.
Сингал выглядит следующим образом:
(активный уровень — низкий)
(значения бит для примера, время в мкс приблизительно)
В начале идет пилотный сигнал, за ним примерно вдвое короче второй пилотный(пауза), за ним стартовый бит длительностью примерно в 8 раз короче чем первый пилотный. Далее начинается битовый поток. Один бит кодируется длительностью паузы(высокий уровень). Если пауза одинарная — это НОЛЬ, если пауза тройная — это ЕДИНИЦА.
Всего при одном нажатии кнопки пульт шлет 2 таких посылки. Первая — заголовок, вторая — команда. Пауза между посылками около 10 мс.
Таким образом заголовок содержит 64 бита, или 8 байт. Сначала идут младшие биты.
Поток данных заголовка выглядит следующим образом:
0100000000000100000001110010000000000000000000000000000001100000
Или в байтах(HEX): 02 20 E0 40 00 00 00 06
Следует заметить, что последний байт является контрольной суммой всех предыдущих.
Теперь немного отвлечемся на сам пульт. Пульт имеет следующие кнопки:
Также есть кнопки для установки текущего времени, таймеров включения и выключения(раздельно, самостоятельно).
3 кнопки, которые отличаются от других, это:
Смысл в том, что эти кнопки не посылают все настройки, а включают/выключают конкретный режим. Т.е. Эти 3 режима можно включать/выключать в любом состоянии кондиционера.
Теперь вернемся к протоколу передачи. Любой команде предшествует заголовок.
Байты заголовка: 02 20 E0 40 00 00 00 06
Далее идет вторая посылка с командой.
Команды трех особых кнопок:
ion: 02 20 E0 04 80 48 33 01
oxyg: 02 20 E0 04 80 50 33 09
quiet: 02 20 E0 04 80 81 33 3A
Отмечу, что, как и в заголовке, последний байт — контрольная сумма.
А вот все остальные кнопки посылают пакет со всеми настройками сразу. И формат этого пакета следующий:
Поток бит:
0100000000000100000001110010000000000000PNF1mmm00ccccc0000000001vvvvFFFF
hhhh0000nnnnnnnnnnnXfffffffffffY000010000000000010000000ttttttttttt00000ssssssss
Расшифровка полей по порядку следования:
P — 1, если нажата кнопка ON/OFF. При нажатии других кнопок здесь 0.
N — 1, если установлен таймер включения
F — 1, если установлен таймер выключения
mmm — режим(Mode). Auto — 0, heat — 4, cool — 3, dry — 2, fan — 6
ccccc — температура. От 16 до 30
vvvv — вертикальное направление. Auto — 15, 1 — под потолок, … 5 — в пол.
FFFF — скорость вентилятора. Auto — 10, F1 — 3, F2 — 4, F3 — 5, F4 — 6, F5 — 7
hhhh — горизонтальное направление. Auto — 13, | | — 6, / / — 9, / | — 10, | \ — 11, \ \ — 12
nnnnnnnnnnn — время включения. Номер минуты в сутках. (например 16:00 = 960)
X — 1, если нажата кнопка установки/сброса таймера
fffffffffff — время выключения. Номер минуты в сутках.
Y — 1, если нажата кнопка установки/сброса таймера
ttttttttttt — текущее время.
ssssssss — контрольная сумма посылки.
Сигналы X и Y — установлены в 1 только когда выполняется установка/сброс таймеров, т.е. когда непосредственно нажимается SET или CANCEL. Не путать с N и F!
N и F — только сообщают, что таймеры включены/выключены, при этом nnnnnnnnnnn или fffffffffff могут быть «не известны»(0x600) в данный момент. Например при нажатии кнопки увеличения температуры, если в данный момент включен любой таймер, значение N/F равно 1, а nnnnnnnnnnn/fffffffffff cсодержит конкретное время только для включенного таймера(для выключенного содержит 0x600). Кондиционер сменит свой статус N/F только если соответсвующий бит X/Y равен 1.
Если таймер включения не установлен, то время включения д.б. 1536 = 0x600 = 0b11000000000. С временем таймера выключения аналогично. По крайней мере так шлет родной пульт.
Разделим поток бит на байты и развернем биты:
Итого команда 19 байт.
Для тестирования я собрал схему(позаимствовал здесь) на макетке для беспаечного монтажа. Компоненты:
Отличие моей схемы от схемы по ссылке — светодиод подключен к PB1, а база транзистора к PB3.
При отпускании кнопки на порту PD7 запускается таймер 1 в режиме CTC с выводом сигнала на PB1/OC1A, а так же таймер 2 с выводом на PB3/OC2 для несущей частоты, на которой и модулируется основной сигнал. Таймеры работают с предделителем 8.
Текущая тестовая реализация пока умеет слать заголовок и фиксированную команду. Но вот тут у меня возник вопрос — а какая должна быть у этого контроллера глубина реализации? Ведь можно контроллеру сказать «включи охлаждение на 23 градуса», а можно просто сказать «передай команду» и передать весь поток бит с уже сформированной командой. А ведь можно еще и тайминги указать. Тогда можно управлять через один контроллер-излучатель различными устройствами. Надеюсь в комментариях вы мне поможете принять решение.
Планируется также подключение пары датчиков температуры для контроля температуры помещения и выходного потока воздуха из кондиционера. Это удобно для обратной связи(например оповещение по смс с текущими параметрами климата) и контроля(а включился ли кондиционер?).
UPD
История получила продожение в статье Беспроводной контроллер бытового кондиционера в OpenHAB по Modbus через RF24Network.
«Вот бы можно было удаленно включить кондишн...» — подумал я.
Итак, у меня имеется сплит-система Panasonic CS-XE9DKE. Идея управлять кондиционером сводилась к созданию «своего» ИК-пульта. Назовем его «ИК-контроллер». А уж управлять самим контроллером — это задача десятая. И это зависит от конкретного места применения. Мне, например, было бы удобнее управлять с домашнего сервера через WizFi200(только вот достать его за приемлемые деньги проблематично). Моим родителям — с помощью отправки SMS(на SIM900) на номер контроллера. Возможны и другие варианты. Но это не главное, о чем я хотел рассказать.
Реверс-инжиниринг
Так как же родной пульт передает команду? Имея в наличии ИК-приемник TSOP17XX я начал анализировать поток данных от пульта. Выяснилось, что пульт шлет 2 посылки с небольшой паузой. Первая — заголовок, вторая — команда. Заголовок был всегда одинаковый.
Ознакомившись с существующими системами кодирования ИК-сигналов(RC5, RC6, NEC, JVC и другие) стало ясно, что здесь применяется что-то другое, хотя принцип схож.
Сингал выглядит следующим образом:
(активный уровень — низкий)
(значения бит для примера, время в мкс приблизительно)
В начале идет пилотный сигнал, за ним примерно вдвое короче второй пилотный(пауза), за ним стартовый бит длительностью примерно в 8 раз короче чем первый пилотный. Далее начинается битовый поток. Один бит кодируется длительностью паузы(высокий уровень). Если пауза одинарная — это НОЛЬ, если пауза тройная — это ЕДИНИЦА.
Всего при одном нажатии кнопки пульт шлет 2 таких посылки. Первая — заголовок, вторая — команда. Пауза между посылками около 10 мс.
Таким образом заголовок содержит 64 бита, или 8 байт. Сначала идут младшие биты.
Поток данных заголовка выглядит следующим образом:
0100000000000100000001110010000000000000000000000000000001100000
Или в байтах(HEX): 02 20 E0 40 00 00 00 06
Следует заметить, что последний байт является контрольной суммой всех предыдущих.
Теперь немного отвлечемся на сам пульт. Пульт имеет следующие кнопки:
- ON/OFF(включение/выключение)
- ± (настройка температуры)
- O2 (генератор кислорода)
- ion (ионизатор)
- quiet (тихий режим)
- mode ( auto, heat, cool, dry, fan)
- fan speed
- swing <>(горизонтальное направление)
- swing ^v (вертикальное направление)
Также есть кнопки для установки текущего времени, таймеров включения и выключения(раздельно, самостоятельно).
3 кнопки, которые отличаются от других, это:
- ion
- quiet
- O2
Смысл в том, что эти кнопки не посылают все настройки, а включают/выключают конкретный режим. Т.е. Эти 3 режима можно включать/выключать в любом состоянии кондиционера.
Теперь вернемся к протоколу передачи. Любой команде предшествует заголовок.
Байты заголовка: 02 20 E0 40 00 00 00 06
Далее идет вторая посылка с командой.
Команды трех особых кнопок:
ion: 02 20 E0 04 80 48 33 01
oxyg: 02 20 E0 04 80 50 33 09
quiet: 02 20 E0 04 80 81 33 3A
Отмечу, что, как и в заголовке, последний байт — контрольная сумма.
А вот все остальные кнопки посылают пакет со всеми настройками сразу. И формат этого пакета следующий:
Поток бит:
0100000000000100000001110010000000000000PNF1mmm00ccccc0000000001vvvvFFFF
hhhh0000nnnnnnnnnnnXfffffffffffY000010000000000010000000ttttttttttt00000ssssssss
Расшифровка полей по порядку следования:
P — 1, если нажата кнопка ON/OFF. При нажатии других кнопок здесь 0.
N — 1, если установлен таймер включения
F — 1, если установлен таймер выключения
mmm — режим(Mode). Auto — 0, heat — 4, cool — 3, dry — 2, fan — 6
ccccc — температура. От 16 до 30
vvvv — вертикальное направление. Auto — 15, 1 — под потолок, … 5 — в пол.
FFFF — скорость вентилятора. Auto — 10, F1 — 3, F2 — 4, F3 — 5, F4 — 6, F5 — 7
hhhh — горизонтальное направление. Auto — 13, | | — 6, / / — 9, / | — 10, | \ — 11, \ \ — 12
nnnnnnnnnnn — время включения. Номер минуты в сутках. (например 16:00 = 960)
X — 1, если нажата кнопка установки/сброса таймера
fffffffffff — время выключения. Номер минуты в сутках.
Y — 1, если нажата кнопка установки/сброса таймера
ttttttttttt — текущее время.
ssssssss — контрольная сумма посылки.
Сигналы X и Y — установлены в 1 только когда выполняется установка/сброс таймеров, т.е. когда непосредственно нажимается SET или CANCEL. Не путать с N и F!
N и F — только сообщают, что таймеры включены/выключены, при этом nnnnnnnnnnn или fffffffffff могут быть «не известны»(0x600) в данный момент. Например при нажатии кнопки увеличения температуры, если в данный момент включен любой таймер, значение N/F равно 1, а nnnnnnnnnnn/fffffffffff cсодержит конкретное время только для включенного таймера(для выключенного содержит 0x600). Кондиционер сменит свой статус N/F только если соответсвующий бит X/Y равен 1.
Если таймер включения не установлен, то время включения д.б. 1536 = 0x600 = 0b11000000000. С временем таймера выключения аналогично. По крайней мере так шлет родной пульт.
Разделим поток бит на байты и развернем биты:
00000010 02 00100000 20 11100000 E0 00000100 04 00000000 00 0mmm1FNP Mode<<4 + 8 + FlagOFF<<2 + FlagON<<1 + Power 00ccccc0 Temperature<<1 10000000 80 FFFFvvvv Fan<<4 + Vert 0000hhhh Horiz nnnnnnnn OnTime&0xFF ffffXnnn OnTime>>8 + OffTime&0x0F + ChangeOn<<3 Yfffffff OffTime>>4 + ChangeOff<<7 00010000 10 00000000 00 00000001 01 tttttttt Time&0xFF 00000ttt Time>>8 ssssssss Sum
Итого команда 19 байт.
Контроллер
Для тестирования я собрал схему(позаимствовал здесь) на макетке для беспаечного монтажа. Компоненты:
- atmega8
- ИК-диод
- транзистор кт361
- резистор 470 на базу транзистора
- резистор 220 ограничивает ток диода
- резистор 10к на RESET
Отличие моей схемы от схемы по ссылке — светодиод подключен к PB1, а база транзистора к PB3.
При отпускании кнопки на порту PD7 запускается таймер 1 в режиме CTC с выводом сигнала на PB1/OC1A, а так же таймер 2 с выводом на PB3/OC2 для несущей частоты, на которой и модулируется основной сигнал. Таймеры работают с предделителем 8.
Прошивка
Исходный код для AVR Studio 4.
#include <avr/io.h>
#include <avr/interrupt.h>
#define F_CPU 8000000
#define LED_CARIER DDB3
#define LED_SIGNAL DDB1
#define BASE 480
#define PAUSE 10000
#define START_1 BASE*8
#define START_2 BASE*4
#define DELIM 520
#define PULSE1 BASE*3
#define PULSE0 BASE
unsigned char state = 0;
unsigned char byteIndex = 0;
unsigned char frameIndex = 0;
unsigned char bit = 0;
unsigned char phase = 0;
unsigned char packetLen = 0;
unsigned char process = 0;
char header[] = {0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x06};
char cmd_ion[] = {0x02, 0x20, 0xE0, 0x04, 0x80, 0x48, 0x33, 0x01};
char cmd_oxygen[] = {0x02, 0x20, 0xE0, 0x04, 0x80, 0x50, 0x33, 0x09};
char cmd_quiet[] = {0x02, 0x20, 0xE0, 0x04, 0x80, 0x81, 0x33, 0x3A};
char* data;
char* cmd;
unsigned char cmdLen;
void stop();
ISR(SIG_OUTPUT_COMPARE1A) {
switch (state) {
case 0:
OCR1AH = START_2 / 256;
OCR1AL = START_2 % 256;
state = 1;
break;
case 1:
OCR1AH = DELIM / 256;
OCR1AL = DELIM % 256;
state = 2;
byteIndex = 0;
frameIndex++;
if (frameIndex == 1) {
data = header;
packetLen = 8;
} else {
data = cmd;
packetLen = cmdLen;
}
bit = 0;
phase = 0;
break;
case 2:
if (byteIndex < packetLen) {
if (phase == 0) {
if (data[byteIndex]&(1 << bit)) {
OCR1AH = PULSE1 / 256;
OCR1AL = PULSE1 % 256;
} else {
OCR1AH = PULSE0 / 256;
OCR1AL = PULSE0 % 256;
}
} else {
OCR1AH = DELIM / 256;
OCR1AL = DELIM % 256;
bit++;
if (bit == 8) {
bit = 0;
byteIndex++;
}
}
phase = 1 - phase;
} else {
OCR1AH = PAUSE / 256;
OCR1AL = PAUSE % 256;
if (frameIndex == 2) {
stop();
} else {
state = 3;
}
}
break;
case 3:
OCR1AH = START_1 / 256;
OCR1AL = START_1 % 256;
state = 0;
break;
}
}
void start() {
state = 0;
byteIndex = 0;
frameIndex = 0;
packetLen = sizeof (header);
TCNT1H = 0;
TCNT1L = 0;
OCR1AH = START_1 / 256;
OCR1AL = START_1 % 256;
TCCR1A = (1 << COM1A0);
TCCR1B = (1 << WGM12) | (1 << CS11);
TCNT2 = 0;
OCR2 = 13;
TCCR2 = (1 << WGM21) | (1 << COM20) | (1 << CS21);
TIMSK = (1 << OCIE1A);
sei();
}
void stop() {
cli();
TCCR2 = (1 << WGM21) | (1 << COM20);
TCCR1A = (1 << FOC1A);
TCCR1B = (1 << WGM12) | (0 << CS12) | (0 << CS11) | (0 << CS10);
TIMSK = 0;
PORTB = (1 << LED_SIGNAL) | (1 << LED_CARIER);
process = 0;
}
int main(void) {
process = 0;
PORTB = (1 << LED_SIGNAL) | (1 << LED_CARIER);
DDRB = (1 << LED_SIGNAL) | (1 << LED_CARIER);
PORTD = (1 << 7); // PD7 input, pull up
DDRD = 0;
while (1) {
int but = (PIND & (1 << PD7));
if (process == 0 && but == 0) {
process = 1;
} else if (process == 1 && but != 0) {
process = 2;
cmd = cmd_oxygen;
cmdLen = 8;
start();
}
}
}
Вывод
Текущая тестовая реализация пока умеет слать заголовок и фиксированную команду. Но вот тут у меня возник вопрос — а какая должна быть у этого контроллера глубина реализации? Ведь можно контроллеру сказать «включи охлаждение на 23 градуса», а можно просто сказать «передай команду» и передать весь поток бит с уже сформированной командой. А ведь можно еще и тайминги указать. Тогда можно управлять через один контроллер-излучатель различными устройствами. Надеюсь в комментариях вы мне поможете принять решение.
Планируется также подключение пары датчиков температуры для контроля температуры помещения и выходного потока воздуха из кондиционера. Это удобно для обратной связи(например оповещение по смс с текущими параметрами климата) и контроля(а включился ли кондиционер?).
UPD
История получила продожение в статье Беспроводной контроллер бытового кондиционера в OpenHAB по Modbus через RF24Network.