47_OBIS: Added SML

git-svn-id: https://svn.fhem.de/fhem/trunk/fhem@11205 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
icinger 2016-04-08 17:54:34 +00:00
parent f4a997acdf
commit 06c56f515e

View File

@ -2,6 +2,8 @@
# #
# 47_OBIS.pm # 47_OBIS.pm
# #
# Thanks to matzefizi for letting me merge this with 70_SMLUSB.pm and for testing
# Tanks to immi for testing and supporting help and tips
# #
# $Id$ # $Id$
@ -14,43 +16,64 @@ use POSIX qw{strftime};
my %OBIS_channels = ( "21"=>"power_L1", my %OBIS_channels = ( "21"=>"power_L1",
"41"=>"power_L2", "41"=>"power_L2",
"61"=>"power_L3", "61"=>"power_L3",
"12"=>"voltage_avg",
"32"=>"voltage_L1", "32"=>"voltage_L1",
"52"=>"voltage_L2", "52"=>"voltage_L2",
"72"=>"voltage_L3", "72"=>"voltage_L3",
"11"=>"current_sum",
"31"=>"current_L1", "31"=>"current_L1",
"51"=>"current_L2", "51"=>"current_L2",
"71"=>"current_L3", "71"=>"current_L3",
"8.1"=>"total_consumption", "1.8"=>"total_consumption",
"8.2"=>"total_feed", "2.8"=>"total_feed",
"2"=>"feed_L1", "2"=>"feed_L1",
"4"=>"feed_L2", "4"=>"feed_L2",
"6"=>"feed_L3", "6"=>"feed_L3",
"1"=>"power"); "1"=>"power",
"15"=>"power",
"16"=>"power");
my %OBIS_codes = ( "Serial" => qr/0-0:96\.1\.255\*255\((.*?)\).*/, my %OBIS_codes = ( "Serial" => qr{0.0.96.1.255.255\((.*?)\).*
"Owner" => qr/1-0:0\.0\.0\*255\((.*?)\).*/, |1-0:0\.0\.[0-9]+\*255\((.*?)\).*}x,
"Status" => qr/1-0:96\.5\.5\*255\((.*?)\).*/, "Owner" => qr{1.0.0.0.0.255\((.*?)\).*}x,
"Channels_sum" => qr/1-0:([246])\.1\.7\*255\(([-+]?\d+\.?\d*).*/, "Status" => qr{1.0.96.5.5.255\((.*?)\).*}x,
"Channels" => qr/1-0:(\d+)\.7\.\d*\*\d*\(([-+]?\d+\.?\d*).*/, "Powerdrops" => qr{0.0.96.7.\d.255\((.*?)\).*},
"Counter" => qr/1-0:([12])\.(8)\.\d\*255\((-?\d+\.?\d*).*/ "Time_param" => qr{0.0.96.2.1.255\((.*?)\).*},
"Time_current" => qr{0.0.1.0.0.255\((.*?)\).*},
"Channels_sum" => qr{1.0.(\d+).1.7.255\((<|>)?([-+]?\d+\.?\d*)\*?(.*)\).*},
"Channels" => qr{1.0.(\d+).7\.\d+.\d+\((<|>)?([-+]?\d+\.?\d*)\*?(.*)\).*},
"Counter" => qr{1.0.([12]).(8)\.(\d).255\((<|>)?(-?\d+\.?\d*)\*?(.*)\).*},
"ManufID" => qr{129-129:199\.130\.3\*255\((.*?)\).*},
"PublicKey" => qr{129-129:199\.130\.5\*255\((.*?)\).*},
"ErrorReg" => qr{0.0.97.97.\d.255\((.*?)\).*}
); );
#{"21"=>"energy_L1","41"=>"energy_L2","61"=>"energy_L3","31"=>"power_L1","51"=>"power_L2","71"=>"power_L3","1"=>"energy_current","8.1"=>"energy_total","8.2"=>"feed_total"}
my %SML_specialities = ("TIME" => [qr{0.0.96.2.1
0.0.1.0.0 }x, sub{return strftime("%d-%m-%Y %H:%M:%S", localtime(unpack("i", pack("I", hex(@_)))))}],
"HEX2" => [qr{1-0:0\.0\.[0-9] }x, sub{my $a=shift;$a=~s/(..)/$1-/g;$a=~s/-$//;return $a;}],
"HEX4" => [qr{1.0.96.5.5
|0.0.96.240.\d+
|129.129.199.130.5}x, sub{my $a=shift;$a=~s/(....)/$1-/g;$a=~s/-$//;return $a;}],
"INFO" => [qr{1-0:0\.0\.[0-9]
|129.129.199.130.3}x, ""],
);
##################################### #####################################
sub OBIS_Initialize($) sub OBIS_Initialize($)
{ {
my ($hash) = @_; my ($hash) = @_;
require "$attr{global}{modpath}/FHEM/DevIo.pm"; require "$attr{global}{modpath}/FHEM/DevIo.pm";
$hash->{Match} = ".*"; $hash->{Match} = "OBIS";
$hash->{ReadFn} = "OBIS_Read"; $hash->{ReadFn} = "OBIS_Read";
$hash->{ReadyFn} = "OBIS_Ready"; $hash->{ReadyFn} = "OBIS_Ready";
$hash->{DefFn} = "OBIS_Define"; $hash->{DefFn} = "OBIS_Define";
$hash->{ParseFn} = "OBIS_Parse"; $hash->{ParseFn} = "OBIS_Parse";
# $hash->{SetFn} = "OBIS_Set";
$hash->{UndefFn} = "OBIS_Undef"; $hash->{UndefFn} = "OBIS_Undef";
$hash->{AttrFn} = "OBIS_Attr"; $hash->{AttrFn} = "OBIS_Attr";
$hash->{AttrList}= "do_not_notify:1,0 interval offset_feed offset_energy IODev channels alignTime pollingMode:on,off ". $hash->{AttrList}= "do_not_notify:1,0 interval offset_feed offset_energy IODev channels directions alignTime pollingMode:on,off unitReadings:on,off ".
$readingFnAttributes; $readingFnAttributes;
} }
@ -62,53 +85,68 @@ sub OBIS_Define($$)
return 'wrong syntax: define <name> OBIS devicename@baudrate[,databits,parity,stopbits]|none [MeterType]' return 'wrong syntax: define <name> OBIS devicename@baudrate[,databits,parity,stopbits]|none [MeterType]'
if(@a < 3); if(@a < 3);
# Log 3,Dumper(%readyfnlist);
DevIo_CloseDev($hash); DevIo_CloseDev($hash);
RemoveInternalTimer($hash); RemoveInternalTimer($hash);
my $name = $a[0]; my $name = $a[0];
my $dev = $a[2]; my $dev = $a[2];
my $type = $a[3]//"Standard"; my $type = $a[3]//"Unknown";
Log3 $hash,4,"OBIS ($name) - Define called...";
$hash->{DeviceName} = $dev; $hash->{DeviceName} = $dev;
$hash->{MeterType}=$type if (defined($type)); $hash->{MeterType}=$type if (defined($type));
my $device_name = "OBIS_".$name; my $device_name = "OBIS_".$name;
$modules{OBIS}{defptr}{$device_name} = $hash; $modules{OBIS}{defptr}{$device_name} = $hash;
Log3 $hash,4,"OBIS ($name) - Starting $name with Device $dev (Type $type).";
if($dev eq "none") { # If device="none", prepeare for an external IO-Module
if($dev=~/none|ext/) {
if (@a == 4){
my $device_name = "OBIS.".$a[4];
$hash->{CODE} = $a[4];
$modules{OBIS}{defptr}{$device_name} = $hash;
}
AssignIoPort($hash); AssignIoPort($hash);
Log3 ($hash,1, "OBIS ($name) - OBIS device is none, commands will be echoed only"); Log3 ($hash,1, "OBIS ($name) - OBIS device is none, commands will be echoed only");
return undef; return undef;
} }
my $baudrate; my $baudrate;
my $devi; my $devi;
($devi, $baudrate) = split("@", $dev); ($devi, $baudrate) = split("@", $dev);
if($baudrate =~ m/(\d+)(,([78])(,([NEO])(,([012]))?)?)?/) {
$baudrate = $1 if(defined($1)); if (defined($baudrate)) { ## added for ser2net connection
if($baudrate =~ m/(\d+)(,([78])(,([NEO])(,([012]))?)?)?/) {
$baudrate = $1 if(defined($1));
}
my %bd=("300"=>"0","600"=>"1","1200"=>"2","2400"=>"3","4800"=>"4","9600"=>"5");
$hash->{helper}{SPEED}=$bd{$baudrate};
} }
if ($baudrate==300) {$hash->{helper}{SPEED}="0"} else {$baudrate=9600; $hash->{helper}{SPEED}="5";}
if ($baudrate==600) {$hash->{helper}{SPEED}="1"}
if ($baudrate==1200) {$hash->{helper}{SPEED}="2"}
if ($baudrate==2400) {$hash->{helper}{SPEED}="3"}
if ($baudrate==4800) {$hash->{helper}{SPEED}="4"}
if ($baudrate==9600) {$hash->{helper}{SPEED}="5"}
my %devs= ( my %devs= (
# Name, Init-String, interval, 2ndInit # Name, Init-String, interval, 2ndInit
"Standard"=>["", -1, ""], "none" => ["", -1, ""],
"VSM102"=> ["/?!".chr(13).chr(10), 600, chr(6)."0".$hash->{helper}{SPEED}."0".chr(13).chr(10)], "Unknown" => ["", -1, ""],
"E110"=> ["/?!".chr(13).chr(10), 600, chr(6)."0".$hash->{helper}{SPEED}."0".chr(13).chr(10)], "SML" => ["", -1, ""],
"Ext" => ["", -1, ""],
"Standard" => ["", -1, ""],
"VSM102" => ["/?!".chr(13).chr(10), 600, chr(6)."0".$hash->{helper}{SPEED}."0".chr(13).chr(10)],
"E110" => ["/?!".chr(13).chr(10), 600, chr(6)."0".$hash->{helper}{SPEED}."0".chr(13).chr(10)],
); );
Log3 $hash,4,"OBIS ($name) - Baudrate is $baudrate"; if (!$devs{$type}) {return 'unknown meterType. Must be one of <nothing>, SML, Standard, VSM102, E110'};
if (!$devs{$type}) {return 'unknown meterType. Must be one of <nothing>, VSM102, E110'};
$devs{$type}[1] = $hash->{helper}{DEVICES}[1] // $devs{$type}[1]; $devs{$type}[1] = $hash->{helper}{DEVICES}[1] // $devs{$type}[1];
$hash->{helper}{DEVICES} =$devs{$type}; $hash->{helper}{DEVICES} =$devs{$type};
$hash->{helper}{TRIGGERTIME}=gettimeofday(); $hash->{helper}{TRIGGERTIME}=gettimeofday();
if( !$init_done ) {
$attr{$name}{"event-on-change-reading"} = ".*";
$attr{$name}{"event-min-interval"} = ".*:10";
}
my $t=OBIS_adjustAlign($hash,AttrVal($name,"alignTime",undef),$hash->{helper}{DEVICES}[1]); my $t=OBIS_adjustAlign($hash,AttrVal($name,"alignTime",undef),$hash->{helper}{DEVICES}[1]);
Log3 ($hash,4,"OBIS ($name) - Internal timer set to ".FmtDateTime($t)) if ($hash->{helper}{DEVICES}[1]>0); Log3 ($hash,5,"OBIS ($name) - Internal timer set to ".FmtDateTime($t)) if ($hash->{helper}{DEVICES}[1]>0);
InternalTimer($t, "GetUpdate", $hash, 0) if ($hash->{helper}{DEVICES}[1]>0); InternalTimer($t, "GetUpdate", $hash, 0) if ($hash->{helper}{DEVICES}[1]>0);
$hash->{helper}{EoM}=-1; $hash->{helper}{EoM}=-1;
Log3 $hash,4,"OBIS ($name) - Opening device..."; Log3 $hash,5,"OBIS ($name) - Opening device...";
return DevIo_OpenDev($hash, 0, "OBIS_Init"); return DevIo_OpenDev($hash, 0, "OBIS_Init");
} }
@ -120,12 +158,12 @@ sub GetUpdate($)
my $type= $hash->{MeterType}; my $type= $hash->{MeterType};
RemoveInternalTimer($hash); RemoveInternalTimer($hash);
my $t=OBIS_adjustAlign($hash,AttrVal($name,"alignTime",undef),$hash->{helper}{DEVICES}[1]);
Log3 ($hash,4,"OBIS ($name) - Internal timer set to ".FmtDateTime($t)) if ($hash->{helper}{DEVICES}[1]>0);
InternalTimer($t, "GetUpdate", $hash, 1) if ($hash->{helper}{DEVICES}[1]>0);
$hash->{helper}{EoM}=-1; $hash->{helper}{EoM}=-1;
if ($hash->{helper}{DEVICES}[1] eq "") {return undef;} if ($hash->{helper}{DEVICES}[1] eq "") {return undef;}
DevIo_SimpleWrite($hash,$hash->{helper}{DEVICES}[0],undef) ; DevIo_SimpleWrite($hash,$hash->{helper}{DEVICES}[0],undef) ;
my $t=OBIS_adjustAlign($hash,AttrVal($name,"alignTime",undef),$hash->{helper}{DEVICES}[1]);
Log3 ($hash,5,"OBIS ($name) - Internal timer set to ".FmtDateTime($t)) if ($hash->{helper}{DEVICES}[1]>0);
InternalTimer($t, "GetUpdate", $hash, 1) if ($hash->{helper}{DEVICES}[1]>0);
} }
sub OBIS_Init($) sub OBIS_Init($)
@ -148,74 +186,189 @@ sub OBIS_Read($)
my $name = $hash->{NAME}; my $name = $hash->{NAME};
my $buf = DevIo_SimpleRead($hash); my $buf = DevIo_SimpleRead($hash);
if ($hash->{helper}{EoM}!=1) {OBIS_Parse($hash,$buf);} OBIS_Parse($hash,$buf) if ($hash->{helper}{EoM}!=1);
return(undef); return(undef);
} }
sub OBIS_trySMLdecode($$)
{
my ( $hash, $remainingSML ) = @_;
if($remainingSML!~/[\x00-\x09|\x10-\x20]/g) {return $remainingSML} else {$hash->{MeterType}="SML"};
$remainingSML=uc(unpack('H*',$remainingSML));
$hash->{MeterType}="SML";
my $newMsg="";
while ($remainingSML=~/(1B1B1B1B010101.*?1B1B1B1B1A[0-9A-F]{6})/mip) {
my $msg=$1;
$remainingSML=${^POSTMATCH};
if (OBIS_CRC16($hash,pack('H*',$msg)) == 1) {
$remainingSML=""; #reset possible further messages if actual CRC ok; if someone misses some sessages we remove it.
my $OBISmsg="";
my $initstr="/";
my $OBISid=$msg=~m/7701([0-9A-F]*?)01/g;
(undef,undef,$OBISid,undef)=OBIS_decodeTL($1);
while ($msg =~ m/(7707)([0-9A-F]{2,999}?)(?=7707|1B1B1B1B)/g) {
my $telegramm = $&;
my @list=$telegramm=~/(7707)([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2,999})/g;
my $line=hex($list[1])."-".hex($list[2]).":".hex($list[3]).".".hex($list[4]).".".hex($list[5])."*255(";
my ($status,$statusL,$statusT,$valTime,$valTimeL,$valTimeT,$unit,$unitL,$unitT,$scaler,$scalerL,$scalerT,$data,$dataL,$dataT,$other);
($statusL,$statusT,$status,$other)=OBIS_decodeTL($list[7]);
($valTimeL,$valTimeT,$valTime,$other)=OBIS_decodeTL($other);
($unitL,$unitT,$unit,$other)=OBIS_decodeTL($other);
($scalerL,$scalerT,$scaler,$other)=OBIS_decodeTL($other);
($dataL,$dataT,$data,$other)=OBIS_decodeTL($other);
# Type String
my $line2="";
if ($dataT ==0 ) {
if($line=~$SML_specialities{"HEX4"}[0]) {
$line2=$SML_specialities{"HEX4"}[1]->($data)
} elsif($line=~$SML_specialities{"HEX2"}[0]) {
$line2=$SML_specialities{"HEX2"}[1]->($data)
} else {
$data=~s/([A-F0-9]{2})/chr(hex($1))/eg;
$line2="$data";
}
# Type signed (=80) and unsigned (=96) Integer
} elsif ($dataT & 0b01010000|$dataT & 0b01100000) {
$unit= $unit eq "1E" ? "Wh" :
$unit eq "1B" ? "W" :
$unit eq "21" ? "A" :
$unit eq "23" ? "V" :
$unit eq "2C" ? "Hz" :
$unit eq "01" ? "" :
$unit eq "1D" ? "varh" :
$unit eq "" ? "" : "var";
$scaler=$scaler ne "" ? 10**unpack("c", pack("C", hex($scaler))) : 0;
if ($scaler==0) {$scaler=1}; # just to make sure
$line2.="<" if ($status=~/[aA]2$/);
$line2.=">" if ($status=~/82$/);
$line2.=(OBIS_hex2int($data)*$scaler).($unit eq "" ? "" : "*$unit") if($dataT ==80);
$line2.=(unpack("i", pack("I", hex($data)))*$scaler).($unit eq "" ? "" : "*$unit") if($dataT ==96);
} elsif ($dataT & 0b01000000) { # Type Boolean - no Idea, where this is used
$line2=OBIS_hex2int($data); # 0=false, everything else is true
} elsif ($dataT & 0b01110000) { # Type List of.... - not sure, if we ever need that
}
$initstr.="$line2\\" if ($line=~$SML_specialities{"INFO"}[0]);
$newMsg.=$line.$line2.")\r\n";
###### TypeLength-Test ends here
}
$initstr=~s/\\$//;
$newMsg=$initstr.chr(13).chr(10).$newMsg;
$newMsg.="!".chr(13).chr(10);
} else {
$hash->{CRC_Errors}+=1;
}
}
return ($newMsg,pack('H*',$remainingSML));
}
sub OBIS_Parse($$) sub OBIS_Parse($$)
{ {
my ($hash, $buf) = @_; my ($hash, $buf) = @_;
my $buf2=uc(unpack('H*',$buf));
if($hash->{MeterType}!~/SML|Unknown/ && $buf2=~m/7701([0-9A-F]*?)01/g) {
Log 3,"OBIS_Ext called";
my (undef,undef,$OBISid,undef)=OBIS_decodeTL($1);
my $device_name = "OBIS.".$OBISid;
Log3 $hash,5,"New Devicename: $device_name";
my $def = $modules{OBIS}{defptr}{"$device_name"};
if(!$def) {
Log3 $hash, 3, "OBIS: Unknown device $device_name, please define it";
return "UNDEFINED $device_name OBIS none Ext $OBISid";
}
}
$hash->{helper}{BUFFER} .= $buf; $hash->{helper}{BUFFER} .= $buf;
return undef if(index($hash->{helper}{BUFFER},chr(13).chr(10)) == -1); if (length($hash->{helper}{BUFFER}) >5000) { #longer than 3 messages, this is a traffic jam
$hash->{helper}{BUFFER} =substr( $hash->{helper}{BUFFER} , -1500);
}
my %dir=("<"=>"out",">"=>"in");
my $buffer=$hash->{helper}{BUFFER};
my $remainingSML;
($buffer,$remainingSML) = OBIS_trySMLdecode($hash,$buffer) if ($hash->{MeterType}=~/SML|Ext|Unknown/);
return undef if(index($buffer,chr(13).chr(10)) == -1);
my $type= $hash->{MeterType}; my $type= $hash->{MeterType};
my $name = $hash->{NAME}; my $name = $hash->{NAME};
readingsBeginUpdate($hash); if(index($buffer,chr(13).chr(10)) ne -1){
while(index($hash->{helper}{BUFFER},chr(13).chr(10)) ne -1) readingsBeginUpdate($hash);
{ while(index($buffer,chr(13).chr(10)) ne -1)
my $rmsg=""; {
$rmsg = substr($hash->{helper}{BUFFER}, 0, index($hash->{helper}{BUFFER},chr(13).chr(10))); my $rmsg="";
Log3 $hash,5,"OBIS ($name) - Msg-Parse: $rmsg"; $rmsg = substr($buffer, 0, index($buffer,chr(13).chr(10)));
if($rmsg=~/\/.*|\d-\d{1,3}:\d{1,3}.\d{1,3}.\d{1,3}\*\d{1,3}\(.*?\)|!/) { Log3 $hash,5,"OBIS ($name) - Msg-Parse: $rmsg";
if($rmsg=~/^([23456789]+)-.*/) { if($rmsg=~/\/.*|\d-\d{1,3}:\d{1,3}.\d{1,3}.\d{1,3}\*\d{1,3}\(.*?\)|!/) {
Log3 $hash,3,"OBIS ($name) - Unknown OBIS-Message, please report: $rmsg"; if ($hash->{MeterType} eq "Unknown") {$hash->{MeterType}="Standard"}
} if($rmsg=~/^([23456789]+)-.*/) {
# End of Message Log3 $hash,3,"OBIS ($name) - Unknown OBIS-Device, please report: $rmsg".chr(13).chr(10)."Please report to User icinger at forum.fhem.de";
if ($rmsg=~/!.*/) { }
$hash->{helper}{EoM}+=1 if ($hash->{helper}{DEVICES}[1]>0);
}
#Version # End of Message
if ($rmsg=~ /.*\/(.*)/) { if ($rmsg=~/!.*/) {
DevIo_SimpleWrite($hash,$hash->{helper}{DEVICES}[2],undef) if (!$hash->{helper}{DEVICES}[2] eq ""); $hash->{helper}{EoM}+=1 if ($hash->{helper}{DEVICES}[1]>0);
if (ReadingsVal($name,"Version","") ne $1) {readingsBulkUpdate($hash, "Version" ,$1); } }
$hash->{helper}{EoM}=0;
}
if ($hash->{helper}{EoM}!=-1) {
for my $code (keys %OBIS_codes) {
if ($rmsg =~ $OBIS_codes{$code}) {
if ($code eq "Channels_sum") {
my $L=$hash->{helper}{Channels}{$1} // $OBIS_channels{$1} // "Unknown_Channel_$1";
readingsBulkUpdate($hash, "sum_$L",$2+0);
}
elsif ($code eq "Channels") { #Version
my $L=$hash->{helper}{Channels}{$1} // $OBIS_channels{$1} // "Unknown_Channel_$1"; if ($rmsg=~ /.*\/(.*)/) {
readingsBulkUpdate($hash, $L,$2+0); DevIo_SimpleWrite($hash,$hash->{helper}{DEVICES}[2],undef) if (!$hash->{helper}{DEVICES}[2] eq "");
} if (ReadingsVal($name,"Version","") ne $1) {readingsBulkUpdate($hash, "Version" ,$1); }
$hash->{helper}{EoM}=0;
}
if ($hash->{helper}{EoM}!=1) {
my @patterns=values %OBIS_codes;
if (!$rmsg~~@patterns) {
Log3 $hash,3,"OBIS ($name) - Unknown Message: $rmsg".chr(13).chr(10)."Please report to User icinger at forum.fhem.de"
} else {
for my $code (keys %OBIS_codes) {
if ($rmsg =~ $OBIS_codes{$code}) {
if ($code eq "Channels_sum") {
my $L=$hash->{helper}{Channels}{$1} // $OBIS_channels{$1} // "Unknown_Channel_$1";
readingsBulkUpdate($hash, "sum_$L",$3.(AttrVal($name,"unitReadings","off") eq "off"?"":" $4"));
readingsBulkUpdate($hash, "dir_sum_$L",$hash->{helper}{directions}{$2} // $dir{$2}) if (length $2);
}
elsif ($code eq "Counter") { elsif ($code eq "Channels") {
my $L=$hash->{helper}{Channels}{$2.".".$1} // $OBIS_channels{$2.".".$1} // "Unknown_Channel_$2.$1"; my $L=$hash->{helper}{Channels}{$1} // $OBIS_channels{$1} // "Unknown_Channel_$1";
if($1==1) { readingsBulkUpdate($hash, "$L",$3.(AttrVal($name,"unitReadings","off") eq "off"?"":" $4"));
readingsBulkUpdate($hash, $L ,$3 +AttrVal($name,"offset_energy",0)); readingsBulkUpdate($hash, "dir_$L",$hash->{helper}{directions}{$2} // $dir{$2}) if (length $2);
} elsif ($1==2) { }
readingsBulkUpdate($hash, $L ,$3 +AttrVal($name,"offset_feed",0));
}
} elsif (ReadingsVal($name,$code,"") ne $1) elsif ($code eq "Counter") {
{readingsBulkUpdate($hash, $code ,$1); } my $L=$hash->{helper}{Channels}{$1.".".$2} // $OBIS_channels{$1.".".$2} // "Unknown_Channel_$1.$2";
} my $chan=$3+0 > 0 ? "_Ch$3" : "";
} if($1==1) {
} readingsBulkUpdate($hash, $L.$chan ,$5 +AttrVal($name,"offset_energy",0).(AttrVal($name,"unitReadings","off") eq "off"?"":" $6"));
} } elsif ($1==2) {
$hash->{helper}{BUFFER} = substr($hash->{helper}{BUFFER}, index($hash->{helper}{BUFFER},chr(13).chr(10))+2);; readingsBulkUpdate($hash, $L.$chan ,$5 +AttrVal($name,"offset_feed",0).(AttrVal($name,"unitReadings","off") eq "off"?"":" $6"));
if($hash->{helper}{EoM}==1) { last;} }
} readingsBulkUpdate($hash, "dir_$L",$hash->{helper}{directions}{$4} // $dir{$4}) if (length $4);
readingsEndUpdate($hash,1);
# Log 3,"Size of Buffer: ".length($hash->{helper}{BUFFER}); } else
{readingsBulkUpdate($hash, $code ,$1);
}
}
}
}
}
if ($hash->{helper}{EoM}==1) {last;}
}
$buffer = substr($buffer, index($buffer,chr(13).chr(10))+2);;
}
readingsEndUpdate($hash,1);
}
if (defined($remainingSML)) {$hash->{helper}{BUFFER}=$remainingSML}
else{$hash->{helper}{BUFFER}=$buffer;}
if($hash->{helper}{EoM}==1) { $hash->{helper}{BUFFER}="";} if($hash->{helper}{EoM}==1) { $hash->{helper}{BUFFER}="";}
return $name; return $name;
} }
##################################### #####################################
sub OBIS_Ready($) sub OBIS_Ready($)
{ {
@ -262,14 +415,20 @@ sub OBIS_Attr(@)
$hash->{helper}{Channels}=undef; $hash->{helper}{Channels}=undef;
} }
} }
if ($aName eq "directions") {
$hash->{helper}{directions}=eval $aVal;
if ($@) {
Log3 $name, 3, "OBIS ($name) - X: Invalid regex in attr $name $aName $aVal: $@";
$hash->{helper}{directions}=undef;
}
}
if ($aName eq "interval") { if ($aName eq "interval") {
if ($aVal=~/^[1-9][0-9]*$/) { if ($aVal=~/^[1-9][0-9]*$/) {
$hash->{helper}{TRIGGERTIME}=gettimeofday(); $hash->{helper}{TRIGGERTIME}=gettimeofday();
RemoveInternalTimer($hash); RemoveInternalTimer($hash);
$hash->{helper}{DEVICES}[1]=$aVal; $hash->{helper}{DEVICES}[1]=$aVal;
# InternalTimer(gettimeofday()+2, "GetUpdate", $hash, 1) if ($aVal>0);
my $t=OBIS_adjustAlign($hash,AttrVal($name,"alignTime",undef),$hash->{helper}{DEVICES}[1]); my $t=OBIS_adjustAlign($hash,AttrVal($name,"alignTime",undef),$hash->{helper}{DEVICES}[1]);
Log3 ($hash,4,"OBIS ($name) - Internal timer set to ".FmtDateTime($t)) if ($hash->{helper}{DEVICES}[1]>0); Log3 ($hash,5,"OBIS ($name) - Internal timer set to ".FmtDateTime($t)) if ($hash->{helper}{DEVICES}[1]>0);
InternalTimer($t, "GetUpdate", $hash, 0) if ($hash->{helper}{DEVICES}[1]>0); InternalTimer($t, "GetUpdate", $hash, 0) if ($hash->{helper}{DEVICES}[1]>0);
} else { } else {
return "OBIS ($name) - $name: attr interval must be a number -> $aVal"; return "OBIS ($name) - $name: attr interval must be a number -> $aVal";
@ -281,7 +440,7 @@ sub OBIS_Attr(@)
RemoveInternalTimer($hash); RemoveInternalTimer($hash);
$hash->{helper}{TRIGGERTIME}=gettimeofday(); $hash->{helper}{TRIGGERTIME}=gettimeofday();
my $t=OBIS_adjustAlign($hash,$aVal,$hash->{helper}{DEVICES}[1]); my $t=OBIS_adjustAlign($hash,$aVal,$hash->{helper}{DEVICES}[1]);
Log3 ($hash,4,"OBIS ($name) - Internal timer set to ".FmtDateTime($t)); Log3 ($hash,5,"OBIS ($name) - Internal timer set to ".FmtDateTime($t));
InternalTimer($t, "GetUpdate", $hash, 0); InternalTimer($t, "GetUpdate", $hash, 0);
} else {return "OBIS ($name): attr alignTime must be a Value >0"} } else {return "OBIS ($name): attr alignTime must be a Value >0"}
} else { } else {
@ -309,11 +468,11 @@ sub OBIS_Attr(@)
sub OBIS_adjustAlign($$$) sub OBIS_adjustAlign($$$)
{ {
my($hash, $attrVal, $interval) = @_; my($hash, $attrVal, $interval) = @_;
# return gettimeofday()+$interval;
if (!$attrVal) {return gettimeofday()+$interval;} if (!$attrVal) {return gettimeofday()+$interval;}
my ($alErr, $alHr, $alMin, $alSec, undef) = GetTimeSpec($attrVal); # "00:00" my ($alErr, $alHr, $alMin, $alSec, undef) = GetTimeSpec($attrVal);
return "$hash->{NAME} alignTime: $alErr" if($alErr); return "$hash->{NAME} alignTime: $alErr" if($alErr);
my $tspec=strftime("\%H:\%M:\%S", gmtime($interval)); my $tspec=strftime("\%H:\%M:\%S", gmtime($interval));
# Obis_adjustAlignTimetest2($hash,AttrVal($hash->{NAME},"alignTime",undef),$hash->{helper}{DEVICES}[1]);
my (undef, $hr, $min, $sec, undef) = GetTimeSpec($tspec); my (undef, $hr, $min, $sec, undef) = GetTimeSpec($tspec);
my $now = time(); my $now = time();
@ -331,14 +490,78 @@ sub OBIS_adjustAlign($$$)
$off += $step; $off += $step;
} }
$ttime += ($alTime-$off); $ttime += ($alTime-$off);
$ttime += $step if($ttime < $now); $ttime += $step if($ttime <= $now);
$hash->{NEXT} = FmtDateTime($ttime); $hash->{NEXT} = FmtDateTime($ttime);
$hash->{helper}{TRIGGERTIME} = ($off2<=$alTime) ? $ttime : (gettimeofday());#-fhemTzOffset($now)); $hash->{helper}{TRIGGERTIME} = ($off2<=$alTime) ? $ttime : (gettimeofday());
return $hash->{helper}{TRIGGERTIME}; return $hash->{helper}{TRIGGERTIME};
} }
##################################### sub OBIS_hex2int {
my ($hexstr) = @_;
return 0
if $hexstr !~ /^[0-9A-Fa-f]{1,35}$/;
my $num = hex($hexstr);
return $num >> 31 ? $num - 2 ** 32 : $num;
}
sub OBIS_CRC16($$) {
my ($hash,$buff)=@_;
my @crc16 = ( 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, 0x8c48,
0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c,
0x75b7, 0x643e, 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, 0x2102, 0x308b, 0x0210,
0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef,
0xea66, 0xd8fd, 0xc974, 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, 0xce4c, 0xdfc5,
0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3,
0x263a, 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, 0x6306, 0x728f, 0x4014, 0x519d,
0x2522, 0x34ab, 0x0630, 0x17b9, 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, 0x7387,
0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862,
0x9af9, 0x8b70, 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, 0x0840, 0x19c9, 0x2b52,
0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e,
0xf2a7, 0xc03c, 0xd1b5, 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, 0xb58b, 0xa402,
0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5,
0x4d7c, 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, 0x4a44, 0x5bcd, 0x6956, 0x78df,
0x0c60, 0x1de9, 0x2f72, 0x3efb, 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, 0x5ac5,
0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3,
0x8238, 0x93b1, 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, 0xf78f, 0xe606, 0xd49d,
0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78);
my $crc=0xFFFF;
my $a=substr($buff,0,-2);
my $b=substr($buff,-2);
my $crc2=OBIS_hex2int(uc(unpack('H*',$b)));
foreach (split //, $a) {
$crc = ($crc >> 8) ^ $crc16[($crc ^ ord($_)) & 0xff];
}
$crc ^= 0xffff;
$crc = (($crc & 0xff) << 8) | (($crc & 0xff00) >> 8);
return $crc2==$crc ? 1 : 0;
}
###############################################
# Input: Whole Datastream, inkl. TL-Byte #
# Output: Length, Type, Value, remaining Data #
###############################################
sub OBIS_decodeTL($){
my ($msg)=@_;
my $msgLength=0;
my $msgType=0;
my $lt="";
$msgType =hex(substr($msg,0,2)) & 0b01110000;
do {
$lt=hex(substr($msg,0,2));
$msg=substr($msg,2);
$msgLength=($msgLength*16) + ($lt & 0b00001111);
} while ($lt & 0b10000000);
$msgLength-=1;
my $valu=substr($msg,0,$msgLength*2);
$msg=substr($msg,$msgLength*2);
return $msgLength,$msgType,$valu,$msg;
}
"Cogito, ergo sum."; "Cogito, ergo sum.";
@ -348,7 +571,7 @@ sub OBIS_adjustAlign($$$)
<a name="OBIS"></a> <a name="OBIS"></a>
<h3>OBIS</h3> <h3>OBIS</h3>
This module is for SmartMeter, that report thier data in OBIS-Standard. This module is for SmartMeters, that report their data in OBIS-Standard. It dosen't matter, wether the data comes as PlainText or SML-encoded.
<br> <br>
<b>Define</b> <b>Define</b>
<code>define &lt;name&gt; OBIS device|none [MeterType] </code><br> <code>define &lt;name&gt; OBIS device|none [MeterType] </code><br>
@ -359,7 +582,9 @@ sub OBIS_adjustAlign($$$)
<br><br> <br><br>
Optional:MeterType can be of Optional:MeterType can be of
<ul><li>VSM102 -&gt; Voltcraft VSM102</li> <ul><li>VSM102 -&gt; Voltcraft VSM102</li>
<li>E110 -&gt; Landis&&;Gyr E110</li></ul> <li>E110 -&gt; Landis&&;Gyr E110</li>
<li>Standard -&gt; Data comes as plainText</li>
<li>SML -&gt; Smart Message Language</li></ul>
<br> <br>
Example: <br> Example: <br>
<code>define myPowerMeter OBIS /dev/ttyPlugwise@@9600,7,E,1 VSM102</code> <code>define myPowerMeter OBIS /dev/ttyPlugwise@@9600,7,E,1 VSM102</code>
@ -383,11 +608,30 @@ sub OBIS_adjustAlign($$$)
<li>41-->Phase 2</li> <li>41-->Phase 2</li>
<li>61-->Phase 3</li></ul> <li>61-->Phase 3</li></ul>
You can change that for example to: You can change that for example to:
<code>attr myOBIS channels {"2"=>"L1", "4"=>"L2", "6"=>"L3"}></code> <code>attr myOBIS channels {"2"=>"L1", "4"=>"L2", "6"=>"L3"}</code>
<br><br></li><li> <br><br></li>
<li><code>directions</code><br>
Some Meters report feeding/comnsuming of power in a statusword.
If this is set, you get an extra reading dir_total_consumption which defaults to "in" and "out"
Here, you can change this text with:
<code>attr myOBIS directions {">" => "pwr consuming", "<"=>"pwr feeding"}</code>
</li>
<li>
<code>interval</code><br> <code>interval</code><br>
The polling-interval in seconds The polling-interval in seconds
</li> </li>
<li>
<code>alignTime</code><br>
Aligns the intervals to a given time. Each interval is repeatedly calculated.
So if alignTime=00:00 and interval=600 aligns the interval to xx:00:00, xx:10:00, xx:20:00 etc....
<code>pollingMode</code><br>
Changes from direct-read to polling-mode.
Useful with meters, that send a continous datastream.
Reduces CPU-load.
<code>unitReadings</code><br>
Adds the units to the readings like w, wH, A etc.
</li>
<br> <br>
</ul> </ul>
@ -397,7 +641,7 @@ sub OBIS_adjustAlign($$$)
<a name="OBIS"></a> <a name="OBIS"></a>
<h3>OBIS</h3> <h3>OBIS</h3>
Modul für Smartmeter, die ihre Daten im OBIS-Standard senden. Modul für Smartmeter, die ihre Daten im OBIS-Standard senden. Hierbei ist es egal, ob die Daten als reiner Text oder aber SML-kodiert kommen.
<br> <br>
<b>Define</b> <b>Define</b>
<code>define &lt;name&gt; OBIS device|none [MeterType] </code><br> <code>define &lt;name&gt; OBIS device|none [MeterType] </code><br>
@ -406,7 +650,9 @@ sub OBIS_adjustAlign($$$)
<br><br> <br><br>
Optional:MeterType kann sein: Optional:MeterType kann sein:
<ul><li>VSM102 -&gt; Voltcraft VSM102</li> <ul><li>VSM102 -&gt; Voltcraft VSM102</li>
<li>E110 -&gt; Landis&&;Gyr E110</li></ul> <li>E110 -&gt; Landis&&;Gyr E110</li>
<li>Standard -&gt; Daten kommen als plainText</li>
<li>SML -&gt; Smart Message Language</li></ul>
<br> <br>
Beispiel: <br> Beispiel: <br>
<code>define myPowerMeter OBIS /dev/ttyPlugwise@@9600,7,E,1 VSM102</code> <code>define myPowerMeter OBIS /dev/ttyPlugwise@@9600,7,E,1 VSM102</code>
@ -431,17 +677,26 @@ sub OBIS_adjustAlign($$$)
<li>61-->Phase 3</li></ul> <li>61-->Phase 3</li></ul>
Beispiel: Beispiel:
<code>attr myOBIS channels {"2"=>"L1", "4"=>"L2", "6"=>"L3"}></code> <code>attr myOBIS channels {"2"=>"L1", "4"=>"L2", "6"=>"L3"}></code>
<br><br></li><li> <br><br>
</li>
<li><code>directions</code><br>
Manche SmartMeter senden im Statusbyte die Stromrichtung.
In diesem Fall gibt es ein extra Reading "dir_total_consumption" welches standardmäßig "in" and "out" beinhaltet
Hiermit kann dieser Text geändert werden:
<code>attr myOBIS directions {">" => "pwr consuming", "<"=>"pwr feeding"}</code>
</li><li>
<code>interval</code><br> <code>interval</code><br>
Abrufinterval der Daten. Abrufinterval der Daten.
</li><li> </li><li>
<code>aglignTime</code><br> <code>algignTime</code><br>
Richtet den Zeitpunkt von <interval> nach einer bestimmten Uhrzeit aus. Richtet den Zeitpunkt von <interval> nach einer bestimmten Uhrzeit aus.
</li><li> </li><li>
<code>pollingMode</code><br> <code>pollingMode</code><br>
Hiermit wird von Direktbenachrichtigung auf Polling umgestellt. Hiermit wird von Direktbenachrichtigung auf Polling umgestellt.
Bei Smartmetern, welche von selbst im Sekundentakt senden, Bei Smartmetern, welche von selbst im Sekundentakt senden,
kann das zu einer spürbaren Senkung der Prozessorleistung führen. kann das zu einer spürbaren Senkung der Prozessorleistung führen.
<code>unitReadings</code><br>
Hängt bei den Readings auch die Einheiten an, zB w, wH, A usw.
</li> </li>
<br> <br>
</ul> </ul>