Предисловие


История начинается с того, что пол года назад я купил усилитель 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
    26.05.2019 16:56
    +1

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


    1. phil_tsarik Автор
      26.05.2019 17:24

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


    1. Javian
      26.05.2019 18:45

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


      1. wlr398
        26.05.2019 19:51

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


        1. Javian
          26.05.2019 20:03
          +1

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


          1. wlr398
            26.05.2019 20:16

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


    1. DeeZ
      28.05.2019 07:23
      +1

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


  1. apple01
    26.05.2019 19:17

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


  1. REPISOT
    26.05.2019 19:43
    +1

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

    image


  1. DEM_dwg
    26.05.2019 21:31
    +1

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


  1. Alyoshka1976
    27.05.2019 07:57

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


    1. phil_tsarik Автор
      27.05.2019 09:19

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


  1. psinetron
    27.05.2019 11:12
    +1

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

    Видео
    1. roboter
      27.05.2019 15:52
      +1

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


      1. psinetron
        27.05.2019 15:56
        +1

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


  1. roboter
    27.05.2019 16:38

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


    1. phil_tsarik Автор
      27.05.2019 18:08

      спасибо, буду знать