Computer control via remote control from an amplifier using Arduino and Node.js
Foreword
The story begins with the fact that half a year ago I bought a Yamaha A-S501 amplifier.
Included was a remote control that could control both the amplifier and the Yamaha CD player, which I naturally did not have. Therefore, most of the buttons on the remote control were simply not used. And in general, the console itself was not necessary, and it always lay on a shelf.
However, looking at him, I was haunted by the idea of using the remote control to its fullest. For example, it would be convenient to lie on the sofa and watch a movie, rewind with a quick movement of the hand, pause it, etc. Of course, for these purposes, I used to use applications on my smartphone to control the MPC-HC, Foobar2000 programs, but the remote control would be faster and more convenient.
As they say, the eyes are afraid, and the hands are doing. With the choice of technology, everything was immediately clear. Arduino - I have long wanted to play with her, and this is just a great chance. For the button handler, Node.js, because I specialize in javascript, and did not want to switch context.
And so, let's go ...
Ready-made solutions
One of the existing analogues that I could find is Flirc . Using it, you can emulate keystrokes on the physical keyboard of a computer.
Such an infrared receiver costs 100 zlotys (≈ $ 28). Looking ahead, it is twice as expensive as what I got. In addition, in terms of functionality, it turned out even better (subjectively).
Buying Parts
I needed:
- Actually, the Arduino Uno board itself. It is worth noting that this is not the original board, but some kind of Polish clone. According to the description - it is completely similar to the original. (27.90 zł)
- Infrared receiver VS1838B HX1838 (voltage: 3.3–5 V, frequency: 38 kHz, angle: 90 °) (1.30 zł)
- Prototyping board + wires (13.90 zł)
- An empty board to solder everything (2.10 zł)
- Connectors for connecting boards (2,51 zł)
Total: 47,71 zł (≈ $ 14)
Software
While waiting for delivery, I started writing a “driver”, which should read data from the serial port from Arduino and perform certain actions for the pressed button on the remote control.
The idea was to be able to customize everything and everything. Each button on the remote control can be assigned certain actions of several types:
- Emulation of a keystroke on a keyboard (via node-key-sender ):
{ "key": "space" }
- Running an arbitrary program with parameters:
{ "exec": ["c:\\Program Files (x86)\\foobar2000\\foobar2000.exe", "/play"] }
- Condition (using ps-list ):
{ "if": { "running": "mpc-hc.exe" }, "then": [ ... ], "else": [ ... ] }
Each type has its own handler, which have the same API, and therefore it was possible to collapse everything into a simple loop that starts all the handlers in sequence.
const runHandlers = require('./handlers')
module.exports = async function run(actions) {
if (!Array.isArray(actions)) {
actions = [actions]
}
for (const act of actions) {
await runHandlers(act)
}
}
Instead of a thousand documentation words, tests tell you everything:
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
It remains to wait for the cherished details.
Iron
I admit, I did not invent anything new, everything has already been done before me. I just used the ready-made diagram from the article How to Set Up an IR Remote and Receiver on an Arduino .
The scheme is quite simple:
On practice:
Firmware
I also honestly borrowed the firmware from the article , for its work I will need the IRremote Arduino Library .
I replaced the button codes with the actual ones from my remote control:
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();
}
}
As soon as the names of the pressed buttons appeared in the port Monitor window in the Arduino IDE, it was necessary to add a component for working with the serial port to the driver.
The result was a wrapper over the serialport library and, in fact, the data stream from the port:
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)
})
}
}
Later, there was a need to turn handlers into a "debounce" function, because the remote control receives a rapidly repeating signal, which, even with a short press on the button, manages to go off several times. However, removing such an option for all buttons is also not entirely appropriate, for example, for volume.
The final code looks like this:
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. Гнёзда стоят неровно относительно друг друга.)
Я намеренно поместил инфракрасный приёмник вовнутрь между платами. Так устройство с лёгкостью помещается под усилителем. Дорожки решил сделать, соединяя оловом отверстия на плате, вместо соединения проводами. И наконец, наклеил скотч наверх, чтобы плата не замыкалась об металлический корпус усилителя.
As a result: a fully working device and software for ≈ $ 14. The experience gained and the joy of the work done and the result are invaluable! :-)
Thanks for attention!
Demo:
PS Thanks ramanchik 'u for the consultation :)