We use cookies to personalise content and ads, to provide social media features and to analyse our traffic. We also share information about your use of our site with our social media, advertising and analytics partners who may combine it with other information that you've provided to them or that they've collected from your use of their services.

Talking to Viessmann Vitodens 200

The boiler is the device using the most of the energy in a house and, which has the highest runnign costs of all the appliances. It does not come as a surprise that since the very beginning I was interested in connecting my Viessmann with the whole home-automation system.

All who own a Viessmann boiler should carefully read the articles posted on openv.wikisapces.com. The authors have invested and enormous amount of effort to decrypt the internal protocols and to design communication devices. The pride and glory belongs to them!

When you open the flap of a Vitodens 200 (and I believe that other devices are similar) you will see a round ON/OFF button and two lights placed on two sides of the letter "V". The left is marked with a thunder and signals malfunctions. The right one lights up when the unit is on. That, however, is not all. One of the lights is a infra-red emitter, the other one is a receiver. The V shape is used to mount a service cable.

The guys from openv.wikispaces.com have designed a published a few devices, which can replace the original service cable costing currently some EUR 130. EUR 130 is an absurd amount of cash for something that can easily be made with home means.

I started with building an USB adapter based on the scheme published on openv. I place here their file with the description, board layouts and explanations.  I hope they do not mind.

I decided to redesign the initial project to make it fit into the smallest case of Kradex. I bought all the elements (the IR led and phototransistor was hard to find; finally I bought it at farnell.eu and received the package 24h later) and began the work.

Making the board by transferring the printer toner with heat onto the board was in this case a nightmare. The details needed for the FT232RL chip are very tiny. The temperature was either to low and the bits peeled off or too high and they were fuzzy. I needed a few attempts to get in in an acceptable form and... understanding that this time the form will be of low quality. Hera is the firt proof-of-concept adapter:

USB Adapter USB1 USB2 USB3

You can see it is all a bit crocked and imperfect, but that's just how it is with the first model. I used it to test the idea and correct the initial design. If anyone is interested here is the pdf file with the board itself.

I attached the a adapter with a double-sided tape, plugged the USB plug into my laptop, launched the v-control (version 3 beta) and.... it all worked! Great! I could see all my boiler parameters right in front of my eyes.

The target was, however, to let the Raspberry Pi do the talking. It is in the main cabinet 4m away from the boiler. When I connected the adapter to the wire near the boiler and the USB cable into my laptop on the other side (4m), I received the message saying 'cannot recognize the device'. The wire was simply too long and inappropriate for USB connection. The project died on the spot.

When I tried again, I wanted to make my life easier. I bought an USB-RS232 adapter on aliexpress for USD 5 including shipment. I divided the scheme of the adapter into two parts and designed the board only with the elements 'after' the FT232RL chip::

USB-FT232RL Small RS232-V RS232-V 2 Small1


The board is tiny (28x14mm), and very flat, so it is possible to lock up the boiler flap. I mounted it with a double-sided tape with a foam inside and made two holes in it with a regular paper puncher.  As the adapter is divided into two parts – the conversion from USB to RS232 is done right at the usb port of the Raspberry Pi. The RS232 protocol is less demanding when it comes to wire quality and perfectly works on a 4m CAT cable.  If anyone is interested in the board itself, here is the pdf with its layout. The resistors are size 1206. The IR led and phototransistor needed to have their legs cut slightly. 

The Raspberry works under linux so – unfortunatelly – one must be ready for some work via a terminal. I tried the vcontrold daemon published at openv.wikispaces.com. It works great when the KW protocol is used but the protocol is slow (it takes c.a. 1.5 minutes to read 20 addresses). When I tried using P300, only reading worked. After many attempts to understand what the problem might be I gave up and installed ViTalk (also available on openv.wikispaces.com). I hope to find a way to get the vcontrold working but for now the ViTalk must suffice.

ViTalk is small and simple. It works like a charm. I ended up extending it a bit (the source is in C so it requires some mind twisting) with a function allowing direct reading of a given address (example usage: “raw 0x2323”. Generally speaking – there is no clear documentation of what can be red at what address. One must try, try and try. If anyone is interested in the amended ViTalk sources, let me know and I will prepare the source files.

I feel very unconfident in the whole linux environment so I will not post all the steps needed to get the ViTalk or vcontrold working. Please read the guide posted at http://openv.wikispaces.com/vcontrold+mit+Raspberry+Pi.

ViTalk, as I understand it, is a telnet server, which intermediates in the communication with a Viessmann boiler. One can talk to it via a console (using: telnet localhost 83) by entering commands (for example: “g power” to get the current burner status or, in my case: “raw 0x2000 2” to read 2 bytes from the address 200). One can connect to it with a script in PYTHON, which could then send the data to a SQL database, which in turn can serve as a source for graphs. One can write a PHP script, which will publish the data as a html page and will enable controlling the unit via web browser (or communication with a WAGO PLC). One should also be able to connect with it directly from the PLC using the Ethernet library.

Here are some files I use as a help and inspiration four your designs:

1. A PYTHON script (vito.py) sending data to SQL database, which has a table named ‘Vito’ with columns ‘time’, ‘comm’ and ‘value’.  You mind find it to be a bit extensive, but I wanted to limit the data stored in the base by sending only those values, which changed.  If you want the file to be executed every minute you should add 1 line to your crone (crontab –e):

*/1 * * * * /var/python/vito.py > /dev/null 2>&1

#!/usr/bin/python
import telnetlib
import MySQLdb
	
from time import  localtime, strftime, strptime, mktime
from datetime import timedelta, datetime
	
timer = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
timer2 = (datetime.now()-timedelta(seconds=1)).strftime("%Y-%m-%d %H:%M:%S")
	
commands = ["mode","power", "k_soll_temp", "k_ist_temp", "ww_soll_temp", "ww_ist_temp", "outdoor_temp"]
	
try:
	tn = telnetlib.Telnet("localhost", 83)
except:
	print "Connection ERROR"
else:
	tn.read_until(b"$", 5)
	replies=[]
	
	# try to read file with previous values
	try:
		storageFile = open('storedData.txt','r')
	except:
		previousData = False
	else:
		previousData = storageFile.read()
		previousDataA = previousData.split("\n")
		i=0
		for line in previousDataA:
			previousDataA[i] = line.split(";")
			i+=1	
		storageFile.close()
	
	# prepare file for writting
	try:
		storageFile = open('storedData.txt', 'w')
	except:
		storageFile = False
	
	# execute commands via telnet
	i=0
	print "Checking Vito data: "
	for command in commands:
		tn.write(b"g "+command+"\n")
		reply=tn.read_until(b"$", 5).strip("\n$")
		print(commands[i].rjust(20)+": "+reply+", previous: "+(previousDataA[i][0] if (previousData!=False and i<len(previousDataA)) else "missing"))
		if storageFile!=False:
			storageFile.write(reply+";")
		if previousData!=False and i<len(previousDataA) and previousDataA[i][0]!= reply:
			storageFile.write("1;\n")
		else :
			storageFile.write("0;\n")
		replies.append(reply)
		i+=1
	print ""
	
	tn.close()
	
	#check for the last timestamp and decide to send full record set
	if previousData!=False :
		try:
			lastTime = strptime(previousDataA[-1][0].strip(),"%Y-%m-%d %H:%M:%S")
		except:
			print "No previous timestamp found"
			enforceFrame=False
			storageFile.write(timer)
		else:
			lastTime = datetime.fromtimestamp(mktime(lastTime))
			timeSinceFrame = datetime.now() - lastTime
			print "Time since last frame: "+str(timeSinceFrame)+ " (H:M:S.ms)"
			if timeSinceFrame.seconds > 3600 :
				enforceFrame = True
				storageFile.write(timer)
				print "Sending full frame"
			else:
				enforceFrame = False
				storageFile.write(previousDataA[-1][0]+";")
	print ""
	
	storageFile.close()
	
	# sending data to SQL
	print "Sending data to database:"
	try:
    		db = MySQLdb.connect("SERVER","USER","PASSWORD","DATABASE" )
  	except:
    		print timer + " : Error connecting to the SQL database"
	else:
    		cursor = db.cursor()
		i=0
		for reply in replies:
			print commands[i].rjust(20)+" : ",
			if enforceFrame or previousData == False  or (previousData!=False and i<len(previousDataA) and reply!=previousDataA[i][0]):
    				try:
      					cursor.execute("INSERT INTO Vito (time, comm, value) VALUES (%s, %s, %s)", (timer, commands[i], float(reply)*10))
      					db.commit()
      					print  "Sending data ok, ",
					if previousDataA[i][1]=="0":
						cursor.execute("INSERT INTO Vito (time, comm, value) VALUES (%s, %s, %s)", (timer2, commands[i], float(previousDataA[i][0])*10))
						db.commit()
	      					print  "including pre-data"
					else :
						print "single data"
    				except:
      					db.rollback()
      					print "Error executing query"
			else:
				print "No new data.  Nothing sent"
			i+=1
    		db.close() 

2. A PHP file (GetData1.php), reading data from the SQL database and presenting them as JSON data.  The commands (values stored in column ‘comm’) are listed in the $commands variable.  The $desc holds explanations presented in the graph legend (=series names).  In the brackets and the end are the descriptions of the units.

<?php
	$commands = array("mode", "power", "raum_soll_temp", "raum_ist_temp", "k_soll_temp", "k_ist_temp", "ww_soll_temp", "ww_ist_temp", "outdoor_temp");
	$desc = array("program(0)", "moc palnika(%)", "temp. pokoju-cel(t)", "temp. pokoju(t)", "temp. kotła-cel(t)", "temp. kotła(t)", "temp. c.wody cel(t)", "temp. c. wody(t)", "temp. zewn.(t)");
	
	
	$link  = mysqli_connect("SERVER", "USER", "PASSWORD","DATABASE");
	if (!$link) {
		printf("Connect failed: %s\n", mysqli_connect_error());
		exit();
	}
	IF  (isset($_POST['DATEFROM']) and $_POST['DATEFROM']!=='') { 	
		$datefrom=$link->real_escape_string($_POST['DATEFROM']);
	}	
	ELSE {
		$phpdate=time()- (1*6 * 60 * 60);
		$datefrom = date( 'Y-m-d H:i:s', $phpdate );
	};
	IF  (isset($_POST['DATETO']) and $_POST['DATETO']!=='')	{
		$dateto=$link->real_escape_string($_POST['DATETO']);
	}	
	ELSE {
		$dateto = date( 'Y-m-d H:i:s', time() );
	};
	
	echo '{"series": [';
	
	$commandsCount = count($commands);
	
	for ($i=0; $i<$commandsCount; $i++) {	
		
		$query = "SELECT * FROM Vito WHERE `time` > '$datefrom' AND `time` < '$dateto' AND `comm` = '$commands[$i]' ORDER BY time ASC";
		
		if ($result = mysqli_query($link, $query)) {
			$rowcount = mysqli_num_rows($result);
			$counter=1;
			echo '{"name": "'.$desc[$i].'", "data":[';
					
			while($r = mysqli_fetch_assoc($result)) {
				echo '['.strtotime($r["time"])*1000 . ', '.$r["value"]/10 .']';
				
				if($counter < $rowcount){ 
					echo ", "; 
				}
				$counter=$counter+1;
			}
			echo'  ], "unit": 0}';
			
			if ($i < ($commandsCount-1)) {
				echo ',';
			}
			mysqli_free_result($result);
			
		}
	};	
	
	echo ']}';
	mysqli_close($link);
?>

3. a HTML file to be placed at a www server, which draws the graphs.  IT uses the highcharts library as well as jQuery and jQueryUI.

<!DOCTYPE HTML>
<html>
	<head>
		<html xmlns="http://www.w3c.org/1999/xhtml" xml:lang="pl" lang="pl">	
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
		<title>Wykresy Vitodens 200</title>
		
		
		<script type="text/javascript" src="/js/jquery.min.js"></script>	
		<script type="text/javascript" src="/js/jquery-ui.custom.min.js"></script>	
		<link type="text/css" href="/css/ui-lightness/jquery-ui.custom.min.css" rel="stylesheet" />	
		
		
		<script type="text/javascript" src="/HighChart/js/highcharts.js"></script>
		<script type="text/javascript" src="/HighChart/js/themes/grid.js"></script>		
		
		<script type="text/javascript">
	 		
			var chart;
			var options; 
			
			function UpdateChart() {
				$.ajax({
					url: 'http://YOUR_SERVER/GetData1.php',
					type: 'POST',
					dataType: 'json',
					data: {DATEFROM: $('#DateFrom').val(), DATETO: $('#DateTo').val()},
					success: function(data) {
						var options1 = $.extend({},data, options, {title:{text: ""}});		
						$('#container').highcharts(options1);
					}
				});	
			};
			var programs = {
				0: "OFF",
				1: "Tylko c. woda",
				2: "Woda i grzanie"
			};
			var tooltipFormatter = {
				"t": function (point) {
					return Highcharts.dateFormat('%a, %d.%b %H:%M', point.x)+'<br><b>'+ point.series.name.substring(point.series.name.length-3, point.series.name) +': '+ point.y +'°C</b>';
				},
				"%": function (point) {
					return Highcharts.dateFormat('%a, %d.%b %H:%M', point.x)+'<br><b>'+ point.series.name.substring(point.series.name.length-3, point.series.name) +': '+ point.y +'%</b>';
				},
				"0": function (point) {
					return Highcharts.dateFormat('%a, %d.%b %H:%M', point.x)+'<br><b>'+ point.series.name.substring(0,point.series.name.length-3) +': '+ programs[point.y] +'</b>';
				}
			};
			
			$(document).ready(function() {
				
				Highcharts.setOptions({
					global: {
						useUTC: false
					},
					lang: {
						months: ['Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec',  'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'],
						shortMonths: ['Sty', 'Lut', 'Mar', 'Kwi', 'Maj', 'Cze', 'Lip', 'Sie', 'Wrz', 'Paź', 'Lis', 'Gru'],
						weekdays: ['Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota']
					}
				});
				
				options = {
					
					xAxis: {
						type: 'datetime',
						dateTimeLabelFormats: {
							day: '%e of %b',
							hour: '%H:%M<br>%d %b'
						}  
					},
					yAxis: [{ // left y axis
						title: {
							text: null
						},
						labels: {
							align: 'left',
							x: 3,
							y: 16,
							formatter: function() {
								return Highcharts.numberFormat(this.value, 0);
							}
						},
						showFirstLabel: false
					}, 
					{ // right y axis
						linkedTo: 0,
						gridLineWidth: 0,
						opposite: true,
						title: {
							text: null
						},
						labels: {
							align: 'right',
							x: -3,
							y: 16,
							formatter: function() {
								return Highcharts.numberFormat(this.value, 0);
							}
						},
						showFirstLabel: false
					}],
					
					legend: {
						labelFormatter: function() {
							var name = this.name;
							name = name.substring(0, name.length-3);
							return name;
						}
					},
					
					tooltip: {
						formatter: function() {
							var name = this.series.name;
							return tooltipFormatter[name.substring(name.length-2,name.length-1)](this);
						}
					},
					plotOptions: {
						series: {
							cursor: 'pointer',
							marker: {
								enabled: false,
								lineWidth: 1
							}
						}
					},
					chart: {
						type: 'line',
						zoomType: 'x'
					}
				};
				
				
				UpdateChart();							
				
				$('#DateFrom').datepicker({
					firstDay: 1,				
					dateFormat: "yy-mm-dd",
					dayNamesMin: ['Nie', 'Pon', 'Wto', 'Sro', 'Czw', 'Pia', 'Sob'],
					monthNames: [ 'Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec',  'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'],
					onClose: function () {
						this.value+=' 00:00:00';
					}		
				});
	
				$('#DateTo').datepicker({
					firstDay: 1,
					dateFormat: "yy-mm-dd",
					dayNamesMin: ['Nie', 'Pon', 'Wto', 'Sro', 'Czw', 'Pia', 'Sob'],
					monthNames: [ 'Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec',  'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'],
					onClose: function () {
						this.value+=' 23:59:59';
					}
				});
	
				$('#SubmitButton').click(function() {
					UpdateChart();
				});
			});
				
		</script>
		
	</head>
	<body>
		<center><p style="font-family: Arial">From: <input type="text" id="DateFrom" readonly="True"> To: <input type="text" id="DateTo" readonly="True"> <input type="button" id="SubmitButton" value="Update!" /></p></center>
		<div id="container" style="width: 1200px; height: 600px; margin: 0 auto"></div>	
	</body
</html>

4. A PHP file, which can be placed at the Raspberry Pi, which asks the telnet server for current data.  It uses the telnet.class.php. The commands to execute are stored in comm.txt

<?php
$time = microtime();
$time = explode(' ', $time);
$time = $time[1]+$time[0];
$start = $time;
?>
	
<html>
    <head>
	<title>Testing vcontrol</title>
    </head>
<body>
	
<?php
echo "<p>Talking with your Vitodens:</p>";
$commands = file ("comm.txt");
require_once "Telnet.class.php";
$telnet = new Telnet('localhost', 83);
$connection=$telnet->connect();
if ($connection==1) {
	foreach($commands as $c) {
		echo $c;
		echo ": ";
		$result=$telnet->exec("g ".rtrim($c));
		echo $result;
		echo "<br>";
	};
}
else {
	echo "ERROR: Unable to connect to vitalk<br>";
}
$telnet->disconnect();
	
//generating the footer with processing time
$time = microtime();
$time = explode(' ', $time);
$time = $time[1]+$time[0];
$finish  = $time;
$total_time = round(($finish - $start),4);
echo "Page genrated in ".$total_time." seconds.";
?>
	
</body>
</html>

…quite a lot of those technical information. I hope, that the files presented above will help you save some time. Digging through all the nuances costed me a few long days…

What works so far? The graphs. I do not intend to record all the data for ever. I just want to look closer on what my boiler does to understand what depends on what. I know now, for example, that the temperature loss of the hot water from midnight until 06:40 nex day is from 58 to 52 degrees Celsius. I want to check how much faster it would cool off with the hot water circulation pomp was left on for the whole night. It is time for tweaking :)

Wykres

What still needs to be done?


1. Visualization and control over www.  The access to the data is already in place.  It all just need some graphical representation.

2. Communication with the PLC. I need to decide if I use the intermediation of a PHP file (and use the existing www query, which works very well on my PLC) or try to access the telnet server directly.

Ok, that is enough.  Please let me know if you have any comments or remarks...