Решил я , что хочу переключать громкость звука и аудио треки на ноутбуке под Windows с инфракрасного пульта. Под руку тут же попали: ардуино уно, кучка проводов с макетной платой, инфракрасный датчик, ноутбук и, собственно, инфракрасный пульт.
Идея есть, железо есть, а вот теория хромает. Как заставить компьютер понимать инфракрасные сигналы пульта и выполнять требуемые действия? Я надумал использовать ардуино для приема сигналов пульта через инфракрасный датчик на макетной плате и посылать сообщения в ноутбук через USB. Для этого требовались хоть какие то познания, как все это работает.
Было решено разобраться.
Знакомим ардуино с пультом
Для приема сигнала от инфракрасного пульта необходим приёмник, который мы подключим к ардуино через макетную плату по следующей схеме:
Для того, чтобы ардуино понимала, по какому протоколу и с какой командой передается сигнал, существует библиотека 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/
Итог
Теперь моей лени нет предела, мне не нужно дотягиваться до ноутбука, чтобы переключить трек или ролик на ютубе. Не зря говорят: Лень - двигатель прогресса.
У подобной комбинации (пульт + виртуальные коды) есть потенциал в управлении разными частями ОС. Например, можно назначить на кнопки запуск программ или сделать из пульта что-то вроде контроллера. Но самое удобное, на мой взгляд, это управление медиа.
Costic
Вы неаккуратно работаете с COM портом. Если не удалось открыть порт, то дальнейшая работа программы не имеет смысла и надо заканчивать работу. return 1;
А вы закрываете дескриптор, который не смогли получить.
И при настройке таймаутов у вас тоже ошибка. Вы закрываете дескриптор (порт), но продолжаете его использовать. Или не закрывайте порт или добавьте return код ошибки;