Hallo,
habe meine Klimaanlage via Infra-Rot (Hersteller TCL) an Symcon angebunden.
Die (indirekte) Protokollbeschreibung lässt sich hier aus den Quellcodes entziffern:
Ich habe die IR-Diode allerdings an meinem Symcon Raspi hängen, deshalb läuft es nicht so, wie für den ESP beschrieben.
Raspian hat das Program LIRC, welches zum IR-Senden und Empfangen gedacht ist. Anleitungen, wie die Schaltung dafür aussehen sollte (Widerstände, Transistor, IR Sende und Empfangsdioden) gibt es einige, ich habe mich hier belesen: Raspberry Pi Fernbedienung: Infrarot Steuerung einrichten. Vorwiderstand für die Dioden (habe 2 in Reihe, da ich noch eine andere Klimaanlage mit anderem Protokoll ansteuere) habe ich deutlich kleiner gewählt, die Sendedioden sind durch Verkabelung im Haus bis vor die Empfangsdioden der Innenteile der Klima verlegt.
Was ich hier mit Euch teilen möchte, ist der Code für das „Zusammenstellen“ des IR-Signals.
Lirc funktioniert so, dass in einer im Ordner /etc/lirc/lircd.conf.d/ abzulegenden Datei mit der Endung *.lircd.conf der „raw“-Code abgelegt wird und dann das Senden via Console-Befehl gestartet und gestoppt wird. Die Datei wird durch Symcon bei meiner Vorgehensweise jedes mal für den gewünschten Sendebefehl neu „überschrieben“ und dann die Übertragung gestartet. Funktioniert sehr zuverlässig.
So, hier nun der Code zur Ermittlung der „Rohdaten“ für die LIRC-Datei:
<?php
/*
Berechnet Befehlszeile für IR TCL in Stube und sendet diese via Lirc zur Klima
Entschlüsselung der Fernbedienung via GitHub und eigenen Messungen
https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Tcl.h
Idee:
IPS berechnet den Raw-Code, schreibt diesen auf den Raspi in das Lirc-Konf-Verzeichnis in eine extra datei, Fernbedienung TCL_Stube und der Befehl heisst command
Lirc wird neu gestartet, um die Datei zu laden
Mit Shellexec wird Lirc senden gestartet und gestoppt
Der Befehl besteht aus 14 Bytes und zwei Zeichen davor. Die 14 Bytes und die Vor-Zeichen sind entweder in Konstanten bzw. im Code gespeichert oder werden in Laufzeit berechnet
*/
#Konstanten einlesen
$Header_const= getvalue(16528);
$Byte00_const = getvalue(53131);
$Byte01_const = getvalue(14315);
$Byte02_const = getvalue(14661);
$Byte03_const = getvalue(20338);
$Byte04_const = getvalue(28144);
$Byte05_const = getvalue(36829);
$Byte06_const = getvalue(40818);
$Byte07_const = getvalue(53732);
$Byte08_const = getvalue(54586);
$Byte09_const = getvalue(55356);
$Byte10_const = getvalue(55605);
$Byte11_const = getvalue(59717);
$Byte12_const = getvalue(21451);
$DauerBitMark_const = getvalue(36844);
$Dauerhigh_const = getvalue(16522);
$Dauerlow_const = getvalue(50269);
#Code
#Byte 0-2 - Konstanten
$Byte00 = $Byte00_const; #Konstant
$Byte01 = $Byte01_const; #Konstant
$Byte02 = $Byte02_const; #Konstant
# Byte 3, Bits 6-7 - Message Type (anderen Bits konstant)
$MsgType_AcNormal = "01";
$MsgType_AcSpecial = "10";
#$Byte03 = $MsgType_AcNormal.$Byte03_const; #Unklar, wann normale nachricht genutzt wird, hier nur Spezial
$Byte03 = $MsgType_AcSpecial.$Byte03_const;
# Byte 4 - Konstante
$Byte04 = $Byte04_const; #Konstant
# Byte 5
/* Jeweils 1 Bit in Byte 5
uint8_t Power :1; Bit 5 - 1(an), 0(aus)
uint8_t OffTimerEnabled :1; Bit 4 - 0 (aus) --> Standard in diesem Code, Timer wird über Symcon geregelt
uint8_t OnTimerEnabled :1; Bit 3 - 0 (aus) --> Standard in diesem Code, Timer wird über Symcon geregelt
uint8_t Quiet :1; Bit 2 - Piepsen an wenn 1
uint8_t Light :1; Bit 1 - Anzeige aus wenn 1
uint8_t Econo :1; Bit 0 - Economy-Modus wenn 1
*/
$Power = intval(getvalue(55043));
$Quiet = intval(getvalue(51313));
$Light = intval(getvalue(32638));
$Econo = intval(getvalue(15029));
$OffTimerEnabled = 0; #Konstant
$OnTimerEnabled= 0; #Konstant
$Byte05 = $Byte05_const.$Power.$OffTimerEnabled.$OnTimerEnabled.$Quiet.$Light.$Econo;
#Byte 6
/* Belegung Byte 6 (Bit 0 und 1 konstant)
uint8_t Mode :4; Bit 4-7
uint8_t Health :1; Bit 3 --> an=1
uint8_t Turbo :1; Bit 2 --> an=1
Wobei
const uint8_t kTcl112AcHeat = 1;
const uint8_t kTcl112AcDry = 2;
const uint8_t kTcl112AcCool = 3;
const uint8_t kTcl112AcFan = 7;
const uint8_t kTcl112AcAuto = 8;
#Big Endian
$Mode_Heat = "1000"; #1
$Mode_Dry = "0100"; #2
$Mode_Cool = "1100"; #3
$Mode_Fan = "1110"; #7
$Mode_Auto = "0001"; #8
*/
#Mode als Integer, dann nach 4-bit Binär und dann Big Endian
$Mode = strrev(sprintf('%04b',getvalue(46860)));
$Health = intval(getvalue(53039)); #Boolean nach Integer umwandeln
$Turbo = intval(getvalue(27114)); #Boolean nach Integer umwandeln
$Byte06 = $Mode.$Health.$Turbo.$Byte06_const;
#Byte 7
/* Belegung Byte 7
uint8_t Temp :4; Bit 4-7, Big endian und dann noch verkehrt herum
uint8_t :4; const
Temp
Big Endian
0 = 31°C
15 = 16°C
Integer ist in diese Werte umzurechnen
0000 (0) 31
1000 30
0100 29
1100 28
0010 27
1010 26
0110 25
1110 24
0001 23
1001 22
0101 21
1101 20
0011 19
1011 18
0111 17
1111 (15) 16
*/
#Temperatur von °C nach umgekehrtenwert (31-Temp), dann nach 4-bit Binär und dann Big Endian
$Temperatur = strrev(sprintf('%04b',31 - getvalue(42469)));
$Byte07 = $Temperatur.$Byte07_const;
#Byte 8
/* Belegung Byte 8 (Fan und Swing Vertikal)
// Byte 8
uint8_t Fan :3; Bit 5-7
uint8_t SwingV :3; Bit 2-4
uint8_t TimerIndicator :1; Bit 1 #Konstant = 0
uint8_t :1; Bit 0 #Konstant = 0
const uint8_t kTcl112AcFanAuto = 000; = 0
const uint8_t kTcl112AcFanMin = 001; = 1
const uint8_t kTcl112AcFanLow = 010; = 2
const uint8_t kTcl112AcFanMed = 011; = 3
const uint8_t kTcl112AcFanHigh = 101; = 5
const uint8_t kTcl112AcSwingVOff = 000; = 0
const uint8_t kTcl112AcSwingVHighest = 001; = 1 --> geht nicht
const uint8_t kTcl112AcSwingVHigh = 010; = 2 --> geht nicht
const uint8_t kTcl112AcSwingVMiddle = 011; = 3 --> geht nicht
const uint8_t kTcl112AcSwingVLow = 100; = 4 --> geht nicht
const uint8_t kTcl112AcSwingVLowest = 101; = 5 --> geht nicht
const uint8_t kTcl112AcSwingVOn = 111; = 7
*/
$Fan = sprintf('%03b',getvalue(54992)); #Integer in 3bit - Binär umwandeln
$SwingV = sprintf('%03b',getvalue(31118));; #Integer in 3bit - Binär umwandeln
$Byte08 = $Fan.$SwingV."0".$Byte08_const;
#Byte 9 bis 12 - Konstant
$Byte09 = $Byte09_const; #als Konstant gewählt
$Byte10 = $Byte10_const; #als Konstant gewählt
$Byte11 = $Byte11_const; #Konstant
$Byte12 = $Byte12_const; #Konstant
#Byte 13 = Berechnung Checksumme (jedes Byte wird erst Big endianisiert, dann zu Dezimal umgerechnet, dann alle addiert, dann wieder zu 8bit Bin und danach wieder little Endian)
$Checksum = bindec(strrev($Byte00))+bindec(strrev($Byte01))+bindec(strrev($Byte02))+bindec(strrev($Byte03))+bindec(strrev($Byte04))+bindec(strrev($Byte05))+bindec(strrev($Byte06))+bindec(strrev($Byte07))+bindec(strrev($Byte08))+bindec(strrev($Byte09))+bindec(strrev($Byte10))+bindec(strrev($Byte11))+bindec(strrev($Byte12));
$Checksum = substr(sprintf('%08b',$Checksum), -8);
$Checksum = strrev($Checksum);
setvalue(57787,$Checksum); #Checksumme für Debug in Variable schreiben
$Byte13 = $Byte00.$Byte01.$Byte02.$Byte03.$Byte04.$Byte05.$Byte06.$Byte07.$Byte08.$Byte09.$Byte10.$Byte11.$Byte12.$Checksum; #komplette Nachricht als Binär
# Umwandeln in Impulszeiten
$StrLength = strlen($Byte13); #Länge des Bit-Strings ermitteln
#String durchlaufen und durch Pulszeiten ersetzen
$StrIR = ""; #Stringvariable initialisieren
for ($i = 0; $i < $StrLength; $i++)
{
$StrIR = $StrIR.$DauerBitMark_const." "; # Start-BitMark setzen
if ($Byte13[$i] == "1")
{
$StrIR=$StrIR.$Dauerhigh_const." "; #Pause für "1"
}
else
{
$StrIR=$StrIR.$Dauerlow_const." "; #Pause für "0"
}
}
$StrIR = $Header_const." ".$StrIR.$DauerBitMark_const; #Header zufügen und Abschluß-Abtasten
setvalue(45337,$StrIR); #Befehl für Debug in Variable schreiben
#Lirc-Datei Schreiben
$Pfad ="/etc/lirc/lircd.conf.d/"; # Lirc-Ordner auf dem Raspi
$DateiPfad = $Pfad."tcl_temp.lircd.conf"; # Pfad und Name der Lirc-Datei
/* Beispielaufbau der Lirc Datei
begin remote
name TCL_Stube
flags RAW_CODES
eps 30
aeps 100
ptrail 0
repeat 0 0
gap 100000
begin raw_codes
name command
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx hier kommt der Pulse-Code hin
end raw_codes
end remote
*/
#Text der Datei (Inhalt) zusammenstellen - \n ist immer ein Zeilenumbruch
$LircText = "begin remote";
$LircText .= "\n";
$LircText .= "\nname TCL_Stube"; #Watchdog stoppen
$LircText .= "\nflags RAW_CODES";
$LircText .= "\neps 30";
$LircText .= "\naeps 100";
$LircText .= "\n";
$LircText .= "\nptrail 0";
$LircText .= "\nrepeat 0 0";
$LircText .= "\ngap 100000";
$LircText .= "\n";
$LircText .= "\nbegin raw_codes";
$LircText .= "\n";
$LircText .= "\nname command";
$LircText .= "\n";
$LircText .= "\n".$StrIR;
$LircText .= "\n";
$LircText .= "\nend raw_codes";
$LircText .= "\nend remote";
$LircText = trim($LircText);
#Datei anlegen + füllen + speichern
$LircDatei = fopen ($DateiPfad, "wb"); //Datei anlegen bzw. vorhandene überschreiben ohne Rückfrage
fwrite($LircDatei, $LircText); //Text darin abspeichern
fclose($LircDatei); //und schließen
IPS_Sleep(100); #Pause
# Befehl senden
shell_exec ("sudo systemctl restart lircd"); #Lirc neu starten, um Datei neu zu laden
IPS_Sleep(100); #Pause
$MessageOn = "sudo irsend SEND_start TCL_Stube command"; //An-Befehl für Senden in Endlosschleife
$MessageOff = "sudo irsend SEND_stop TCL_Stube command"; //Aus-Befehl Abschalten Endlosschleife
shell_exec ($MessageOn); //Schaltet das Senden einer IR-Sequenz als Endlosschleife an
IPS_Sleep (1000); // lässt Sende-Schleife 1 Sekunde laufen
shell_exec ($MessageOff); //stellt Schleife wieder ab
#Ende
Hier die Variablen und Konstanten: