#!/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{'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; }