Viessmann Heizung VScotHO1: Daten auslesen

Langfristiges Ziel von mir war es über mein Smarthome System sehen zu können, wenn die Heizung in eine Störung gelaufen ist und allgemein was die Heizung und die Solarthermie so treiben.

UPDATE: Fortsetzung hier

Eine Heizung von Viessmann besitzt eine serielle Diagnose-Schnittstelle (Infrarot) etwas „versteckt“ hinter der grünen Betriebs-LED (rechts) und der roten Störungs-LED (links). Es gibt im Netz mehrere aufwändige oder weniger aufwändige elektronische Schaltungen mit IR-LEDs um mit dieser Schnittstelle zu kommunizieren. Viele Informationen findet man im Wiki von Openv auf github hier. Da ich wie immer die Schaltung so simpel wie möglich haben wollte, habe ich mir in diesem Wiki die Variante mit einem ESP8266 (mit dem Wemos D1 mini habe ich ja bereits die Wortuhr gebaut) genauer angesehen. Die nötigen Widerstände, den IR-Transistor (SFH 309 FA) sowie die IR-LED (SFH 487P OSO) habe ich bei Reichelt für kleines Geld bestellt.

Nach Lieferung schnell auf einem Breadboard zusammen gesteckt, den Mikrocontroller mit einem einfachen Sketch (Wifi-Serial-Bridge), welches es zu Hauf im Netz gibt, bespielt und mit einer TV-Fernbedienung schonmal den Empfang von IR-Signalen getestet. Leider musste ich die Fernbedienung, welche schon eine recht starke LED besitzt, bis auf unter einem Zentimeter an den Transistor halten um etwas zu Empfangen. Da konnte etwas nicht stimmen, aber was? Zig mal habe ich die doch recht überschaubare Schaltung überprüft und die Bauteile angezweifelt.

Schließlich bin ich über eine Antwort auf einen von mir erstellten Forumbeitrag auf die Lösung gekommen. Der Pin, den ich für den Empfang verwendet habe (RX), ist anscheinend mit einem Widerstand versehen. Vielleicht hätte ich den auch mit „pinMode“ umstellen können. Ich habe aber letztlich mit SoftwareSerial auf die Pins D1 und D2 gewechselt und alles war super.

Für weitere Tests habe ich die Schaltung auf eine Lochrasterplatine gelötet. In der Größe der rechteckigen Aussparung an der Heizung eine Hartschaumplatte geschnitten, 2 Lagen übereinander geklebt um mehr Abstand zubekommen, 2 Löcher für die LED und den Transistor reingebohrt und letztlich wieder ca. 2 mm abgeschliffen damit meine LED/Transistor genau vor denen der Heizung sitzen.

Die Schnittstelle, welche meinen Smarthome-Server mit der Heizung verbindet bzw. die Befehle sendet, habe ich per php-Script auf meinem Synology NAS umgesetzt:

<?php
function getTemp(&$socket, $firstByte, $secondByte, $factor) {
	$buf = "";
	$out = "";
	$checksum = 5 + 1 + $firstByte + $secondByte + 2;

	$out = pack("C8", 0x41, 0x05, 0x00, 0x01, $firstByte, $secondByte, 2, $checksum);
		
	socket_write($socket, $out);
	
	$bytes = socket_recv($socket, $buf, 1, 0);
	$bytestring = implode(unpack("H*", $buf));
	if ($bytestring == "06") {
		$bytes = socket_recv($socket, $buf, 1, 0);
		$bytestring = implode(unpack("H*", $buf));
		if ($bytestring == "41") {
			$bytes = socket_recv($socket, $buf, 6, MSG_WAITALL);
			$bytes = socket_recv($socket, $buf, 2, MSG_WAITALL);
			$bytestring = implode(unpack("s*", $buf));
	
			socket_recv($socket, $buf, 1, 0);

			if ($factor !== 0) {
				return $bytestring/$factor;
			} else {
				return $bytestring;
			}

		} else {
			echo "Fehler keine Daten";
			return null;
		}
	} else {
		echo "Fehler Adresse ungültig, Antwort: ".$bytestring;
		return null;
	}

	resetConnection($socket);
	$buf ="";
}

function getValue(&$socket, $firstByte, $secondByte, $responseBytes, $factor) {
	$buf = "";
	$out = "";
	$checksum = 5 + 1 + $firstByte + $secondByte + $responseBytes;

	$out = pack("C8", 0x41, 0x05, 0x00, 0x01, $firstByte, $secondByte, $responseBytes, $checksum);
	
	socket_write($socket, $out);
	
	$bytes = socket_recv($socket, $buf, 1, 0);
	$bytestring = implode(unpack("H*", $buf));
	if ($bytestring == "06") {
		$bytes = socket_recv($socket, $buf, 1, 0);
		$bytestring = implode(unpack("H*", $buf));
		if ($bytestring == "41") {
			$bytes = socket_recv($socket, $buf, 6, MSG_WAITALL);
			$bytes = socket_recv($socket, $buf, $responseBytes, MSG_WAITALL);
			$bytestring = implode(unpack("v*", $buf));
			if ($responseBytes == 1) {
				$bytestring = implode(unpack("C", $buf));
			} else {
				$bytestring = implode(unpack("v*", $buf)); //experimentell
			}
	
			socket_recv($socket, $buf, 1, 0);

			if ($factor !== 0) {
				return $bytestring/$factor;
			} else {
				return $bytestring;
			}
			
		} else {
			echo "Fehler keine Daten";

			return null;
		}
	} else {
		echo "Fehler Adresse ungültig, Antwort: ".$bytestring;

		return null;
	}
	$buf ="";
}

function getString(&$socket, $firstByte, $secondByte, $responseBytes) {
	$buf = "";
	$out = "";
	$checksum = 5 + 1 + $firstByte + $secondByte + $responseBytes;

	$out = pack("C8", 0x41, 0x05, 0x00, 0x01, $firstByte, $secondByte, $responseBytes, $checksum);

	socket_write($socket, $out);
	
	$bytes = socket_recv($socket, $buf, 1, 0);
	$bytestring = implode(unpack("H*", $buf));
	if ($bytestring == "06") {
		$bytes = socket_recv($socket, $buf, 1, 0);
		$bytestring = implode(unpack("H*", $buf));
		if ($bytestring == "41") {
			$bytes = socket_recv($socket, $buf, 6, MSG_WAITALL);
			$bytes = socket_recv($socket, $buf, $responseBytes, MSG_WAITALL);

			$result = implode(unpack("H*", $buf));
			socket_recv($socket, $buf, 1, 0);

			return $result;
		} else {
			echo "Fehler: keine Daten, Antwort: ".$bytestring;
			return null;
		}
	} else {
		echo "Fehler: Adresse ungültig, Antwort: ".$bytestring;
		return null;
	}

	$buf ="";
}


function resetConnection(&$socket) {
	$out = pack("C", 0x04);
	socket_write($socket, $out);
	socket_recv($socket, $buf, 1, 0);
}

function initConnection(&$socket) {
	resetConnection($socket);
	$buf = "";
	$bytes = socket_recv($socket, $buf, 1, 0);
	$bytestring = implode(unpack("H*", $buf));
	
	if ($bytestring == "05") {
		$out = pack("C3", 0x16, 0x00, 0x00);
		socket_write($socket, $out);
		$bytes = socket_recv($socket, $buf, 1, 0);
		$bytestring = implode(unpack("H*", $buf));
		if ($bytestring == "06") {
			
		return true;
		}
	} else {
		echo "Fehler: Schnittstelle nicht bereit, Antwort: ".$bytestring;
		resetConnection($socket);
		return null;
	}
}

function getAußenTemp(&$socket) {
	return getTemp($socket, 0x08, 0x00, 10);
}
function getBetriebsstunden(&$socket) {
	return getValue($socket, 0x65, 0x68, 4, 3600);
}
function getAbgasTemp(&$socket) {
	return getTemp($socket, 0x08, 0x08, 10);
}
function getLadespeicherObenTemp(&$socket) {
	return getTemp($socket, 0x08, 0x12, 10);
}
function getLadespeicherUntenTemp(&$socket) {
	return getTemp($socket, 0x08, 0x14, 10);
}
function getKesselIstTemp(&$socket) {
	return getTemp($socket, 0x08, 0x02, 10);
}
function getKesselSollTemp(&$socket) {
	return getTemp($socket, 0x55, 0x5A, 10);
}
function getWarmwasserSollTemp(&$socket) {
	return getTemp($socket, 0x63, 0x00, 1);
}
function getWarmwasserIstTemp(&$socket) {
	return getTemp($socket, 0x08, 0x04, 10);
}
function getVorlaufSollTemp(&$socket) {
	return getTemp($socket, 0x35, 0x44, 10);
}
function getVorlaufTemp(&$socket) {
	return getTemp($socket, 0x39, 0x00, 10);
}
function getKollektorTemp(&$socket) {
	return getTemp($socket, 0x65, 0x64, 10);
}
function getSolarspeicherTemp(&$socket) {
	return getTemp($socket, 0x65, 0x66, 10);
}
function getNachladeunterdrueckung(&$socket) {
	return getValue($socket, 0x65, 0x51, 1, 1);
}
function getSolarPumpe(&$socket) {
	return getValue($socket, 0x65, 0x52, 1, 1);
}
function getWaermemenge(&$socket) {
	return getValue($socket, 0x65, 0x60, 4, 10);
}
function getSolarTagesertrag(&$socket) {
	return getValue($socket, 0xCF, 0x30, 4, 10);
}
function getSolarInfo(&$socket) {
	return getValue($socket, 0x77, 0x54, 1, 1);
}
function getSpeicherladepumpe(&$socket) {
	return getValue($socket, 0x65, 0x13, 1, 1);
}
function getBetriebsart(&$socket) {
	return getValue($socket, 0x33, 0x23, 1, 1);
}
function getSparbetrieb(&$socket) {
	return getValue($socket, 0x33, 0x02, 1, 1);
}
function getBrennerlaufzeit(&$socket) {
	return getValue($socket, 0x08, 0x86, 4, 3600);
}
function getBrennerstoerung(&$socket) {
	return getValue($socket, 0x08, 0x83, 1, 1);
}
function getBrennerstarts(&$socket) {
	return getValue($socket, 0x08, 0x8A, 4, 10);
}
function getBrennerstatus(&$socket) {
	return getValue($socket, 0xA3, 0x8F, 2, 1);
}
function getBrennerleistung(&$socket) {
	return getValue($socket, 0xA3, 0x8F, 1, 2);
}
function getFlamme(&$socket) {
	return getValue($socket, 0x55, 0xD3, 1, 1);
}
function getFlowSwitch(&$socket) {
	return getValue($socket, 0x08, 0x83, 1, 1);
}
function getUmschaltventil(&$socket) {
	return getValue($socket, 0x0A, 0x10, 1, 1);
}
function getUmwaelzpumpe(&$socket) {
	return getValue($socket, 0x76, 0x60, 1, 1);
}
function getHeizkreispumpe(&$socket) {
	return getValue($socket, 0x39, 0x06, 1, 1);
}
function getStatusStoerung(&$socket) {
	return getValue($socket, 0x0A, 0x82, 1, 1);
}
function getStoerungsmeldung1(&$socket) {
	$result = getString($socket, 0x75, 0x07, 9);
	$errorCode = substr($result,0,2);
	$datetime = date_create(substr($result,2,14));
	return date_format($datetime, "d.m.Y H:i:s").": ".getErrorMessage($errorCode);
}
function getStoerungsmeldung2(&$socket) {
	$result = getString($socket, 0x75, 0x10, 9);
	$errorCode = substr($result,0,2);
	$datetime = date_create(substr($result,2,14));
	return date_format($datetime, "d.m.Y H:i:s").": ".getErrorMessage($errorCode);
}
function getStoerungsmeldung3(&$socket) {
	$result = getString($socket, 0x75, 0x19, 9);
	$errorCode = substr($result,0,2);
	$datetime = date_create(substr($result,2,14));
	return date_format($datetime, "d.m.Y H:i:s").": ".getErrorMessage($errorCode);
}
function getStoerungsmeldung4(&$socket) {
	$result = getString($socket, 0x75, 0x22, 9);
	$errorCode = substr($result,0,2);
	$datetime = date_create(substr($result,2,14));
	return date_format($datetime, "d.m.Y H:i:s").": ".getErrorMessage($errorCode);
}
function getStoerungsmeldung5(&$socket) {
	$result = getString($socket, 0x75, 0x2B, 9);
	$errorCode = substr($result,0,2);
	$datetime = date_create(substr($result,2,14));
	return date_format($datetime, "d.m.Y H:i:s").": ".getErrorMessage($errorCode);
}
function getStoerungsmeldung6(&$socket) {
	$result = getString($socket, 0x75, 0x34, 9);
	$errorCode = substr($result,0,2);
	$datetime = date_create(substr($result,2,14));
	return date_format($datetime, "d.m.Y H:i:s").": ".getErrorMessage($errorCode);
}
function getStoerungsmeldung7(&$socket) {
	$result = getString($socket, 0x75, 0x3D, 9);
	$errorCode = substr($result,0,2);
	$datetime = date_create(substr($result,2,14));
	return date_format($datetime, "d.m.Y H:i:s").": ".getErrorMessage($errorCode);
}
function getStoerungsmeldung8(&$socket) {
	$result = getString($socket, 0x75, 0x46, 9);
	$errorCode = substr($result,0,2);
	$datetime = date_create(substr($result,2,14));
	return date_format($datetime, "d.m.Y H:i:s").": ".getErrorMessage($errorCode);
}
function getStoerungsmeldung9(&$socket) {
	$result = getString($socket, 0x75, 0x4F, 9);
	$errorCode = substr($result,0,2);
	$datetime = date_create(substr($result,2,14));
	return date_format($datetime, "d.m.Y H:i:s").": ".getErrorMessage($errorCode);
}
function getStoerungsmeldung10(&$socket) {
	$result = getString($socket, 0x75, 0x58, 9);
	$errorCode = substr($result,0,2);
	$datetime = date_create(substr($result,2,14));
	return date_format($datetime, "d.m.Y H:i:s").": ".getErrorMessage($errorCode);
}

function getErrorMessage($errorCode) {
	$messages = array("00" => "Regelbetrieb (kein Fehler)",
						"0F" => "Wartung (für Reset Codieradresse 24 auf 0 stellen)",
						"10" => "Kurzschluss Außentemperatursensor",
						"18" => "Unterbrechung Außentemperatursensor",
						"20" => "Kurzschluss Vorlauftemperatursensor",
						"21" => "Kurzschluss Rücklauftemperatursensor",
						"28" => "Unterbrechung Außentemperatursensor",
						"29" => "Unterbrechung Rücklauftemperatursensor",
						"30" => "Kurzschluss Kesseltemperatursensor",
						"38" => "Unterbrechung Kesseltemperatursensor",
						"40" => "Kurzschluss Vorlauftemperatursensor M2",
						"42" => "Unterbrechung Vorlauftemperatursensor M2",
						"50" => "Kurzschluss Speichertemperatursensor",
						"58" => "Unterbrechung Speichertemperatursensor",
						"92" => "Solar: Kurzschluss Kollektortemperatursensor",
						"93" => "Solar: Kurzschluss Sensor S3",
						"94" => "Solar: Kurzschluss Speichertemperatursensor",
						"9A" => "Solar: Unterbrechung Kollektortemperatursensor",
						"9B" => "Solar: Unterbrechung Sensor S3",
						"9C" => "Solar: Unterbrechung Speichertemperatursensor",
						"9F" => "Solar: Fehlermeldung Solarteil (siehe Solarregler)",
						"A7" => "Bedienteil defekt",
						"B0" => "Kurzschluss Abgastemperatursensor",
						"B1" => "Kommunikationsfehler Bedieneinheit",
						"B4" => "Interner Fehler (Elektronik)",
						"B5" => "Interner Fehler (Elektronik)",
						"B6" => "Ungültige Hardwarekennung (Elektronik)",
						"B7" => "Interner Fehler (Kesselkodierstecker)",
						"B8" => "Unterbrechung Abgastemperatursensor",
						"B9" => "Interner Fehler (Dateneingabe wiederholen)",
						"BA" => "Kommunikationsfehler Erweiterungssatz für Mischerkreis M2",
						"BC" => "Kommunikationsfehler Fernbedienung Vitorol, Heizkreis M1",
						"BD" => "Kommunikationsfehler Fernbedienung Vitorol, Heizkreis M2",
						"BE" => "Falsche Codierung Fernbedienung Vitorol",
						"C1" => "Externe Sicherheitseinrichtung (Kessel kühlt aus)",
						"C2" => "Kommunikationsfehler Solarregelung",
						"C5" => "Kommunikationsfehler drehzahlgeregelte Heizkreispumpe, Heizkreis M1",
						"C6" => "Kommunikationsfehler drehzahlgeregelte Heizkreispumpe, Heizkreis M2",
						"C7" => "Falsche Codierung der Heizkreispumpe",
						"C9" => "Störmeldeeingang am Schaltmodul-V aktiv",
						"CD" => "Kommunikationsfehler Vitocom 100 (KM-BUS)",
						"CE" => "Kommunikationsfehler Schaltmodul-V",
						"CF" => "Kommunikationsfehler LON Modul",
						"D1" => "Brennerstörung",
						"D4" => "Sicherheitstemperaturbegrenzer hat ausgelöst oder Störmeldemodul nicht richtig gesteckt",
						"DA" => "Kurzschluss Raumtemperatursensor, Heizkreis M1",
						"DB" => "Kurzschluss Raumtemperatursensor, Heizkreis M2",
						"DD" => "Unterbrechung Raumtemperatursensor, Heizkreis M1",
						"DE" => "Unterbrechung Raumtemperatursensor, Heizkreis M2",
						"E4" => "Fehler Versorgungsspannung",
						"E5" => "Interner Fehler (Ionisationselektrode)",
						"E6" => "Abgas- / Zuluftsystem verstopft",
						"F0" => "Interner Fehler (Regelung tauschen)",
						"F1" => "Abgastemperaturbegrenzer ausgelöst",
						"F2" => "Temperaturbegrenzer ausgelöst",
						"F3" => "Flammensigal beim Brennerstart bereits vorhanden",
						"F4" => "Flammensigal nicht vorhanden",
						"F7" => "Differenzdrucksensor defekt",
						"F8" => "Brennstoffventil schließt zu spät",
						"F9" => "Gebläsedrehzahl beim Brennerstart zu niedrig",
						"FA" => "Gebläsestillstand nicht erreicht",
						"FD" => "Fehler Gasfeurungsautomat",
						"FE" => "Starkes Störfeld (EMV) in der Nähe oder Elektronik defekt",
						"FF" => "Starkes Störfeld (EMV) in der Nähe oder interner Fehler");
	return $messages[strtoupper($errorCode)];
}

?>

Hier zeige ich Euch, wie ihr die Socket-Verbindung zum Mikrocontroller aufbaut und das vito_interface benutzen könnt:

include_once "vito_interface.php";
$address = '***.***.***.***';
$port = 81;

$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
$result = socket_connect($socket, $address, $port);

resetConnection($socket);

// Dein Code hier

Ich frage minütlich alle Werte ab und visualisiere mir das Ganze in EDOMI:

UPDATE: Fortsetzung hier

Categories: Allgemein