/**
* Custom HDMI 10x2 Matrix switcher
*
* WokWii: https://wokwi.com/projects/392319956254099457
*
* Automatiseren van de eerste HDMI switches in de ketting; het eerste aparaat bediend tevens de switches er achter.
* Hardware:
* - HDMI 2x1 switch A (de fisieke knop word aan arduino gekoppeld om deze met de IR remotes van de 6x2 en 4x2 te kunnen bedienen)
* - HDMI 2x1 switch B (de fisieke knop word aan arduino gekoppeld om deze met de IR remotes van de 6x2 en 4x2 te kunnen bedienen)
* - HDMI 6x2 matrix switch (IR remote output 1 word gekoppeld aan input 1 van 2x1 switch A)
* - HDMI 6x2 matrix switch (IR remote output 2 word gekoppeld aan input 1 van 2x1 switch B)
* - HDMI 4x2 matrix switch (IR remote output 1 word gekoppeld aan input 2 van 2x1 switch A)
* - HDMI 4x2 matrix switch (IR remote output 2 word gekoppeld aan input 2 van 2x1 switch B)
* - IR Receiver TSOP1738
* - IR Send LED; deze wordt gebruikt om signalen te herhalen (6x2 en 4x2 switches ir ontvanger zitten niet in het zicht)
*
* @todo: Repeat incomming IR signals
* @todo: Use INPUT pin to enable/disable Serial instead of relying on ENABLE_SERIAL_PRINT ? (not sure...)
* @todo: TvInput also verify protocol to prevent mismathes from other remotes
**/
#include <IRremote.h>
// IR Sender
const byte IR_SEND_PIN = 10;
// IR Receiver
#define DECODE_NEC
const byte IR_RECEIVE_PIN = 9;
#define ENABLE_LED_FEEDBACK true
#define USE_DEFAULT_FEEDBACK_LED_PIN 0
// hdmi 2x1 switch 1
const byte HDMI2x1_SWTICH_1_INPUT_1_ENABLE_PIN = 4;
const byte HDMI2x1_SWTICH_1_INPUT_2_ENABLE_PIN = 5;
// hdmi 2x1 switch 2
const byte HDMI2x1_SWTICH_2_INPUT_1_ENABLE_PIN = 6;
const byte HDMI2x1_SWTICH_2_INPUT_2_ENABLE_PIN = 7;
// HDMI 6x2 matrix switch
int HDMI6x2_SWTICH_1_OUTPUT_1_IR_CODES[6] = {
82, //in1
81, //in2
83, //in3
84, //in4
85, //in5
86, //in6
};
int HDMI6x2_SWTICH_1_OUTPUT_2_IR_CODES[6] = {
87, //in1
89, //in2
91, //in3
88, //in4
90, //in5
92, //in6
};
// HDMI 4x2 matrix switch
int HDMI4x2_SWTICH_1_OUTPUT_1_IR_CODES[4] = {
1, //in1
3, //in2
4, //in3
6, //in4
};
int HDMI4x2_SWTICH_1_OUTPUT_2_IR_CODES[4] = {
7, //in1
9, //in2
10, //in3
31, //in4
};
// Enable/Disable Serial print
const bool ENABLE_SERIAL_PRINT = true;
/**
* Enkele functies voor het aan/uit zetten van Serial.print / Serial.printnl, als IR_RECEIVE_PIN uit staat (false) dan word Serial niet geladen.
* Serial functies kosten rekenkracht en geheugen; deze schakelen we uit via ENABLE_SERIAL_PRINT = false; zodat we deze enkel gebruiken tijdens het programeren en testen.
**/
void serialBegin(bool enableLogger) {
if (enableLogger){
Serial.begin(9600);
Serial.println("Logger enabled!");
}
}
// stuur berichten naar console, als geformateerde strings e.g. printLine(true, "hier een getal met decimalen: %d", 10.12);
void printLine(bool lineBreak, const char* input...) {
if (!ENABLE_SERIAL_PRINT) {
return;
}
va_list args;
va_start(args, input);
for (const char* i = input; *i != 0; ++i) {
if (*i != '%') {
Serial.print(*i);
continue;
}
switch (*(++i)) {
case '%': Serial.print('%'); break;
case 's': Serial.print(va_arg(args, char*)); break;
case 'd': Serial.print(va_arg(args, int), DEC); break;
case 'b': Serial.print(va_arg(args, int), BIN); break;
case 'o': Serial.print(va_arg(args, int), OCT); break;
case 'x': Serial.print(va_arg(args, int), HEX); break;
case 'f': Serial.print(va_arg(args, double), 2); break;
case 'B': Serial.print(va_arg(args, int) ? "true" : "false"); break;
case 'a': Serial.print("["); int* val = va_arg(args, int*); int size = sizeof(val); for (int i=0; i<size; i++) {Serial.print(val[i]); Serial.print(", ");} Serial.print("]");break;
}
}
if (lineBreak) {
Serial.println();
}
va_end(args);
}
/**
* Wissel tussen kanaal 1 en 2 op een 1x2 hdmi switch, door een pin van +3.3v naar 0v te schakelen
**/
class Hdmi2x1Switch {
public:
byte input1EnablePin;
byte input2EnablePin;
int input1;
int input2;
Hdmi2x1Switch(
byte input1EnablePin,
byte input2EnablePin,
int input1,
int input2
) {
this->input1EnablePin = input1EnablePin;
this->input2EnablePin = input2EnablePin;
this->input1 = input1;
this->input2 = input2;
}
void disablePins() {
printLine(true, "Hdmi2x1Switch: Disabling all inputs...");
this->disablePin(this->input1EnablePin);
this->disablePin(this->input2EnablePin);
}
void disablePin(int pin) {
printLine(true, "Hdmi2x1Switch: disabled {input: %d}", pin);
digitalWrite(pin, HIGH);
}
void enablePin(int pin) {
printLine(true, "Hdmi2x1Switch: enabled {input: %d}", pin);
digitalWrite(pin, LOW);
}
void enableOutput1() {
printLine(true, "Hdmi2x1Switch: enabling Ooutput1...");
this->enablePin(this->input1EnablePin);
this->disablePin(this->input2EnablePin);
}
void enableOutput2() {
printLine(true, "Hdmi2x1Switch: enabling Ooutput2...");
this->enablePin(this->input2EnablePin);
this->disablePin(this->input1EnablePin);
}
void setup() {
printLine(true, "Hdmi2x1Switch: setup: {input1: %d, input2: %d}", this->input1EnablePin, this->input2EnablePin);
pinMode(this->input1EnablePin, OUTPUT);
pinMode(this->input2EnablePin, OUTPUT);
this->disablePins();
}
void loop(int enablePin) {
if (enablePin == this->input1) {
this->enableOutput1();
return;
}
if (enablePin == this->input2) {
this->enableOutput2();
return;
}
return;
}
};
/**
* Decodeer InfraRed codes naar unieke HDMI 2x1 inputs (1 tot 4)
**/
class TvInput {
public:
// return codes for switching hdmi2x1; we gebruiken hier static zodat we deze waardes kunnen gebruiken via TvInput::var_naam
// Deze staan niet in de configuratie bovenaan omdat deze enkel uniek hoeven te zijn en niet veranderen, als er meer hdmi switches bij komen tellen we enkel verder door...
static const byte HDMI2x1_1_INPUT_1 = 1;
static const byte HDMI2x1_1_INPUT_2 = 2;
static const byte HDMI2x1_2_INPUT_1 = 3;
static const byte HDMI2x1_2_INPUT_2 = 4;
// Commands from hdmi6x2-output1
int* output_1_input_1 = HDMI6x2_SWTICH_1_OUTPUT_1_IR_CODES;
// Commands from hdmi4x2-output1
int* output_1_input_2 = HDMI4x2_SWTICH_1_OUTPUT_1_IR_CODES;
// Commands from hdmi6x2-output2
int* output_2_input_1 = HDMI6x2_SWTICH_1_OUTPUT_2_IR_CODES;
// Commands from hdmi4x2-output2
int* output_2_input_2 = HDMI4x2_SWTICH_1_OUTPUT_2_IR_CODES;
// test if input is in array
bool inArray(int code, int codes[], int length) {
for (int i = 0; i < length; i++) {
if (codes[i] == code) {
return true;
}
}
return false;
}
// Deze setup hoeft op dit moment (nog?) niets te doen, maar we houden deze functie er in zodat het duidelijk is dat er in de setup niets hoeft te gebeuren voor deze class
void setup() {
printLine(true, "TvInput: setup");
}
// loop: controleer of de IR code geprogrammeerd is en schakel vertaal dit naar een unieke code (1 t/m 4 in ons geval)
int loop(IRData iRrecieveData) {
int code = iRrecieveData.command;
if (code < 1) {
return 0;
}
if (this->inArray(code, this->output_1_input_1, 6)) {
printLine(true, "TvInput: output_1_input_1_enabled");
return TvInput::HDMI2x1_1_INPUT_1;
}
if (this->inArray(code, this->output_1_input_2, 4)) {
printLine(true, "TvInput: output_1_input_2_enabled");
return TvInput::HDMI2x1_1_INPUT_2;
}
if (this->inArray(code, this->output_2_input_1, 6)) {
printLine(true, "TvInput: output_2_input_1_enabled");
return TvInput::HDMI2x1_2_INPUT_1;
}
if (this->inArray(code, this->output_2_input_2, 4)) {
printLine(true, "TvInput: output_2_input_2_enabled");
return TvInput::HDMI2x1_2_INPUT_2;
}
}
};
/**
* Configureer IR ontvanger en geef de IR code terug als uitvoer van loop (deze code kunnen we gebruiken in onze andere classes)
**/
class InfraRedReceive {
public:
byte IrReceivePin;
bool enableLedFeedback;
bool useDefaultFeedbackLedPin;
InfraRedReceive(byte IrReceivePin, bool enableLedFeedback, bool useDefaultFeedbackLedPin) {
this->IrReceivePin = IrReceivePin;
this->enableLedFeedback = enableLedFeedback;
this->useDefaultFeedbackLedPin = useDefaultFeedbackLedPin;
}
// Configureer IR ontvanger
void setup() {
printLine(true, "InfraRedReceive: setup: {IrReceivePin: %d, enableLedFeedback: %B, useDefaultFeedbackLedPin: %B}", IrReceivePin, enableLedFeedback, useDefaultFeedbackLedPin);
pinMode(13, OUTPUT); //LED feedback
IrReceiver.begin(this->IrReceivePin, this->enableLedFeedback, this->useDefaultFeedbackLedPin);
}
// loop: als de ontvangen IR code anders is dan 0 geef deze mee als uitvoer
IRData loop() {
if (IrReceiver.decode()) {
IrReceiver.resume();
}
int code = IrReceiver.decodedIRData.command;
IrReceiver.decodedIRData.command = 0;
if (code != 0){
printLine(true, "InfraRedReceive: {code: %d, protocol: %s}", code, IrReceiver.decodedIRData.protocol);
}
return IrReceiver.decodedIRData;
}
};
/**
* Configureer IR verzender
**/
class InfraRedSend {
public:
byte IrSendPin;
bool enableLedFeedback;
bool useDefaultFeedbackLedPin;
InfraRedSend(byte IrSendPin, bool enableLedFeedback, bool useDefaultFeedbackLedPin) {
this->IrSendPin = IrSendPin;
this->enableLedFeedback = enableLedFeedback;
this->useDefaultFeedbackLedPin = useDefaultFeedbackLedPin;
}
// Configureer IR ontvanger
void setup() {
IRsend irsend;
printLine(
true,
"InfraRedSend: setup: {IrSendPin: %d, enableLedFeedback: %B, useDefaultFeedbackLedPin: %B}",
this->IrSendPin,
this->enableLedFeedback,
this->useDefaultFeedbackLedPin
);
pinMode(13, OUTPUT); //LED feedback
IrSender.begin(this->IrSendPin, this->enableLedFeedback, this->useDefaultFeedbackLedPin);
}
// loop: gebruik de irReceiveData van InfraRedReceive als commando om te verzenden.
void loop(IRData irReceiveData) {
int code = irReceiveData.command;
if (code < 1) {
return;
}
printLine(
true,
"InfraRedSend: loop: {decodedRawDataArray: %a, rawlen: %d, frequencyKilohertz: %d}",
irReceiveData.decodedRawDataArray,
irReceiveData.rawlen,
38
);
// IrSender.sendRaw(irReceiveData.decodedRawDataArray, irReceiveData.rawlen, 38);
}
};
/**
* Initialiseer de classes met de configuratie variabelen boven aan de broncode
**/
TvInput tvInput;
InfraRedReceive infraRedReceive(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK, USE_DEFAULT_FEEDBACK_LED_PIN);
InfraRedSend infraRedSend(IR_SEND_PIN, ENABLE_LED_FEEDBACK, USE_DEFAULT_FEEDBACK_LED_PIN);
Hdmi2x1Switch tvHdmi2x1Switch1(
HDMI2x1_SWTICH_1_INPUT_1_ENABLE_PIN,
HDMI2x1_SWTICH_1_INPUT_2_ENABLE_PIN,
TvInput::HDMI2x1_1_INPUT_1,
TvInput::HDMI2x1_1_INPUT_2
);
Hdmi2x1Switch tvHdmi2x1Switch2(
HDMI2x1_SWTICH_2_INPUT_1_ENABLE_PIN,
HDMI2x1_SWTICH_2_INPUT_2_ENABLE_PIN,
TvInput::HDMI2x1_2_INPUT_1,
TvInput::HDMI2x1_2_INPUT_2
);
/**
* Arduino Setup: wordt maar een keer uitgevoerd.
**/
void setup() {
serialBegin(ENABLE_SERIAL_PRINT);
printLine(true, "--SETUP START--");
tvHdmi2x1Switch1.setup();
tvHdmi2x1Switch2.setup();
infraRedReceive.setup();
infraRedSend.setup();
tvInput.setup();
printLine(true, "--SETUP FINISHED--");
}
/**
* Arduino Loop: wordt continue uitgevoerd.
**/
void loop() {
IRData IRreceiveData = infraRedReceive.loop();
infraRedSend.loop(IRreceiveData);
int tvInputCode = tvInput.loop(IRreceiveData);
tvHdmi2x1Switch1.loop(tvInputCode);
tvHdmi2x1Switch2.loop(tvInputCode);
}