2
0
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:
pahenning 2013-02-12 08:32:06 +00:00
parent 1627ad8879
commit 15af99f98e
7 changed files with 1222 additions and 275 deletions

View File

@ -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>

View File

@ -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 &lt;name&gt; LogM
&lt;string&gt;|</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 &lt;name&gt; &lt;channel&gt;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>

View File

@ -985,7 +985,8 @@ sub OWXMULTI_SetValues($@) {
<br />unit of measurement (temperature scale), default is Celsius = &deg;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>

View File

@ -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>

View File

@ -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
View 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 &lt;name&gt; EMX &lt;code&gt; &lt;rpunit&gt;</code> or <br/>
<code>define &lt;name&gt; EMX emulator</code>
<br /><br /> Define an EMX device or an emulated EM1000-WZ device <br /><br />
</p>
<ul>
<li>
<code>&lt;code&gt;</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>&lt;rpunit&gt;</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 &lt;name&gt; midnight &lt;int&gt;</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 &lt;name&gt; midnight</code></a>
<br /> Returns the value of the counter at midnight </li>
<li><a name="emx_month">
<code>get &lt;name&gt; 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 &lt;name&gt; &lt;LogM&gt;
&lt;string&gt;</code></a>
<br />Device name (<i>not file name</i>) of the monthly logfile </li>
<li><a name="emx_logy"><code>attr &lt;name&gt; &lt;LogY&gt;
&lt;string&gt;</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

View File

@ -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", "&deg;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 &lt;name&gt; NT5000 &lt;device&gt; </code>
<br /><br /> Define a NT5000 solar inverter</p>
<ul>
<li>
<code>&lt;name&gt;</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 &lt;name&gt; reading</code></a>
<br /> read all current data </li>
<li><a name="nt5000_month">
<code>get &lt;name&gt; month</code></a>
<br /> read all data from current month </li>
<li><a name="nt5000_year">
<code>get &lt;name&gt; year</code></a>
<br /> read all data from current year </li>
<li><a name="nt5000_present">
<code>get &lt;name&gt; present</code></a>
<br /> 1 if device present, 0 if not </li>
<li><a name="nt5000_serial">
<code>get &lt;name&gt; serial</code></a>
<br /> inverter serial number </li>
<li><a name="nt5000_proto">
<code>get &lt;name&gt; proto</code></a>
<br /> inverter protocol </li>
</ul>
<br />
<a name="NT5000attr"></a>
<h4>Attributes</h4>
<ul>
<li><a name="nt5000_Area"><code>attr &lt;name&gt; Area &lt;float&gt;</code>
</a>
<br />Effective area [m<sup>2</sup>of the installation</li>
<li><a name="nt5000_PSP"><code>attr &lt;name&gt; PSP &lt;float&gt;</code>
</a>
<br />Peak Solar Power [kW] of the installation</li>
<li><a name="nt5000_Wx_M"><code>attr &lt;name&gt; Wx_M<n> &lt;float&gt;</code>
</a>
<br />Expected yield [kWh] for month <n>=1...12</li>
<li><a name="nt5000_Wx_Y"><code>attr &lt;name&gt; Wx_Y &lt;float&gt;</code>
</a>
<br />Expected yield [kWh] for a full year</li>
<li><a name="nt5000_MERR"><code>attr &lt;name&gt; MERR &lt;list&gt;</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