Prefácio
A história começa com o fato de que há meio ano eu comprei um amplificador Yamaha A-S501.

Incluído estava um controle remoto que podia controlar o amplificador e o CD player Yamaha, que eu naturalmente não tinha. Portanto, a maioria dos botões no controle remoto simplesmente não foi usada. E, em geral, o console em si não era necessário e sempre ficava em uma prateleira.
No entanto, olhando para ele, fiquei assombrado com a idéia de usar o controle remoto ao máximo. Por exemplo, seria conveniente deitar no sofá e assistir a um filme, retroceder com um movimento rápido da mão, pausar etc. Obviamente, para esses fins, eu costumava usar aplicativos no meu smartphone para controlar os programas MPC-HC, Foobar2000, mas o controle remoto seria mais rápido e conveniente.
Como se costuma dizer, os olhos estão com medo e as mãos estão fazendo. Com a escolha da tecnologia, tudo ficou imediatamente claro. Arduino - Eu sempre quis brincar com ela, e essa é apenas uma grande chance. Para o manipulador de botão, Node.js, porque Eu me especializei em javascript e não queria mudar de contexto.
E então vamos lá ...
Soluções prontas
Um dos análogos existentes que eu pude encontrar é o Flirc . Com ele, você pode emular as teclas digitadas no teclado físico de um computador.

Esse receptor infravermelho custa 100 zlotys (($ 28). Olhando para o futuro, é duas vezes mais caro do que recebi. Além disso, em termos de funcionalidade, ficou ainda melhor (subjetivamente).
Compra de peças





Eu precisava:
- Na verdade, o Arduino Uno embarca sozinho. Vale a pena notar que este não é o conselho original, mas algum tipo de clone polonês. De acordo com a descrição - é completamente semelhante ao original. (27,90 zł)
- Receptor de infravermelho VS1838B HX1838 (tensão: 3,3–5 V, frequência: 38 kHz, ângulo: 90 °) (1,30 zł)
- Placa de prototipagem + fios (13,90 zł)
- Um tabuleiro vazio para soldar tudo (2.10 zł)
- Conectores para placas de conexão (2,51 zł)
Total: 47,71 zł (≈ $ 14)
De software
Enquanto aguardava a entrega, comecei a escrever um "driver", que deveria ler os dados da porta serial do Arduino e executar determinadas ações para o botão pressionado no controle remoto.
A idéia era poder personalizar tudo e tudo. Cada botão no controle remoto pode receber determinadas ações de vários tipos:
{ "key": "space" }
- Executando um programa arbitrário com parâmetros:
{ "exec": ["c:\\Program Files (x86)\\foobar2000\\foobar2000.exe", "/play"] }
{ "if": { "running": "mpc-hc.exe" }, "then": [ ... ], "else": [ ... ] }
Cada tipo tem seu próprio manipulador, que possui a mesma API e, portanto, foi possível recolher tudo em um loop simples que inicia todos os manipuladores em sequência.
const runHandlers = require('./handlers') module.exports = async function run(actions) { if (!Array.isArray(actions)) { actions = [actions] } for (const act of actions) { await runHandlers(act) } }
Em vez de mil palavras na documentação, os testes dizem tudo:
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
Resta aguardar os detalhes estimados.
Ferro
Admito, não inventei nada de novo, tudo já foi feito diante de mim. Acabei de usar o diagrama pronto do artigo Como configurar um remoto IR e receptor em um Arduino .
O esquema é bastante simples:

Na prática:


Firmware
Também honestamente, peguei emprestado o firmware do artigo , para o seu trabalho vou precisar da Biblioteca do IRremote Arduino .
Substituí os códigos dos botões pelos reais do meu controle remoto:
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(); } }

Assim que os nomes dos botões pressionados apareceram na janela Monitor da porta no Arduino IDE, foi necessário adicionar um componente para trabalhar com a porta serial no driver.
O resultado foi um invólucro na biblioteca serialport e, de fato, o fluxo de dados da porta:
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) }) } }
Mais tarde, houve a necessidade de transformar os manipuladores em uma função de "debounce", porque o controle remoto recebe um sinal de repetição rápida que, mesmo com um breve toque no botão, consegue disparar várias vezes. No entanto, remover essa opção para todos os botões também não é totalmente apropriado, por exemplo, para o volume.
O código final fica assim:
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)
Criando um Conselho Independente
Depois de me certificar de que o protótipo funcione, continuei criando um quadro. Vale a pena notar, para mim esta é a primeira experiência em tais assuntos. Eu não tinha um ferro de soldar adequado com uma pequena agulha - apenas a velha grande soviética com um fio apertado.
Com tristeza ao meio, consegui soldar as "pernas" (dos dois grandes conectores de 8 pinos, apenas 2 pinos sobreviveram). Tudo o resto foi mais fácil.

(Torto. Provavelmente devido ao clone do Arduino. Os ninhos são desiguais entre si.)

Coloquei intencionalmente o receptor infravermelho entre as placas. Portanto, o dispositivo cabe facilmente sob o amplificador. Decidi fazer as faixas conectando os furos na placa com estanho, em vez de conectá-los com fios. E, finalmente, colei a fita para que a placa não ficasse na caixa de metal do amplificador.


Como resultado: um dispositivo e software totalmente funcionais por US $ 14. A experiência adquirida e a alegria do trabalho realizado e o resultado são inestimáveis! :-)
Obrigado pela atenção!
Demo:
Fontes no Github .
PS Obrigado ramanchik 'u pela consulta :)