From 15af99f98e22e0b905a83c15ef1353f2cf622e0c Mon Sep 17 00:00:00 2001
From: pahenning <>
Date: Tue, 12 Feb 2013 08:32:06 +0000
Subject: [PATCH] git-svn-id: https://svn.fhem.de/fhem/trunk@2703
2b470e98-0d58-463d-a4d8-8e2adae1ed80
---
fhem/FHEM/21_OWAD.pm | 10 +-
fhem/FHEM/21_OWCOUNT.pm | 151 ++++++--
fhem/FHEM/21_OWMULTI.pm | 3 +-
fhem/FHEM/21_OWSWITCH.pm | 3 +-
fhem/FHEM/21_OWTHERM.pm | 38 +-
fhem/contrib/15_EMX.pm | 773 ++++++++++++++++++++++++++++++++++++++
fhem/contrib/70_NT5000.pm | 519 ++++++++++++++-----------
7 files changed, 1222 insertions(+), 275 deletions(-)
create mode 100755 fhem/contrib/15_EMX.pm
diff --git a/fhem/FHEM/21_OWAD.pm b/fhem/FHEM/21_OWAD.pm
index c8c988967..15b30cf50 100644
--- a/fhem/FHEM/21_OWAD.pm
+++ b/fhem/FHEM/21_OWAD.pm
@@ -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.
Standard attributes alias , comment , event-on-update-reading , event-on-change-reading , event-on-change-reading , stateFormat , room , eventMap , loglevel ,
webCmd
diff --git a/fhem/FHEM/21_OWCOUNT.pm b/fhem/FHEM/21_OWCOUNT.pm
index 801e20a71..5c70e2498 100644
--- a/fhem/FHEM/21_OWCOUNT.pm
+++ b/fhem/FHEM/21_OWCOUNT.pm
@@ -36,7 +36,7 @@
# Additional attributes are defined in fhem.cfg, in some cases per channel, where =A,B
# Note: attributes are read only during initialization procedure - later changes are not used.
#
-# attr UnitInReading = whether the physical unit is written into the reading = 1 (default) or 0
+# attr LogM = device name (not file name) of monthly log file
# attr Name | = name for the channel | a type description for the measured value
# attr Unit | = unit of measurement for this channel | its abbreviation
# attr Offset = 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{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{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( ){
+ #-- line looks as
+ # 2013-02-09_23:59:31 day D_09 : 180.0 cts : 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{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($$$) {
Attributes
+
For each of the following attributes, the channel identification A,B may be used.
attr <name> <channel>Name
@@ -1187,7 +1263,8 @@ sub OWXCOUNT_SetPage($$$) {
factor multiplied to (reading+offset) in this channel.
Standard attributes alias , comment , event-on-update-reading , event-on-change-reading , event-on-change-reading , stateFormat , room , eventMap , loglevel ,
webCmd
diff --git a/fhem/FHEM/21_OWMULTI.pm b/fhem/FHEM/21_OWMULTI.pm
index 10d9eeb29..bd3c0a5bc 100644
--- a/fhem/FHEM/21_OWMULTI.pm
+++ b/fhem/FHEM/21_OWMULTI.pm
@@ -985,7 +985,8 @@ sub OWXMULTI_SetValues($@) {
unit of measurement (temperature scale), default is Celsius = °C
Standard attributes alias , comment , event-on-update-reading , event-on-change-reading , event-on-change-reading , stateFormat , room , eventMap , loglevel ,
webCmd
diff --git a/fhem/FHEM/21_OWSWITCH.pm b/fhem/FHEM/21_OWSWITCH.pm
index 126794624..2d7222277 100644
--- a/fhem/FHEM/21_OWSWITCH.pm
+++ b/fhem/FHEM/21_OWSWITCH.pm
@@ -1198,7 +1198,8 @@ sub OWXSWITCH_SetState($$) {
display for on | off condition
Standard attributes alias , comment , event-on-update-reading , event-on-change-reading , event-on-change-reading , stateFormat , room , eventMap , loglevel ,
webCmd
diff --git a/fhem/FHEM/21_OWTHERM.pm b/fhem/FHEM/21_OWTHERM.pm
index f0ad2f222..3b83d419d 100755
--- a/fhem/FHEM/21_OWTHERM.pm
+++ b/fhem/FHEM/21_OWTHERM.pm
@@ -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).
Standard attributes alias , comment , event-on-update-reading , event-on-change-reading , event-on-change-reading , stateFormat , room , eventMap , loglevel ,
webCmd
diff --git a/fhem/contrib/15_EMX.pm b/fhem/contrib/15_EMX.pm
new file mode 100755
index 000000000..731f0eab2
--- /dev/null
+++ b/fhem/contrib/15_EMX.pm
@@ -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
+#
+# where
+# may be replaced by any name string
+# is a number 1 - 12 or the keyword "emulator".
+# is the scale factor = rotations per kWh or m^3 (not needed for emulator)
+#
+# get midnight => todays starting value for counter
+# get 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
+#
+# Start and end of daytime rate - optional
+# attr emx RateDStart
+# attr emx RateDEnd
+#
+# Rate during nighttime (cost per kWh) - only if needed
+# attr emx RateN
+#
+########################################################################################
+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 EMX "
+ 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{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( ){
+ #-- line looks like
+ # 2013-02-09_23:59:31 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
+
+
+ EMX
+ FHEM module to commmunicate EM1000 power/gas sensors
+ NOTE: 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.
+
+ Example
+
+ define E_Verbrauch EMX 1 75
+
+
+
+ Define
+
+ define <name> EMX <code> <rpunit>
or
+ define <name> EMX emulator
+ Define an EMX device or an emulated EM1000-WZ device
+
+
+
+
+ <code>
Defines the sensor model, currently the following values are permitted:
+
+ 1 .. 4: EM1000-WZ power meter sensor => unit is kWh
+ 5 .. 8: EM1000-EM power sensor => unit is kWh
+ 9 .. 12: EM1000-GZ gas meter sensor => unit is m3
+
+
+
+ <rpunit>
Fcator to scale the reading into units
+
+ EM1000-WZ devices: rotations per kWh, usually 75 or 150
+ EM1000-EM devices: digits per kWh, usually 100
+ EM1000-GZ devices: digits per 3 , usually 100
+
+
+
+
+ Set
+
+
+
+ Get
+
+
+
+ Attributes
+
+
+=end html
+=cut
+
diff --git a/fhem/contrib/70_NT5000.pm b/fhem/contrib/70_NT5000.pm
index 30db53037..afd459e0e 100755
--- a/fhem/contrib/70_NT5000.pm
+++ b/fhem/contrib/70_NT5000.pm
@@ -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
+# define NT5000
#
-# where nt5000 may be replaced by any name string and
+# where may be replaced by any name string and
# is a serial (USB) device or the keyword "emulator".
# In the latter case, a 4.5 kWP solar installation is simulated
#
+# get present => 1 if device present, 0 if not
+# get serial => inverter serial number
+# get proto => protocol
+# get reading => measurement for all channels
+# get month => monthly measurement
+# get year => yearly measurement
+#
+# set 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
# 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 m2 ", "Kilowatthours", "Kilowatthours", "percent");
+ my @unitabbr = ("V", "A", "kW", "V", "A", "kW", "°C", "W/m2 ", "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
+
+
+ NT5000
+ FHEM module to commmunicate with a Sunways NT5000 solar inverter
+
+ Example
+
+ define nt5000 NT5000 /dev/ttyUSB0
+
+
+ Define
+
+ define <name> NT5000 <device>
+ Define a NT5000 solar inverter
+
+
+ <name>
+ Serial device port or the keyword emulator
. In the latter case, a 4.5 kWP solar installation is simulated
+
+
+
+ Set
+
+
+
+ Get
+
+
+
+ Attributes
+
+
+=end html
+=cut
+