mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-03-12 16:46:35 +00:00
git-svn-id: https://svn.fhem.de/fhem/trunk@2703 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
1627ad8879
commit
15af99f98e
@ -437,8 +437,13 @@ sub OWAD_FormatValues($) {
|
||||
} else {
|
||||
$vval = "???";
|
||||
}
|
||||
|
||||
#-- low alarm value
|
||||
$vlow =$owg_vlow[$i];
|
||||
$main::attr{$name}{$owg_fixed[$i]."Low"}=$vlow;
|
||||
#-- high alarm value
|
||||
$vhigh=$owg_vhigh[$i];
|
||||
$main::attr{$name}{$owg_fixed[$i]."High"}=$vhigh;
|
||||
|
||||
#-- string buildup for return value, STATE and alarm
|
||||
$svalue .= sprintf( "%s: %5.3f %s", $owg_channel[$i], $vval,$unarr[1]);
|
||||
@ -795,7 +800,7 @@ sub OWAD_Set($@) {
|
||||
if($value ne "none" && $value ne "low" && $value ne "high" && $value ne "both");
|
||||
#-- put into attribute value
|
||||
if( $main::attr{$name}{$owg_fixed[$channo]."Alarm"} ne $value ){
|
||||
Log 1,"OWAD: Correcting attribute value ".$owg_fixed[$channo]."Alarm";
|
||||
#Log 1,"OWAD: Correcting attribute value ".$owg_fixed[$channo]."Alarm";
|
||||
$main::attr{$name}{$owg_fixed[$channo]."Alarm"} = $value
|
||||
}
|
||||
#-- put into device
|
||||
@ -1472,7 +1477,8 @@ sub OWXAD_SetPage($$) {
|
||||
alarm. </li>
|
||||
<li>Standard attributes <a href="#alias">alias</a>, <a href="#comment">comment</a>, <a
|
||||
href="#event-on-update-reading">event-on-update-reading</a>, <a
|
||||
href="#event-on-change-reading">event-on-change-reading</a>, <a href="#room"
|
||||
href="#event-on-change-reading">event-on-change-reading</a>, <a
|
||||
href="#stateFormat">stateFormat</a>, <a href="#room"
|
||||
>room</a>, <a href="#eventMap">eventMap</a>, <a href="#loglevel">loglevel</a>,
|
||||
<a href="#webCmd">webCmd</a></li>
|
||||
</ul>
|
||||
|
@ -36,7 +36,7 @@
|
||||
# Additional attributes are defined in fhem.cfg, in some cases per channel, where <channel>=A,B
|
||||
# Note: attributes are read only during initialization procedure - later changes are not used.
|
||||
#
|
||||
# attr <name> UnitInReading = whether the physical unit is written into the reading = 1 (default) or 0
|
||||
# attr <name> LogM <string> = device name (not file name) of monthly log file
|
||||
# attr <name> <channel>Name <string>|<string> = name for the channel | a type description for the measured value
|
||||
# attr <name> <channel>Unit <string>|<string> = unit of measurement for this channel | its abbreviation
|
||||
# attr <name> <channel>Offset <float> = offset added to the reading in this channel
|
||||
@ -132,8 +132,8 @@ sub OWCOUNT_Initialize ($) {
|
||||
$hash->{SetFn} = "OWCOUNT_Set";
|
||||
|
||||
#-- see header for attributes
|
||||
my $attlist = "IODev do_not_notify:0,1 showtime:0,1 model:DS2423 loglevel:0,1,2,3,4,5 ".
|
||||
"event-on-update-reading event-on-change-reading ";
|
||||
my $attlist = "IODev do_not_notify:0,1 showtime:0,1 model:DS2423 loglevel:0,1,2,3,4,5 LogM ".
|
||||
$readingFnAttributes;
|
||||
for( my $i=0;$i<int(@owg_fixed);$i++ ){
|
||||
$attlist .= " ".$owg_fixed[$i]."Name";
|
||||
$attlist .= " ".$owg_fixed[$i]."Offset";
|
||||
@ -351,6 +351,7 @@ sub OWCOUNT_FormatValues($) {
|
||||
my $tn = TimeNow();
|
||||
my ($sec, $min, $hour, $day, $month, $year, $wday,$yday,$isdst) = localtime(time);
|
||||
my ($seco,$mino,$houro,$dayo,$montho,$yearo,$dayrest);
|
||||
my ($dt,$dv,$dval,$delt,$delf);
|
||||
my $daybreak = 0;
|
||||
my $monthbreak = 0;
|
||||
|
||||
@ -372,11 +373,11 @@ sub OWCOUNT_FormatValues($) {
|
||||
$period = $hash->{READINGS}{"$owg_channel[$i]"}{PERIOD};
|
||||
$runit = $hash->{READINGS}{"$owg_rate[$i]"}{UNITABBR};
|
||||
|
||||
#-- skip some thing if undefined
|
||||
#-- skip some things if undefined
|
||||
if( $owg_val[$i] eq ""){
|
||||
$svalue .= $owg_channel[$i].": ???";
|
||||
}else{
|
||||
#-- only if attribute value Mode=daily, take the midnight value from memory
|
||||
#-- only if attribute value mode=daily, take the midnight value from memory
|
||||
if( defined($attr{$name}{$owg_fixed[$i]."Mode"} )){
|
||||
if( $attr{$name}{$owg_fixed[$i]."Mode"} eq "daily"){
|
||||
$midnight = $owg_midnight[$i];
|
||||
@ -407,43 +408,44 @@ sub OWCOUNT_FormatValues($) {
|
||||
|
||||
#-- safeguard against the case where no previous measurement
|
||||
if( length($oldtim) > 0 ){
|
||||
#-- time difference in seconds
|
||||
#-- previous measurement time
|
||||
($yearo,$montho,$dayrest) = split(/-/,$oldtim);
|
||||
$dayo = substr($dayrest,0,2);
|
||||
($houro,$mino,$seco) = split(/:/,substr($dayrest,3));
|
||||
my $delt = ($hour-$houro)*3600 + ($min-$mino)*60 + ($sec-$seco);
|
||||
#-- debugging: changing the day every 10 minutes
|
||||
#$delt =
|
||||
#-- correct time for wraparound at midnight
|
||||
if( ($delt<0) && ($present==1)){
|
||||
|
||||
#-- time dfifference to previous measurement and to midnight
|
||||
$delt = ($hour-$houro)*3600 + ($min-$mino)*60 + ($sec-$seco);
|
||||
$delf = $hour *3600 + $min *60 + $sec - 86400;
|
||||
if( ($delf+$hash->{INTERVAL}) >= 0 ){
|
||||
$daybreak = 1;
|
||||
$delt += 86400;
|
||||
#-- Timer data from tomorrow
|
||||
my ($secn,$minn,$hourn,$dayn,$monthn,$yearn,$wdayn,$ydayn,$isdstn) = localtime(time() + 24*60*60);
|
||||
#-- Check, whether we have a new month
|
||||
if( (($delf+$hash->{INTERVAL}) > 0) && ($dayn == 1) ){
|
||||
$monthbreak =1;
|
||||
}
|
||||
}
|
||||
|
||||
#-- correct $vval for wraparound of 32 bit counter
|
||||
if( ($vval < $oldval) && ($daybreak==0) && ($present==1) ){
|
||||
Log 1,"OWCOUNT TODO: Counter wraparound";
|
||||
}
|
||||
|
||||
if( $daybreak==1 ){
|
||||
#-- linear interpolation
|
||||
my $dt = ((24-$houro)*3600 -$mino*60 - $seco)/( ($hour+24-$houro)*3600 + ($min-$mino)*60 + ($sec-$seco) );
|
||||
my $dv = $oldval*(1-$dt)+$vval*$dt;
|
||||
#-- correct reading in daily mode
|
||||
if( $midnight > 0.0 ){
|
||||
$vval -= $dv;
|
||||
$delt *= (1-$dt);
|
||||
$oldval = 0.0;
|
||||
}
|
||||
#-- linear extrapolation
|
||||
$dt = -$delf/$delt;
|
||||
$dv = ($vval-$oldval)*$dt;
|
||||
$dval = $vval+$dv;
|
||||
|
||||
#-- in any mode store the interpolated value in the midnight store
|
||||
$midnight += $dv;
|
||||
OWXCOUNT_SetPage($hash,14+$i,sprintf("%f",$midnight));
|
||||
OWXCOUNT_SetPage($hash,14+$i,sprintf("%f",$dval));
|
||||
#-- string buildup for monthly logging
|
||||
$dvalue .= sprintf( "%s: %5.1f %s", $owg_channel[$i], $dv,$unit);
|
||||
$dvalue .= sprintf( "%s: %5.1f %s", $owg_channel[$i], $dval,$unit);
|
||||
if( $day<$dayo ){
|
||||
$monthbreak = 1;
|
||||
Log 1, "OWCOUNT: Change of month";
|
||||
#-- string buildup for yearly logging
|
||||
$mvalue .= sprintf( "%s: %5.1f %s", $owg_channel[$i], $dv,$unit);
|
||||
$mvalue .= sprintf( "%s: %5.1f %s", $owg_channel[$i], $dval,$unit);
|
||||
}
|
||||
}
|
||||
#-- rate
|
||||
@ -480,24 +482,25 @@ sub OWCOUNT_FormatValues($) {
|
||||
#-- insert space
|
||||
if( $i<int(@owg_fixed)-1 ){
|
||||
$svalue .= " ";
|
||||
$dvalue .= " ";
|
||||
}
|
||||
}
|
||||
#-- STATE
|
||||
readingsBulkUpdate($hash,"state",$svalue);
|
||||
|
||||
|
||||
#-- Daily/monthly cumulated value
|
||||
if( $daybreak == 1 ){
|
||||
$svalue = sprintf("D_%d",$dayo);
|
||||
$dvalue = sprintf("%d-%d-%d_23:59:59 %s",$yearo,$montho,$dayo,$dvalue);
|
||||
readingsBulkUpdate($hash,$svalue,$dvalue);
|
||||
Log 1,$name." ".$dvalue;
|
||||
#-- TODO: recall the monthly summary
|
||||
#--my @month = OWCOUNT_GetMonth($hash);
|
||||
#my $total = $month[0]+$vval;
|
||||
readingsBulkUpdate($hash,"day",$dvalue);
|
||||
if( $monthbreak == 1){
|
||||
$svalue = sprintf("M_%d",$montho);
|
||||
$mvalue = sprintf("%d-%d-%d_23:59:59 %s",$yearo,$montho,$dayo,$mvalue);
|
||||
readingsBulkUpdate($hash,$svalue,$mvalue);
|
||||
Log 1,$name." ".$mvalue;
|
||||
$mvalue = sprintf("M_%02d SOME VALUE",$month);
|
||||
readingsBulkUpdate($hash,"month",$mvalue);
|
||||
Log 1,$name." has monthbreak ".$mvalue;
|
||||
}
|
||||
}
|
||||
|
||||
#-- STATE
|
||||
readingsBulkUpdate($hash,"state",$svalue);
|
||||
readingsEndUpdate($hash,1);
|
||||
|
||||
return $svalue;
|
||||
@ -645,6 +648,74 @@ sub OWCOUNT_Get($@) {
|
||||
|
||||
}
|
||||
|
||||
########################################################################################
|
||||
#
|
||||
# OWCOUNT_GetMonth Read monthly data from a file
|
||||
#
|
||||
# Parameter hash
|
||||
#
|
||||
# Returns total value up to last day, including this day and average including this day
|
||||
#
|
||||
########################################################################################
|
||||
|
||||
sub OWCOUNT_GetMonth($) {
|
||||
|
||||
my ($hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
my $regexp = ".*$name.*";
|
||||
my $val;
|
||||
my @month = ();
|
||||
my @month2 = ();
|
||||
my @channel;
|
||||
my ($total,$total2,$deltim,$av);
|
||||
|
||||
#-- Check current logfile
|
||||
my $ln = $attr{$name}{"LogM"};
|
||||
if( !(defined($ln))){
|
||||
Log 1,"OWCOUNT_GetMonth: Attribute LogM is missing";
|
||||
return undef;
|
||||
} else {
|
||||
my $lf = $defs{$ln}{currentlogfile};
|
||||
my $ret = open(OWXFILE, "< $lf" );
|
||||
if( $ret) {
|
||||
while( <OWXFILE> ){
|
||||
#-- line looks as
|
||||
# 2013-02-09_23:59:31 <name> day D_09 <aname>: 180.0 cts <bname>: 180.0 cts etc.
|
||||
my $line = $_;
|
||||
chomp($line);
|
||||
if ( $line =~ m/$regexp/i){
|
||||
my @linarr = split(' ',$line);
|
||||
my $day = $linarr[3];
|
||||
$day =~ s/D_0+//;
|
||||
@channel = ();
|
||||
for (my $i=0;$i<int(@owg_fixed);$i++){
|
||||
$val = $linarr[5+3*$i];
|
||||
push(@channel,$val);
|
||||
}
|
||||
push(@month,[@channel]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#-- sum and average
|
||||
for (my $i=0;$i<int(@owg_fixed);$i++){
|
||||
$total = 0.0;
|
||||
for (my $j=0;$j<int(@month);$j++){
|
||||
$total += $month[$j][$i];
|
||||
}
|
||||
#-- add data from current day
|
||||
$total = int($total*100)/100;
|
||||
my ($sec,$min,$hour,$day,$month,$year,$wday,$yday,$isdst) = localtime(time);
|
||||
my $deltim = ($hour+$min/60.0 + $sec/3600.0)/24.0;
|
||||
my $total2 = int(100*($total+$hash->{READINGS}{"$owg_channel[$i]"}{VAL}))/100;
|
||||
my $av = int(100*$total2/(int(@month)+$deltim))/100;
|
||||
|
||||
push(@month2,[($total,$total2,$av)]);
|
||||
}
|
||||
return @month2;
|
||||
}
|
||||
}
|
||||
|
||||
#######################################################################################
|
||||
#
|
||||
# OWCOUNT_GetValues - Updates the reading from one device
|
||||
@ -1168,6 +1239,11 @@ sub OWXCOUNT_SetPage($$$) {
|
||||
<br />
|
||||
<a name="OWCOUNTattr"></a>
|
||||
<h4>Attributes</h4>
|
||||
<ul>
|
||||
<li><a name="owcount_logm"><code>attr <name> LogM
|
||||
<string>|</code></a>
|
||||
<br />device name (not file name) of monthly log file.</li>
|
||||
</ul>
|
||||
<p>For each of the following attributes, the channel identification A,B may be used.</p>
|
||||
<ul>
|
||||
<li><a name="owcount_cname"><code>attr <name> <channel>Name
|
||||
@ -1187,7 +1263,8 @@ sub OWXCOUNT_SetPage($$$) {
|
||||
<br />factor multiplied to (reading+offset) in this channel. </li>
|
||||
<li>Standard attributes <a href="#alias">alias</a>, <a href="#comment">comment</a>, <a
|
||||
href="#event-on-update-reading">event-on-update-reading</a>, <a
|
||||
href="#event-on-change-reading">event-on-change-reading</a>, <a href="#room"
|
||||
href="#event-on-change-reading">event-on-change-reading</a>, <a
|
||||
href="#stateFormat">stateFormat</a>, <a href="#room"
|
||||
>room</a>, <a href="#eventMap">eventMap</a>, <a href="#loglevel">loglevel</a>,
|
||||
<a href="#webCmd">webCmd</a></li>
|
||||
</ul>
|
||||
|
@ -985,7 +985,8 @@ sub OWXMULTI_SetValues($@) {
|
||||
<br />unit of measurement (temperature scale), default is Celsius = °C </li>
|
||||
<li>Standard attributes <a href="#alias">alias</a>, <a href="#comment">comment</a>, <a
|
||||
href="#event-on-update-reading">event-on-update-reading</a>, <a
|
||||
href="#event-on-change-reading">event-on-change-reading</a>, <a href="#room"
|
||||
href="#event-on-change-reading">event-on-change-reading</a>, <a
|
||||
href="#stateFormat">stateFormat</a>, <a href="#room"
|
||||
>room</a>, <a href="#eventMap">eventMap</a>, <a href="#loglevel">loglevel</a>,
|
||||
<a href="#webCmd">webCmd</a></li>
|
||||
</ul>
|
||||
|
@ -1198,7 +1198,8 @@ sub OWXSWITCH_SetState($$) {
|
||||
<br />display for on | off condition </li>
|
||||
<li>Standard attributes <a href="#alias">alias</a>, <a href="#comment">comment</a>, <a
|
||||
href="#event-on-update-reading">event-on-update-reading</a>, <a
|
||||
href="#event-on-change-reading">event-on-change-reading</a>, <a href="#room"
|
||||
href="#event-on-change-reading">event-on-change-reading</a>, <a
|
||||
href="#stateFormat">stateFormat</a>, <a href="#room"
|
||||
>room</a>, <a href="#eventMap">eventMap</a>, <a href="#loglevel">loglevel</a>,
|
||||
<a href="#webCmd">webCmd</a></li>
|
||||
</ul>
|
||||
|
@ -142,7 +142,6 @@ sub OWTHERM_Define ($$) {
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
my ($name,$model,$fam,$id,$crc,$interval,$ret);
|
||||
my $tn = TimeNow();
|
||||
|
||||
#-- default
|
||||
$name = $a[0];
|
||||
@ -334,8 +333,8 @@ sub OWTHERM_FormatValues($) {
|
||||
|
||||
#-- correct values for proper offset, factor
|
||||
$vval = ($owg_temp + $offset)*$factor;
|
||||
$vlow = ($owg_tl + $offset)*$factor;
|
||||
$vhigh = ($owg_th + $offset)*$factor;
|
||||
$vlow = floor(($owg_tl + $offset)*$factor+0.5);
|
||||
$vhigh = floor(($owg_th + $offset)*$factor+0.5);
|
||||
|
||||
$main::attr{$name}{"tempLow"} = $vlow;
|
||||
$main::attr{$name}{"tempHigh"} = $vhigh;
|
||||
@ -553,30 +552,35 @@ sub OWTHERM_Set($@) {
|
||||
|
||||
#-- set tempLow or tempHigh
|
||||
if( (lc($key) eq "templow") || (lc($key) eq "temphigh")) {
|
||||
#-- First we have to read the current data, because alarms may not be set independently
|
||||
OWTHERM_GetValues($hash);
|
||||
|
||||
|
||||
my $interface = $hash->{IODev}->{TYPE};
|
||||
my $offset = defined($hash->{tempf}{offset}) ? $hash->{tempf}{offset} : 0.0;
|
||||
my $factor = defined($hash->{tempf}{factor}) ? $hash->{tempf}{factor} : 1.0;
|
||||
|
||||
#-- Only integer values are allowed
|
||||
$value = floor($value+0.5);
|
||||
|
||||
#-- First we have to read the current data, because alarms may not be set independently
|
||||
$owg_tl = floor($main::attr{$name}{"tempLow"}/$factor-$offset+0.5);
|
||||
$owg_th = floor($main::attr{$name}{"tempHigh"}/$factor-$offset+0.5);
|
||||
|
||||
#-- find upper and lower boundaries for given offset/factor
|
||||
my $mmin = (-55+$offset)*$factor;
|
||||
my $mmax = (125+$offset)*$factor;
|
||||
my $mmin = floor((-55+$offset)*$factor+0.5);
|
||||
my $mmax = floor((125+$offset)*$factor+0.5);
|
||||
return sprintf("OWTHERM: Set with wrong value $value for $key, range is [%3.1f,%3.1f]",$mmin,$mmax)
|
||||
if($value < $mmin || $value > $mmax);
|
||||
|
||||
#-- seems to be ok, correcting for offset and factor
|
||||
$a[2] = int($value/$factor-$offset);
|
||||
$a[2] = floor($value/$factor-$offset+0.5);
|
||||
#-- put into attribute value
|
||||
if( lc($key) eq "templow" ){
|
||||
if( $main::attr{$name}{"tempLow"} != $a[2] ){
|
||||
$main::attr{$name}{"tempLow"} = $a[2];
|
||||
if( $main::attr{$name}{"tempLow"} != $value ){
|
||||
$main::attr{$name}{"tempLow"} = $value;
|
||||
}
|
||||
}
|
||||
if( lc($key) eq "temphigh" ){
|
||||
if( $main::attr{$name}{"tempHigh"} != $a[2] ){
|
||||
$main::attr{$name}{"tempHigh"} = $a[2];
|
||||
if( $main::attr{$name}{"tempHigh"} != $value ){
|
||||
$main::attr{$name}{"tempHigh"} = $value;
|
||||
}
|
||||
}
|
||||
#-- put into device
|
||||
@ -593,10 +597,9 @@ sub OWTHERM_Set($@) {
|
||||
if(defined($ret));
|
||||
}
|
||||
|
||||
#-- process results - we have to reread the device
|
||||
#-- process results
|
||||
$hash->{PRESENT} = 1;
|
||||
#
|
||||
#OWTHERM_FormatValues($hash);
|
||||
OWTHERM_FormatValues($hash);
|
||||
Log 4, "OWTHERM: Set $hash->{NAME} $key $value";
|
||||
|
||||
return undef;
|
||||
@ -1019,7 +1022,8 @@ sub OWXTHERM_SetValues($@) {
|
||||
value). </li>
|
||||
<li>Standard attributes <a href="#alias">alias</a>, <a href="#comment">comment</a>, <a
|
||||
href="#event-on-update-reading">event-on-update-reading</a>, <a
|
||||
href="#event-on-change-reading">event-on-change-reading</a>, <a href="#room"
|
||||
href="#event-on-change-reading">event-on-change-reading</a>, <a
|
||||
href="#stateFormat">stateFormat</a>, <a href="#room"
|
||||
>room</a>, <a href="#eventMap">eventMap</a>, <a href="#loglevel">loglevel</a>,
|
||||
<a href="#webCmd">webCmd</a></li>
|
||||
</ul>
|
||||
|
773
fhem/contrib/15_EMX.pm
Executable file
773
fhem/contrib/15_EMX.pm
Executable file
@ -0,0 +1,773 @@
|
||||
########################################################################################
|
||||
#
|
||||
# 15_EMX.pm MUST be saved as 15_CUL_EM.pm !!!
|
||||
#
|
||||
# FHEM module to read the data from an EM1000 S/IR power sensor
|
||||
#
|
||||
# Version 1.0 - January 21, 2013
|
||||
#
|
||||
# Prof. Dr. Peter A. Henning, 2011
|
||||
#
|
||||
#----------------------------------------------------------------------------------------------------
|
||||
#
|
||||
# Setup as:
|
||||
# define <emx> EMX <code> <rpkwh>
|
||||
#
|
||||
# where
|
||||
# <name> may be replaced by any name string
|
||||
# <code> is a number 1 - 12 or the keyword "emulator".
|
||||
# <rpunit> is the scale factor = rotations per kWh or m^3 (not needed for emulator)
|
||||
#
|
||||
# get <name> midnight => todays starting value for counter
|
||||
# get <name> month => summary of current month
|
||||
#
|
||||
# Attributes are set as
|
||||
#
|
||||
# Monthly and yearly log file
|
||||
# attr emx LogM EnergyM
|
||||
# attr emx LogY EnergyY
|
||||
#
|
||||
# NOT YET OPERATIVE:
|
||||
#
|
||||
# Basic fee per Month (€ per Month)
|
||||
# attr emx FixedM
|
||||
#
|
||||
# Rate during daytime (€ per kWh)
|
||||
# attr emx RateD <rate in €/kWh>
|
||||
#
|
||||
# Start and end of daytime rate - optional
|
||||
# attr emx RateDStart <time as hh:mm>
|
||||
# attr emx RateDEnd <time as hh:mm>
|
||||
#
|
||||
# Rate during nighttime (cost per kWh) - only if needed
|
||||
# attr emx RateN <rate in €/kWh>
|
||||
#
|
||||
########################################################################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my %gets = (
|
||||
"midnight" => "",
|
||||
"month" => ""
|
||||
);
|
||||
|
||||
my %sets = (
|
||||
"meter" => "M",
|
||||
"midnight" => "T"
|
||||
);
|
||||
|
||||
#-- Global variables for the raw readings
|
||||
my $emx_seqno; # number of received datagram in sequence, runs from 2 to 255
|
||||
my $emx_cnt; # current count from device. This value has an arbitrary offset at each start of the device
|
||||
my $emx_5min; # count during last 5 min interval
|
||||
my $emx_peak; # peak count during last 5 min interval
|
||||
|
||||
#--Forward definition
|
||||
sub EMX_Parse($$);
|
||||
|
||||
########################################################################################
|
||||
#
|
||||
# EMX_Initialize
|
||||
#
|
||||
########################################################################################
|
||||
|
||||
#-- stub function for CUL_EM replacement
|
||||
|
||||
sub CUL_EM_Initialize ($) {
|
||||
my ($hash) = @_;
|
||||
|
||||
return EMX_Initialize ($hash);
|
||||
}
|
||||
|
||||
#-- real initialization function
|
||||
sub EMX_Initialize ($) {
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{DefFn} = "EMX_Define";
|
||||
$hash->{UndefFn} = "EMX_Undef";
|
||||
$hash->{ParseFn} = "EMX_Parse";
|
||||
$hash->{SetFn} = "EMX_Set";
|
||||
$hash->{GetFn} = "EMX_Get";
|
||||
$hash->{Match} = "^E0.................\$";
|
||||
|
||||
$hash->{AttrList} = "IODev " .
|
||||
"model:EMEM,EMWZ,EMGZ loglevel LogM LogY RateD RateDStart RateDEnd RateN BaseFee ".
|
||||
$readingFnAttributes;
|
||||
}
|
||||
|
||||
########################################################################################
|
||||
#
|
||||
# EMX_Define - Implements DefFn function
|
||||
#
|
||||
# Parameter hash, definition string
|
||||
#
|
||||
########################################################################################
|
||||
|
||||
sub EMX_Define ($$) {
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
return "wrong syntax: define <name> EMX <code> <rpunit>"
|
||||
if(int(@a) < 3 || int(@a) > 4);
|
||||
|
||||
my $name = $a[0];
|
||||
|
||||
#-- emulator mode ------------------------------------------------------------
|
||||
if( $a[2] eq "emulator") {
|
||||
$hash->{CODE} = "emulator";
|
||||
Log 1, "EMX with emulator mode";
|
||||
|
||||
#-- counts per unit etc.
|
||||
$hash->{READINGS}{"reading"}{FACTOR} = 150;
|
||||
$hash->{READINGS}{"reading"}{UNIT} = "Kilowattstunden";
|
||||
$hash->{READINGS}{"reading"}{UNITABBR}= "kWh";
|
||||
$hash->{READINGS}{"rate"}{PERIOD} = "h";
|
||||
$hash->{READINGS}{"rate"}{UNIT} = "Kilowatt";
|
||||
$hash->{READINGS}{"rate"}{UNITABBR} = "kW";
|
||||
CommandAttr(undef,"$name model emulator");
|
||||
|
||||
#-- set/ get artificial data
|
||||
my $msg=EMX_emu(0,12345);
|
||||
$hash->{READINGS}{"count"}{midnight}=12345;
|
||||
EMX_store($hash);
|
||||
$hash->{emumsg}=$msg;
|
||||
|
||||
$modules{EMX}{defptr}{0} = $hash;
|
||||
|
||||
# Call emulator in 15 seconds again, and then cyclic repetition
|
||||
InternalTimer(gettimeofday()+15, "EMX_Parse", $hash, 0);
|
||||
|
||||
} else {
|
||||
#-- Real device definition -----------------------------------------------------
|
||||
return "EMX_Define $a[0]: wrong CODE format: valid is 1-12 or \"emulator\""
|
||||
if( $a[2] !~ m/^\d+$/ || $a[2] < 1 || $a[2] > 12 );
|
||||
$hash->{CODE} = $a[2];
|
||||
|
||||
#--counts per unit etc.
|
||||
if($a[2] >= 1 && $a[2] <= 4) { # EMWZ
|
||||
$hash->{READINGS}{"reading"}{FACTOR} = $a[3];
|
||||
$hash->{READINGS}{"reading"}{UNIT} = "Kilowattstunden";
|
||||
$hash->{READINGS}{"reading"}{UNITABBR} = "kWh";
|
||||
$hash->{READINGS}{"rate"}{PERIOD} = "h";
|
||||
$hash->{READINGS}{"rate"}{UNIT} = "Kilowatt";
|
||||
$hash->{READINGS}{"rate"}{UNITABBR} = "kW";
|
||||
CommandAttr (undef,"$name model EMWZ");
|
||||
} elsif($a[2] >= 5 && $a[2] <= 8) { # EMEM
|
||||
$hash->{READINGS}{"reading"}{FACTOR} = $a[3];
|
||||
$hash->{READINGS}{"reading"}{UNIT} = "Kilowattstunden";
|
||||
$hash->{READINGS}{"reading"}{UNITABBR} = "kWh";
|
||||
$hash->{READINGS}{"rate"}{PERIOD} = "h";
|
||||
$hash->{READINGS}{"rate"}{UNIT} = "Kilowatt";
|
||||
$hash->{READINGS}{"rate"}{UNITABBR} = "kW";
|
||||
CommandAttr (undef,"$name model EMEM");
|
||||
} elsif($a[2] >= 9 && $a[2] <= 12) { # EMGZ
|
||||
$hash->{READINGS}{"reading"}{FACTOR} = $a[3];
|
||||
$hash->{READINGS}{"reading"}{UNIT} = "Kubikmeter";
|
||||
$hash->{READINGS}{"reading"}{UNITABBR} = "m^3";
|
||||
$hash->{READINGS}{"rate"}{PERIOD} = "h";
|
||||
$hash->{READINGS}{"rate"}{UNIT} = "Kubikmeter/Stunde";
|
||||
$hash->{READINGS}{"rate"}{UNITABBR} = "m^3/h";
|
||||
CommandAttr (undef,"$name model EMGZ");
|
||||
}
|
||||
|
||||
#-- Couple to I/O device
|
||||
$modules{EMX}{defptr}{$a[2]} = $hash;
|
||||
AssignIoPort($hash);
|
||||
}
|
||||
|
||||
readingsSingleUpdate($hash,"state","defined",1);
|
||||
Log 3, "EMX: Device $name defined.";
|
||||
#-- Start timer for initialization in a few seconds
|
||||
InternalTimer(time()+3, "EMX_InitializeDevice", $hash, 0);
|
||||
return undef;
|
||||
}
|
||||
|
||||
########################################################################################
|
||||
#
|
||||
# EMX_InitializeDevice - Sets up the device after start
|
||||
#
|
||||
# Parameter hash
|
||||
#
|
||||
########################################################################################
|
||||
|
||||
sub EMX_InitializeDevice ($) {
|
||||
my ($hash) = @_;
|
||||
|
||||
my $ret;
|
||||
|
||||
my $name = $hash->{NAME};
|
||||
Log 1,"EMX_InitializeDevice $name";
|
||||
|
||||
#-- read starting value of the day
|
||||
$ret = EMX_recall($hash);
|
||||
Log 1, $ret
|
||||
if( defined($ret));
|
||||
return $ret
|
||||
if( defined($ret));
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
########################################################################################
|
||||
#
|
||||
# EMX_FormatValues - Calculate display values
|
||||
#
|
||||
# Parameter hash
|
||||
#
|
||||
########################################################################################
|
||||
|
||||
sub EMX_FormatValues ($) {
|
||||
my ($hash) = @_;
|
||||
|
||||
#Log 1," seqno $emx_seqno cnt $emx_cnt 5min $emx_5min peak $emx_peak";
|
||||
|
||||
my $name = $hash->{NAME};
|
||||
my ($model,$factor,$period,$unit,$runit,$midnight,$cval,$vval,$rval,$pval,$dval,$deltim,$delcnt,$msg);
|
||||
my ($svalue,$dvalue,$mvalue) = ("","","");
|
||||
|
||||
my ($sec, $min, $hour, $day, $month, $year, $wday,$yday,$isdst) = localtime(time);
|
||||
my ($seco,$mino,$houro,$dayo,$montho,$yearo,$dayrest);
|
||||
my $daybreak = 0;
|
||||
my $monthbreak = 0;
|
||||
|
||||
#-- Check, whether we have a new "day"
|
||||
# emulator: less than 15 seconds from 5-minute period
|
||||
if( $hash->{CODE} eq "emulator"){
|
||||
$deltim = $min%5+$sec/60.0 - 4.75;
|
||||
if( $deltim>0 ){
|
||||
$yearo = $year+1900;
|
||||
$montho = $month;
|
||||
$dayo = $day."-".$hour."-".$min."-".$sec;
|
||||
$daybreak = 1;
|
||||
#-- Check, whether we have a new "month" = three 5 minute periods
|
||||
if( $min%15 == 0){
|
||||
$monthbreak = 1;
|
||||
}
|
||||
}
|
||||
# normal mode: less than 5 minutes from midnight
|
||||
}else {
|
||||
$deltim = $hour*60.0+$min+$sec/60.0 - 1435.0;
|
||||
#-- TODO abfangen, wenn Messung um Mitternacht !
|
||||
if( $deltim>0 ){
|
||||
$daybreak = 1;
|
||||
#-- Timer data from tomorrow
|
||||
my ($secn,$minn,$hourn,$dayn,$monthn,$yearn,$wdayn,$ydayn,$isdstn) = localtime(time() + 24*60*60);
|
||||
#-- Check, whether we have a new month
|
||||
if( $dayn == 1 ){
|
||||
$monthbreak = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$model = $main::attr{$name}{"model"};
|
||||
$midnight = $hash->{READINGS}{"count"}{midnight};
|
||||
$factor = $hash->{READINGS}{"reading"}{FACTOR};
|
||||
$unit = $hash->{READINGS}{"reading"}{UNITABBR};
|
||||
$period = $hash->{READINGS}{"rate"}{PERIOD};
|
||||
$runit = $hash->{READINGS}{"rate"}{UNITABBR};
|
||||
|
||||
my $emx_cnt_prev;
|
||||
my $emx_cnt_tim;
|
||||
|
||||
#-- skip some things if undefined
|
||||
if( $emx_cnt eq ""){
|
||||
$svalue = "???";
|
||||
}else {
|
||||
#-- put into READINGS
|
||||
readingsBeginUpdate($hash);
|
||||
readingsBulkUpdate($hash,"count",$emx_cnt);
|
||||
$svalue = "raw $emx_cnt";
|
||||
|
||||
#-- get the old values (raw counts, always integer)
|
||||
$emx_cnt_prev = $hash->{READINGS}{"count"}{VAL};
|
||||
$emx_cnt_tim = $hash->{READINGS}{"count"}{TIME};
|
||||
$emx_cnt_tim = "" if(!defined($emx_cnt_tim));
|
||||
|
||||
#-- safeguard against the case where no previous measurement
|
||||
if( length($emx_cnt_tim) > 0 ){
|
||||
|
||||
#-- correct counter wraparound since last reading
|
||||
# Careful: we have seen, that sometimes this causes an error when a false message is received
|
||||
if( $emx_cnt < $emx_cnt_prev) {
|
||||
$emx_cnt_prev -= 65536;
|
||||
}
|
||||
#-- correct counter wraparound since last day
|
||||
if( $emx_cnt < $midnight) {
|
||||
$midnight -= 65536;
|
||||
}
|
||||
|
||||
#-- For this calculation we could use either $emx_5min
|
||||
# or ($emx_cnt - $emx_cnt_prev) since they are the same.
|
||||
my $delcnt = ($emx_cnt-$emx_cnt_prev);
|
||||
|
||||
#-- Extrapolate these values when a new day will be started (0<deltim<5)
|
||||
if( $daybreak==1 ) {
|
||||
$emx_cnt += $deltim /5.0 *$delcnt;
|
||||
$cval = $emx_cnt-$midnight;
|
||||
#-- store corrected counter value at midnight
|
||||
$hash->{READINGS}{"count"}{midnight} = $emx_cnt;
|
||||
EMX_store($hash);
|
||||
#-- no daybreak -> subtract only midnight count
|
||||
}else{
|
||||
$cval = $emx_cnt-$midnight;
|
||||
}
|
||||
#-- Translate from device into physical units
|
||||
# $factor = no. of counts per unit
|
||||
# $emx_peak has to be divided by 20 = 60 min/ 5 min
|
||||
if( $model eq "EMWZ" ){
|
||||
$vval = int($cval/$factor*1000)/1000;
|
||||
$rval = int($emx_5min*12/$factor*1000)/1000;
|
||||
$pval = int($emx_peak/($factor*20)*1000)/1000;
|
||||
$svalue = sprintf("W: %5.2f %s P: %5.2f %s",$vval,$unit,$rval,$runit);
|
||||
} elsif( $model eq "EMEM" ){
|
||||
$vval = int($cval/($factor*10)*1000)/1000;
|
||||
$rval = int($emx_5min*12/$factor*1000)/1000;
|
||||
$pval = int($emx_peak/($factor*20)*1000)/1000;
|
||||
$svalue = sprintf("W: %5.2f %s P: %5.2f %s",$vval,$unit,$rval,$runit);
|
||||
} elsif( $model eq "EMGZ" ){
|
||||
$vval = int($cval/$factor*1000)/1000;
|
||||
$rval = int($emx_5min*12/$factor*1000)/1000;
|
||||
$pval = int($emx_peak/($factor*20)*1000)/1000;
|
||||
$svalue = sprintf("W: %5.2f %s P: %5.2f %s",$vval,$unit,$rval,$runit);
|
||||
} else {
|
||||
Log 3,"EMX: Wrong device model $model";
|
||||
}
|
||||
#-- Calculate cost
|
||||
#--
|
||||
|
||||
#-- put into READING
|
||||
readingsBulkUpdate($hash,"reading",$vval);
|
||||
readingsBulkUpdate($hash,"rate",$rval);
|
||||
readingsBulkUpdate($hash,"peak",$pval);
|
||||
|
||||
#-- Daily/monthly cumulated value
|
||||
if( $daybreak == 1 ){
|
||||
my @month = EMX_GetMonth($hash);
|
||||
my $total = $month[0]+$vval;
|
||||
$dvalue = sprintf("D_%02d Wd: %5.2f Wm: %6.2f",$day,$vval,$total);
|
||||
readingsBulkUpdate($hash,"day",$dvalue);
|
||||
if( $monthbreak == 1){
|
||||
$mvalue = sprintf("M_%02d Wm: %6.2f",$month,$total);
|
||||
readingsBulkUpdate($hash,"month",$mvalue);
|
||||
Log 1,$name." has monthbreak $msg ".$mvalue;
|
||||
}
|
||||
}
|
||||
}
|
||||
#-- STATE
|
||||
readingsBulkUpdate($hash,"state",$svalue);
|
||||
readingsEndUpdate($hash,1);
|
||||
}
|
||||
}
|
||||
|
||||
########################################################################################
|
||||
#
|
||||
# EMX_Get - Implements GetFn function
|
||||
#
|
||||
# Parameter hash, argument array
|
||||
#
|
||||
########################################################################################
|
||||
|
||||
sub EMX_Get ($@) {
|
||||
my ($hash, @a) = @_;
|
||||
|
||||
#-- empty argument list
|
||||
return join(" ", sort keys %gets)
|
||||
if(@a < 2);
|
||||
|
||||
#-- check syntax
|
||||
my $name = $hash->{NAME};
|
||||
return "EMX_Get with unknown argument $a[1], choose one of " . join(" ", sort keys %gets)
|
||||
if(!defined($gets{$a[1]}));
|
||||
|
||||
$name = shift @a;
|
||||
my $key = shift @a;
|
||||
my $value;
|
||||
my $ret;
|
||||
|
||||
#-- midnight counter value
|
||||
if($key eq "midnight"){
|
||||
$value = $hash->{READINGS}{"count"}{midnight};
|
||||
}
|
||||
|
||||
#-- monthly summary
|
||||
if($key eq "month"){
|
||||
my @month = EMX_GetMonth($hash);
|
||||
$value = "Wm ".$month[1]." kWh (av. ".$month[2]." kWh)";
|
||||
}
|
||||
|
||||
Log GetLogLevel($name,3), "EMX_Get => $key $value";
|
||||
return "EMX_Get => $key $value";
|
||||
}
|
||||
|
||||
########################################################################################
|
||||
#
|
||||
# EMX_Set - Implements SetFn function
|
||||
#
|
||||
# Parameter hash, argument array
|
||||
#
|
||||
########################################################################################
|
||||
|
||||
sub EMX_Set ($@) {
|
||||
my ($hash, @a) = @_;
|
||||
|
||||
#-- empty argument list
|
||||
return join(" ", sort keys %sets)
|
||||
if(@a < 3);
|
||||
|
||||
#-- check syntax
|
||||
return "EMX_Set needs at least two parameters"
|
||||
if(@a < 3);
|
||||
my $name = $hash->{NAME};
|
||||
Log GetLogLevel($name,3), "EMX Set request $a[1] $a[2]";
|
||||
return "EMX_Set with unknown argument $a[1], choose one of " . join(" ", sort keys %sets)
|
||||
if(!defined($sets{$a[1]}));
|
||||
|
||||
$name = shift @a;
|
||||
my $key = shift @a;
|
||||
my $value = join("", @a);
|
||||
my $tn = TimeNow();
|
||||
my $ret;
|
||||
|
||||
#-- value of meter reading may be set at runtime
|
||||
if($key eq "meter"){
|
||||
|
||||
}
|
||||
|
||||
#-- midnight counter value may be set at runtime
|
||||
if($key eq "midnight"){
|
||||
return "EMX_Set: Wrong midnight value for counter, must be -65536 <= value < 65536"
|
||||
if( ($value < -65536) || ($value > 65535) );
|
||||
$hash->{READINGS}{"count"}{midnight}=$value;
|
||||
#-- store this for later usage
|
||||
$ret = EMX_store($hash);
|
||||
return "EMX_Set: ".$ret
|
||||
if( defined($ret) );
|
||||
}
|
||||
|
||||
Log GetLogLevel($name,3), "EMX_Set => $key $value";
|
||||
return "EMX_Set => $key $value";
|
||||
}
|
||||
|
||||
########################################################################################
|
||||
#
|
||||
# EMX_Undef - Implements UndefFn function
|
||||
#
|
||||
# Parameter hash, name
|
||||
#
|
||||
########################################################################################
|
||||
|
||||
sub EMX_Undef ($$) {
|
||||
my ($hash, $name) = @_;
|
||||
delete($modules{EMX}{defptr}{$hash->{CODE}});
|
||||
return undef;
|
||||
}
|
||||
|
||||
########################################################################################
|
||||
#
|
||||
# EMX_Parse - Parse the message string send by CUL_EM
|
||||
#
|
||||
# Parameter hash, msg = message string
|
||||
#
|
||||
########################################################################################
|
||||
|
||||
sub EMX_Parse ($$) {
|
||||
my ($hash,$msg) = @_;
|
||||
|
||||
if( !($msg) ) {
|
||||
$msg=$hash->{emumsg};
|
||||
}
|
||||
|
||||
# 0123456789012345678
|
||||
# E01012471B80100B80B -> Type 01, Code 01, Cnt 10
|
||||
my @a = split("", $msg);
|
||||
my $tpe = ($a[1].$a[2])+0;
|
||||
my $cde = hex($a[3].$a[4]);
|
||||
|
||||
#-- emulator
|
||||
if( $cde eq "00"){
|
||||
$cde = "emulator";
|
||||
}
|
||||
|
||||
#-- return, if the defice is undefided
|
||||
if( not($modules{EMX}{defptr}{$cde}) ){
|
||||
Log 1, "EMX detected, Code $cde";
|
||||
return "EMX_Parse: Undefined EMX_$cde EMX $cde";
|
||||
}
|
||||
|
||||
my $def = $modules{EMX}{defptr}{$cde};
|
||||
$hash = $def;
|
||||
my $name = $hash->{NAME};
|
||||
return "" if(IsIgnored($name));
|
||||
|
||||
$emx_seqno = hex($a[5].$a[6]);
|
||||
$emx_cnt = hex($a[ 9].$a[10].$a[ 7].$a[ 8]);
|
||||
$emx_5min = hex($a[13].$a[14].$a[11].$a[12]);
|
||||
$emx_peak = hex($a[17].$a[18].$a[15].$a[16]);
|
||||
EMX_FormatValues($hash);
|
||||
|
||||
#-- emulator mode - must be triggered here since not received by CUL
|
||||
# Call us in 15 seconds minutes again.
|
||||
if( $hash->{CODE} eq "emulator"){
|
||||
# Next sequence number
|
||||
$emx_seqno++;
|
||||
$emx_seqno =0 if($emx_seqno > 255);
|
||||
|
||||
# Get artificial data
|
||||
my $msg=EMX_emu($emx_seqno, $emx_cnt);
|
||||
$hash->{emumsg}=$msg;
|
||||
|
||||
#-- restart timer for updates
|
||||
RemoveInternalTimer($hash);
|
||||
InternalTimer(gettimeofday()+15, "EMX_Parse", $hash,1);
|
||||
}
|
||||
|
||||
return $hash->{NAME};
|
||||
}
|
||||
|
||||
########################################################################################
|
||||
#
|
||||
# Store daily start value in a file
|
||||
#
|
||||
# Parameter hash
|
||||
#
|
||||
########################################################################################
|
||||
|
||||
sub EMX_store($) {
|
||||
my ($hash) = @_;
|
||||
|
||||
my $name = $hash->{NAME};
|
||||
my $mp = AttrVal("global", "modpath", ".");
|
||||
my $ret = open(EMXFILE, "> $mp/FHEM/EMX_$name.dat" );
|
||||
my $msg;
|
||||
if( $ret) {
|
||||
#-- Timer data
|
||||
my ($sec,$min,$hour,$day,$month,$year,$wday,$yday,$isdst) = localtime(time);
|
||||
|
||||
if( $hash->{CODE} eq "emulator"){
|
||||
$msg = sprintf "%4d-%02d-%02d %02d:%02d:%02d %02d", $year+1900,$month,$day,$hour,$min,$sec,$hash->{READINGS}{"count"}{midnight};
|
||||
} else {
|
||||
$msg = sprintf "%4d-%02d-%02d midnight %d",$year+1900,$month,$day,$hash->{READINGS}{"count"}{midnight};
|
||||
}
|
||||
print EMXFILE $msg;
|
||||
Log 1, "EMX_store: $msg";
|
||||
close(EMXFILE);
|
||||
} else {
|
||||
Log 1,"EMX_store: Cannot open EMX_name.dat for writing!";
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
########################################################################################
|
||||
#
|
||||
# Recall daily start value from a file
|
||||
#
|
||||
# Parameter hash
|
||||
#
|
||||
########################################################################################
|
||||
|
||||
sub EMX_recall($) {
|
||||
my ($hash) = @_;
|
||||
|
||||
my $name= $hash->{NAME};
|
||||
my $mp = AttrVal("global", "modpath", ".");
|
||||
my $ret = open(EMXFILE, "< $mp/FHEM/EMX_$name.dat" );
|
||||
my $msg;
|
||||
if( $ret ){
|
||||
my $line = readline EMXFILE;
|
||||
close(EMXFILE);
|
||||
my @a=split(' ',$line);
|
||||
#-- Timer data from yesterday
|
||||
my ($sec,$min,$hour,$day,$month,$year,$wday,$yday,$isdst) = localtime(time() - 24*60*60);
|
||||
$msg = sprintf "%4d-%02d-%02d", $year+1900,$month,$day;
|
||||
if( $msg ne $a[0]){
|
||||
Log 1, "EMX_recall: midnight value $a[2] for $name not from last day, but from $a[0]";
|
||||
$hash->{READINGS}{"count"}{midnight}=$a[2];
|
||||
} else {
|
||||
Log 1, "EMX_recall: recalled midnight value $a[2] for $name";
|
||||
$hash->{READINGS}{"count"}{midnight} = $a[2];
|
||||
}
|
||||
} else {
|
||||
Log 1, "EMX_recall: Cannot open EMX_$name.dat for reading!";
|
||||
$hash->{READINGS}{"count"}{midnight}=0;
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
########################################################################################
|
||||
#
|
||||
# Read monthly data from a file
|
||||
#
|
||||
# Parameter hash
|
||||
#
|
||||
# Returns total value up to last day, including this day and average including this day
|
||||
#
|
||||
########################################################################################
|
||||
|
||||
sub EMX_GetMonth($) {
|
||||
|
||||
my ($hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
my $regexp = ".*$name.*";
|
||||
my @month;
|
||||
|
||||
#-- Check current logfile
|
||||
my $ln = $attr{$name}{"LogM"};
|
||||
if( !(defined($ln))){
|
||||
Log 1,"EMX_GetMonth: Attribute LogM is missing";
|
||||
return undef;
|
||||
} else {
|
||||
my $lf = $defs{$ln}{currentlogfile};
|
||||
my $ret = open(EMXFILE, "< $lf" );
|
||||
if( $ret) {
|
||||
while( <EMXFILE> ){
|
||||
#-- line looks like
|
||||
# 2013-02-09_23:59:31 <name> day D_09 Wd: 0.00 Wm: 171.70
|
||||
|
||||
my $line = $_;
|
||||
chomp($line);
|
||||
if ( $line =~ m/$regexp/i){
|
||||
my @linarr = split(' ',$line);
|
||||
my $day = $linarr[3];
|
||||
$day =~ s/D_0+//;
|
||||
my $val = $linarr[5];
|
||||
push(@month,$val);
|
||||
}
|
||||
}
|
||||
}
|
||||
#-- sum and average
|
||||
my $total = 0.0;
|
||||
foreach(@month){
|
||||
$total +=$_;
|
||||
}
|
||||
#-- add data from current day
|
||||
$total = int($total*100)/100;
|
||||
my ($sec,$min,$hour,$day,$month,$year,$wday,$yday,$isdst) = localtime(time);
|
||||
my $deltim = ($hour+$min/60.0 + $sec/3600.0)/24.0;
|
||||
my $total2 = int(100*($total+$hash->{READINGS}{"reading"}{VAL}))/100;
|
||||
my $av = int(100*$total2/(int(@month)+$deltim))/100;
|
||||
|
||||
return ($total,$total2,$av);
|
||||
}
|
||||
}
|
||||
|
||||
########################################################################################
|
||||
#
|
||||
# Emulator section - to be used, if the real device is not attached.
|
||||
#
|
||||
########################################################################################
|
||||
|
||||
sub EMX_emu ($$) {
|
||||
|
||||
#-- Timer data
|
||||
my ($sec,$min,$hour,$day,$month,$year,$wday,$yday,$isdst) = localtime(time);
|
||||
|
||||
#-- parse incoming parameters
|
||||
my ($seqno,$Wd_cnt_old)=@_;
|
||||
|
||||
#-- setup message
|
||||
my $sj=sprintf("%02x",$seqno);
|
||||
# power value = 0.6 from 0:00 - 06:00 / 1.8 kW from 6:00 - 22:00/1.2 kW from 22:00 - 24:00
|
||||
my $Pac_cnt;
|
||||
my $Wd_cnt = $Wd_cnt_old%65536;
|
||||
if( ($hour+$min/60.0)<6.0 ){
|
||||
$Pac_cnt= 0.64*150.0/12.0;
|
||||
$Wd_cnt+= 0.64*150.0/12.0;
|
||||
} elsif ( ($hour+$min/60.0)<22.0 ) {
|
||||
$Pac_cnt= 1.92*150.0/12.0;
|
||||
$Wd_cnt+= 1.92*150.0/12.0;
|
||||
} else {
|
||||
$Pac_cnt= 1.28*150.0/12.0;
|
||||
$Wd_cnt+= 1.28*150.0/12.0;
|
||||
}
|
||||
my $cj=sprintf("%02x",int($Pac_cnt/256));
|
||||
my $ck=sprintf("%02x",$Pac_cnt%256);
|
||||
my $tj=sprintf("%02x",int($Wd_cnt/256));
|
||||
my $tk=sprintf("%02x",$Wd_cnt%256);
|
||||
|
||||
my $msg="E0100".$sj.$tk.$tj.$ck.$cj."0000";
|
||||
#Log 1,"cj = $cj, ck=$ck, tj=$tj, tk=$tk";
|
||||
return $msg;
|
||||
}
|
||||
1;
|
||||
|
||||
=pod
|
||||
=begin html
|
||||
|
||||
<a name="EMX"></a>
|
||||
<h3>EMX</h3>
|
||||
<p>FHEM module to commmunicate EM1000 power/gas sensors <br />
|
||||
<br /> <b>NOTE:</b> This module is currently NOT registered in the client list of 00_CUL.pm.
|
||||
Therefore ist must be saved under the name 15_CUL_EM.pm or entered into the client list manually.
|
||||
<br /></p>
|
||||
<br /><h4>Example</h4>
|
||||
<p>
|
||||
<code>define E_Verbrauch EMX 1 75</code>
|
||||
</p>
|
||||
<br />
|
||||
<a name="EMXdefine"></a>
|
||||
<h4>Define</h4>
|
||||
<p>
|
||||
<code>define <name> EMX <code> <rpunit></code> or <br/>
|
||||
<code>define <name> EMX emulator</code>
|
||||
<br /><br /> Define an EMX device or an emulated EM1000-WZ device <br /><br />
|
||||
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<code><code></code><br /> Defines the sensor model, currently the following values are permitted:
|
||||
<ul>
|
||||
<li>1 .. 4: EM1000-WZ power meter sensor => unit is kWh</li>
|
||||
<li>5 .. 8: EM1000-EM power sensor => unit is kWh</li>
|
||||
<li>9 .. 12: EM1000-GZ gas meter sensor => unit is m<sup>3</sup></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<code><rpunit></code><br/>Fcator to scale the reading into units
|
||||
<ul>
|
||||
<li>EM1000-WZ devices: rotations per kWh, usually 75 or 150</li>
|
||||
<li>EM1000-EM devices: digits per kWh, usually 100</li>
|
||||
<li>EM1000-GZ devices: digits per <sup>3</sup>, usually 100</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<a name="EMXset"></a>
|
||||
<h4>Set</h4>
|
||||
<ul>
|
||||
<li><a name="emx_midnight">
|
||||
<code>set <name> midnight <int></code></a><br /> Value of counter at midnight </li>
|
||||
</ul>
|
||||
<br />
|
||||
<a name="EMXget"></a>
|
||||
<h4>Get</h4>
|
||||
<ul>
|
||||
<li><a name="emx_midnight2">
|
||||
<code>get <name> midnight</code></a>
|
||||
<br /> Returns the value of the counter at midnight </li>
|
||||
<li><a name="emx_month">
|
||||
<code>get <name> month</code>
|
||||
</a>
|
||||
<br /> Returns a summary of the current month</li>
|
||||
</ul>
|
||||
<br />
|
||||
<a name="EMXattr"></a>
|
||||
<h4>Attributes</h4>
|
||||
<ul>
|
||||
<li><a name="emx_logm"><code>attr <name> <LogM>
|
||||
<string></code></a>
|
||||
<br />Device name (<i>not file name</i>) of the monthly logfile </li>
|
||||
<li><a name="emx_logy"><code>attr <name> <LogY>
|
||||
<string></code></a>
|
||||
<br />Device name (<i>not file name</i>) of the yearly logfile </li>
|
||||
<li>Standard attributes <a href="#alias">alias</a>, <a href="#comment">comment</a>, <a
|
||||
href="#event-on-update-reading">event-on-update-reading</a>, <a
|
||||
href="#event-on-change-reading">event-on-change-reading</a>, <a
|
||||
href="#stateFormat">stateFormat</a>, <a href="#room"
|
||||
>room</a>, <a href="#eventMap">eventMap</a>, <a href="#loglevel">loglevel</a>,
|
||||
<a href="#webCmd">webCmd</a></li>
|
||||
</ul>
|
||||
|
||||
=end html
|
||||
=cut
|
||||
|
@ -4,23 +4,35 @@
|
||||
#
|
||||
# FHEM module to read the data from a Sunways NT5000 solar inverter
|
||||
#
|
||||
# Prof. Dr. Peter A. Henning, 2011
|
||||
# Prof. Dr. Peter A. Henning, 2008
|
||||
#
|
||||
# Version 1.0 - February 21, 2012
|
||||
# Version 2.0 - February 2013
|
||||
#
|
||||
# Setup as:
|
||||
# define nt5000 NT5000 <device>
|
||||
# define <name> NT5000 <device>
|
||||
#
|
||||
# where nt5000 may be replaced by any name string and <device>
|
||||
# where <name> may be replaced by any name string and <device>
|
||||
# is a serial (USB) device or the keyword "emulator".
|
||||
# In the latter case, a 4.5 kWP solar installation is simulated
|
||||
#
|
||||
# get <name> present => 1 if device present, 0 if not
|
||||
# get <name> serial => inverter serial number
|
||||
# get <name> proto => protocol
|
||||
# get <name> reading => measurement for all channels
|
||||
# get <name> month => monthly measurement
|
||||
# get <name> year => yearly measurement
|
||||
#
|
||||
# set <name> time => set inverter clock
|
||||
#
|
||||
# Additional attributes are defined in fhem.cfg as
|
||||
# attr nt5000 room Solaranlage
|
||||
# Area of solar installation
|
||||
# attr nt5000 Area 32.75
|
||||
# Peak Solar Power
|
||||
# attr nt5000 PSP 4.5
|
||||
# Monthly and yearly log file
|
||||
# attr nt5000 LogM SolarLogM
|
||||
# attr nt5000 LogY SolarLogY
|
||||
# Months with erroneous readings - see line 83 ff
|
||||
# attr nt5000 MERR <list>
|
||||
# Expected yields per month / year
|
||||
@ -71,11 +83,12 @@ my $cline=0;
|
||||
|
||||
#-- These we may get on request
|
||||
my %gets = (
|
||||
"present" => "",
|
||||
"serial" => "S",
|
||||
"proto" => "P",
|
||||
"reading" => "R",
|
||||
"month" => "M",
|
||||
"year" => "Y",
|
||||
"serial" => "S",
|
||||
"proto" => "P"
|
||||
"year" => "Y"
|
||||
);
|
||||
|
||||
#-- These occur in a pulldown menu as settable values
|
||||
@ -83,11 +96,6 @@ my %sets = (
|
||||
"time" => "T"
|
||||
);
|
||||
|
||||
#-- These we may get on request
|
||||
my %attrs = (
|
||||
"Wyx" => "R",
|
||||
);
|
||||
|
||||
|
||||
########################################################################################
|
||||
#
|
||||
@ -112,13 +120,14 @@ sub NT5000_Initialize ($) {
|
||||
# which is the following one.
|
||||
# WxM1 .. WxM12 = Expected yield from January .. December
|
||||
# WxY = Expected yield per year
|
||||
# LogM, LogY = name of the monthly and yearly log file
|
||||
$hash->{AttrList}= "Area PSP MERR ".
|
||||
"Wx_M1 Wx_M2 Wx_M3 Wx_M4 Wx_M5 Wx_M6 Wx_M7 Wx_M8 Wx_M9 Wx_M10 Wx_M11 Wx_M12 ".
|
||||
"Wx_Y ".
|
||||
"loglevel:0,1,2,3,4,5,6";
|
||||
"Wx_Y LogM LogY ".
|
||||
"loglevel ".
|
||||
$readingFnAttributes;
|
||||
}
|
||||
|
||||
#######################################################################################
|
||||
########################################################################################
|
||||
#
|
||||
# NT5000_Define - Implements DefFn function
|
||||
@ -132,9 +141,8 @@ sub NT5000_Define($$) {
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
return "Define the serial device as a parameter, use none or emulator for a fake device"
|
||||
if(@a != 3);
|
||||
$hash->{STATE} = "Initialized";
|
||||
|
||||
if(@a != 3);
|
||||
|
||||
my $dev = $a[2];
|
||||
|
||||
Log 1, "NT5000 device is none, commands will be echoed only"
|
||||
@ -150,22 +158,23 @@ sub NT5000_Define($$) {
|
||||
Log 2, "NT5000 opened device $dev";
|
||||
$hash->{USBDev} = $nt5000_serport;
|
||||
sleep(1);
|
||||
$nt5000_serport->close();
|
||||
|
||||
$nt5000_serport->close();
|
||||
}
|
||||
|
||||
$hash->{DeviceName} = $dev;
|
||||
$hash->{Timer} = 60; # call every 60 seconds
|
||||
$hash->{INTERVAL} = 60; # call every 60 seconds
|
||||
$hash->{Cmd} = "reading"; # get all data, min/max unchange
|
||||
$hash->{SerialNumber} = "";
|
||||
$hash->{Protocol} = "";
|
||||
$hash->{Firmware} = "";
|
||||
$hash->{STATE} = "offline";
|
||||
my $tn = TimeNow();
|
||||
|
||||
$modules{NT5000}{defptr}{$a[0]} = $hash;
|
||||
|
||||
#-- InternalTimer blocks if init_done is not true
|
||||
my $oid = $init_done;
|
||||
$init_done = 1;
|
||||
readingsSingleUpdate($hash,"state","initialized",1);
|
||||
|
||||
NT5000_GetStatus($hash);
|
||||
$init_done = $oid;
|
||||
return undef;
|
||||
@ -180,55 +189,52 @@ sub NT5000_Define($$) {
|
||||
########################################################################################
|
||||
|
||||
sub NT5000_Get ($@) {
|
||||
my ($hash, @a) = @_;
|
||||
my ($hash, @a) = @_;
|
||||
|
||||
return "NT5000_Get needs exactly one parameter" if(@a != 2);
|
||||
my $name = $hash->{NAME};
|
||||
my $v;
|
||||
#-- check syntax
|
||||
return "NT5000_Get needs exactly one parameter" if(@a != 2);
|
||||
my $name = $hash->{NAME};
|
||||
my $v;
|
||||
|
||||
if($a[1] eq "reading")
|
||||
{
|
||||
$v = NT5000_GetLine($hash,"reading");
|
||||
if(!defined($v))
|
||||
{
|
||||
#-- get present
|
||||
if($a[1] eq "present") {
|
||||
$v = ($hash->{READINGS}{"state"}{VAL} =~ m/.*kW/) ? 1 : 0;
|
||||
return "$a[0] present => $v";
|
||||
}
|
||||
|
||||
#-- current reading
|
||||
if($a[1] eq "reading") {
|
||||
$v = NT5000_GetLine($hash,"reading");
|
||||
if(!defined($v)) {
|
||||
Log GetLogLevel($name,2), "NT5000_Get $a[1] error";
|
||||
return "$a[0] $a[1] => Error";
|
||||
}
|
||||
$v =~ s/[\r\n]//g; # Delete the NewLine
|
||||
$hash->{READINGS}{$a[1]}{VAL} = $v;
|
||||
$hash->{READINGS}{$a[1]}{TIME} = TimeNow();
|
||||
}
|
||||
elsif($a[1] eq "month")
|
||||
{
|
||||
$v = NT5000_GetLine($hash,"month");
|
||||
if(!defined($v))
|
||||
{
|
||||
}
|
||||
$v =~ s/[\r\n]//g; # Delete the NewLine
|
||||
readingsSingleUpdate($hash,"reading",$v,1);
|
||||
#-- monthly reading
|
||||
} elsif($a[1] eq "month") {
|
||||
$v = NT5000_GetLine($hash,"month");
|
||||
if(!defined($v)) {
|
||||
Log GetLogLevel($name,2), "NT5000_Get $a[1] error";
|
||||
return "$a[0] $a[1] => Error";
|
||||
}
|
||||
$v =~ s/[\r\n]//g; # Delete the NewLine
|
||||
$hash->{READINGS}{$a[1]}{VAL} = $v;
|
||||
$hash->{READINGS}{$a[1]}{TIME} = TimeNow();
|
||||
}
|
||||
elsif($a[1] eq "year")
|
||||
{
|
||||
$v = NT5000_GetLine($hash,"year");
|
||||
if(!defined($v))
|
||||
{
|
||||
}
|
||||
$v =~ s/[\r\n]//g; # Delete the NewLine
|
||||
readingsSingleUpdate($hash,"month",$v,1);
|
||||
#-- yearly reading
|
||||
} elsif($a[1] eq "year") {
|
||||
$v = NT5000_GetLine($hash,"year");
|
||||
if(!defined($v)) {
|
||||
Log GetLogLevel($name,2), "NT5000_Get $a[1] error";
|
||||
return "$a[0] $a[1] => Error";
|
||||
}
|
||||
$v =~ s/[\r\n]//g; # Delete the NewLine
|
||||
$hash->{READINGS}{$a[1]}{VAL} = $v;
|
||||
$hash->{READINGS}{$a[1]}{TIME} = TimeNow();
|
||||
}
|
||||
else
|
||||
{
|
||||
return "NT5000_Get with unknown argument $a[1], choose one of " . join(",", sort keys %gets);
|
||||
}
|
||||
}
|
||||
$v =~ s/[\r\n]//g; # Delete the NewLine
|
||||
readingsSingleUpdate($hash,"year",$v,1);
|
||||
} else {
|
||||
return "NT5000_Get with unknown argument $a[1], choose one of " . join(",", sort keys %gets);
|
||||
}
|
||||
|
||||
Log GetLogLevel($name,3), "NT5000_Get $a[1] $v";
|
||||
return "$a[0] $a[1] => $v";
|
||||
Log GetLogLevel($name,3), "NT5000_Get $a[1] $v";
|
||||
return "$a[0] $a[1] => $v";
|
||||
}
|
||||
|
||||
########################################################################################
|
||||
@ -258,7 +264,6 @@ sub NT5000_Set ($@) {
|
||||
$res = "not yet implemented";
|
||||
}
|
||||
Log GetLogLevel($name,3), "NT5000_Set $name ".join(" ",@a)." => $res";
|
||||
DoTrigger($name, undef) if($init_done);
|
||||
return "NT5000_Set => $name ".join(" ",@a)." => $res";
|
||||
}
|
||||
}
|
||||
@ -275,8 +280,9 @@ sub NT5000_GetStatus ($) {
|
||||
my ($hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
# Call us in n minutes again.
|
||||
InternalTimer(gettimeofday()+ $hash->{Timer}, "NT5000_GetStatus", $hash,1);
|
||||
#-- restart timer for updates
|
||||
RemoveInternalTimer($hash);
|
||||
InternalTimer(gettimeofday()+ $hash->{INTERVAL}, "NT5000_GetStatus", $hash,1);
|
||||
|
||||
# Obtain the current reading
|
||||
my $result = NT5000_GetLine($hash, "reading");
|
||||
@ -284,26 +290,28 @@ sub NT5000_GetStatus ($) {
|
||||
# If one of these applies we must assume that the inverter is offline (no retry !)
|
||||
# Logging only if this is a change from the previous state
|
||||
if( !defined($result) ) {
|
||||
Log GetLogLevel($name,1), "NT5000 cannot be read, inverter offline" if( $hash->{STATE} ne "offline" );
|
||||
Log GetLogLevel($name,1), "NT5000 cannot be read, inverter offline" if( $hash->{READINGS}{"state"}{VAL} ne "offline" );
|
||||
#Log 3, "NT5000 cannot be read, inverter offline";
|
||||
$hash->{STATE} = "offline";
|
||||
return $hash->{STATE};
|
||||
readingsSingleUpdate($hash,"state","offline",1);
|
||||
return "offline";
|
||||
} elsif( length($result) < 13 ){
|
||||
Log GetLogLevel($name,1), "NT5000 returns incomplete line, inverter offline" if( $hash->{STATE} ne "starting" );
|
||||
Log GetLogLevel($name,1), "NT5000 returns incomplete line, inverter offline" if( $hash->{READINGS}{"state"}{VAL} ne "starting" );
|
||||
#Log 3, "NT5000 returns incomplete line";
|
||||
$hash->{STATE} = "starting";
|
||||
return $hash->{STATE};
|
||||
readingsSingleUpdate($hash,"state","incomplete",1);
|
||||
return "incomplete";
|
||||
}else {
|
||||
# we have obtained a reading: inverter is online
|
||||
#Log 3, "NT5000 has answered 13 bytes";
|
||||
my $tn = TimeNow();
|
||||
my @names = ("Udc", "Idc", "Pdc", "Uac", "Iac", "Pac", "Temp", "S", "Wd", "Wtot", "Eta");
|
||||
readingsBeginUpdate($hash);
|
||||
my @names = ("Udc", "Idc", "Pdc", "Uac", "Iac", "Pac", "Temp", "S", "Wd", "Wtot", "Eta");
|
||||
my @units = ("Volt", "Ampere", "Kilowatt", "Volt", "Ampere", "Kilowatt", "Celsius", "Watt per m<sup>2</sup>", "Kilowatthours", "Kilowatthours", "percent");
|
||||
my @unitabbr = ("V", "A", "kW", "V", "A", "kW", "°C", "W/m<sup>2</sup>", "kWh", "kWh", "%");
|
||||
my @type = ("voltage", "current", "power", "voltage", "current", "power", "temperature", "power density", "energy", "energy","efficiency");
|
||||
|
||||
if( !($hash->{STATE} =~ m/.*kW/) ) {
|
||||
# we have turned online recently
|
||||
#-- we are in the first reading, have turned online recently
|
||||
if( $hash->{READINGS}{"state"}{VAL} !~ m/.*kW.*/ ) {
|
||||
Log GetLogLevel($name,2), "NT5000 inverter is online";
|
||||
$hash->{STATE} = "starting";
|
||||
# Obtain the serial number and protocol
|
||||
readingsBulkUpdate($hash,"state","online");
|
||||
#-- Obtain the serial number and protocol
|
||||
my $serial = NT5000_GetLine($hash, "serial");
|
||||
$serial =~ s/^.*S://;
|
||||
$serial =~ s/[\r\n ]//g;
|
||||
@ -314,43 +322,48 @@ sub NT5000_GetStatus ($) {
|
||||
$hash->{Firmware} = substr($proto,0,1).".".substr($proto,1,1);
|
||||
$hash->{Protocol} = substr($proto,2,1).".".substr($proto,4,2);
|
||||
|
||||
# Obtain monthly readings in 70 seconds - only once
|
||||
# Obtain monthly readings in 20 seconds - only once
|
||||
InternalTimer(gettimeofday()+ 20, "NT5000_GetMonth", $hash,1);
|
||||
|
||||
# Obtain yearly readings in 10 seconds - only once
|
||||
# Obtain yearly readings in 40 seconds - only once
|
||||
InternalTimer(gettimeofday()+ 40, "NT5000_GetYear", $hash,1);
|
||||
|
||||
my $resmod ="header: ";
|
||||
#Put a header line into the log file
|
||||
#-- Put header lines into the log file
|
||||
my $resmod ="";
|
||||
for(my $i = 0; $i < int(@names); $i++) {
|
||||
$resmod .= $names[$i]." ";
|
||||
}
|
||||
$hash->{CHANGED}[$main::cline++] = "$resmod";
|
||||
readingsBulkUpdate($hash,"header1",$resmod);
|
||||
|
||||
$resmod ="";
|
||||
for(my $i = 0; $i < int(@unitabbr); $i++) {
|
||||
$resmod .= "[".$unitabbr[$i]."] ";
|
||||
}
|
||||
readingsBulkUpdate($hash,"header2",$resmod);
|
||||
#-- set units properly
|
||||
for(my $i = 0; $i < int(@names); $i++) {
|
||||
$hash->{READINGS}{$names[$i]}{UNIT} = $units[$i];
|
||||
$hash->{READINGS}{$names[$i]}{UNITABBR} = $unitabbr[$i];
|
||||
$hash->{READINGS}{$names[$i]}{TYPE} = $type[$i];
|
||||
}
|
||||
};
|
||||
|
||||
#-- Log level 5
|
||||
#-- put into READINGS
|
||||
readingsBulkUpdate($hash,"reading",$result);
|
||||
Log GetLogLevel($name,5), "NT5000 online result = $result";
|
||||
|
||||
# All data items in one line saves a lot of place
|
||||
my $resmod = $result;
|
||||
#$resmod =~ s/;/ /g;
|
||||
$hash->{CHANGED}[$main::cline++] = "reading: $resmod";
|
||||
|
||||
#-- split result for writing into hash
|
||||
my @data = split(' ',$result);
|
||||
$hash->{STATE} = sprintf("%5.3f kW",$data[5]);
|
||||
readingsBulkUpdate($hash,"state", sprintf("%5.3f %s",$data[5],$unitabbr[5]));
|
||||
for(my $i = 0; $i < int(@names); $i++) {
|
||||
# This puts individual pairs into the tabular view
|
||||
$hash->{READINGS}{$names[$i]}{VAL} = $data[$i];
|
||||
$hash->{READINGS}{$names[$i]}{TIME} = $tn;
|
||||
readingsBulkUpdate($hash,$names[$i],$data[$i]);
|
||||
}
|
||||
|
||||
DoTrigger($name, undef) if($init_done);
|
||||
|
||||
readingsEndUpdate($hash,1);
|
||||
$result =~ s/;/ /g;
|
||||
}
|
||||
|
||||
return $hash->{STATE};
|
||||
return $hash->{READINGS}{"state"}{VAL};
|
||||
}
|
||||
|
||||
########################################################################################
|
||||
@ -362,9 +375,15 @@ sub NT5000_GetStatus ($) {
|
||||
########################################################################################
|
||||
|
||||
sub NT5000_GetMonth ($) {
|
||||
my ($hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
my ($hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
my ($ln,$lf,$ret,$daten);
|
||||
|
||||
my ($sec,$min,$hour,$dayn,$month,$year,$wday,$yday,$isdst) = localtime(time);
|
||||
my $yearn = $year+1900;
|
||||
my $monn = $month+1;
|
||||
|
||||
#-- Obtain the monthly reading
|
||||
my $result = NT5000_GetLine($hash, "month");
|
||||
$result =~ s/^.*M://;
|
||||
@ -375,40 +394,47 @@ my $name = $hash->{NAME};
|
||||
my $day = $data[0];
|
||||
|
||||
#-- Expected yield for month
|
||||
my ($sec,$min,$hour,$dayn,$month,$year,$wday,$yday,$isdst) = localtime(time);
|
||||
|
||||
my $mex = "Wx_M".($month+1);
|
||||
my $wex = $attr{$name}{$mex};
|
||||
my $wac = 0;
|
||||
my $wre;
|
||||
|
||||
my @names = ("W_D1","W_D2","W_D3","W_D4","W_D5","W_D6","W_D7","W_D8","W_D9","W_D10",
|
||||
my @names = ("W_D01","W_D02","W_D03","W_D04","W_D05","W_D06","W_D07","W_D08","W_D09","W_D10",
|
||||
"W_D11","W_D12","W_D13","W_D14","W_D15","W_D16","W_D17","W_D18","W_D19","W_D20",
|
||||
"W_D21","W_D22","W_D23","W_D24","W_D25","W_D26","W_D27","W_D28","W_D29","W_D30","W_D31");
|
||||
|
||||
my $yearn = $year+1900;
|
||||
my $monn = $month+1;
|
||||
|
||||
#-- we are not sure, if this is logged now or in the next minute, hence an "or" in the regexp
|
||||
$hash->{LASTM}=sprintf("%4d-%02d-%02d_%02d:(%02d|%02d)",$yearn,$monn,$day,$hour,$min,$min+1);
|
||||
|
||||
for(my $i = 0; $i < $day; $i++)
|
||||
{
|
||||
|
||||
my $dayn = $i+1;
|
||||
my $daten = $yearn."-".$monn."-".$dayn."_23:59:59";
|
||||
$wac += $data[$day-$i];
|
||||
if( $wex )
|
||||
{
|
||||
$wre = int(1000*$wac/$wex)/10 if ($wex>0 );
|
||||
};
|
||||
# Put one item per line into the log file
|
||||
# +1 necessary - otherwise will be overridden by the changes in the daily readings
|
||||
$main::cline++;
|
||||
$hash->{CHANGED}[$main::cline++] = "$names[$i]: $daten $data[$day-$i] $wac $wre";
|
||||
# This puts individual pairs into the tabular view
|
||||
#$hash->{READINGS}{$names[$i]}{TIME} = $tn;
|
||||
#$hash->{READINGS}{$names[$i]}{VAL} = $data[$day-$i];
|
||||
#-- Check current logfile
|
||||
$ln = $attr{$name}{"LogM"};
|
||||
if( !(defined($ln))){
|
||||
Log 1,"NT5000_GetMonth: Attribute LogM is missing";
|
||||
#-- here some other output of monthly data
|
||||
} else {
|
||||
$lf = $defs{$ln}{currentlogfile};
|
||||
$ret = open(NT5000FILE, "> $lf" );
|
||||
if( $ret) {
|
||||
print NT5000FILE "monthly data: Day Wd Wm Wex\n";
|
||||
for(my $i = 0; $i < $day; $i++) {
|
||||
$dayn = $i+1;
|
||||
#-- for current day actual time, otherwise dummy time
|
||||
if( $i == ($day-1) ){
|
||||
$daten = sprintf("%4d-%02d-%02d_%02d:%02d:%02d",$yearn,$monn,$dayn,$hour,$min,$sec);
|
||||
}else{
|
||||
$daten = sprintf("%4d-%02d-%02d_23:59:59",$yearn,$monn,$dayn);
|
||||
}
|
||||
$wac += $data[$day-$i];
|
||||
if( $wex ){
|
||||
$wre = int(1000*$wac/$wex)/10 if ($wex>0 );
|
||||
};
|
||||
# Put one item per line into the log file
|
||||
printf NT5000FILE "%s %s %5s %6.3f %5.1f %5.1f\n",$daten,$name,$names[$i],$data[$day-$i],$wac,$wre;
|
||||
};
|
||||
Log 1,"NT5000_GetMonth: File overwritten";
|
||||
close(NT5000FILE);
|
||||
} else {
|
||||
Log 1,"NT5000_GetMonth: Cannot open $lf for writing!";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
########################################################################################
|
||||
@ -420,48 +446,64 @@ my $name = $hash->{NAME};
|
||||
########################################################################################
|
||||
|
||||
sub NT5000_GetYear ($) {
|
||||
my ($hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
my ($hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
#-- Obtain the yearly reading
|
||||
my $result = NT5000_GetLine($hash, "year");
|
||||
$result =~ s/^.*Y://;
|
||||
$result =~ s/[\r\n ]//g;
|
||||
Log GetLogLevel($name,3), "NT5000 yearly result = $result";
|
||||
$result=~ s/,/./g;
|
||||
my @data = split(";", $result);
|
||||
|
||||
#-- Expected yield for year
|
||||
my ($sec,$min,$hour,$dayn,$month,$year,$wday,$yday,$isdst) = localtime(time);
|
||||
my $wex = $attr{$name}{Wx_Y};
|
||||
my $wac = 0;
|
||||
my $wre;
|
||||
|
||||
my @names = ("W_M01","W_M02","W_M03","W_M04","W_M05","W_M06","W_M07","W_M08","W_M09","W_M10",
|
||||
"W_M11","W_M12");
|
||||
my ($ln,$lf,$ret,$monn,$daten,$mex,$mmex);
|
||||
|
||||
my ($sec,$min,$hour,$dayn,$month,$year,$wday,$yday,$isdst) = localtime(time);
|
||||
my $yearn = $year+1900;
|
||||
#-- we are not sure, if this is logged now or in the next minute, hence an "or" in the regexp
|
||||
$hash->{LASTY}=sprintf("%4d-%02d-%02d_%02d:(%02d|%02d)",$yearn,$month+1,$dayn,$hour,$min,$min+1);
|
||||
|
||||
for(my $i = 0; $i <= $month; $i++) {
|
||||
my $monn = $i+1;
|
||||
my $daten = $yearn."-".$monn."-28_23:59:59";
|
||||
my $mex = "Wx_M".($monn);
|
||||
my $mmex = $attr{$name}{$mex};
|
||||
$wac += $data[$month+1-$i];
|
||||
if( $wex )
|
||||
{
|
||||
$wre = int(1000.0*$wac/$wex)/10 if ($wex > 0);
|
||||
};
|
||||
#-- Put one item per line into the log file
|
||||
# +1 necessary - otherwise will be overridden by the changes in the daily readings
|
||||
$hash->{CHANGED}[$main::cline++] = "$names[$i]: $daten $data[$month+1-$i] $mmex $wac $wre";
|
||||
#-- This puts individual pairs into the tabular view
|
||||
# $hash->{READINGS}{$names[$i]}{TIME} = $tn;
|
||||
# $hash->{READINGS}{$names[$i]}{VAL} = $data[$i];
|
||||
};
|
||||
}
|
||||
|
||||
#-- Obtain the yearly reading
|
||||
my $result = NT5000_GetLine($hash, "year");
|
||||
$result =~ s/^.*Y://;
|
||||
$result =~ s/[\r\n ]//g;
|
||||
Log GetLogLevel($name,3), "NT5000 yearly result = $result";
|
||||
$result=~ s/,/./g;
|
||||
my @data = split(";", $result);
|
||||
|
||||
#-- Expected yield for year
|
||||
my $wex = $attr{$name}{Wx_Y};
|
||||
my $wac = 0;
|
||||
my $wre;
|
||||
|
||||
my @names = ("W_M01","W_M02","W_M03","W_M04","W_M05","W_M06","W_M07","W_M08","W_M09","W_M10",
|
||||
"W_M11","W_M12");
|
||||
|
||||
#-- Check current logfile
|
||||
$ln = $attr{$name}{"LogY"};
|
||||
if( !(defined($ln))){
|
||||
Log 1,"NT5000_GetYear: Attribute LogY is missing";
|
||||
#-- here some other output of yearly data
|
||||
} else {
|
||||
$lf = $defs{$ln}{currentlogfile};
|
||||
$ret = open(NT5000FILE, "> $lf" );
|
||||
if( $ret) {
|
||||
print NT5000FILE "yearly data: Month Wm Wex Wy Wy \n";
|
||||
for(my $i = 0; $i <= $month; $i++) {
|
||||
$monn = $i+1;
|
||||
#-- for current month actual time, otherwise dummy time
|
||||
if( $i == $month ){
|
||||
$daten = sprintf("%4d-%02d-28_%02d:%02d:%02d",$yearn,$monn,$hour,$min,$sec);
|
||||
}else{
|
||||
$daten = sprintf("%4d-%02d-28_23:59:59",$yearn,$monn);
|
||||
}
|
||||
$mex = "Wx_M".($monn);
|
||||
$mmex = $attr{$name}{$mex};
|
||||
$wac += $data[$month+1-$i];
|
||||
if( $wex ){
|
||||
$wre = int(1000.0*$wac/$wex)/10 if ($wex > 0);
|
||||
};
|
||||
# Put one item per line into the log file
|
||||
printf NT5000FILE "%s %s %5s %5.1f %3d %6.1f %5.1f\n",$daten,$name,$names[$i],$data[$month+1-$i],$mmex,$wac,$wre;
|
||||
}
|
||||
Log 1,"NT5000_GetYear: File overwritten";
|
||||
close(NT5000FILE);
|
||||
} else {
|
||||
Log 1,"NT5000_GetYear: Cannot open $lf for writing!";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
########################################################################################
|
||||
#
|
||||
@ -708,9 +750,7 @@ sub NT5000_GetLine ($$) {
|
||||
# cmd = 5 byte parameter to query the device properly
|
||||
#
|
||||
########################################################################################
|
||||
sub
|
||||
NT5000_5to13($$$)
|
||||
{
|
||||
sub NT5000_5to13($$$) {
|
||||
|
||||
my $retry = 0;
|
||||
my ($hash,$dev,$cmd) = @_;
|
||||
@ -721,74 +761,39 @@ my ($i,$j,$k);
|
||||
if( $dev eq "none" ) #no inverter attached
|
||||
{
|
||||
return "\x00\x01\x02\x03\x04\x05\x06\x07\x07\x09\x0a\x0b\x0c";
|
||||
|
||||
} elsif ( $dev eq "emulator" ) #emulator attached
|
||||
{
|
||||
#-- read from emulator
|
||||
|
||||
#-- calculate checksum
|
||||
} elsif ( $dev eq "emulator" ) {
|
||||
my $CS = unpack("%32C*", $cmd);
|
||||
$CS=$CS%256;
|
||||
my $cmd2=sprintf("%s%c",$cmd,$CS);
|
||||
#-- control
|
||||
#print "Sending out:\n";
|
||||
#for(my $i=0;$i<5;$i++)
|
||||
# { my $j=int(ord(substr($cmd2,$i,1))/16);
|
||||
# my $k=ord(substr($cmd2,$i,1))%16;
|
||||
# print "byte $i = 0x$j$k\n";
|
||||
# }
|
||||
|
||||
my $result = NT5000_emu(5,$cmd);
|
||||
#print "[I] Answer 13 bytes received\n";
|
||||
#for($i=0;$i<13;$i++)
|
||||
# { $j=int(ord($invBuffer[$i])/16);
|
||||
# $k=ord($invBuffer[$i])%16;
|
||||
# print "byte $i = 0x$j$k\n";
|
||||
# }
|
||||
return($result);
|
||||
|
||||
} else # here we do the real thing
|
||||
{
|
||||
#Just opening the old device does not reaaly work.
|
||||
#my $serport = $hash->{USBDev};
|
||||
#-- here we do the real thing
|
||||
} else {
|
||||
my $serport = new Device::SerialPort ($dev);
|
||||
if(!$serport) {
|
||||
Log 1, "NT5000: Can't open $dev: $!";
|
||||
return undef;
|
||||
}
|
||||
#Log 3, "NT5000 opened";
|
||||
$serport->reset_error();
|
||||
$serport->baudrate(9600) || die "failed setting baudrate";
|
||||
$serport->databits(8) || die "failed setting databits";
|
||||
$serport->parity('none') || die "failed setting parity";
|
||||
$serport->stopbits(1) || die "failed setting stopbits";
|
||||
$serport->handshake('none') || die "failed setting handshake";
|
||||
$serport->write_settings || die "no settings";
|
||||
|
||||
#my $rm = "NT5000 timeout reading the answer";
|
||||
$serport->baudrate(9600);
|
||||
$serport->databits(8);
|
||||
$serport->parity('none');
|
||||
$serport->stopbits(1);
|
||||
$serport->handshake('none');
|
||||
$serport->write_settings;
|
||||
|
||||
#-- calculate checksum
|
||||
#-- calculate checksum and send
|
||||
my $CS = unpack("%32C*", $cmd);
|
||||
$CS=$CS%256;
|
||||
my $cmd2=sprintf("%s%c",$cmd,$CS);
|
||||
#-- control
|
||||
#print "Sending out:\n";
|
||||
#for(my $i=0;$i<5;$i++)
|
||||
# { my $j=int(ord(substr($cmd2,$i,1))/16);
|
||||
# my $k=ord(substr($cmd2,$i,1))%16;
|
||||
# print "byte $i = 0x$j$k\n";
|
||||
# }
|
||||
|
||||
my $count_out = $serport->write($cmd2);
|
||||
Log 3, "NT5000 write failed\n" unless ($count_out);
|
||||
Log 3, "NT5000 write incomplete $count_out ne ".(length($cmd2))."\n" if ( $count_out != 5 );
|
||||
#Log 3, "write complete $count_out \n" if ( $count_out == 5 );
|
||||
#-- sleeping 0.03 seconds
|
||||
Log 3, "NT5000 write incomplete $count_out ne ".(length($cmd2))."\n" if ( $count_out != 5 );;
|
||||
#-- sleeping 0.05 seconds
|
||||
select(undef,undef,undef,0.05);
|
||||
my ($count_in, $string_in) = $serport->read(13);
|
||||
#Log 3, "NT5000 read unsuccessful, $count_in bytes \n" unless ($count_in == 13);
|
||||
#Log 3, "read complete $count_in \n" if ( $count_in == 13 );
|
||||
#-- sleeping 0.03 seconds
|
||||
#-- sleeping 0.05 seconds
|
||||
select(undef,undef,undef,0.05);
|
||||
$serport->close();
|
||||
return($string_in);
|
||||
@ -1093,6 +1098,86 @@ elsif( $count == 5)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
1;
|
||||
|
||||
|
||||
=pod
|
||||
=begin html
|
||||
|
||||
<a name="NT5000"></a>
|
||||
<h3>NT5000</h3>
|
||||
<p>FHEM module to commmunicate with a Sunways NT5000 solar inverter<br />
|
||||
</p>
|
||||
<h4>Example</h4>
|
||||
<p>
|
||||
<code>define nt5000 NT5000 /dev/ttyUSB0 </code>
|
||||
</p><br />
|
||||
<a name="NT5000define"></a>
|
||||
<h4>Define</h4>
|
||||
<p>
|
||||
<code>define <name> NT5000 <device> </code>
|
||||
<br /><br /> Define a NT5000 solar inverter</p>
|
||||
<ul>
|
||||
<li>
|
||||
<code><name></code>
|
||||
Serial device port or the keyword <code>emulator</code>. In the latter case, a 4.5 kWP solar installation is simulated
|
||||
</li>
|
||||
</ul>
|
||||
<a name="NT5000set"></a>
|
||||
<h4>Set</h4>
|
||||
<ul>
|
||||
<li><a name="nt5000_time">
|
||||
Not yet implemented</li>
|
||||
</ul>
|
||||
<br />
|
||||
<a name="NT5000get"></a>
|
||||
<h4>Get</h4>
|
||||
<ul>
|
||||
<li><a name="nt5000_reading">
|
||||
<code>get <name> reading</code></a>
|
||||
<br /> read all current data </li>
|
||||
<li><a name="nt5000_month">
|
||||
<code>get <name> month</code></a>
|
||||
<br /> read all data from current month </li>
|
||||
<li><a name="nt5000_year">
|
||||
<code>get <name> year</code></a>
|
||||
<br /> read all data from current year </li>
|
||||
<li><a name="nt5000_present">
|
||||
<code>get <name> present</code></a>
|
||||
<br /> 1 if device present, 0 if not </li>
|
||||
<li><a name="nt5000_serial">
|
||||
<code>get <name> serial</code></a>
|
||||
<br /> inverter serial number </li>
|
||||
<li><a name="nt5000_proto">
|
||||
<code>get <name> proto</code></a>
|
||||
<br /> inverter protocol </li>
|
||||
</ul>
|
||||
<br />
|
||||
<a name="NT5000attr"></a>
|
||||
<h4>Attributes</h4>
|
||||
<ul>
|
||||
<li><a name="nt5000_Area"><code>attr <name> Area <float></code>
|
||||
</a>
|
||||
<br />Effective area [m<sup>2</sup>of the installation</li>
|
||||
<li><a name="nt5000_PSP"><code>attr <name> PSP <float></code>
|
||||
</a>
|
||||
<br />Peak Solar Power [kW] of the installation</li>
|
||||
<li><a name="nt5000_Wx_M"><code>attr <name> Wx_M<n> <float></code>
|
||||
</a>
|
||||
<br />Expected yield [kWh] for month <n>=1...12</li>
|
||||
<li><a name="nt5000_Wx_Y"><code>attr <name> Wx_Y <float></code>
|
||||
</a>
|
||||
<br />Expected yield [kWh] for a full year</li>
|
||||
<li><a name="nt5000_MERR"><code>attr <name> MERR <list></code>
|
||||
</a>
|
||||
<br />List of months with erroneous logging, see lines 83ff</li>
|
||||
<li>Standard attributes <a href="#alias">alias</a>, <a href="#comment">comment</a>, <a
|
||||
href="#event-on-update-reading">event-on-update-reading</a>, <a
|
||||
href="#event-on-change-reading">event-on-change-reading</a>, <a href="#room"
|
||||
>room</a>, <a href="#eventMap">eventMap</a>, <a href="#loglevel">loglevel</a>,
|
||||
<a href="#webCmd">webCmd</a></li>
|
||||
</ul>
|
||||
|
||||
=end html
|
||||
=cut
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user