Управление компьютером через ПДУ от усилителя с помощью Arduino и Node.js +16



Предисловие


История начинается с того, что пол года назад я купил усилитель Yamaha A-S501.


Yamaha A-S501


В комплекте с ним шёл пульт дистанционного управления, который мог управлять и усилителем, и ямаховским CD-плеером, которого у меня естественно не было. Поэтому большинство кнопок на пульте попросту не использовались. Да и в целом в самом пульте не было необходимости, и он всегда лежал на полке.


Однако глядя на него, мне не давала покоя мысль задействовать пульт на полную катушку. Например, было бы удобно лёжа на диване и смотря фильм, быстрым движением руки перемотать, поставить его на паузу и т.д. Конечно, для этих целей я раньше использовал приложения на смартфоне для управления программами MPC-HC, Foobar2000, но пультом было бы быстрее и удобнее.


Как говорится, глаза боятся, а руки делают. С выбором технологий было сразу всё понятно. Arduino — давно хотел с ней поиграться, и это — как раз отличный шанс. Для обработчика кнопок — Node.js, т.к. специализируюсь на джаваскрипте, и не хотел переключать контекст.


И так, поехали...


Готовые решения


Один из существующих аналогов, который я смог найти, — это Flirc. С помощью него можно эмулировать нажатия клавиш на физической клавиатуре компьютера.



Такой ИК-приемник стоит здесь 100 злотых (?$28). Забегая вперед, это вдвое дороже того, что у меня вышло. К тому же, по функциональности у меня получилось даже лучше (субъективно).


Покупка деталей



Мне понадобилось:


  • Собственно, сама плата Arduino Uno. Стоит заметить это не оригинальная плата, а какой-то польский клон. По описанию — она полностью аналогична оригиналу. (27,90 zl)
  • Инфракрасный приёмник VS1838B HX1838 (напряжение: 3,3–5 V, частота: 38 kHz, угол: 90°) (1,30 zl)
  • Плата для прототипирования + провода (13,90 zl)
  • Пустая плата, чтобы всё спаять (2,10 zl)
  • Коннекторы для соединения плат (2,51 zl)

Итого: 47,71 zl (?$14)


Программное обеспечение


Пока ждал доставку я начал писать "драйвер", который должен считывать данные из последовательного порта от Arduino и выполнять определённые действия для нажатой кнопке на пульте.


Идея была такая, чтобы иметь возможность настройки всего и вся. Каждой кнопке на пульте можно назначать определенные действия нескольких типов:


  • Эмуляция нажатия клавиши на клавиатуре (через node-key-sender):

{ "key": "space" }

  • Запуск произвольной программы с параметрами:

{ "exec": ["c:\\Program Files (x86)\\foobar2000\\foobar2000.exe", "/play"] }

  • Условие (используется ps-list):

{ "if": { "running": "mpc-hc.exe" }, "then": [ ... ], "else": [ ... ] }

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


const runHandlers = require('./handlers')

module.exports = async function run(actions) {
  if (!Array.isArray(actions)) {
    actions = [actions]
  }
  for (const act of actions) {
    await runHandlers(act)
  }
}

Вместо тысячи слов документации всё расскажут тесты:


run
  when "exec" action
    v executes the specified file without args (as array) (4ms)
    v executes the specified file without args (as string) (1ms)
    v executes the specified file with args
    v rejects if "exec" has wrong type (5ms)
  when "key" action
    v sends the specified key press if passed string (1ms)
    v sends the specified key combination if passed array
    v rejects if "key" has wrong type (1ms)
  when "if" action
    v rejects if no "then" (1ms)
    v rejects if operator is not supported
    when operator if "running"
      v runs "then" actions if the condition is true (1ms)
      v runs "else" actions if the condition is false
      v does not run anything if the condition is false and no "else" statement (1ms)
  when multiple actions
    v executes all actions (1ms)
  when multiple actions are mixed into one
    v runs only first one alphabetically

Осталось дождаться заветных деталек.


Железо


Признаюсь, я не изобретал ничего нового, всё уже давно было сделано до меня. Я просто воспользовался готовой схемой из статьи How to Set Up an IR Remote and Receiver on an Arduino.


Схема довольна проста:



На практике:




Прошивка


Прошивку я также честно позаимствовал из статьи, для её работы понадобится IRremote Arduino Library.


Коды кнопок заменил на актуальные от моего пульта:


void loop() {
  if (irrecv.decode(&results)) {
    if (results.value == 0xFFFFFFFF) {
      results.value = key_value;
    }

    switch (results.value) {
    case 0x9E6140BF:
      Serial.println("play");
      break;
    case 0x9E61AA55:
      Serial.println("pause");
      break;
    /* ...*/
    case 0x5EA1A857:
      Serial.println("cd");
      break;
    default:
      Serial.println(results.value, HEX);
      break;
    }
    key_value = results.value;
    irrecv.resume();
  }
}


Как только в окошке Монитора порта в Arduino IDE появились названия нажатых кнопок необходимо было добавить в драйвер компонент для работы с последовательным портом.


Получилась обёртка над библиотекой serialport и, собственно, потоком данных из порта:


const SerialPort = require('serialport')

module.exports = class SerialPortReader {
  constructor(port) {
    const serialPort = new SerialPort(port)
    this.lineStream = serialPort.pipe(new SerialPort.parsers.Readline())
  }

  start(handler) {
    this.lineStream.on('readable', () => {
      const data = this.lineStream.read().trim()
      handler(data)
    })
  }
}

Позже появилась необходимость превратить обработчики в "debounce" функцию, т.к из пульта поступает быстро повторяющийся сигнал, который даже при кратковременном нажатии на кнопку успевает отправиться несколько раз. Однако убирать такую опцию для всех кнопок тоже не совсем уместно, например, для громкости.


Финальный код выглядит так:


const debounce = require('debounce')
const settings = require('./lib/settings')
const run = require('./lib/run')
const SerialPortReader = require('./lib/SerialPortReader')

const simpleHandle = async button => {
  const actions = settings.mappings[button]
  if (!actions) {
    console.warn(`Action not found for remote control button "${button}"`)
    return
  }

  try {
    await run(actions)
  } catch (e) {
    console.error(e.message)
    process.exit(1)
  }
}

const debouncedHandle = debounce(simpleHandle, settings.debounceDelay, true)

const callHandleFn = button => {
  return (settings.noDebounce.includes(button) ? simpleHandle : debouncedHandle)(button)
}

const reader = new SerialPortReader(settings.serialPort)
reader.start(callHandleFn)

Создание независимой платы


Убедившись, что прототип работает, я приступил к созданию платы. Стоит отметить, для меня это первый опыт в подобных делах. У меня и паяльника-то подходящего не было с маленькой иглой — только старый советский большой с тугим проводом.


С горем пополам мне удалось припаять "ножки" (из двух больших коннекторов по 8 пин уцелело только 2 пина). Со всем остальным уже было попроще.



(Кривовато. Скорее всего из-за клона Arduino. Гнёзда стоят неровно относительно друг друга.)



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




В итоге: полностью рабочий девайс и программное обеспечение за ?$14. Полученный опыт и радость от проделанной работы и результата — бесценно! :-)


Спасибо за внимание!




Демо:



Исходники на Гитхабе.




P.S. Спасибо ramanchik'у за консультацию :)

Вы можете помочь и перевести немного средств на развитие сайта



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

  1. natan555
    /#20201290 / +1

    Как сложно, одного ИК светодиода и Lirc/WinLirc было бы достаточно

    • phil_tsarik
      /#20201354

      Да, но иногда хочется написать свой велосипед :)

    • Javian
      /#20201544

      Когда-то самодельный ИК-приемник на COM-порте и WinLirc c плеером LightAlloy народ впечатляли.

      • wlr398
        /#20201688

        В начале нулевых на PIC контроллерах делали, на 12C509, 16F84.
        А программную поддержку на Дельфи. Винампом управлять и прочим по желанию.

        • Javian
          /#20201706 / +1

          Микроконтроллер — это другой уровень сложности для повторения, чем просто приемник из нескольких деталей+ софт
          image

          • wlr398
            /#20201744

            На 12с509 тоже не особо сложно было.
            image
            Прошивка готовая, зашивалась простейшим программатором.
            Но в общем согласен. Ваша схема и WinLirc сильно проще.
            Я то ещё и софт сам писал.

    • DeeZ
      /#20208894 / +1

      Писал курсовую по радиотехнике в универе по этой теме. Управление компа с любого пульта. Использовал Girder, прямо в нем можно ловить коды и задавать реакцию. В т.ч последовательности действий и условия\ветвления. Даже игрушки простенькие играть можно было с пульта.

  2. apple01
    /#20201612

    Радость от творчества бесценна, но цена вопроса на самом деле $6 (USB приемник + специализированный ремоут) и сэкономленное время.

  3. REPISOT
    /#20201668 / +1

    У меня есть вот это, только под SMD, размером с флешку. И под любой пульт RC-5.

    image

  4. DEM_dwg
    /#20201956 / +1

    По мне так и одного USB ИК приёмника + AutoIT хватило бы за глаза и за уши.

  5. Alyoshka1976
    /#20202948

    Судя по частоте, у Вашего пульта протокол NEC? Но при повторе он отправляет полный код кнопки, а не короткий код повтора?

    • phil_tsarik
      /#20203268

      Да, NEC
      В прошивке как раз есть обработка повторов, чтобы отправлялись последнее значение кнопки, а не FFFFFF

  6. psinetron
    /#20204006 / +1

    Интересно. Я совсем недавно собирал на digispark подобную систему. Очень компактно и удобно получилось. Самое интересное то, что использовать можно вообще любой ИК пульт. И напрограммировать не только клавиши, но и мышку. А ведь изначально собирался сделать простой регулятор громкости.

    • roboter
      /#20206084 / +1

      + за digispark — дешевле, компактней, сразу эмулирует клаву (ATmega32u4).

      • psinetron
        /#20206100 / +1

        Да, самое положительно то, что ничего дополнительного на сам компьютер устанавливать не надо. Подключил и работает

  7. roboter
    /#20206390

    Гнёзда стоят неровно относительно друг друга
    — Это своеобразный ключ, защита от переворота шилдов.