Digg StumbleUpon LinkedIn YouTube Flickr Facebook Twitter RSS Reset

Адаптация пульта на ИК лучах для использования с компьютером

Предисловие. Телевидение как таковое я не смотрю, а использую телевизор для просмотра фильмов и Торрент ТВ на медиаплеере 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(); // Принимаем следующее значение с ИК пульта
}
}