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


AliExpress RU&CIS

Решил я , что хочу переключать громкость звука и аудио треки на ноутбуке под 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/

Итог

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

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

Теги:




Комментарии (19):

  1. Costic
    /#22999052 / +3

    Вы неаккуратно работаете с COM портом. Если не удалось открыть порт, то дальнейшая работа программы не имеет смысла и надо заканчивать работу. return 1;
    А вы закрываете дескриптор, который не смогли получить.

    if (Port == INVALID_HANDLE_VALUE)
        {
            printf("\nError to Get the COM state\n");
            CloseHandle(Port);
        }

    И при настройке таймаутов у вас тоже ошибка. Вы закрываете дескриптор (порт), но продолжаете его использовать. Или не закрывайте порт или добавьте return код ошибки;

  2. 1delovoj1
    /#22999120 / +1

    Мне одному кажется что собирать ИК приемник на Arduino это как микроскопом гвозди забивать?
    Ну конечно в новых супер модных лабораториях найти микроскоп наверно проще чем молоток.
    Но ведь из 10 компонентов просто в домашнем хламе найденных можно собрать.
    Ну или для мсье знающих толк в извращениях есть это
    image

    • REPISOT
      /#22999198 / +1

      или это:
      ИК приемник для дистанционного управления компьютером. [Радио, 2009, №10] Работает с ТВ пультами RC-5. Эмулирует клавиатуру, позволяет вешать на кнопки сочетания клавиш.
      image

    • /#22999456 / +1

      Ну ардуина это в целом та же Атмега с обвязкой и немного софта поверх этого всего, но вообще я бы ожидал увидеть HID-устройство (кажется, можно сделать даже на обычной атмеге, подтащив библиотеку V-USB или как там ее — готов поспорить, что именно так и сделали на «радиокоте»), имитирующее нажатие «мультимедийных» кнопок, а не вот это вот поделие «через COM-порт».

    • DeeZ
      /#23001330

      Милениалы изобрели lirc ))
      у меня в универе была курсовая такая, но там порты были com. до сих пор валяется в ящике «прототип» :)

    • MegaShIzoID
      /#23001498 / -1

      просто зумер-автор собрал очередной бесполезный дилдак на ардуине, ничего нового

  3. riky
    /#22999368 / +1

    Лет 15 назад делали просто с помощью транзистора и ик фотодиода, через лпт. Программа была вроде бы irlink.
    Сейчас esp отправляет команды в домашний mqtt сервер, оттуда одним пультом может и свет включать и компами рулить (winthings)

  4. DrPass
    /#22999444 / +6

    Как-то вы явно намудрили со схемой ИК-приёмника. Вот эта как прекрасно работала 15 лет назад, так работает и сегодня:
    image
    Хотя Ардуино, это как блютуз — любая самоделка становится лучше, если туда добавить блютуз или ардуину :)
    Софтовая поддержка тоже есть, собственно, LIRC/WinLIRC и называется, для ОС на букву L и W соответственно.

    • UksusoFF
      /#22999462

      Аналогично, собирали ее с отцом еще в школе.
      Только постепенно надобность отпала в связи с появлением всяких remote приложений.

  5. Serge78rus
    /#22999478

    Я использовал С++ и Visual Studio так как там есть удобная для этой задачи библиотека Windows.h
    Это не библиотека, а заголовочный файл. И использовать его можно с любым C или C++ компилятором, Visual Studio в этом смысле вовсе не «пуп земли».

  6. roboter
    /#22999780 / +2

    Мне нравится вот такая платка.
    image
    Умеет USB HID и тогда не нужна прога под windows.

    • DeeZ
      /#23001366

      То ли я руко%оп то ли китайцы, но у меня они регулярно окирпичиваются (IDE не видит ее, при подключении — устройство не опознанно), пиходится перешивать. Первый раз думал — спалил, благо не выбросил. Когда скопилось 3 штуки — пошел гуглить.
      Может кому пригодится.

      • roboter
        /#23001410

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

  7. alex-khv
    /#22999876

    в продолжении темы Хлопковый выключатель на Arduino

    • alex-khv
      /#22999884

      Интересно было бы посмотреть на хлопковый выключатель на нейронных сетях.

  8. DmitriiR
    /#23000074 / +2

    Интересно былоб… если бы наконец схемы начали рисовать нормально.
    Это к автору.

    • /#23000410 / +1

      Авторов Wiring надо лупить по голове книжкой Борисова, там вопрос «чем схема отличается от картинки» разбирается в одной из первых глав, сразу после решения проблемы «где взять диод для детекторного приемника».

  9. unsignedchar
    /#23000478 / +2

    "Решил я, что хочу XXXX. Под руку тут же попали YYYY, ZZZZ.."


    Придумалась идея игры для радиолюбителей. Берётся рандомный набор деталей. Если из него удалось собрать задуманное — все выпивают :D

  10. DeeZ
    /#23001358 / +1

    Изобретать велосипед это полезно, хотя бы потому что позволяет разобраться в вопросе глубже.
    Но все уже давно изобретено. Погуглите lirc (winlirс), для управления полно софта. Под винду пользовался girder32 (умел слать нажатия кнопок в свернутое приложение, в тч логика работы была, скрипты. Умеет получать заголовки или текст в обьектах. Что можно использовать в скриптах.