Управляем Windows пультом от телевизора или как передать сигналы через последовательный порт

от автора

Решил я , что хочу переключать громкость звука и аудио треки на ноутбуке под Windows с инфракрасного пульта. Под руку тут же попали: ардуино уно, кучка проводов с макетной платой, инфракрасный датчик, ноутбук и, собственно, инфракрасный пульт.

Идея есть, железо есть, а вот теория хромает. Как заставить компьютер понимать инфракрасные сигналы пульта и выполнять требуемые действия? Я надумал использовать ардуино для приема сигналов пульта через инфракрасный датчик на макетной плате и посылать сообщения в ноутбук через USB. Для этого требовались хоть какие то познания, как все это работает.

Было решено разобраться.

Знакомим ардуино с пультом

Для приема сигнала от инфракрасного пульта необходим приёмник, который мы подключим к ардуино через макетную плату по следующей схеме:

кодировка сигнала по протоколу NEC
кодировка сигнала по протоколу NEC

Для того, чтобы ардуино понимала, по какому протоколу и с какой командой передается сигнал, существует библиотека IRremote, которую в новых версиях Arduino IDE можно добавить из стандартных библиотек.

Сама библиотека

Моим желанием было научиться менять громкость звука компьютера и управлять медиа (пауза/ переключение треков). Для этого необходимо 5 кнопок пульта.

Для того, чтобы понять, какую информацию нам передает пульт, необходимо воспользоваться командой IrReceiver.decodedIRData.decodedRawData. На мониторе порта мы увидим подробную информацию о том, что содержит сигнал. Здесь нас интересует значение команд. Каждая кнопка пульта содержит свою команду, их мы и будем использовать для управления медиа. Прощёлкав все интересующие нас кнопки и записав коды команд, мы можем написать следующее:

#include <IRremote.h> int IR_RECEIVE_PIN = 2; // Получаем сигнал на 2-ой пин long command;  void setup() {   Serial.begin(9600);   IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK);  }  void loop() {   if (IrReceiver.decode()) // Расшифровываем последовательность сигналов пульта   {     command = IrReceiver.decodedIRData.decodedRawData; /* Проверяем какие комманды пульта      																											соответствуют нужным нам кнопкамм*/     switch(command) //Задаем действия в соответствии с полученным провеяремым сигналом     {       case 0xEA15FF00:         Serial.write("D"); delay(120);       break;        case 0xB946FF00:         Serial.write("U"); delay(120);       break;        case 0xBF40FF00:         Serial.write("P"); delay(120);       break;        case 0xBC43FF00:         Serial.write("N"); delay(120);       break;        case 0xBB44FF00:         Serial.write("R"); delay(120);       break;     }          IrReceiver.resume(); // Продолжаем получать сигналы   } }

Данный код сравнивает команды в принимаемом сигнале пульта с командами нужных нам кнопок, и если нужная кнопка была нам нажата, отправляет сообщение по USB.

Управление воспроизведением и громкостью Windows

Как управлять громкостью и медиа Windows, я нашел в этом посте.

Управление можно осуществлять с помощью виртуальных кодов — имитации действий клавиатурой и мышью. Я использовал С++ и Visual Studio так как там есть удобная для этой задачи библиотека Windows.h

Для того, чтобы программа имитировала нажатие клавиш, необходимо использовать функцию SendInput и написать следующее:

 INPUT Input = { 0 };  Input.type = INPUT_KEYBOARD;  Input.ki.wVk = VK_VOLUME_UP; /* Пишем здесь нужный нам виртуальный код,   																который мы хотим имитировать*/  SendInput(1, &Input, sizeof(Input));  ZeroMemory(&Input, sizeof(Input));

Нас интересуют следующие коды: увеличение и уменьшение громкости (VK_VOLUME_UP, VK_VOLUME_DOWN); проигрывание и пауза медиа (VK_MEDIA_PLAY_PAUSE); «перелистывание» медиа (VK_MEDIA_NEXT_TRACK, VK_MEDIA_PREV_TRACK)

Полный набор виртуальных кодов доступен здесь.

Что такое Serial port и как с этим бороться?

Ардуино уно передает сигналы компьютеру через USB, эмулируя последовательный порт (Serial port), который в Windows называется COM порт, как дань памяти старым последовательным портам IBM PC. Для того, чтобы получить сообщение от ардуино и выполнить какое то действие в зависимости от сообщения, необходима программа. Функции работы с последовательным портом также есть в библиотеке Windows.h

#include <Windows.h> #include <stdio.h> #include <string.h>  int main(void) {     HANDLE Port;     BOOL Status;     DCB dcbSerialParams = { 0 };     COMMTIMEOUTS timeouts = { 0 };     DWORD dwEventMask;     char ReadData;     DWORD NoBytesRead;     bool Esc = FALSE;      Port = CreateFile(L"\\\\.\\COM3", GENERIC_READ, 0, NULL,  // Открываем последовательный порт     OPEN_EXISTING, 0, NULL);      if (Port == INVALID_HANDLE_VALUE)     {         printf("\nError to Get the COM state\n");         CloseHandle(Port);     }     else     {             printf("\nopening serial port is succesful\n");      }      dcbSerialParams.DCBlength = sizeof(dcbSerialParams);      Status = GetCommState(Port, &dcbSerialParams); 			// Принимаем существующие настройки порта     if (Status == FALSE)     {         printf("\n Error to Get the COM state \n");         CloseHandle(Port);     }      dcbSerialParams.BaudRate = CBR_9600;									// Задаем настройки порта     dcbSerialParams.ByteSize = 8;     dcbSerialParams.StopBits = ONESTOPBIT;     dcbSerialParams.Parity = NOPARITY;      Status = SetCommState(Port, &dcbSerialParams);           if (Status == FALSE)     {         printf("\n Error to Setting DCB Structure \n ");         CloseHandle(Port);     }      timeouts.ReadIntervalTimeout = 10;						/* Задаем временные интервалы приема сигналов    																								с порта (я их от балды поставил) */    timeouts.ReadTotalTimeoutConstant = 200;				     timeouts.ReadTotalTimeoutMultiplier = 2;      if (SetCommTimeouts(Port, &timeouts) == FALSE)     {         printf("\n Error to Setting Timeouts");         CloseHandle(Port);     }          while (Esc == FALSE)         {                        Status = SetCommMask(Port, EV_RXCHAR);              if (Status == FALSE)             {                 printf("\nError to in Setting CommMask\n");                 CloseHandle(Port);             }              Status = WaitCommEvent(Port, &dwEventMask, NULL); 		/* Задаем ожидание события            																												(поступления сообщения в порт) */             if (Status == FALSE)             {                 printf("\nError! in Setting WaitCommEvent () \n");                 CloseHandle(Port);             }               Status = ReadFile(Port, &ReadData, 3, &NoBytesRead, NULL); // Считываем сообщение                printf("\nNumber of bytes received = % d\n\n", sizeof(ReadData) - 1);              switch (ReadData)																/* В зависимости от сообщения             																									симулируем нажатие медиа клавиш */             {                 case 'U':                 {                     INPUT Input = { 0 };                     Input.type = INPUT_KEYBOARD;                     Input.ki.wVk = VK_VOLUME_UP;                     SendInput(1, &Input, sizeof(Input));                     ZeroMemory(&Input, sizeof(Input));                 }                 break;                  case 'D':                 {                     INPUT Input = { 0 };                     Input.type = INPUT_KEYBOARD;                     Input.ki.wVk = VK_VOLUME_DOWN;                     SendInput(1, &Input, sizeof(Input));                     ZeroMemory(&Input, sizeof(Input));                 }                 break;                  case 'P':                 {                     INPUT Input = { 0 };                     Input.type = INPUT_KEYBOARD;                     Input.ki.wVk = VK_MEDIA_PLAY_PAUSE;                     SendInput(1, &Input, sizeof(Input));                     ZeroMemory(&Input, sizeof(Input));                 }                 break;                  case 'N':                 {                     INPUT Input = { 0 };                     Input.type = INPUT_KEYBOARD;                     Input.ki.wVk = VK_MEDIA_NEXT_TRACK;                     SendInput(1, &Input, sizeof(Input));                     ZeroMemory(&Input, sizeof(Input));                 }                 break;                  case 'R':                 {                     INPUT Input = { 0 };                     Input.type = INPUT_KEYBOARD;                     Input.ki.wVk = VK_MEDIA_PREV_TRACK;                     SendInput(1, &Input, sizeof(Input));                     ZeroMemory(&Input, sizeof(Input));                 }                 break;                  default:                     printf("\n Error\n");                     break;             }                         PurgeComm(Port, PURGE_RXCLEAR);    // Очищаем порт от всякого мусора          }       CloseHandle(Port);  /* Закрываем порт при завершении работы программы,     										чтобы дргуие программы могли получить к нему доступ */  }  

Информацию по работе с последовательными порта я нашел здесь.

https://www.xanthium.in/Serial-Port-Programming-using-Win32-API

http://citforum.ru/hardware/articles/comports/

Итог

Теперь моей лени нет предела, мне не нужно дотягиваться до ноутбука, чтобы переключить трек или ролик на ютубе. Не зря говорят: Лень — двигатель прогресса.

У подобной комбинации (пульт + виртуальные коды) есть потенциал в управлении разными частями ОС. Например, можно назначить на кнопки запуск программ или сделать из пульта что-то вроде контроллера. Но самое удобное, на мой взгляд, это управление медиа.

ссылка на оригинал статьи https://habr.com/ru/post/555728/