Benutzer-Werkzeuge

Webseiten-Werkzeuge


ebus:linuxkonnektor

Hier im Anhang eine abgespeckte Linux-Version zum Auslesen einer Wolf Solaranlage mit BM und SM.

Diese Software versteht nur ein Teil von dem was auf dem eBus auftauchen kann: nur Broadcast Telegramme werden verstanden und nur die Typen 5017 und 5018 werden dekodiert.

Fragen? Anregungen?

Vielen Dank an Bernhard Hopf, ohne dessen Input und Hilfe (und Wiki) wäre dies nicht möglich gewesen!

WolfSolar.pl
#!/usr/bin/perl -w
 
use strict;
 
use warnings;
 
use Fcntl;
 
my $DATASTARTPOS=10;
my $TELEGRAMSTART='71fe';
 
# use udev persistent rules to create an alias
my $SERIALPORT= '/dev/Wolf';
 
 
use DBI;
 
my $db='energy_logger';
my $host='maggie.weislan.lu';
my $user='energylogger';
my $password='pA5Lxs48d4YQ8PG9';
 
 
my $dbh = DBI->connect("DBI:mysql:$db:$host",
    "$user", "$password",
    { PrintError => 0}) || die $DBI::errstr;
 
# Wolf and eBus-specific stuff
 
my %resolution=('char' => 1, 'signed char' => 1, 'signed int' => 1, 'word' => 1, 'bcd' =>1, 'data1b' => 1, 'data1c' => 2, 'data2b' => 256, 'data2c' => 16); 
my %serviceDefs=(
	5018 => 'Solarleistung=data2b | ErtragTagL=word | ErtragTagH=word | SummeErtragL=word | SummeErtragH=word | SummeErtragM=word',
	5017 => 'SolarPumpe=char | unknown1=char | KollektorTemp=data2c | WWSolarTemp=data2c',
	5023 => 'Unknown1=Analyze( 9 )',
);
 
my %SQL=(
	5018 => 'UPDATE SolarErtragCurrent Set Leistung=%Solarleistung%, Tagesertrag=%ErtragTagH% * 1000 + %ErtragTagL%, SummeErtrag=%SummeErtragM% * 1000000 + %SummeErtragH% * 1000 + %SummeErtragL%, Timst=NOW()',
	5017 => 'UPDATE eBusCurrent Set S1Pumpe=%SolarPumpe%, S1KollektorTemp=%KollektorTemp%, S1WWTemp=%WWSolarTemp%, Timst=NOW()',
);
 
my $debug=0;
 
 
 
 
use Device::SerialPort;
 
# Initialising Port
my $port = &openSerialDevice;
 
 
my @CRC_Tab8Value = (
0, 155, 173, 54, 193, 90, 108, 247, 25, 130, #0 -9 
180, 47, 216, 67, 117, 238, 50, 169, 159, 4, #10-19
243, 104, 94, 197, 43, 176, 134, 29, 234, 113, #20
71, 220, 100, 255, 201, 82, 165, 62, 8, 147,   #30
125, 230, 208, 75, 188, 39, 17, 138, 86, 205, 
251, 96, 151, 12, 58, 161, 79, 212, 226, 121, 
142, 21, 35, 184, 200, 83, 101, 254, 9, 146, 
164, 63, 209, 74, 124, 231, 16, 139, 189, 38, 
250, 97, 87, 204, 59, 160, 150, 13, 227, 120, 
78, 213, 34, 185, 143, 20, 172, 55, 1, 154, 
109, 246, 192, 91, 181, 46, 24, 131, 116, 239, #100
217, 66, 158, 5, 51, 168, 95, 196, 242, 105, 
135, 28, 42, 177, 70, 221, 235, 112, 11, 144, 
166, 61, 202, 81, 103, 252, 18, 137, 191, 36, 
211, 72, 126, 229, 57, 162, 148, 15, 248, 99, 
85, 206, 32, 187, 141, 22, 225, 122, 76, 215, 
111, 244, 194, 89, 174, 53, 3, 152, 118, 237, 
219, 64, 183, 44, 26, 129, 93, 198, 240, 107, 
156, 7, 49, 170, 68, 223, 233, 114, 133, 30, 
40, 179, 195, 88, 110, 245, 2, 153, 175, 52, 
218, 65, 119, 236, 27, 128, 182, 45, 241, 106, #200
92, 199, 48, 171, 157, 6, 232, 115, 69, 222, 
41, 178, 132, 31, 167, 60, 10, 145, 102, 253, 
203, 80, 190, 37, 19, 136, 127, 228, 210, 73, 
149, 14, 56, 163, 84, 207, 249, 98, 140, 23, 
33, 186, 77, 214, 224, 123
);
 
sub crc{
	my $hexdata=shift;
	my $data=hex($hexdata);
	my $init =shift;
	return ($CRC_Tab8Value[$init] ^ $data);
}
 
 
 
my $buffer='';
my $lastGoodRead=time;
#wait for first interesting Telegram to start
$buffer=&sync($TELEGRAMSTART);
 
print "Lets start: $buffer\n" if $debug;
# Now we have the (start of the) first interesting Telegram in the buffer
while (1){
	print "Main Loop: ". substr($buffer, 0, 20) . "\n" if $debug;
 
	# Daten lesen
	while (length $buffer <100){
		my ($l,$data) = &getCharsFromSerialPort; #$port->read(100);
		$buffer .= unpack ("H*",$data) if $l;
	}
	$buffer=~s/^(aa)*//;
	# we should probably trim other stuff here too? ACKs NACKs....
	if (length $buffer >40){
		$buffer=&decodeTelegram($buffer);
	}else{
		print "." if $debug;
	}
 
	if (time - $lastGoodRead > 60){
	      print "Need to resync? $buffer\n";
	      $port->close;
	      $port=&openSerialDevice;
	      $buffer=&sync($TELEGRAMSTART);
	      $lastGoodRead=time;
	}
 
}
 
 
sub decodeTelegram{
	my $buffer=shift;
 
 
	my %telegram; $telegram{'_splitBytes'}=0;
	my $dataSize=hex(substr($buffer, 8, 2));
 
	my $size=2*$dataSize+12;
	#no need to start if we have not enough data	
	return $buffer if (length $buffer<$size);
 
 
	$telegram{'from'}=hex(substr($buffer,0,2));
	$telegram{'to'}=hex(substr($buffer,2,2));
 
	$telegram{'service'}=substr($buffer,4,4);
 
	my $serviceDef=$serviceDefs{$telegram{'service'}};
	#copy part 'left of data'
        $telegram{'text'}=substr($buffer,0,10);
	my @fields=split(' \| ', $serviceDef);
	my $pos=$DATASTARTPOS;
	foreach my $paramDef(@fields){
		print "$pos ParamDef $ paramDef\n" if $debug;
		my ($name, $typeDef)=split('=', $paramDef);
		next if ($typeDef =~ /^Analyze/);
		&readValue($buffer, \$pos, $typeDef, \%telegram, $name);
		if ($pos < 0){ #not a successful read, need more data
			return $buffer;
		}
	}
 
 
 
	print "Service: $telegram{'service'}, Pos: $pos\n" if $debug; 
	print substr($buffer, 0, 60) ."\n";
 
	my $csPos=$size-2+2*$telegram{'_splitBytes'};;
	# need to read checksum (can be a90x too!
	&readValue($buffer, \$csPos, 'char', \%telegram, 'checkSum');
 
	if ($telegram{'_splitBytes'}){
		$size+=2*$telegram{'_splitBytes'};
		print "Splitbytes: $telegram{'_splitBytes'}\n";
	}
 
	if (&checkCRC(substr($buffer, 0, $size), $telegram{'checkSum'})){
	    $lastGoodRead=time;
	    &writeDB(\%telegram);
	}else{
	    print "CRC error: ". substr($buffer, 0, $size) . ": ". $telegram{'checkSum'} ."\n";
	}
	return substr($buffer, $size);
 
}
 
sub checkCRC{
    my $text=shift;
    my $crc=shift;
 
    #cut trailing CRC from text
    if(($crc == hex(0xaa)) or ($crc == hex(0xa9))){
	$text=substr($text, 0, -4);
    }else{
	$text=substr($text, 0, -2);
    }
    my $crcCalc=0; my $i=0;
    while ($i<length($text)){
		$crcCalc=&crc(substr($text, $i, 2), $crcCalc);
		$i+=2;
    }
 
    if ($crc == $crcCalc){
	return 1;
    }else{
	print "ERRROR: $text, $crc, $crcCalc\n";
	return 0;
    }    
}
 
sub writeDB{
	my $tgRef=shift;
	if ($SQL{$tgRef->{'service'}}){
		my $sql=$SQL{$tgRef->{'service'}};
		foreach my $k (keys %$tgRef){
			$sql =~ s/%$k%/$tgRef->{$k}/g;
		}
		my $sth= $dbh->prepare($sql);
		$sth->execute || die DBI::err.": ".$DBI::errstr;
		print time . " ".$sql. "\n";	
	}
}
 
sub readValue{
	my $buffer=shift;
	my $posref=shift;
	my $pos = $$posref;
	my $type=shift;
	my $tgRef=shift;
	my $name=shift;
 
	my $size=2;
	if ($type =~ /data2|word/){
		$size=4;
	}
	my $value='';
	my $hex='';
	my $byte=0;
	my $read=0;
	while ((not $read) and ($pos+2 < length($buffer)) and ($size > 0)){
		$byte = substr($buffer, $pos,2);
		$tgRef->{'text'} .= $byte;
		$pos+=2;
		#do we have a split 'aa' value?
		if ($byte eq 'a9'){
			if ($pos+2 < length($buffer)){
				my $byte2 = substr($buffer, $pos,2);
				$tgRef->{'text'} .= $byte2;
				$tgRef->{'_splitBytes'}++;
				if ($byte2 eq '01'){$byte='aa';}
				$pos+=2; $size-=2;
			}
		}else{
			$size-=2;
		}
		# must swap BYTES around!!!!
		$value=$byte.$value;
 
		if ($size==0){
			$read=1;
		}
	}
 
	if ($read){
		$$posref=$pos;
		print "Val: $value Dec: ". hex($value) . " Divi: " . (hex($value)/$resolution{$type}) ."\n" if $debug;
		$tgRef->{$name}=(hex($value)/$resolution{$type});
		return;
	}else{
		#we could not read the entire data section yet
		$$posref=-1; return 0;
	}
 
 
}
 
 
sub sync{
	my $lookFor=shift;
	my $buffer='';
	my $Sync=0;
 
	my $debug=0;
	while (not $Sync ){
		my ($l,$data) = &getCharsFromSerialPort ; #$port->read(100);
		$buffer .= unpack ("H*",$data) if $l;
		if ($buffer =~/($lookFor.*)/){
			$buffer=$1;
			print "Initial sync OK: $buffer\n" if $debug;
			$Sync=1;
		}else {
			print "Initial sync..... $buffer\n" if $debug;
		}
 
	}
	return $buffer;
}
 
 
sub getCharsFromSerialPort{
	my $timeout=5;
	my $l; my $data;
	eval {
		local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required
		alarm $timeout;
		($l,$data) = $port->read(100);
		alarm 0;
	};
	if ($@) {
		die unless $@ eq "alarm\n";   # propagate unexpected errors
		# timed out
		print "Nothing read within $timeout seconds....\n";
		return (0, '');	
	}
	else {
		return ($l, $data);
	}
 
 
}
sub openSerialDevice{
my $port = Device::SerialPort->new ($SERIALPORT)
    || die "Can't open device: $!\n";
 
$port->baudrate(2400);
$port->parity("none");
$port->databits(8);
$port->stopbits(1);
$port->error_msg(1);  # prints hardware messages like "Framing Error"
$port->user_msg(1);   # prints function messages like "Waiting for CTS"
$port->handshake("none");
$port->buffers(512, 512);
$port->write_settings;
 
return $port;
}

– Main.FrankWeis - 2011-05-27

It took me some time, but I have put together and cleaned up the software that I had written last year to decode eBus messages from a Weishaupt WTC25 heating system.<br />This is in C and targetted for Linux, but contains many debugging routines and is quite adaptable to other brands. Also, it contains a few reverse-engineered values for Weishaupt that took me some time to find out, so this may eventually help building a spec for this specific model.

This source code is designed for Linux, and performs a live capture from a serial device (/dev/ttyS) on which is connected an eBus to serial converter. I used gcc to compile it. Run it with -h to get a list of supported options. A dump to a raw octet stream is supported, but no read from raw dump is implemented.

Linuxprogramm zum Sammenl von Daten für Weishaupt WTC25 von Main.LiAins:

serial_dump_v0.28.c
/* Serial dump v0.26 */
// Compile with gcc using the following command line:
//gcc serial_dump.c -o serial_dump
 
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>	// For open() and related constants
#include <termios.h>
#include <stdio.h>
#include <string.h>	// For bzero()
#include <sys/time.h>	// For gettimeofday()
#include <time.h>	// For strftime()
#include <stdlib.h>	// For exit()
#include <unistd.h>	// For read(), write()
 
static char progname[]="serial_dump";
 
// This is for ttyS0, 9600 8N1
#define BAUDRATE B2400	//B9600
#define DFLT_SERIALDEVICE "/dev/ttyS0"
#define DFLT_DUMPFILE "/tmp/ebus_capture_dump.bin"
#define _POSIX_SOURCE 1 /* POSIX compliant source */
#define SERBUFSZ 1024
#define EBUSMSGBUFSZ 512
 
/* For my Weisshaupt WTC25, these are the addresses (for bytes QQ et ZZ) for the heater and the user remote control/regulation */
#define ADDR_BURNER_CTRL  0x03
#define ADDR_HEATING_CTRL 0x10
#define ADDR_BM           0x30
#define ADDR_CONTRL       0x50
#define ADDR_MM           0x51
#define ADDR_SM2          0x71
#define ADDR_BURNER       0xf1
#define ADDR_ALL          0xfe
 
#define EBUS_SYN          0xaa
#define EBUS_ESC          0xa9
#define EBUS_ESCAPED_SYN  0x01
#define EBUS_ESCAPED_ESC  0x00
 
/* e-bus decoding flags (bitfield) defines */
#define F_DISP_BAD_CRC (1)
#define F_DISP_HEXUMP (1<<1)
#define F_DISP_REPEATED_MSG (1<<2)
 
#define max(a,b) \
	({ typeof (a) _a = (a); \
	   typeof (b) _b = (b); \
	   _a > _b ? _a : _b; })
#define min(a,b) \
	({ typeof (a) _a = (a); \
	   typeof (b) _b = (b); \
	   _a < _b ? _a : _b; })
/* The macros above are compatible with gcc and allows a and b to be evaluated only once (avoid a++ type issues) */
 
#define COLOURED_OUTPUT
#ifdef COLOURED_OUTPUT
	char TTY_GREEN[] = { 0x1b, '[', '1', ';', '3', '2', 'm', '\0' };
	char TTY_RED[] = { 0x1b, '[', '1', ';', '3', '1', 'm', '\0' };
	char TTY_NORMAL[] = { 0x1b, '[', '0', 'm', '\0' };
	#define DUMPSTART_STYLE       TTY_GREEN
	#define DUMPSTARTERROR_STYLE  TTY_RED
	#define DUMPEND_STYLE         TTY_NORMAL
	#define TIMESTAMPSTART_STYLE  TTY_RED
	#define TIMESTAMPEND_STYLE    TTY_NORMAL
#else
	#define DUMPSTART_STYLE ""
	#define DUMPEND_STYLE ""
	#define DUMPSTARTERROR_STYLE ""
	#define TIMESTAMPSTART_STYLE ""
	#define TIMESTAMPEND_STYLE ""
#endif
 
char *dayofweek[] = { "", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };
 
 
/**
 * @name bcd_to_u8
 * Converts a BCD byte to an unsigned 8-bit byte
 * @param bcd_in the BCD-encoded byte to convert
 * @return the 8-bit encoded value or 0xff if the conversion failed
**/
inline unsigned char bcd_to_u8(unsigned char bcd_in) {
	if ((bcd_in & 0x0f) > 0x0a ||
	    ((bcd_in >> 4) & 0x0f) > 0x0a) {
		fprintf(stderr, "%s: Error: Invalid BCD value %02x\n", __func__, bcd_in);
		return 0xff;
	}
	return ( (((bcd_in >> 4) & 0x0f) * 10) +
	        (bcd_in & 0x0f) );
}
 
#define byte1c_repl (0xff)	// Replacement value for byte1c (means invalid value)
/**
 * @name data1c_to_int
 * Convert an 8-bit byte1c-encoded value into a signed int.
 * Details for the data1c format can be found inside the eBus specification Application Layer OSI 7 v1.6.1 (chap 2.4, page 7)
 * @param byte is the byte encoded in data1c format
 * @param result_int is the signed result (must be a non NULL pointer)
 * @return 1 or -1 if the value was successfully decoded and is respectively positive or negative, or 0 if the value is invalid (replacement value)
**/
inline int data1c_to_int(unsigned char byte, signed int *result_int) {
 
	if (!result_int) {
		fprintf(stderr, "%s: Error: NULL result_int pointer provided. Aborting\n", __func__);
		return 0;	/* Error: pointer must be non-NULL */
	}
 
	if (byte>=0 && byte<=127) {	/* Positive value */
		*result_int = byte;
		if (byte==0) {
			return 0;	/* Value is null */
		}
		else {
			return 1;	/* Value is positive */
		}
	}
	/* In all other cases, value is negative */
	*result_int = -(0xff - byte + 1);
	return -1;
}
 
#define u16_get_msb(x) ((x & 0xff00)>>8)
#define u16_get_lsb(x) (x & 0xff)
#define byte2b_repl (0x8000)	// Replacement value for byte2b (means invalid value)
/**
 * @name data2b_to_int
 * Convert a 16-bit byte2b-encoded value into two ints (one for the integral part, one for the decimal part (in 1/100) units.
 * Details for the data2b format can be found inside the eBus specification Application Layer OSI 7 v1.6.1 (chap 2.4, page 7)
 * @param msb is the most significant byte (usually first byte received on the serial line)
 * @param lsb is the least significant byte (usually seconde byte received on the serial line)
 * @param result_int is the signed integral part of the result (must be a non NULL pointer)
 * @param result_dec_centi is the 1/100 decimal part (optional)
 * @param result_dec_milli is the 1/1000 decimal part (optional and more precise than result_dec_milli)
 * @return 1 or -1 if the value was successfully decoded and is respectively positive or negative, or 0 if the value is invalid (replacement value)
**/
inline int data2b_to_int(unsigned char msb, unsigned char lsb, signed int *result_int, unsigned char *result_dec_centi, unsigned int *result_dec_milli) {
 
	unsigned int tmp_milli;	/* Decimal part with full 1/1000 resolution */
	if (!result_int) {
		fprintf(stderr, "%s: Error: NULL result_int pointer provided. Aborting\n", __func__);
		return 0;	/* Error: pointer must be non-NULL */
	}
 
	if (msb == (u16_get_msb(byte2b_repl)) &&
	    lsb == (u16_get_lsb(byte2b_repl)) ) {	/* We got byte2_repl */
		*result_int = 0;
		*result_dec_centi = 0;
		return 0;
	}
 
	if (msb & 0x80) {	/* Negative value */
		unsigned char neg_lsb;	/* Temporary byte to work on LSB */
		neg_lsb = ((unsigned char)0xff ^ lsb);
		*result_int = (signed int)((unsigned char)0xff ^ msb);
		if (neg_lsb == 0xff) {
			(*result_int)++;	/* Propagate carry if deci is going to oeverflow */
		}
		*result_int = -(*result_int);	/* Negative integer part */
		neg_lsb++;
		tmp_milli = ((unsigned int)(neg_lsb)*1000) >> 8;	/* >>8 for /256 */
	}
	else {
		*result_int = (signed int)(msb);
		tmp_milli = ((unsigned int)(lsb)*1000) >> 8;	/* >>8 for /256 */
	}
	if (result_dec_milli)	/* Do we want the 1/1000 resolution decimal part? */
		(*result_dec_milli)=tmp_milli;
	if (result_dec_centi) {	/* Do we want the 1/100 resolution decimal part? */
		(*result_dec_centi)=tmp_milli/10;	/* Truncate */
		if (tmp_milli%10 >=5) {	/* Round to higher value (+0.01) if 1/1000 part is higher than 0.005 */
			(*result_dec_centi)++;
		}
	}
	if (msb & 0x80)
		return -1;
	else
		return 1;
}
 
/**
 * @name open_serial
 * Configure a serial port, keeping track of the previous serial settings, and return a file descriptor to this serial port
 * @param device the name of the serial device to use (for example /dev/ttyS0)
 * @param filedesc pointer to a file descriptor that will be set to the serial port that has been opened
 * @param oldtio pointer to a termios struct that will be filled in with the previous settings of the serial port
 * @return 0 if succeeded, -1 otherwise (filedesc and oldtio are then invalid)
**/
int open_serial(char *device, int *filedesc, struct termios *oldtio) {
 
	struct termios newtio;
 
 
	if (!filedesc) return -1;
 
	/* Open the serial port */
	*filedesc = open(device, O_RDWR | O_NOCTTY | O_NDELAY);
	fcntl(*filedesc, F_SETFL, 0);
 
	if (oldtio) {
		/* Get the current settings for the serial port */
		tcgetattr(*filedesc, oldtio);
	}
 
	bzero(&newtio, sizeof(newtio));
 
	/* Set raw input, 1 char minimum before read returns */
	newtio.c_cflag = BAUDRATE | CS8 | CLOCAL | CREAD;
	newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
	newtio.c_iflag = IGNPAR;
	newtio.c_oflag &= ~OPOST;
	newtio.c_cc[VMIN]  = 1;
	newtio.c_cc[VTIME] = 0;
 
	tcflush(*filedesc, TCIFLUSH);
	/* Reset the serial port options to what we have defined above */
	tcsetattr(*filedesc, TCSANOW, &newtio);
	return 0;
}
 
 
/**
 * @name get_delta_time
 * Calculate the delta of time between two time references
 * @param ref_stop latest of the two timestamps to compare
 * @param ref_start earliest of the two timestamps to compare
 * @param res_delta_sec pointer to store the result (in seconds) of the time difference
 * @param res_delta_usec pointer to store the µs part of the result of the time difference, if the pointer is non NULL
 * @return 1 if ref_stop is earlier than ref_start (result pointers are then unchanged), 0 if success
**/
int get_delta_time(const struct timeval *ref_stop, const struct timeval *ref_start, time_t *res_delta_sec, suseconds_t *res_delta_usec) {
 
 
	if (!ref_stop || !ref_start) {
		fprintf(stderr, "%s: Error: NULL timeval struct provided as argument\n", __func__);
		exit(1);
	}
	if (!res_delta_sec) {
		fprintf(stderr, "%s: Warning: NULL res_delta_sec pointer\n", __func__);
	}
	/* Note: we allow a NULL res_delta_usec... this simply means that the caller does not care about a µsec precision */
	if (ref_stop->tv_sec < ref_start->tv_sec) {
		fprintf(stderr, "%s: Error: time is going backwards ds<0!\n", __func__);
		return 1;
	}
	else {
		*res_delta_sec = ref_stop->tv_sec - ref_start->tv_sec;
		if (ref_stop->tv_usec < ref_start->tv_usec) {	/* µsec overflow, convert back one sec in µsec */
			if (*res_delta_sec>0) {
				(*res_delta_sec)--;
				if (res_delta_usec)	/* If the caller cares about usecs */
					*res_delta_usec = ref_stop->tv_usec + 1000000 - ref_start->tv_usec;	/* Add the second removed in the line above, and converts it as 10^6µs */
			}
			else {
				fprintf(stderr, "%s: Error: time is going backwards (ds=0, dus<0)!\n", __func__);
				return 1;
			}
		}
		else {
			if (res_delta_usec)	/* If the caller cares about µsecs */
				*res_delta_usec = ref_stop->tv_usec - ref_start->tv_usec;
		}
	}
	return 0;
}
 
 
/**
 * @name crc8_ebus_calc_step
 * Perform one CRC calculation step (on one input  data byte)
 * @param data an input data byte to use for CRC calculation
 * @param crc_init init value for the CRC calculation
 * @return a byte containing the 8-bit CRC for the input
**/
unsigned char crc8_ebus_calc_step(unsigned char data, unsigned char crc_init) {
 
	unsigned char crc;
	unsigned char polynom;
	int i;
 
 
	crc = crc_init;
	for (i = 0; i < 8; i++) {
		if (crc & 0x80)
			polynom = (unsigned char) 0x9B;
		else
			polynom = (unsigned char) 0;
 
		crc = (unsigned char)((crc & ~0x80) << 1);
		if (data & 0x80) crc = (unsigned char)(crc | 1);
 
		crc = (unsigned char)(crc ^ polynom);
		data = (unsigned char)(data << 1);
	}
	return crc;
}
 
 
/**
 * @name crc8_ebus
 * Compute a CRC8 value from a data buffer, using the polynomial x8 + x7 + x4 + x3 + x + 1 as specified in eBus specification OSI I/II spec v1.3.1 (chap 7.2, page 15)
 * Note: this CRC, when included in a data message, might be escaped if the result is 0xaa or 0xa9 since these values are not allowed on the serial comm (see eBus spec mentionned above, (chap 5.1 & 5.7, page 8))
 * @param buffer an input byte stream to use for CRC calculation
 * @param size the size of the input stream
 * @return a byte containing the 8-bit CRC calculated from the input stream
**/
unsigned char crc8_ebus(const unsigned char *buffer, unsigned int size) {
 
	unsigned char result = 0;
 
 
	while (size != 0) {
		result = crc8_ebus_calc_step(*buffer, result);
		size--;
		buffer++;
	}
	return result;
}
 
 
/**
 * @name search_first_non_syn
 * Searches for the first non-SYN byte (as per the eBus specification) in a buffer
 * @param bufptr a byte buffer to use as input
 * @param buf_sz the maximum size of the buffer to use as input
 * @return the number of successive SYN characters found at the very beginning of the buffer, or 0 otherwise
**/
int search_first_non_syn(const unsigned char *bufptr, int buf_sz) {
 
	int syn_stripped = 0;
 
 
	while (syn_stripped < buf_sz && bufptr[syn_stripped]==EBUS_SYN) {
		syn_stripped++;	/* Strip one more 0xaa char at the beginning of the buffer */
		//fprintf(stderr, "-1 0xaa<< ");
	}
	return syn_stripped;
}
 
 
/**
 * @name search_first_syn
 * Searches for the first SYN byte (as per the eBus specification) in a buffer
 * @param bufptr a byte buffer to use as input
 * @param buf_sz the maximum size of the buffer to use as input
 * @return the offset of the first SYN bytes (within the buffer) or -1 if no SYN-byte was found. 0 means that the first character in the buffer was a SYN byte
**/
int search_first_syn(const unsigned char *bufptr, int buf_sz) {
 
	int syn_offs = 0;
 
 
	while (syn_offs < buf_sz) {
		if (bufptr[syn_offs]==EBUS_SYN) {
			//~ fprintf(stderr, "-1 0xaa>> (%d/%d) ", syn_offs, buf_sz);
			return syn_offs;
		}
		else {
			syn_offs++;	/* Strip one more 0xaa char at the beginning of the buffer */
		}
	}
	return -1;
}
 
 
/**
 * @name unescape_ebus
 * Unescape an eBus byte stream (escape sequences using 0xaa and 0xa9 are detailed inside the eBus specification OSI I/II spec v1.3.1 (chap 5.1 & 5.7, page 8))
 * @param ebus_msg a pointer to the escaped eBus stream (this buffer will be overwritten with the unescaped stream)
 * @param ebus_msg_sz the size of the input buffer to use as input
 * @return the size of the unescaped byte stream (the content being in the byte buffer pointed by ebus_msg)
**/
int unescape_ebus(unsigned char *ebus_msg, int ebus_msg_sz) {
 
	int pos = 0;
	int unescaped_sz = 0;
	/* Note: unescaped_sz should ALWAYS be less or equal to pos... otherwise we will overwrite ourselves in a loop (even if it will not be inifite because of the max length test) */
	/* pos is where we read in */
	/* unescaped_sz is where we write out the unescaped sequence */
	/* unescaped_sz is equal to pos if no escape sequence is found */
 
 
	while (pos < ebus_msg_sz) {
		if (ebus_msg[pos] != EBUS_ESC) {
//			if (unescaped_sz<pos) {	/* Move ahead bytes if escaping has already been processed */
				ebus_msg[unescaped_sz] = ebus_msg[pos];
//			} // I just comment this out as a test is probably more CPU consumming than a byte copy!
			pos++;
			unescaped_sz++;
		}
		else { /* Escaped character */
			if (pos + 1 < ebus_msg_sz) {
				if (ebus_msg[pos+1] == EBUS_ESCAPED_SYN) { /* We have an escaped SYN */
					pos+=2;
					ebus_msg[unescaped_sz++] = EBUS_SYN;	/* Restore the SYN in the buffer */
				}
				else if (ebus_msg[pos+1] == EBUS_ESCAPED_ESC) { /* We have an escaped ESC */
					pos+=2;
					ebus_msg[unescaped_sz++] = EBUS_ESC;
				}
				else {
					fprintf(stderr, "%s: Error: unknown escape sequence %02x %02x will not be modified\n", __func__, ebus_msg[pos], ebus_msg[pos+1]);
					pos++;
					unescaped_sz++;
				}
			}
			else {
				fprintf(stderr, "%s: Error: escaped character was the last character of a packet\n", __func__);
				return unescaped_sz;	/* Do not count this last escape character... but packet will probably be damaged anyway */
			}
		}
	}
	return unescaped_sz;
}
 
 
/**
 * Dump a buffer to a stream
 * The buffer is pointed by buf
 * The size of the buffer is provided via buf_sz
 * dumptype allows to align the output:
 * - 16 will display 16 hex values per line
 * - 8 will display 8 ehx values per line
 * - -1 will raw dump the bytes themselves
 * - 0 will dump all hex values one one unique line, without any carriage return
 * - 1 will dump 1 character per line
**/
void hexdump(FILE *stream, const unsigned char *buf, const int buf_sz, int dumptype) {
 
	int c;
 
	if (buf_sz == 0)
		return;
	if (buf_sz < 0) {
		fprintf(stderr, "\n%s: Error: wrong buffer size %d\n", __func__, buf_sz);
		return;
	}
	for (c=0; c<buf_sz;) {
		if (dumptype != -1)
			fprintf(stream, "%02x", buf[c]);	/* Dump a 2-digit hexdump for each byte */
		else
			fputc(buf[c], stream);	/* Raw dump binary */
		c++;
		if ((dumptype == 1) ||
		    (dumptype == 16 && ((c & 16)==0)) ||
		    (dumptype == 8 && ((c & 8)==0))) {
			fputc('\n', stream);
			continue;	// If carriage return, do not dump a space, just loop once more
		}
		if (dumptype != -1 && c!=buf_sz) {
			fputc(' ', stream);
		}
	}
}
 
 
typedef struct {
	struct timeval payload_ts;
	unsigned char  payload[8];	/* Command 50.14 is usually 8 bytes long */
	unsigned int   payload_sz;	/* Length of the payload */
} last_cmd_50_14_t;
 
typedef struct {
	struct timeval payload_ts;
	unsigned char  payload[9];	/* Command 05.07 is usually 9 bytes long */
	unsigned int   payload_sz;	/* Length of the payload */
} last_cmd_05_07_t;
 
typedef struct {
	struct timeval payload_ts;
	unsigned char  payload[13];	/* Command 50.0a is usually 13 bytes long */
	unsigned int   payload_sz;	/* Length of the payload */
} last_cmd_50_0a_t;
 
typedef struct {
	unsigned char h;	/* Hour (0-24) */
	unsigned char m;	/* Minute (0-59) */
	unsigned char s;	/* Second (0-59) */
	unsigned char dow;	/* Day of week (1-7) (see dayofweek(] table above) */
	unsigned char dom;	/* Day of month (0-31) */
	unsigned char moy;	/* Month of year (0-12) */
	unsigned char yy;	/* 2 last digits of year (11 = 2011) */
	struct timeval ts;	/* Timestamp for when we received the information above (in system time, not in boiler time) */
} last_cmd_07_00_date_t;	/* Stores the date & time as provided by command 07.00 */
 
/**
 * @name decode_ebus_msg
 * Decodes a buffer containing the payload of an ebus packet (starting from first data byte, excluding src, dst, cmd, len)
 * @param src_addr ebus source address of this packet
 * @param dst_addr ebus destination address of this packet
 * @param cmd ebus command value
 * @param subcmd ebus subcommand value
 * @param msg_len length of the ebus data as specified in the packet (thus excluding CRC)
 * @param crc_correct boolean indicating if the calculated CRC of the bytes in the packet corresponds to the CRC provided at the end of the packet
 * @param calculated_msg_crc CRC calculated from the bytes in the ebus packet
 * @param ebus_msg_payload pointer to a byte buffer containing the ebus packet data
 * @param ebus_msg_payload_sz size of the ebus packet data as read from the stream (may be different from msg_len). This is the size of the buffer pointed by ebus_msg_payload
 * @param flags is a bitfield containing flags for how to behave when decoding messages (see "e-bus decoding flags" above)
 * @param ts is a struct timeval containing the timestamp at which this message was received
**/
void decode_ebus_msg(unsigned char  src_addr,
                     unsigned char  dst_addr,
                     unsigned char  cmd,
                     unsigned char  subcmd,
                     unsigned char  msg_len,	/* This is the message length as extracted from the ebus packet */
                     unsigned char  crc_correct,	/* Does the calculated_msg_crc below match with the message CRC? */
                     unsigned char  calculated_msg_crc,	/* Calculated value for CRC */
                     unsigned char  *ebus_msg_payload,	/* This is the content of the ebus packet (having excluded addresses, command ids and length */
                     int            ebus_msg_payload_sz,
                     unsigned int   flags,
                     struct timeval ts) {	/* This is the size of the ebus packet we have captured), it may be bigger than msg_len if there are trailing garbage bytes */
 
 
	char *src_str, *dst_str;
	char *cmd_str;
	int disable_hexdump = (flags & F_DISP_HEXUMP)?0:1;
	static last_cmd_50_14_t last_cmd_50_14 = { .payload_sz=0 };	/* Force an empty payload at initialisation... warning, this is a static variable, it is kept intact during successive calls to decode_ebus_msg() */
	static last_cmd_05_07_t last_cmd_05_07 = { .payload_sz=0 };	/* Force an empty payload at initialisation... warning, this is a static variable, it is kept intact during successive calls to decode_ebus_msg() */
	static last_cmd_50_0a_t last_cmd_50_0a = { .payload_sz=0 };	/* Force an empty payload at initialisation... warning, this is a static variable, it is kept intact during successive calls to decode_ebus_msg() */
	static last_cmd_07_00_date_t last_cmd_07_00_date = { .h=0, .m=0, .s=0, .dow=0, .dom=0, .moy=0, .yy=0 };	/* Invalid value at initialisation, this can be tested with .dom that should never be 0 */
	static unsigned char last_cmd_05_07_byte0 = 0x00;	/* Track change of byte at offset 0 in command 05.07 */
	static unsigned char last_cmd_05_07_byte1 = 0x00;	/* Track change of byte at offset 1 in command 05.07 */
	static unsigned char last_cmd_05_07_byte7 = 0x00;	/* Track change of byte at offset 7 in command 05.07 */
 
/* Note: some areas are not decoded due to the fact the associated counter/gauge is not provisionned for the bioler installation/model I have
   However, the default value (known as "replacement value" in eBus jargon), can give us a hint on how this variable is encoded.
   Replacement values (as extracted from  eBus specification Application Layer OSI 7 v1.6.1 (chap 2.4.1 and 2.4.2, page 7):
	0xff can be a char, a byte, a BCD or data1c
	0x80 can be a signed char or data1b (or it can be the MSB of a 2-byte variable as indicated below)
	0x8000 can be a signed int, a data2b or data2c 
*/
 
/**
 * This section identifies the source address for the ebus packet
**/
	src_str = "";
	switch (src_addr) {
		case ADDR_BURNER_CTRL:
			src_str = "(ADDR_BURNER_CTRL)";
			break;
		case ADDR_HEATING_CTRL:
			src_str = "(ADDR_HEATING_CTRL)";
			break;
		case ADDR_BM:
			src_str = "(ADDR_BM)";
			break;
		case ADDR_CONTRL:
			src_str = "(ADDR_CONTRL)";
			break;
		case ADDR_MM:
			src_str = "(ADDR_MM)";
			break;
		case ADDR_SM2:
			src_str = "(ADDR_SM2)";
			break;
		case ADDR_BURNER:
			src_str = "(ADDR_BURNER)";
			break;
		case ADDR_ALL:
			src_str = "(ADDR_ALL)";
			break;
	}
/**
 * This section identifies the destination address for the ebus packet
**/
	dst_str = "";
	switch (dst_addr) {
		case ADDR_BURNER_CTRL:
			dst_str = "(ADDR_BURNER_CTRL)";
			break;
		case ADDR_HEATING_CTRL:
			dst_str = "(ADDR_HEATING_CTRL)";
			break;
		case ADDR_BM:
			dst_str = "(ADDR_BM)";
			break;
		case ADDR_CONTRL:
			dst_str = "(ADDR_CONTRL)";
			break;
		case ADDR_MM:
			dst_str = "(ADDR_MM)";
			break;
		case ADDR_SM2:
			dst_str = "(ADDR_SM2)";
			break;
		case ADDR_BURNER:
			dst_str = "(ADDR_BURNER)";
			break;
		case ADDR_ALL:
			dst_str = "(ADDR_ALL)";
			break;
	}
 
/**
 * This section identifies the command and subcommand for the ebus packet
**/
	cmd_str = "(?)";
	/* cmd 0x07 is system information */
	if (cmd == 0x07 && subcmd == 0x00 && msg_len == 0) {
		cmd_str = "(RESET)";
	}
	if (cmd == 0x07 && subcmd == 0xfe && msg_len == 0) {
		cmd_str = "(INIT)";
	}
	if (cmd == 0x07 && subcmd == 0x04 && msg_len == 0) {
		cmd_str = "(HEARTBEAT)";
		disable_hexdump = 1;	/* Do not hexump command 07.04, it is empty */
		return;	/* Do not dump the heartbeat packet for now */
	}
	if (cmd == 0x07 && subcmd == 0x00) {
		cmd_str = "(DATE)";
	}
	if (cmd == 0x50 && subcmd == 0x0a) {
		cmd_str = "(TEMP)";
		if (crc_correct && !(flags & F_DISP_REPEATED_MSG)) {
			//~ Todo: put all the code below in a generic function check_repeated_msg() because it is repeated 3 times...s
			if (last_cmd_50_0a.payload_sz != 0 && msg_len==last_cmd_50_0a.payload_sz) {	/* Struct last_cmd_50_0a is valid, and last 50.0a command has the same size at this newly received command */
				if (memcmp(last_cmd_50_0a.payload, ebus_msg_payload, msg_len) == 0) {	/* Compare both payloads */
					time_t delta_50_0a_tv_sec;    /* Delta between two 50.0a commands in secs */
					suseconds_t delta_50_0a_tv_usec;    /* Delta between two 50.0a commands (additionnal µs precision) */
 
					int result = get_delta_time(&ts, &(last_cmd_50_0a.payload_ts), &delta_50_0a_tv_sec, &delta_50_0a_tv_usec);
					last_cmd_50_0a.payload_ts = ts;
 
					if (result == 0) {
// Lionel: Commented out: we will never display two consecutive identical 50.0a packets, whatever the time delta
//						if (delta_05_0a_tv_sec < 1) {	/* Less than 1s between two identical messages... skip displaying */
							//~ fprintf(stderr, "Last 50.0a cmd repeated after %d.%03d s(skipped)\n", (int)delta_50_0a_tv_sec, (int)(delta_50_0a_tv_usec/1000));
							return;	/* Return immediately... do not print this new packet... it does not bring any new information */
//						}
					}
					else {
						fprintf(stderr, "%s: Warning: Last 50.0a cmd repeated but there was an error in delta calculation\n", __func__);
					}
				}
			}
			last_cmd_50_0a.payload_ts = ts;	/* Copy timestamp */
		}
		if (msg_len<=sizeof(last_cmd_50_0a.payload)) {	/* Message fits in buffer */
			memcpy(last_cmd_50_0a.payload, ebus_msg_payload, msg_len) ;	/* Copy the last message if it is different from the last one... */
			last_cmd_50_0a.payload_sz = msg_len;
		}
		else {
			fprintf(stderr, "%s: Error: Message 50.0a is %d bytes long and won't fit in the buffer\n", __func__, msg_len);
			last_cmd_50_0a.payload_sz = 0;
		}
	}
	if (cmd == 0x09 && subcmd == 0x03) {
		cmd_str = "(FWVERSION?)";
		if (!crc_correct)
			return;	/* Do not display packets with incorrect CRC for this command... we don't know what it means anyway */
		/* In case we find an unusual payload for this command, issue a large message to notify this on the screen */
		/* This serie of two packets seems to be sent 6 times every minute (I actually measured 16.299s, 12.740s, 15.182s, 12.775s, 15.208s on between successive broadcasts of these 2 packets) */
		if (msg_len != 4 ||
		    (ebus_msg_payload[0] != 0x76 && ebus_msg_payload[0] != 0x78) ||
		    ebus_msg_payload[1] != 0x00 ||
		    ebus_msg_payload[2] != 0x18 ||
		    ebus_msg_payload[3] != 0xfc) {
			/* We usually only have two consecutive packets for command 09.03: */
			/* 76 00 18 fc, then 78 00 18 fc (2.5s later), same value seen on different days */
			/* Issue a warning the day we will see something else! */
			fflush(stdout);
			fprintf(stderr, "[%s!!!NEVER SEEN THIS 09.03 PACKET BEFORE!!!%s]",  TTY_RED, TTY_NORMAL);
		}
		else {
			return;	/* If we have the usual 2 packets, do not even print them as we have no clue about their use */
		}
	}
	if (cmd == 0x05 && subcmd == 0x07) {
		cmd_str = "(BURNERCMD)";
		if (crc_correct && !(flags & F_DISP_REPEATED_MSG)) {
			if (last_cmd_05_07.payload_sz != 0 && msg_len==last_cmd_05_07.payload_sz) {	/* Struct last_cmd_05_07 is valid, and last 05.07 command has the same size at this newly received command */
				if (memcmp(last_cmd_05_07.payload, ebus_msg_payload, msg_len) == 0) {	/* Compare both payloads */
					time_t delta_05_07_tv_sec;    /* Delta between two 05.07 commands in secs */
					suseconds_t delta_05_07_tv_usec;    /* Delta between two 05.07 commands (additionnal µs precision) */
 
					int result = get_delta_time(&ts, &(last_cmd_05_07.payload_ts), &delta_05_07_tv_sec, &delta_05_07_tv_usec);
					last_cmd_05_07.payload_ts = ts;
 
					if (result == 0) {
// Lionel: Commented out: we will never display two consecutive identical 05.07 packets, whatever the time delta
//						if (delta_05_07_tv_sec < 1) {	/* Less than 1s between two identical messages... skip displaying */
							//~ fprintf(stderr, "Last 05.07 cmd repeated after %d.%03d s(skipped)\n", (int)delta_05_07_tv_sec, (int)(delta_05_07_tv_usec/1000));
							return;	/* Return immediately... do not print this new packet... it does not bring any new information */
//						}
					}
					else {
						fprintf(stderr, "%s: Warning: Last 05.07 cmd repeated but there was an error in delta calculation\n", __func__);
					}
				}
			}
			last_cmd_05_07.payload_ts = ts;	/* Copy timestamp */
		}
		if (msg_len<=sizeof(last_cmd_05_07.payload)) {	/* Message fits in buffer */
			memcpy(last_cmd_05_07.payload, ebus_msg_payload, msg_len) ;	/* Copy the last message if it is different from the last one... */
			last_cmd_05_07.payload_sz = msg_len;
		}
		else {
			fprintf(stderr, "%s: Error: Message 05.07 is %d bytes long and won't fit in the buffer\n", __func__, msg_len);
			last_cmd_05_07.payload_sz = 0;
		}
	}
	if (cmd == 0x50 && subcmd == 0x14) {
		cmd_str = "(BOILERSTAT)";
		if (crc_correct && !(flags & F_DISP_REPEATED_MSG)) {
			if (last_cmd_50_14.payload_sz != 0 && msg_len==last_cmd_50_14.payload_sz) {	/* Struct last_cmd_50_14 is valid, and last 50.14 command has the same size at this newly received command */
				if (memcmp(last_cmd_50_14.payload, ebus_msg_payload, msg_len) == 0) {	/* Compare both payloads */
					time_t delta_50_14_tv_sec;    /* Delta between two 50.14 commands in secs */
					suseconds_t delta_50_14_tv_usec;    /* Delta between two 50.14 commands (additionnal µs precision) */
 
					int result = get_delta_time(&ts, &(last_cmd_50_14.payload_ts), &delta_50_14_tv_sec, &delta_50_14_tv_usec);
					last_cmd_50_14.payload_ts = ts;
 
					if (result == 0) {
// Lionel: Commented out: we will never display two consecutive identical 05.07 packets, whatever the time delta
//						if (delta_50_14_tv_sec < 1) {	/* Less than 1s between two identical messages... skip displaying */
							//~ fprintf(stderr, "Last 50.14 cmd repeated after %d.%03d s(skipped)\n", (int)delta_50_14_tv_sec, (int)(delta_50_14_tv_usec/1000));
							return;	/* Return immediately... do not print this new packet... it does not bring any new information */
//						}
					}
					else {
						fprintf(stderr, "%s: Warning: Last 50.14 cmd repeated but there was an error in delta calculation\n", __func__);
					}
				}
			}
			last_cmd_50_14.payload_ts = ts;	/* Copy timestamp */
		}
		if (msg_len<=sizeof(last_cmd_50_14.payload)) {	/* Message fits in buffer */
			memcpy(last_cmd_50_14.payload, ebus_msg_payload, msg_len) ;	/* Copy the last message if it is different from the last one... it is probably going to be the first of a new group of successive messages */
			last_cmd_50_14.payload_sz = msg_len;
		}
		else {
			fprintf(stderr, "%s: Error: Message 50.14 is %d bytes long and won't fit in the buffer\n", __func__, msg_len);
			last_cmd_50_14.payload_sz = 0;
		}
	}
 
	printf("%02x%s->%02x%s, Cmd %02x.%02x%s, Len=%d ",
	       src_addr, /*src_str*/"", dst_addr, /*dst_str*/"", cmd, subcmd, cmd_str, msg_len);	// Lionel: For now, I am commenting out the text-string display for src and dst address... it makes the output more difficult to read...
	// Lionel: Commenting out the CRC check result
	//~ printf("(calculated CRC=%02x", calculated_msg_crc);
	//~ if (crc_correct)
		//~ printf("->OK");
	//~ else
		//~ printf("->Error crc provided in msg was %02x", ebus_msg_payload[msg_len]);	/* Dump the CRC byte */
	//~ printf("): ");
 
/**
 * This section decodes the payload of the ebus packet (which meaning depends on the command & subcommand)
**/
 
	/*** Command 07.00 ***/
	if (cmd == 0x07 && subcmd == 0x00 && msg_len>8) {
		/* Store the date in the static struct last_cmd_07_00_date so that this struct always contain the latest up to date date & time */
		last_cmd_07_00_date.h = bcd_to_u8(ebus_msg_payload[4]);
		last_cmd_07_00_date.m = bcd_to_u8(ebus_msg_payload[3]);
		last_cmd_07_00_date.s = bcd_to_u8(ebus_msg_payload[2]);
		last_cmd_07_00_date.dow = ebus_msg_payload[7];
		last_cmd_07_00_date.dom = bcd_to_u8(ebus_msg_payload[5]);
		last_cmd_07_00_date.moy = bcd_to_u8(ebus_msg_payload[6]);
		last_cmd_07_00_date.yy = bcd_to_u8(ebus_msg_payload[8]);
		last_cmd_07_00_date.ts = ts;	/* Record a timestamp of when this date/time packet was received (if both clocks are synchronised, this should roughly correspond to the date in .h, .m, .s etc... above) */
 
		{
			signed int ext_temp;
			data1c_to_int(ebus_msg_payload[1], &ext_temp);
 
			printf("[EXT: %d°C, "
			       "TIME: %02u:%02u:%02u, "
			       "DATE: %s %02d/%02d/20%02d] ",
			       ext_temp,
			       last_cmd_07_00_date.h, last_cmd_07_00_date.m, last_cmd_07_00_date.s,
			       (last_cmd_07_00_date.dow<8)?dayofweek[last_cmd_07_00_date.dow]:"---", last_cmd_07_00_date.dom, last_cmd_07_00_date.moy, last_cmd_07_00_date.yy);
		}
 
		if (ebus_msg_payload[0] != 0x00) {	/* Never seen this value here! */
			printf("%sByte0!=0x00(0x%02x)%s ", DUMPSTARTERROR_STYLE, ebus_msg_payload[0], DUMPEND_STYLE);
		}
		if (msg_len == 9) disable_hexdump = 1;	/* For command 07.00, we know how to fully decode the whole 9 bytes, thus do not hexdump */
	}
 
	/*** Command 50.0a ***/
	if (cmd == 0x50 && subcmd == 0x0a && msg_len>12) {
 
		printf("[CHAUD: %d.%01d°C, "
		       "ECS: %d.%01d°C, ",
		       ebus_msg_payload[5]/2, (ebus_msg_payload[5]%2==0)?0:5,	/* CHAUD is represented as data1c */
		       ebus_msg_payload[7]/2, (ebus_msg_payload[7]%2==0)?0:5);	/* ECS is represented as data1c */
 
		{
			signed int ext_temp;
			data1c_to_int(ebus_msg_payload[9], &ext_temp);
 
			printf("EXT: %d°C, ", ext_temp);	/* EXT is represented as data1b */
		}
 
		{
			signed int result_int;
			unsigned char result_dec_centi;
 
			if (data2b_to_int(ebus_msg_payload[11],	/* MSB */
			                  ebus_msg_payload[10], /* LSB */
			                  &result_int,
			                  &result_dec_centi,
			                  NULL)!=0) {	/* 0 returned means replacement value... we won't display in that case */
				/* This represents a simulated floating-point (1/256) precision value extracted from the EXT measurement and its evolution trend (speed of increasing or decreasing) */
				/* This value is artificially increased or decreased to reach EXT, and its progress stops immediately when the simulated value and EXT have the same integer part */
				/* Most often if the difference between the integer part of the simulated value and integer measured value is different, we will adjust the simulated float value by 1/256 (corresponds to +/-1 on LSB) */
				if (result_dec_centi % 10 == 0) {	/* We probably have only 1/10 precision, only show 1 decimal */
					printf("Simulfloat_EXT: %d.%01d°C", result_int, ((int)result_dec_centi)/10);
				}
				else {
					printf("Simulfloat_EXT: %d.%02d°C", result_int, (int)result_dec_centi);
				}
				printf("] ");
			}
		}
 
		if (ebus_msg_payload[0] != 0x01) { /* Never seen this value here! */
			printf("%sByte0!=0x01(0x%02x)%s ", DUMPSTARTERROR_STYLE, ebus_msg_payload[0], DUMPEND_STYLE);
		}
		if (ebus_msg_payload[6] != 0xff) { /* Never seen this value here! */
			printf("%sByte6!=0xff(0x%02x)%s ", DUMPSTARTERROR_STYLE, ebus_msg_payload[6], DUMPEND_STYLE);
		}
 
		if (ebus_msg_payload[2] == 0x07) {
			printf(",burner flame&circ off");
			if (ebus_msg_payload[1] != 0x00) {	/* Byte1 is always 00 when Byte2 is 0x07 */
				printf(" but %sByte2!=0x00(0x%02x) !!!NEVER SEEN THIS VALUE BEFORE!!!%s", TTY_RED, ebus_msg_payload[2], TTY_NORMAL);
			}
		}
		else if (ebus_msg_payload[2] == 0x47) {
			printf(",burner flame off,burner circ on");
			if (ebus_msg_payload[1] == 0x00) {	/* Byte1 can be 0x00 when Byte3 is 0x47 (this is the majority of what we see) */
				printf(",Byte1=0x00");
			}
			else if (ebus_msg_payload[1] == 0x02) {	/* Byte1 can be 0x02 when Byte3 is 0x07 (this is seen just before burner is on again, which means Byte2 will be 0x7f at next 50.0a command) */
				printf(",Byte1=0x02(burner circ-with-low-temp-needs-power?)");
			}
			else if (ebus_msg_payload[1] == 0x08) {	/* Byte1 can be 0x08 when Byte3 is 0x07 (this is seen just after burner is on, before burner is off again, which means Byte2 will be 0x47 or 0x00 at next 50.0a command*/
				printf(",Byte1=0x08(burner circ-reaches-end)");
			}
			else {	/* Byte1 has always been 00, 02 or 08 when Byte3 is 0x47 */
				printf(" but %sByte1!=0x00(0x%02x) !!!NEVER SEEN THIS VALUE BEFORE!!!%s", TTY_RED, ebus_msg_payload[1], TTY_NORMAL);
			}
		}
		else if (ebus_msg_payload[2] == 0x7f) {	/* 0x7f means burner is on */
			printf(",burner flame&circ on");
			if (ebus_msg_payload[1] == 0x06) {	/* Byte1 is most of the time 0x06 when Byte2 is 0x7f (this is the majority of what we see) */
				printf(",Byte1=0x06");
			}
			else if (ebus_msg_payload[1] == 0x0e) {	/* When Byte1 is 0x0e, previous Byte2 was 0x47 and next Byte2 will probably be 0x7f */
				printf(",Byte1=0x0e(start-flame-low-temp-req?)");
			}
			else if (ebus_msg_payload[1] == 0x0d) {	/* When Byte1 is 0x0d, previous Byte2 was 0x47 and next Byte2 will probably be 0x7f */
				/* Only seen once at 08:30, but when happening, it means we need a very high temperature (>60°C) */
				printf(",Byte1=0x0e(start-flame-high-temp-req?)");
			}
			else if (ebus_msg_payload[1] == 0x0c) {	/* When Byte1 is 0x0c, previous Byte2 was 0x7f and next Byte2 will probably be 0x47 */
				/* Only seen once on 13/10/2011 at 20:45, but when happening, we were at a very high temperature (>70°C) */
				/* And this was during a cycle of water heating not room temp heating */
				printf(",Byte1=0x0e(stop-flame-on-high-temp?)");
			}
			else {	/* For Byte1, we have also seen values of 0x0d, 0x0e, 0x0f when Byte2 is 0x7f */
			// See grep 50\.0a *.txt | sed -n -e 's/^\([^:]*\):.*\(01 .. .. .. .. .. .. .. .. .. .. .. ..\).*$/\1:\2/p' | grep '01 .. 7f' | grep -v '01 06 7f'
				printf(",Byte1=0x%02x", ebus_msg_payload[1]);
			}
		}
		//~ else if (ebus_msg_payload[2] == 0x77) {	/* Seen only once at 03:21 in the night */
		//~ }
		else { /* Never seen this value here! */
			printf(",%sByte2=0x%02x !!!NEVER SEEN THIS VALUE BEFORE!!!%s,Byte1=0x%02x", TTY_RED, ebus_msg_payload[2], TTY_NORMAL, ebus_msg_payload[1]);
		}
 
		printf("[Byte3=0x%02x,Byte4=0x%02x",
		       ebus_msg_payload[3],
		       ebus_msg_payload[4]);
		if (ebus_msg_payload[8] != 0) {	/* We usually see 0 here */
			printf(",%sByte8=0x%02x !!!NEVER SEEN THIS VALUE BEFORE!!!%s", TTY_RED, ebus_msg_payload[8], TTY_NORMAL);
		}
		printf("] ");
		/* bus_msg_payload[12] is 0x08 all the time except when byte3 is 0x7f (or if Byte3 is 0x47 during a few packets after having been 0x7f, where it has been seen with values between 0x26 and 0x2d */
		// Simulated is encoded as byte2b... print it
 
		/* Use the following command to understand more about command 50.0a */
		// grep 50\.0a *.txt | sed -n -e 's/^\([^:]*\):.*\(01 .. .. .. .. .. .. .. .. .. .. .. ..\).*$/\1:\2/p' | sort -u | less
	}
 
	/*** Command 05.07 ***/
	if (cmd == 0x05 && subcmd == 0x07 && msg_len>7) {
		/* Handle byte at offset 0 */
		printf("[circ-heating");
		if (ebus_msg_payload[0] == 0xbb) {
			printf("=on");
		}
		else if (ebus_msg_payload[0] == 0x55) {
			printf("=off");
		}
		else {
			printf("=unknown value(0x%02x)", ebus_msg_payload[0]);
		}
		if (last_cmd_05_07_byte0 != ebus_msg_payload[0]) {
			last_cmd_05_07_byte0 = ebus_msg_payload[0];
			printf("%s(!!!CHANGE DETECTED!!!)%s", TTY_RED, TTY_NORMAL);
		}
 
		/* Handle byte at offset 1 */
		if (ebus_msg_payload[1] == 0x03) {
			printf(",Byte1=0x03(cycle-circ off)");
		}
		else if (ebus_msg_payload[1] == 0x04) {
			printf(",Byte1=0x03(cycle-circ on)");
		}
		else {
			/* Values seen: only 0x03 and 0x04 */
			/* Seen 4 times switching from 0x04 to 0x03 at 20:00 */
			/* Current prog for cycle circ is on from 17:00 to 20:00 */
			printf(",%sByte1=0x%02x !!!NEVER SEEN THIS VALUE BEFORE!!!%s", TTY_RED, ebus_msg_payload[1], TTY_NORMAL);
		}
		if (last_cmd_05_07_byte1 != ebus_msg_payload[1]) {
			last_cmd_05_07_byte1 = ebus_msg_payload[1];
			printf("%s(!!!CHANGE DETECTED!!!)%s", TTY_RED, TTY_NORMAL);
		}
 
		/* Values seen for Byte2 and Byte3 that seem to be both part of a 16-bit encoded (somehow) value:
1d 03
26 03
3d 03
66 04
83 03
85 03
8b 03
8d 03
96 03
b0 04
c6 02
c8 02
ca 02
cb 02
ce 02
d0 02
d2 02
d6 02
d8 02
da 02
db 02
e3 02
 
This seems confirmed by the fact the replacement value seems to be 00 80 (thus MSB is Byte3, LSB is byte2, and value is either signed int or byte2b or byte2c
*/
		/* Handle byte at offset 2 */
		if (ebus_msg_payload[0] == 0x55) {	/* Burner is off... we usually have 0x80 in Byte2 and 0x00 in Byte3, if not, display it */
			if (ebus_msg_payload[2] == 0x80) { /* This value has also been seen when Byte0=0xbb */
			}
			else {
				printf(",Byte2=0x%02x", ebus_msg_payload[2]);
			}
		}
		else {
			printf(",Byte2=0x%02x", ebus_msg_payload[2]);
			/* In Byte2, when burner is on, we usually have a value that changes at the same time or just before a change in delta (Byte1 and Byte2 of command 50.14 */
			/* Byte2 seems to decrease when Tdelta increases */
			/* Byte2 takes many different values, it is probably the representation of an analog value */
			/* If Byte2=0x80, then Byte3 is 0x00, could Byte2==0x80 mean 0, <0x80 mean a negative value and >0x80 mean a positive value*/
			/* See below, for an attempt to decode Byte2 and Byte3 as byte2c */
		}
 
		/* Handle byte at offset 3 */
		/* We have a small value in Byte3 */
		/* Mostly 0x00 here when Byte0=0x55, but I saw once a continuous Byte3=0x01 + Byte0=0x55 after 21:15 on a Friday,
		and also once only one 05.07 packet with Byte3=0x01 + Byte0=0x55 when switching from cons temp red to cons comfort */
		/* Could 0x02 be the target temperature in programmation mode (2): 0x02 meanning cons temp red, 0x04 meanning need to increase strongly (>1°C) to cons confort, 0x03 meaning regulating around temp confort, and maybe 0x00 means no heating */
		/* It seems we move from 0x00 (Byte0=0x55) to 0x02 or 0x04, or from 0x01 (Byte0=0x55) to 0x01 */
		/* Byte2 and byte3 seem to represent a unique value (default being 08 00) */
		/* Byte4 is always 0x00 */
		/* Byte5 is always 0x80 (could be a signed char or data1b) */
		/* Byte6 is always 0xff (could be a byte, a BCD or data1c) */
		/* Byte8 is always 0xff (could be a byte, a BCD or data1c) */
		if (ebus_msg_payload[0] == 0x55 && ebus_msg_payload[2] == 0x80) {
			if (ebus_msg_payload[3] == 0x00) {
				//~ printf(",Byte3=0x00(usual)");
			}
			else {
				printf(",%sByte3=0x%02x !!!NEVER SEEN THIS VALUE BEFORE!!!%s", TTY_RED, ebus_msg_payload[3], TTY_NORMAL);
			}
		}
		else {
			/* Values seen: 0x02 and 0x04 */
			/* Seen once switching from 0x02 to 0x04 at 8:15 on sat morning */
			printf(",Byte3=0x%02x", ebus_msg_payload[3]);
			if (ebus_msg_payload[3] == 0x00) {
				printf("(decrease to lower temp)");
			}
			else if (ebus_msg_payload[3] == 0x02) {
				printf("(heating function is on)");
			}
			else if (ebus_msg_payload[3] == 0x03) {
				printf("(regulation around heating temp?)");
			}
			else if (ebus_msg_payload[3] == 0x04) {
				printf("(high increase to heating temp?)");
			}
			else {
				printf("(unknown)");
			}
 
			/* The lines below try to represent Byte3 and Byte2 as a value */
			signed int result_int;
			unsigned char result_dec_centi;
 
			if (data2b_to_int(ebus_msg_payload[3],	/* MSB */
			                  ebus_msg_payload[3], /* LSB */
			                  &result_int,
			                  &result_dec_centi,
			                  NULL)!=0) {	/* 0 returned means replacement value... we won't display in that case */
				printf("[heating effort to target via data2b_to_int()=%d.%02d] ", result_int, (int)result_dec_centi);
			}	/* Usually, we allow the boiler to go as low as target heating water - 8°C */
 
		}
 
		/* Handle Byte7 of command 05.07 */
		/* It seems byte 7 indicates a slowly changing status of the boiler (water cycle/heating cycle/off etc...?) */
		/* Already seen values: 0x6b, 0x50 */
		/* Also seen 0x6a (at 20:40) when the burner is on, and heating the hot water tank during hot water cycle (target 55°C), this is */
		/* Always true during hot water cycle as long as hot water temp is lower than 55°C */
		/* During hot water cycle, if hot water tank is 55°C or higher, we will get Byte7=0x50, the hot water tank may however */
		/* be circulating, but without burner on (just transferring the burner remaining temperature to hot water) */
		/* Always seen switching from 0x50 to 0x6b at 21:15, and switches back from 0x6b to 0x50 at 23:00, and remains at 0x50 during the whole night */
		/* On my boiler, this matches with the hot water program (from 21:15 to 23:00) */
		// When 0x6b, as soon as boiler starts, we also get Byte3=0x02 instead of Byte3=0x03 usually
		printf(",Byte7=");
		if (last_cmd_05_07_byte7 != ebus_msg_payload[7]) {
			last_cmd_05_07_byte7 = ebus_msg_payload[7];
			printf("%s(!!!CHANGE DETECTED!!!)%s", TTY_RED, TTY_NORMAL);
		}
		if (ebus_msg_payload[7] == 0x6a) {
			printf("0x6a(hot water generation in progress)");
		}
		else if (ebus_msg_payload[7] == 0x6b) {
			printf("0x6b(in hot water cycle, but water temp>=minimum)");
		}
		else if (ebus_msg_payload[7] == 0x50) {
			printf("0x50(outside of hot water cycle)");
		}
		else {
			printf("%s0x%02x !!!NEVER SEEN THIS VALUE BEFORE!!!%s", TTY_RED, ebus_msg_payload[7], TTY_NORMAL);
		}
		printf("] ");
		// All bytes seem useful (except byte offs 6 + 8 that are always 0xff for now)
	}
 
	/*** Command 50.14 ***/
	if (cmd == 0x50 && subcmd == 0x14 && msg_len>5) {
		{
			signed int result_int;
			unsigned char result_dec_centi;
 
			if (data2b_to_int(ebus_msg_payload[4],	/* MSB */
			                  ebus_msg_payload[3], /* LSB */
			                  &result_int,
			                  &result_dec_centi,
			                  NULL)!=0) {	/* 0 returned means replacement value... we won't display in that case */
				if (result_dec_centi % 10 == 0) {	/* We probably have only 1/10 precision, only show 1 decimal */
					printf("[Tint: %d.%01d°C] ", result_int, ((int)result_dec_centi)/10);
				}
				else {
					printf("[Tint: %d.%02d°C] ", result_int, (int)result_dec_centi);
				}
			}
		}
 
		printf("[circ heating");
		if (ebus_msg_payload[0]==0x61) {
			printf("=off ");
		}
		else if (ebus_msg_payload[0]==0x63) {
			printf("=on ");
		}
		else {
			printf("%s=unknown%s", DUMPSTARTERROR_STYLE, DUMPEND_STYLE);	/* We have never seens anything else than 61 or 63 */
		}
		printf("0x%02x", ebus_msg_payload[0]);
		if (ebus_msg_payload[0]==0x63) {
			signed int result_int;
			unsigned char result_dec_centi;
 
			if (data2b_to_int(ebus_msg_payload[2],	/* MSB */
			                  ebus_msg_payload[1], /* LSB */
			                  &result_int,
			                  &result_dec_centi,
			                  NULL)!=0) {	/* 0 returned means replacement value... we won't display in that case */
				if (result_dec_centi % 10 == 0) {	/* We probably have only 1/10 precision, only show 1 decimal */
					printf("[TARGET HEATING WATER: %d.%01d] ", result_int, ((int)result_dec_centi)/10);
				}
				else {
					printf("[TARGET HEATING WATER: %d.%02d] ", result_int, (int)result_dec_centi);
				}
			}	/* Usually, we allow the boiler to go as low as tagret heating water _ 8°C */
			if (ebus_msg_payload[5]!=0x00) {	/* Always seen 00 when Byte0 = 0x63 */
				printf(",%sByte5!=0x00(0x%02x)%s", DUMPSTARTERROR_STYLE, ebus_msg_payload[5], DUMPEND_STYLE);
			}
		}
		else if (ebus_msg_payload[0]==0x61) {
			if (ebus_msg_payload[1]!=0x00) {
				printf(",%sByte1!=0x00(0x%02x)%s", DUMPSTARTERROR_STYLE, ebus_msg_payload[1], DUMPEND_STYLE);
			}
			if (ebus_msg_payload[2]!=0x08) {
				printf(",%sByte2!=0x08(0x%02x)%s", DUMPSTARTERROR_STYLE, ebus_msg_payload[2], DUMPEND_STYLE);
			}
			if (ebus_msg_payload[5]!=0x08) {
			/* Mostly seen 0x08 when Byte0 = 0x61, except a few times with value Byte5 = 0x00:
			See a serie of 11 packets with Byte5 = 0x00 when decreasing the cons temp confort temperature value setting, thus making the heating stop.
			Also seen a serie of 11 packets with Byte5 = 0x00 when switching from cons temp confort to no heating
			Also seen a serie of 11 packets with Byte5 = 0x00 when turning on the boiler, after around 90 previous packets including 50.14 cmd packets with Byte0 = 0x61 and Byte5 = 0x08, heating was off from the moment the boiler started, but during power up cycle, the boiler quickly starts the burner and then stops, this could be the reason */
				printf(",%sByte5!=0x08(0x%02x%s)%s", DUMPSTARTERROR_STYLE, ebus_msg_payload[5], (ebus_msg_payload[5]==0)?"":"=stop burner", DUMPEND_STYLE);
			}
		}
		if (msg_len>6 && ebus_msg_payload[6]!=0x00) {
			printf(",%sByte6!=0x00(0x%02x)%s", DUMPSTARTERROR_STYLE, ebus_msg_payload[6], DUMPEND_STYLE);
		} /* Byte6 has always been 00 */
		if (msg_len>7 && ebus_msg_payload[7]!=0x00) {
			printf(",%sByte7!=0x00(0x%02x)%s", DUMPSTARTERROR_STYLE, ebus_msg_payload[7], DUMPEND_STYLE);
		} /* Byte7 has always been 00 */
		/* To quickly find 50.14 command, just grep on '6[13] [[:xdigit:]][[:xdigit:]] [[:xdigit:]][[:xdigit:]] [[:xdigit:]][[:xdigit:]] [[:xdigit:]][[:xdigit:]] [[:xdigit:]][[:xdigit:]] 00 00' */
		printf("] ");
		//~ disable_hexdump = 1;	/* For command 50.14, do not hexdump */
	}
 
	if (!disable_hexdump) {
		printf("%s", crc_correct?DUMPSTART_STYLE:DUMPSTARTERROR_STYLE);
 
		int usable_msg_sz = min(msg_len, ebus_msg_payload_sz);	/* msg_len should always be smaller than the actual buffer, but we perform this check to avoid buffer overrun */
		hexdump(stdout, ebus_msg_payload, usable_msg_sz, 0);
		printf("%s", DUMPEND_STYLE);
		if (msg_len<ebus_msg_payload_sz) {	/* There are trailing bytes after msg_len in the buffer */
			printf(" + ");
			hexdump(stdout, &(ebus_msg_payload[usable_msg_sz]), ebus_msg_payload_sz-usable_msg_sz, 0);	/* Dump the trailing bytes for information */
		}
	}
	printf("\n");
	fflush(stdout);
}
 
 
/**
 * @name extract_ebus_msg
 * This function takes an octet stream representing an ebus message, checks the CRC and extracts the source address,
 * destination address, command and subcommand value, as well as packet length
 * @param ebus_msg a pointer to the ebus packet received
 * @param ebus_msg_sz the size of the buffer pointed by ebus_msg
 * @param flags is a bitfield containing flags for how to behave when decoding messages (see "e-bus decoding flags" above)
 * @param ts is a struct timeval containing the timestamp at which this message was received
 * @return 1 if everything is fine, 0 if there was an error
**/
int extract_ebus_msg(unsigned char *ebus_msg, int ebus_msg_sz, unsigned int flags, struct timeval ts) {
 
	int crc_correct = 0;
	unsigned char src_addr, dst_addr;
	unsigned char cmd, subcmd;
	unsigned char calculated_msg_crc = 0xff;	/* Undefined CRC */
	int msg_len = 0;
 
	//~ printf("\nGot ebus message: ");
	if (ebus_msg_sz>EBUSMSGBUFSZ) {	/* The buffer size seems to be bigger than the allocated buffer... */
		fflush(stdout);	/* Make sure we flush stdout before sending something to stderr to avoid interlacing output */
		fprintf(stderr, "%s: Warning: Buffer is smaller that the reported message length %d\n", __func__, ebus_msg_sz);	/* Issue an error */
	}	/* But continue anyway... we may have a segfault later on... */
	if (ebus_msg_sz<5) {
		if (!(flags & F_DISP_BAD_CRC)) {
			return 0;
		}
		printf("Wrong ebus length, raw dump (not unescaped): %s", DUMPSTARTERROR_STYLE);
		hexdump(stdout, ebus_msg, ebus_msg_sz, 0);
		printf("%s\n", DUMPEND_STYLE);
	}
	else {	/* We have at least 5 bytes in the buffer, enough for source, dest, command, subcommand and length bytes */
		src_addr = ebus_msg[0];
		dst_addr = ebus_msg[1];
		cmd = ebus_msg[2];
		subcmd = ebus_msg[3];
		msg_len=(int)ebus_msg[4];
		if (msg_len+5 <= ebus_msg_sz) {	/* Check we have enough buffer to perform CRC calculation */
			calculated_msg_crc = crc8_ebus(ebus_msg, msg_len+5);	/* Calculate the CRC on the actual ebus message length (and not on the received buffer as trailing bytes may be present) */
		}
		else {	/* Otherwise, issue a warning: the message will probably be incomplete */
			if (!(flags & F_DISP_BAD_CRC)) {
				return 0;
			}
			fflush(stdout);	/* Make sure we flush stdout before sending something to stderr to avoid interlacing output */
			fprintf(stderr, "%s: Warning: Short read\n", __func__);
		}
		ebus_msg+=5;	/* Have ebus_msg point to the 6th byte */
		ebus_msg_sz-=5;	/* Reduce the buffer accordingly */
 
		ebus_msg_sz = unescape_ebus(ebus_msg, ebus_msg_sz);
		if (ebus_msg_sz>=msg_len+1) {	/* Do we have at least one more byte for CRC after the message? */
			crc_correct = (calculated_msg_crc==ebus_msg[msg_len]);
		}
		if (crc_correct || (flags & F_DISP_BAD_CRC))
			decode_ebus_msg(src_addr, dst_addr, cmd, subcmd, msg_len, crc_correct, calculated_msg_crc, ebus_msg, ebus_msg_sz, flags, ts);
	}
	return 1;
}
 
/**
 * @name display_usage
 * Display the command-line parameters instructions
 * @param stream a pointer to the stream on which the text is output
**/
void display_usage(FILE *stream) {
 
	fprintf(stream, "%s: Decode e-Bus messages\n", progname);
	fprintf(stream, "Usage: %s [-s <serial_dev>] [-d <dump_file>] -c -H -r\n", progname);
	fprintf(stream, "With	-s <serial_dev>: specifies the serial device used to get ebus messages from\n");
	fprintf(stream, "    	-d <dump_file>: specifies the file to dump a raw copy of ebus bytes as received from the serial device\n");
	fprintf(stream, "    	-c: also displays messages with CRC errors\n");
	fprintf(stream, "    	-H: also displays hexdumps of messages\n");
	fprintf(stream, "    	-r: displays repeated messages\n");
}
 
/**
 * @name main
 * Read ebus messages from the serial line (provided by the global variable DFLT_SERIALDEVICE or on the command line using -s)
 * Prints the decoded messages to the display
**/
int main(int argc, char **argv) {
	int serialfd, input_sz;
	int dumpfd;
	int int_c;	/* Tool for counting, calculation */
	unsigned char buf[SERBUFSZ];
	unsigned char *payload;
	unsigned char ebusmsgbuf[EBUSMSGBUFSZ];
	int ebusmsgbufpos = 0;
	int result;
	unsigned int flags = 0;
	int terminate = 0;	/* Should we stop parsing the ebus messages */
	int op = 1;	/* Option number (for cmdline arg parsing) */
 
	int in_ebus_msg;
	int leading_syn_in_current_read;
	int trailing_syn_in_current_read;
 
	int dump_to_file;	/* Set to one in order to dump the received serial data to a file */
	char *dump_filename = NULL;	/* Specify the dump filename */
	char *serial_device = NULL;	/* Serial device name */
 
	struct timeval tlastdata; /* Time last data byte was received on serial line */
	struct timeval tlastsyn; /* Time last syn byte was received on serial line */
	struct timeval tlastin; /* Time last byte (syn or data) was received on serial line */
	struct timeval tnow;    /* Stores current time now */
	time_t delta_tv_sec;    /* Delta between two timestamps in secs */
	suseconds_t delta_tv_usec;    /* Delta between two timestamps (additionnal µsec precision) */
 
	struct termios oldtio;
 
	while (argc > 1 && argv[op][0] == '-') {
 
		if (strcmp(argv[op], "-h") == 0 || strcmp(argv[op], "--help") == 0) {
			display_usage(stderr);
			exit(0);
		}
		else if (strcmp(argv[op], "-s") == 0) {
			if (argc>1) {
				argc--;
				op++;
				serial_device = argv[op];
			}
			else {
				fprintf(stderr, "Argument -s requires one parameter\n");
				exit(1);
			}
		}
		else if (strcmp(argv[op], "-d") == 0) {
			if (argc>1) {
				argc--;
				op++;
				dump_filename = argv[op];
				fprintf(stderr, "Using \"%s\" as dump file\n", dump_filename);
			}
			dump_to_file = 1;
		}
		else if (strcmp(argv[op], "-H") == 0) {
			flags |= F_DISP_HEXUMP;
		}
		else if (strcmp(argv[op], "-c") == 0) {
			flags |= F_DISP_BAD_CRC;
		}
		else if (strcmp(argv[op], "-r") == 0) {
			flags |= F_DISP_REPEATED_MSG;
		}
  		op++;
		argc--;
	}
 
	if (!serial_device) {
		serial_device = DFLT_SERIALDEVICE;
		fprintf(stderr, "Using default serial port %s\n", serial_device);
	}
	if (dump_to_file) {
		if (!dump_filename || *dump_filename=='\0') {
			dump_filename = DFLT_DUMPFILE;
			fprintf(stderr, "Using default dump file \"%s\"\n", dump_filename);
		}
	}
 
	if (open_serial(serial_device, &serialfd, &oldtio)!=0) {
		fprintf(stderr, "Error opening serial port %s\n", serial_device);
		exit(1);
	}
 
	gettimeofday(&tlastsyn, NULL);	/* Get starting point for last SYN byte received */
	tlastin=tlastsyn;
 
	tlastdata.tv_sec=(time_t)0; tlastdata.tv_usec=(suseconds_t)0; 	/* Time for last received data byte is never */	
	in_ebus_msg = 0;
 
	if (dump_to_file) {
		dumpfd=open(dump_filename, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
		if (dumpfd == -1) {
			fprintf(stderr, "Could not open dump file \"%s\"\n", dump_filename);
			dump_to_file = 0;	/* Disable dumping to a file */
		}
	}
#ifdef DEBUG
	fputs("Started... listening\n", stderr);
#endif
 
	while (!terminate) {	/* Loop for input */
		input_sz = read(serialfd, buf, SERBUFSZ);	/* Returns after at least one char has been read from serial port */
		if (input_sz > 0) {	/* We actually received something */
			if (dump_to_file) {
				write(dumpfd, buf, input_sz);
			}
			gettimeofday(&tnow, NULL);	/* Get a timestamp for this transmission and record it in tnow */
#ifdef DEBUG
			fprintf(stderr, "\nReceived %d bytes at t=%d.%03d: ", input_sz, (int)tnow.tv_sec, (int)(tnow.tv_usec/1000);
			for (int_c=0; int_c<input_sz; int_c++) {
				fprintf(stderr, "%02x ", buf[int_c]);
			}
			fprintf(stderr, "(in_msg=%d, ebusmsgptr=%d) ", in_ebus_msg, ebusmsgbufpos);
#endif
 
			payload = buf;	/* Initially, we consider the whole buffer as the payload (we will maybe strip leading characters just below) */
 
start_process_buf:
			/* Strip leading SYN bytes */
			leading_syn_in_current_read = 0;
			if (!in_ebus_msg) {	/* We are at the beginning of a stream */
				int_c = search_first_non_syn(payload, input_sz);
				if (int_c) {
					leading_syn_in_current_read = 1;	/* We found at least one SYN byte as prefix in the bytes read */
					payload += int_c;
					input_sz -= int_c;
				}
			}
 
//Lionel: FIXME: this means we will always start the if (input_sz>0) statement below with in_ebus_msg=1... check this, it may not be correct. Let's rather set in_ebus_msg to 1 in the if statement, whenever needed only
			if (!in_ebus_msg && input_sz>0) {	/* After having stripped out all leading 0xaa, we still have bytes in the incoming buffer... we are now reading an ebus message */
				in_ebus_msg=1;
			}
//			else
//				printf("In stream\n"); /////////////////////
			if (input_sz > 0) {	/* We have data (non-SYN) bytes incoming, handle them */
				result = get_delta_time(&tnow, &tlastdata, &delta_tv_sec, &delta_tv_usec);
#ifdef DEBUG
				fprintf(stderr, "non empty frame dt=%d.%03d ", (int)delta_tv_sec, (int)(delta_tv_usec/1000));
#endif
				if ((result) || (delta_tv_sec>2 || (delta_tv_sec == 1 && delta_tv_usec > 70*1000))) {	/* Limit for grouping bytes is 1.070s; above, we will consider that bytes are independant, above, we will consider bytes form a unique stream */
					/* We also consider an error in the time delta as a sign that bytes cannot be grouped together. This can happen if the host clock is resynchronised back (for example using NTP) */
					/* Do not use a delay longer than 30s as this is what allows us to detect the initial startup carrier lost */
					if (in_ebus_msg && ebusmsgbufpos) { /* We timed out receiving bytes from the same message, consider that the message ended */
						extract_ebus_msg(ebusmsgbuf, ebusmsgbufpos, flags, tnow);
					}
					in_ebus_msg = 0;    /* Set in message to 0 because we have dumped the previously buffered message (even if there are new bytes, they may only be SYN) */
					ebusmsgbufpos = 0;	/* We reset the buffer though... we are getting ready for the next message (that is actually inside our current read buffer) */
 
					/* Strip leading SYN from the buffer (has not been done above because we thought we were still inside a message */
					int_c = search_first_non_syn(payload, input_sz);
					if (int_c) {
						leading_syn_in_current_read = 1;	/* We found at least one SYN byte as prefix in the bytes read */
						payload += int_c;
						input_sz -= int_c;
					}
 
//					fprintf(stdout, "\n%s[%d.%03d]%s ", TIMESTAMPSTART_STYLE, (int)delta_tv_sec, (int)(delta_tv_usec/1000), TIMESTAMPEND_STYLE);	/* Print only s.ms on the display... just discard anything below the ms */
//					fflush(stdout);
				}
				else {
   					if (delta_tv_sec>1 || (delta_tv_usec > 500*1000)) {	/* Limit for grouping bytes is 0.500s; above, we will consider that bytes are independant, above, we will consider bytes form a unique stream */
						/* For more than 500ms between data bytes, issue a warning but consider these bytes as one same messages */
    					if (in_ebus_msg && ebusmsgbufpos) { /* We have a long wait during reception of bytes from the same message, issue a warning */	
							fprintf(stderr, "Warning: Long wait (dt=%d.%03d) between bytes of a same message\n",  (int)delta_tv_sec, (int)(delta_tv_usec/1000));
						}
					}
/*					else {
						fprintf(stdout, "[linked]\n");
					}*/
				}
 
				tlastdata=tnow;	/* Reference will be this last byte received */
			}
			/* Check the existence of a 0xaa in the message... this will force the ebus frame to end */
 
			trailing_syn_in_current_read = 0;
			int_c = search_first_syn(payload, input_sz); 
			if (int_c != -1) {	/* There was a SYN in our payload */
				if (in_ebus_msg || int_c) {	/* We have a pending message or there are bytes for a new messages before the SYN byte */
					in_ebus_msg = 1;
					trailing_syn_in_current_read = 1;	/* We actually found a SYN byte, not leading the message but inside the message (probably at the end, or there is going to be an error with the protocol ) */
					/* We already have bytes in the buffer... concatenate with the leading bytes (before the SYN) and process the message */
					if (input_sz >= int_c) {	/* This should always be the case */
						//if (int_c) {    /* There is at least one byte before the SYN byte, it will be concatenated to the previous buffer */
						//Commented out because the for loop below will not be processed if int_c==0
							input_sz -= int_c;
							while (ebusmsgbufpos<EBUSMSGBUFSZ && int_c>0) {
								ebusmsgbuf[ebusmsgbufpos++]=*payload++;
								int_c--;
							}
						//}
						extract_ebus_msg(ebusmsgbuf, ebusmsgbufpos, flags, tnow);
						ebusmsgbufpos = 0;
					}
					else {
						fprintf(stderr, "Error: search_first_syn returned an out of bound index %d/%d\n", int_c, input_sz);
					}
					in_ebus_msg = 0;
				}
				else {	/* Nothing interesting before the SYN, just skip to the character */
					payload += int_c;	/* Point payload and input_sz to the first SYN byte found */
					input_sz -= int_c;
					fprintf(stderr, "Warning: no data bytes but a SYN character found in the middle of a packet (at pos %d)... skipping all before SYN but this looks dodgy\n", int_c);
				}
				if (input_sz) {	/* We still have remaining bytes in the buffer after the SYN identified above */
					payload++;	/* Skip the SYN */
					input_sz--;
					//~ fprintf(stderr, "Re-reading the buffer after the SYN byte, size is now %d\n", input_sz);
					goto start_process_buf;	/* Restart processing the buffer from the next byte */
				}
			}
 
			for (int_c=0; int_c<input_sz && ebusmsgbufpos<EBUSMSGBUFSZ; int_c++) {
					in_ebus_msg = 1;	/* We are adding bytes to the buffer, thus we are in a message stream */
					ebusmsgbuf[ebusmsgbufpos++]=payload[int_c];
			}
 
			if (leading_syn_in_current_read || trailing_syn_in_current_read) {	/* We had at least one SYN, and at most only SYNs, reset the SYN watchdog */
				if (get_delta_time(&tnow, &tlastsyn, &delta_tv_sec, &delta_tv_usec) == 0) {
					if (delta_tv_sec>2) {	/* Limit for carrier down is 2s without any SYN message; above, we will consider that the carrier has been lost */
						fputs("\nCarrier lost\n", stderr);
						/////////////////// FIXME: change this in a timeout as we don't want to wait for the next byte to say carrier was lost previously!
						if (in_ebus_msg && ebusmsgbufpos) { /* We timed out receiving bytes from the same message, consider that the message ended */
							extract_ebus_msg(ebusmsgbuf, ebusmsgbufpos, flags, tnow);
						}
						in_ebus_msg=0;
						ebusmsgbufpos=0;
					}
				}
				else {	/* Handle calculation error */
					fprintf(stderr, "Error while calculating time delta\n");
					exit(1);
				}
				tlastsyn=tnow;
			}
			/* Check here if SYN was found leading/trailing and how this matches with the status of in_ebus_msg as we can maybe dump the packet now instead of waiting for the next SYN to realize it */
			fflush(stdout);
			tlastin=tnow;	/* Record a timestamp for the last byte stream received */
		}
	}
	if (dump_to_file && dumpfd!=-1) {
		close(dumpfd);
	}
 
	tcsetattr(serialfd,TCSANOW,&oldtio);
	close(serialfd);
	return 0;
}

– Main.LiAins - 2012-08-28

ebus/linuxkonnektor.txt · Zuletzt geändert: 2015/10/11 15:11 von bernhardh