Vor knapp 20 Jahren hatte ich mir wie in diesem Artikel beschrieben etwas zusammengebastelt, um vom Mac aus die Surroundanlage bedienen zu können.
Denn mein macMini ist mein Mediencenter, von dem aus ich auch Filme schaue und ich will alles mit dem Trackpad steuern und nicht noch eine Fernbedieung für die Lautstärke in die Hände nehmen müssen.

Leider hat mein macMini nach 20 Jahren das Zeitliche gesegnet und das IR-Dongle will mangels Software nicht mehr mit meinem neuen macMini M4 zusammenarbeiten. Ich hatte schon vergebens viel Zeit damit verbraten, mit LIRC das Dongle wieder verwenden zu können, aber meine Zeit dann lieber darin investiert, mit Hilfe eines Arduinos selbst etwas zusammenzubasteln. Denn zwecks Funksteckdosen-Steuerung und Luftdruck-Aufzeichnung hängt sowieso schon ein Arduino Nano am Mac. An dem sind noch ein paar digitale Pins frei :-)

Hardware

Neben einem Arduino benötigt man einen IR-Empfänger (ich habe mir einen TSOP4838 bei AliExpress besorgt) und einen IR-Sender (da habe ich mir eine stinknormale LED Diode bei AliExpress besorgt). Und dann noch einen kleinen 100 Ohm Widerstand.

Wieso ein Empfänger?
Ich will von der originalen Fernbedienung den Code, den sie beim Drücken einer Taste sendet, aufzeichnen, damit ich genau diesen Code nachher per Computerbefehl über die LED Diode senden kann.

Der TSOP4838 verträgt 2.5 – 5.5 V, ich habe ihn an den 3.3 V Anschluss und D2 gehängt, die IR Diode hängt an D3.

Software: Die ersten Tests auf dem Arduino

Ich verwende in der Arduino IDE die Bibliothek IRRemote von shirriff, um den angeschlossenen Empfänger und den Sender verwenden zu können.

Hier ein Beispiels-Sketch, welcher mit der Bibliothek mitgeliefert wird und den IR Code der Fernbedienung im seriellen Monitor (9600 Baud) ausgibt, wenn man eine Fernbedienung an den Empfänger hält und eine Taste drückt.
Da es sich um eine Panasonic-Fernbedienung handelt, habe ich jene #define-Zeile aktiviert und die anderen auskommentiert gelassen.

#include <Arduino.h>

//#define DECODE_DENON        // Includes Sharp
//#define DECODE_JVC
//#define DECODE_KASEIKYO
#define DECODE_PANASONIC    // alias for DECODE_KASEIKYO
//#define DECODE_LG
//#define DECODE_NEC          // Includes Apple and Onkyo
//#define DECODE_SAMSUNG
//#define DECODE_SONY
//#define DECODE_RC5
//#define DECODE_RC6
//#define DECODE_BOSEWAVE
//#define DECODE_LEGO_PF
//#define DECODE_MAGIQUEST
//#define DECODE_WHYNTER
//#define DECODE_FAST
//#define DECODE_DISTANCE_WIDTH // Universal decoder for pulse distance width protocols
//#define DECODE_HASH         // special decoder for all protocols
//#define DECODE_ONKYO        // Disables NEC and Apple
//#define DECODE_BEO          // This protocol must always be enabled manually, i.e. it is NOT enabled if no protocol is defined. It prevents decoding of SONY!
//#define DECODE_DISTANCE_WIDTH // Universal decoder for pulse distance width protocols
//#define DECODE_HASH         // special decoder for all protocols

#include <IRremote.hpp>

#define IR_RECEIVE_PIN 2 // Anschluss D2

void setup() {
    Serial.begin(9600);
    IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK);
    Serial.print(F("Ready to receive IR signals of protocols: "));
    printActiveIRProtocols(&Serial);
    }

void loop() {
    if (IrReceiver.decode()) {
        Serial.println();
        IrReceiver.printIRResultShort(&Serial);
        IrReceiver.printIRSendUsage(&Serial);

        IrReceiver.resume();
       }
    }

Hier habe ich z.B. die Ein-/Ausschalttaste auf der Fernbedienung gedrückt und erhalte als Ausgabe:

Protocol=Panasonic Address=0x1CA, Command=0x3D, Raw-Data=0x813D1CA0, 48 bits, LSB first, Gap=897200us, Duration=58750us
Send with: IrSender.sendPanasonic(0x1CA, 0x3D, <numberOfRepeats>);

Das heißt: Wenn ich einen Sketch schreibe zum IR Signale senden, würde ich mit dieser Code-Zeile das Signal zum ein-/ausschalten senden – die Bibliothek wandelt mir das IR-Signal praktischerweise gleich in eine Code-Zeile um, die ich später 1:1 wieder verwenden kann:

IrSender.sendPanasonic(0x1CA, 0x3D, 0);

Mein Test-Sketch zum senden:

#include <Arduino.h>

#define IR_SEND_PIN  3 // Anschluss D3
#include <IRremote.hpp>

void setup() {
    pinMode(LED_BUILTIN, OUTPUT);
    Serial.begin(9600);
    Serial.print(F("Send IR signals at pin "));
    Serial.println(IR_SEND_PIN);
    }


void loop() {
    Serial.println(F("sende..."));  
    IrSender.sendPanasonic(0x1CA, 0x3D, 0); // Sende IR-Signal
    delay(1000);
    }

Ob tatsächlich etwas gesendet wird, kann man am besten erkennen, wenn man am Handy die Kamera öffnet und über das Handydisplay frontal auf die LED schaut. Wenn ein Signal gesendet wird, sollte man auf dem Handydisplay ein Aufblitzen in der Diode sehen.
Auch wenn Infrarot für das menschliche Auge unsichtbar ist: Eine Handykamera macht's sichtbar :-)

Software: Mein Arduino-Sketch

Nachdem ich wie eben beschrieben alle IR-Codes in Erfahrung gebracht habe, die ich zukünftig vom Computer aus senden möchte, habe ich folgenden Sketch auf meinen Arduino geflasht. In irSenden() sind alle Sende-Codes aufgelistet, die verwendet werden sollen.

#define DECODE_PANASONIC
#define IR_RECEIVE_PIN  2
#define IR_SEND_PIN     3

#include <IRremote.hpp>

String input  = "";

void setup() {
  Serial.begin(9600);
  Serial.println("INFO:Initalisierung.");

  IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK);  
  }

void loop() {

  // Serial-Port abfragen
  // (Nimmt Befehle vom angeschlossenen Computer entgegen)
  int chr = Serial.read();
  if (chr > 0) {
    input += (char)chr;
  } else if (chr == -1 && input != "") {
    if (input.substring(0, 3) == "IR:") {
      // Infrarotsignal senden
      irSenden(input.substring(3).toInt());
      }
  input = "";
  }

  // IR Empfänger
  if (IrReceiver.decode()) {
      Serial.print("IR-RCV: ");
      IrReceiver.printIRResultShort(&Serial);
      IrReceiver.printIRSendUsage(&Serial);
      IrReceiver.resume();
      }

  // Kurze Pause
  delay(100);
  }

void irSenden(unsigned int codenr) {
    Serial.print("IR:");
    Serial.print(codenr);
    if (codenr == 1) {
      IrSender.sendPanasonic(0x1CA, 0x3D, 0);
      Serial.println(" (POWER)");
    } else if (codenr == 2) {
      IrSender.sendPanasonic(0xA, 0x20, 0);
      Serial.println(" (VOL UP)");
    } else if (codenr == 3) {
      IrSender.sendPanasonic(0xA, 0x21, 0);
      Serial.println(" (VOL DOWN)");
    } else if (codenr == 4) {
      IrSender.sendPanasonic(0xA, 0x32, 0);
      Serial.println(" (MUTE)");
    } else if (codenr == 5) {
      IrSender.sendPanasonic(0xA, 0xD5, 0);
      Serial.println(" (SUBWOOFER LEVEL)");
    } else if (codenr == 6) {
      IrSender.sendPanasonic(0x10A, 0x83, 0);
      Serial.println(" (SRD-ENH)");
    } else if (codenr == 7) {
      IrSender.sendPanasonic(0xA, 0xB4, 0);
      Serial.println(" (DOLBY)");
    } else if (codenr == 8) {
      IrSender.sendPanasonic(0xA, 0x9A, 0);
      Serial.println(" (SRC)");
    } else {
      Serial.println(" (nicht bekannt)");
      }
    }

Einerseits gibt der Arduino im seriellen Monitor die IR-Codes aus, wenn man auf einer Fernbedienung eine Taste drückt und der Empfänger des Arduinos das Signal aufschnappt (um den Sketch später ggf zu erweitern oder Automationen am Computer mit einer Fernbedienung auslösen zu können).

Andererseits sendet der Arduino ein Signal, wenn man über die serielle Schnittstelle den entsprechenden Befehl gibt.
Sende ich ein IR:1 wird die Power-Taste der Fernbedienung gesendet, bei IR:2 Lautstärke-rauf, etc.

Software: Mac

Ich habe einerseits ein Skript, welches die Schnittstelle zum Arduino ständig liest und im Hintergrund ständig läuft. Hier würde dann der Text auftauchen, wenn der IR-Empfänger ein Signal erhält.
Außerdem habe ich ein Skript, das aufgerufen werden kann, um IR-Signale zu senden.

Damit der Arduino am Mac angesprochen werden kann, muss ein Treiber installiert sein und muss den Port kennen.
Am besten in Terminal ls /dev/tty.* ausführen. Wenn eine Zeile mit usb und/oder serial auftaucht, dann sollte dies der Arduino sein.

Außerdem müsste, wenn man Perl verwendet, die Bibliotheken Device::Serial und Device::Serial::Arduino installiert werden.

Beispiel für ein "Empfänger-Skript" in Perl:

arduino-listener.pl
#!/usr/bin/perl

use Device::SerialPort::Arduino;

use utf8;
no utf8;

my $arduino = Device::SerialPort::Arduino->new(
                                port     => "/dev/tty.usbserial-1130",
                                baudrate => 9600,
                                databits => 8,
                                parity   => 'none'
                                );

while (1) {
        my $received = $arduino->receive(5);
        print "$received\n";
        }

Beispiel für ein Sende-Skript in Perl:

arduino-send.pl
#!/usr/bin/perl

use Device::SerialPort::Arduino;

my $arduino = Device::SerialPort::Arduino->new(
                                port     => "/dev/tty.usbserial-1130",
                                baudrate => 9600,
                                databits => 8,
                                parity   => 'none'
                                );

if ($ARGV[0] eq "IR") {
   # IR senden
   print("Sende an Arduino IR:".$ARGV[1]."\n");
   $arduino->communicate("\0IR:".$ARGV[1]."\0"); # <-- \0 als Prefix und Suffix für Arduino Nano!
} else {
   print("Befehl nicht erkannt.\nErstes Argument muss IR sein.\n");
   }
 

Verwendung: arduino-send.pl IR 1
Sollte letztendlich dafür sorgen, dass die Power-Taste der Fernbedienung simuliert wird (siehe Arduino-Sketch, funktion irSenden(), wo Code Nr 1 IrSender.sendPanasonic(0x1CA, 0x3D, 0); auslöst).

Oder wer lieber Python verwendet...hier müsste noch die Bibliothekpyserial installiert werden:

arduino-set.py
import serial
import sys

arduino = serial.Serial(port='/dev/tty.usbserial-1130', baudrate=9600, timeout=.1)

if sys.argv[1] == "IR":
  cmd = "IR:" + sys.argv[2]
  print("Sende an Arduino: " + cmd)
  arduino.write(cmd.encode('utf-8'))

Verwendung: python3 arduino-send.py IR 1

IR als erstes Argument gibt es bei mir, weil der Arduino auch noch andere Einsatzzwecke hat, die hier im Code allerdings nirgends vorkommen.
An sich kann man das erste Argument natürlich auch weg lassen, bzw rausprogrammieren ;-)

GUI

Die Lautstärke der Soundanlage per Kommandozeile zu ändern ist zwar ziemlich geeky, allerdings nicht wirklich praktisch.
Ich habe in meinem Lieblingstool Butler ein Apple-Script-Item hinzugefügt, das beim Drücken der Taste F11 (= leiser) das Python-Skript aufruft (leider kann Butler nicht direkt Python, deshalb der Umweg über AppleScript:

do shell script "/opt/local/bin/python3 /pfad/zu/arduino-set.py IR 3"

Und natürlich noch weitere Tasten für lauter, Stumm und Ton an/aus.

Die ganze Sache funktioniert verzögerungsarm genug, dass man es gut für einen Filmeabend verwenden kann :-)