Хабрахабр

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

В этом разделе содержатся короткие коды, для которых требуется плагинJannah Extinsions. Установите его из меню темы & gt; Установите плагины.

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

Yamaha A-S501

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

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

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

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

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

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

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

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

Итого: 47,71 zł (≈$14)

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

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

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

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

{ "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 √ executes the specified file without args (as array) (4ms) √ executes the specified file without args (as string) (1ms) √ executes the specified file with args √ rejects if "exec" has wrong type (5ms) when "key" action √ sends the specified key press if passed string (1ms) √ sends the specified key combination if passed array √ rejects if "key" has wrong type (1ms) when "if" action √ rejects if no "then" (1ms) √ rejects if operator is not supported when operator if "running" √ runs "then" actions if the condition is true (1ms) √ runs "else" actions if the condition is false √ does not run anything if the condition is false and no "else" statement (1ms) when multiple actions √ executes all actions (1ms) when multiple actions are mixed into one √ 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. 🙂

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

Демо:

Исходники

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

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть