Предисловие. Телевидение как таковое я не смотрю, а использую телевизор для просмотра фильмов и Торрент ТВ на медиаплеере KODI.
Т.к. KODI установлен на PC/Android TV BOX/RaspberryPi, то пульт всё время лежит без дела и используется только для включения ТВ.
Для управления KODI приходится пользоваться подручными средствами, клавиатурой и мышью, также пользовался одно время гироскопической мышью,
так называемой AirMouse — впечатления от неё не самые лучшие. Точность наведения страдает особенно при попытке нажать кнопку ОК, только навёлся и тут
курсор смещается и в результате нажимается не та кнопка на экране какую хотел. Еще у неё по умолчанию при пробуждении включается гиромышь, появляется
экранное меню и курсор. Это очень отвлекает особенно когда пульт активируется случайно. Гораздо удобнее в большинстве случаев управлять медиаплеером
с эмулятора клавиатуры, т.к. по умолчанию практически все функции управления с клавиатуры занесены в конфигурационный файл KODI.
Осталось только запрограммировать нужные комады на физические кнопки пульта. Также нужно использовать пульт по прямому назначению для управления ТВ.
Ещё иногда требуется мышь, в редких случаях, для использования на рабочем столе операционной системы например если KODI вылетит, то запустить его снова.
Если медиаплеер подключен к ТВ по HDMI, то можно использовать CEC (у LG это называется SimpLink) для управления несколькими устройствами от одного пульта.
Но в моём случае комп подключен по VGA разъёму.
Для реализации всего этого применено устройство на основе китайского клона Arduino Pro Micro выполненного на МК AtMega32U4 со встроенным интерфейсом USB.
В Ардуино для этого МК есть библиотеки позволяющие включать эмуляцию клавиатуры и мыши, а при имении опыта и любого другого HID устройства, например джойстика.
Для приёма и ретрансляции ИК команд применена библиотека Arduino-IRremote-master. Т.к. Arduino Pro Micro выпускается компанией SparkFun, а не Arduino,
то библиотека некорректно работает с этой платой. Нужно откорректировать файл boarddefs.h в секции #elif defined(__AVR_ATmega32U4__) раскомментировать строку
#define IR_USE_TIMER3 // tx = pin 9, а остальные закомментировать. Также нужно в секции Timer3 (16 bits) закомментировать все строки от //#if defined(CORE_OC3A_PIN)
до конца секции и в конце прописать #define TIMER_PWM_PIN 5
Таким образом генерация сигнала для излучающего ИК диода будет осуществляться таймером 3 на выводе 5.
Устройство работает в трёх режимах. Первый — эмуляция клавиатуры. Второй — ретрансляция команд с пульта на телевизор при этом передающий светодиод нужно закрепить
на окне приёмника ТВ сделав экран от внешнего пространства так чтобы сигналы не попадали непосредственно с пульта. Третий режим — эмуляция мыши.
Кнопка включения телевизора работает в любом режиме одинаково, т.к. на пульте никакой индикации нет, а добавлять ещё одного светлячка освещающего комнату во время сна не хочется, да и просто так удобней.
Для изготовления потребуются: Arduino Pro Micro, ИК приёмник TSOP1738 (мой пульт работает на частоте 38 кГц, но при этом с приёмником на 36 кГц работает также хорошо), ИК светодиод, резистор на 1кОм. С вывода 5 подключается резистор, с него анод ИК диода.
Катодом ИК диод подключается к GND. Выход ИК приёмника подключается к выводу А0, питание на приёмник подаётся с вывода Vcc.
Программа использования пульта на ИК лучах от телевизора, в данном случае это пульт LG AKB73615306
#include <Keyboard.h> #include <Mouse.h> #include <IRremote.h> // подключаем библиотеку для IR приемника /* Пульт имеет 51 кнопку, плюс пульт отправляет повтор команды в виде кода 0xFFFFFFFF, поэтому создаём массив на 52 элемента. * Для получения кодов кнопок и определения кодировки (алгоритма) используем скетч из примеров IRrecvDump, в серийном мониторе получаем значения. * Как оказалось LG использует кодировку NEC. Команда представляет собой 32 битное число, поэтому для хранения его требуется тип данных * unsigned long или в универсальном виде uint32_t * * Занесём эти значения в массив buttons, чтобы в дальнейшем обращаться к кодовой посылке по номеру элемента массива */ const static unsigned long buttons [52] = { 0x20DF10EF, // 0 power 0x20DF5EA1, // 1 help 0x20DF9E61, // 2 ratio 0x20DFD02F, // 3 input 0x20DF0FF0, // 4 tv/radio 0x20DF8877, // 5 1 0x20DF48B7, // 6 2 0x20DFC837, // 7 3 0x20DF28D7, // 8 4 0x20DFA857, // 9 5 0x20DF6897, // 10 6 0x20DFE817, // 11 7 0x20DF18E7, // 12 8 0x20DF9867, // 13 9 0x20DFCA35, // 14 list 0x20DF08F7, // 15 0 0x20DF58A7, // 16 q.view 0x20DF40BF, // 17 vol+ 0x20DFC03F, // 18 vol- 0x20DF7887, // 19 fav 0x20DF55AA, // 20 info 0x20DF906F, // 21 mute 0x20DF00FF, // 22 program+ 0x20DF807F, // 23 program- 0x20DFC23D, // 24 settings 0x20DF3EC1, // 25 home 0x20DF42BD, // 26 my apps 0x20DF14EB, // 27 back 0x20DFD52A, // 28 guide 0x20DFDA25, // 29 exit 0x20DF02FD, // 30 up 0x20DF827D, // 31 down 0x20DFE01F, // 32 left 0x20DF609F, // 33 right 0x20DF22DD, // 34 ok 0x20DF4EB1, // 35 red 0x20DF8E71, // 36 green 0x20DFC639, // 37 yellow 0x20DF8679, // 38 blue 0x20DF04FB, // 39 text 0x20DF847B, // 40 t.opt 0x20DFA25D, // 41 q.menu 0x20DF8D72, // 42 stop 0x20DF0DF2, // 43 play 0x20DF5DA2, // 44 pause 0x20DFF10E, // 45 rewing 0x20DF718E, // 46 forward 0x20DFBD42, // 47 rec 0x20DFA956, // 48 energy saving 0x20DF8976, // 49 ad 0x20DFF906, // 50 app/* 0xFFFFFFFF // 51 зажатая кнопка }; unsigned long repeatTempIR, repeatTempKey; /* Объявляем переменные repeatTempIR, repeatTempKey. В repeatTempIR хранится код последней принятой команды с ИК пульта. * В repeatTempKey хранится код последней посланной команды с эмулятора клавиатуры. * */ int modeIRremote = 0; // Задаём переменную для установки режима работы пульта int dirMouse = 0; // Задаём переменную используемую в функции повтора перемещения курсора мыши. Задаёт направление движения курсора int speedMouse = 10; // Задаём переменную скорости (шага в пикселях) перемещения курсора мыши, число по умолчанию IRrecv irrecv(A0); // указываем пин, к которому подключен IR приемник IRsend irsend; // IR LED должен быть подключен к Arduino PWM pin 5, смотри строку 19. decode_results results; const unsigned int DelayTimeRepeatPressingTheArrowsUpDown = 50; const unsigned int DelayTimeRepeatPressingTheArrows = 50; const unsigned int DelayTimeRepeatPressingVolume = 50; const unsigned int DelayTimeRepeatPressing = 50; const unsigned int DelayMouse = 50; // Выше задаём задержки нажатия клавиш эмулятора клавиатуры, чтобы исключить нежелательный повтор ввода команды void setup() { Serial.begin(115200); // Запускаем серийный монитор на скорости (значение). Вывод данных в порт осуществляется только для контроля работоспособности программы и не является обязательным irrecv.enableIRIn(); // запускаем прием инфракрасного сигнала pinMode(A0, INPUT); // пин A0 будет входом Keyboard.begin(); // Запускаем клавиатуру Mouse.begin(); // Запускаем мышь } void loop() { if (irrecv.decode(&results)) { // если данные пришли выполняем команды Serial.println(results.value, HEX); // отправляем полученные данные на порт if (results.value == buttons [50]) { // если нажата кнопка выбора функции modeIRremote++; // то сменить функцию пульта if (modeIRremote >= 3) { modeIRremote = 0; repeatTempKey = 0;} // если счётчик функции равен 3, то сбросить счётчик и repeatTempKey в 0 Serial.println(modeIRremote); } if (results.value == buttons [0]) { // Включаем и отключаем ТВ, независимо от режима работы irsend.sendNEC(results.value, 32); irrecv.enableIRIn(); // После передачи ИК команды снова запускаем прием инфракрасного сигнала } if (results.value != buttons [51]) { repeatTempIR = results.value; } // если получен код другой кнопки, а не 51 (код повтора нажатия), то записать код во временную переменную switch (modeIRremote) { // Для выбора функции применим оператор Switch () case case 0: // Первая функция - управления медиаплеером KODI if (results.value == buttons [32]) { // 32 left. Если код соответствует значению в массиве кнопок, Keyboard.write(KEY_LEFT_ARROW); // то послать нажатие кнопки клавиатуры repeatTempKey = (KEY_LEFT_ARROW); // Записать значение нажатой кнопки во временную переменную для работы в функции повтора нажатия клавиши delay(DelayTimeRepeatPressingTheArrows); // делаем задержку } if (results.value == buttons [33]) { // 33 right Keyboard.write(KEY_RIGHT_ARROW); repeatTempKey = (KEY_RIGHT_ARROW); delay(DelayTimeRepeatPressingTheArrows); } if (results.value == buttons [30]) { // 30 up Keyboard.write(KEY_UP_ARROW); repeatTempKey = (KEY_UP_ARROW); delay(DelayTimeRepeatPressingTheArrowsUpDown); } if (results.value == buttons [31]) { // 31 down Keyboard.write(KEY_DOWN_ARROW); repeatTempKey = (KEY_DOWN_ARROW); delay(DelayTimeRepeatPressingTheArrowsUpDown); } if (results.value == buttons [34]) { // 34, Enter, ввод Keyboard.write(KEY_RETURN); repeatTempKey = (KEY_RETURN); delay(DelayTimeRepeatPressing); } if (results.value == buttons [27]) { // 27, Escape Keyboard.write(KEY_ESC); repeatTempKey = (KEY_ESC); delay(DelayTimeRepeatPressing); } if (results.value == buttons [43]) { // 43, Space, Play Keyboard.write(0x20); repeatTempKey = (0x20); delay(DelayTimeRepeatPressing); } if (results.value == buttons [42]) { // 42, X, Stop Keyboard.write(0x78); repeatTempKey = (0x78); delay(DelayTimeRepeatPressing); } if (results.value == buttons [17]) { // 17, +, Volume Up Keyboard.write(0x3d); repeatTempKey = (0x3d); delay(DelayTimeRepeatPressingVolume); } if (results.value == buttons [4]) { // 4, Page Up, След. аудио дорожка (нужно настроить в конфиге KODI) Keyboard.write(KEY_PAGE_UP); repeatTempKey = (KEY_PAGE_UP); delay(DelayTimeRepeatPressing); } if (results.value == buttons [18]) { // 18, -, Volume Down Keyboard.write(0x2d); repeatTempKey = (0x2d); delay(DelayTimeRepeatPressingVolume); } if (results.value == buttons [3]) { // 3, L, Next subtitle Keyboard.write(0x6c); repeatTempKey = (0x6c); delay(DelayTimeRepeatPressing); } if (results.value == buttons [2]) { // 2, m, Экранное меню OSD Keyboard.write(0x6d); repeatTempKey = (0x6d); delay(DelayTimeRepeatPressing); } if (results.value == buttons [21]) { // 21, F8, Mute Keyboard.write(KEY_F8); repeatTempKey = (KEY_F8); delay(DelayTimeRepeatPressing); } if (results.value == buttons [16]) { // 16, TAB, Fullscreen playback Keyboard.write(KEY_TAB); repeatTempKey = (KEY_TAB); delay(DelayTimeRepeatPressing); } if (results.value == buttons [41]) { // 41, T, Вкл/Выкл субтитров Keyboard.write(0x74); repeatTempKey = (0x74); delay(DelayTimeRepeatPressing); } if (results.value == buttons [51]) { // 51, повтор нажатия кнопки предыдущего кода в переменной Keyboard.write(repeatTempKey); delay(DelayTimeRepeatPressingVolume); } break; // Для выхода из условного оператора обязательно применение ключевого слова break, иначе операторы будут выполнятся дальше по списку case 1: // Вторая функция управления телевизором путём ретрансляции команд через ИК диод закреплённый напротив приёмника телевизора //, при этом ИК приёмник должен быть изолирован от попадания команд непосредственно с пульта if (results.value != buttons [0]) { irsend.sendNEC(results.value, 32); irrecv.enableIRIn(); // После передачи ИК команды снова запускаем прием инфракрасного сигнала } break; case 2: // Третья функция - эмуляция мыши speedMouse = 10; // Задаём скорость (шага в пикселях) перемещения курсора мыши при одиночном нажатии // Двигаем курсор мыши с кнопок 1,2,3,4,6,7,8,9, а также крестовиной на пульте if ((results.value == buttons [8]) || (results.value == buttons [32])) { Mouse.move(-speedMouse, 0); // move mouse left delay(DelayMouse); dirMouse = 4; } if (results.value == buttons [5]) { Mouse.move(-speedMouse, -speedMouse); // move mouse left-up delay(DelayMouse); dirMouse = 1; } if ((results.value == buttons [6]) || (results.value == buttons [30])) { Mouse.move(0, -speedMouse); // move mouse up delay(DelayMouse); dirMouse = 2; } if (results.value == buttons [7]) { Mouse.move(speedMouse, -speedMouse); // move mouse up-right delay(DelayMouse); dirMouse = 3; } if ((results.value == buttons [10]) || (results.value == buttons [33])) { Mouse.move(speedMouse, 0); // move mouse right delay(DelayMouse); dirMouse = 6; } if (results.value == buttons [13]) { Mouse.move(speedMouse, speedMouse); // move mouse right-down delay(DelayMouse); dirMouse = 9; } if ((results.value == buttons [12]) || (results.value == buttons [31])) { Mouse.move(0, speedMouse); // move mouse down delay(DelayMouse); dirMouse = 8; } if (results.value == buttons [11]) { Mouse.move(-speedMouse, speedMouse); // move mouse down-left delay(DelayMouse); dirMouse = 7; } // Щёлкаем кнопками мыши if ((results.value == buttons [9]) || (results.value == buttons [34])) { Mouse.click(MOUSE_LEFT); // perform mouse left click, кнопка 5 dirMouse = 0; } if ((results.value == buttons [14]) || (results.value == buttons [27])) { Mouse.click(MOUSE_LEFT); // perform mouse left double click, кнопка list delay(100); Mouse.click(MOUSE_LEFT); dirMouse = 0; } if ((results.value == buttons [15]) || (results.value == buttons [28])) { Mouse.click(MOUSE_MIDDLE); // perform mouse middle click, кнопка 0 dirMouse = 0; } if ((results.value == buttons [16]) || (results.value == buttons [29])) { Mouse.click(MOUSE_RIGHT); // perform mouse left click, кнопка Q.VIEW dirMouse = 0; } // Добавим вспомогательные клавиши, для браузера if (results.value == buttons [22]) { Keyboard.write(KEY_PAGE_UP); // нажать кнопку PAGE_UP delay(DelayTimeRepeatPressing); dirMouse = 0; } if (results.value == buttons [23]) { Keyboard.write(KEY_PAGE_DOWN); // нажать кнопку PAGE_DOWN delay(DelayTimeRepeatPressing); dirMouse = 0; } if (results.value == buttons [17]) { Keyboard.write(KEY_HOME); // нажать кнопку HOME delay(DelayTimeRepeatPressing); dirMouse = 0; } if (results.value == buttons [18]) { Keyboard.write(KEY_END); // нажать кнопку END delay(DelayTimeRepeatPressing); dirMouse = 0; } if (results.value == buttons [21]) { Keyboard.write(KEY_F11); // нажать кнопку F11 (развернуть браузер на полный экран) delay(DelayTimeRepeatPressing); dirMouse = 0; } if (results.value == buttons [51]) { // Если поступила команда 51, повтор нажатия кнопки предыдущего кода if ((repeatTempIR == buttons [5]) || (repeatTempIR == buttons [6]) || (repeatTempIR == buttons [7]) || (repeatTempIR == buttons [10]) || (repeatTempIR == buttons [13]) || (repeatTempIR == buttons [12]) || (repeatTempIR == buttons [11]) || (repeatTempIR == buttons [8]) || (repeatTempIR == buttons [30]) || (repeatTempIR == buttons [31]) || (repeatTempIR == buttons [32]) || (repeatTempIR == buttons [33])) { // Если предыдущей командой было перемещение курсора мыши speedMouse = 60; // Задаём скорость (шага в пикселях) перемещения курсора мыши при повторном нажатии switch (dirMouse) { // то повторить нажатие в соответствии с направлением dirMouse case 4: Mouse.move(-speedMouse, 0); // move mouse left break; case 1: Mouse.move(-speedMouse, -speedMouse); // move mouse left-up break; case 2: Mouse.move(0, -speedMouse); // move mouse up break; case 3: Mouse.move(speedMouse, -speedMouse); // move mouse up-right break; case 6: Mouse.move(speedMouse, 0); // move mouse right break; case 9: Mouse.move(speedMouse, speedMouse); // move mouse right-down break; case 8: Mouse.move(0, speedMouse); // move mouse down break; case 7: Mouse.move(-speedMouse, speedMouse); // move mouse down-left break; case 0: break; } } } break; } irrecv.resume(); // Принимаем следующее значение с ИК пульта } }