mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-01-31 12:49:34 +00:00
80dbd7ef53
git-svn-id: https://svn.fhem.de/fhem/trunk@26125 2b470e98-0d58-463d-a4d8-8e2adae1ed80
915 lines
31 KiB
Perl
Executable File
915 lines
31 KiB
Perl
Executable File
########################################################################################
|
|
#
|
|
# 15_EMX.pm MUST be saved as 15_CUL_EM.pm !!!
|
|
#
|
|
# FHEM module to read the data from an EM1000 WZ/EM/GZ power sensor
|
|
#
|
|
# Prof. Dr. Peter A. Henning, 2011
|
|
#
|
|
# $Id: 15_EMX.pm 2.0 2013-02 - pahenning $
|
|
#
|
|
########################################################################################
|
|
#
|
|
# define <emx> EMX <code> <rpunit>
|
|
#
|
|
# 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 and power meter
|
|
# get <name> cnt_midnight => todays starting value for counter
|
|
# get <name> pm_midnight => todays starting value for power meter
|
|
# get <name> month => summary of current month
|
|
#
|
|
# set <name> cnt_midnight => todays starting value for counter
|
|
# set <name> pm_midnight => todays starting value for power meter
|
|
# set <name> pm_current => current power meter reading
|
|
#
|
|
# Attributes are set as
|
|
#
|
|
# Monthly and yearly log file
|
|
# attr emx LogM EnergyM
|
|
# attr emx LogY EnergyY
|
|
#
|
|
# Basic fee per Month (€ per Month)
|
|
# attr emx CostM
|
|
#
|
|
# Cost rate during daytime (€ per kWh)
|
|
# attr emx CostD <cost rate in €/unit>
|
|
#
|
|
# Start and end of daytime cost rate - optional
|
|
# attr emx CDStart <time as hh:mm>
|
|
# attr emx CDEnd <time as hh:mm>
|
|
#
|
|
# Cost rate during nighttime (cost per unit) - only if needed
|
|
# attr emx CostN <cost rate in €/unit>
|
|
#
|
|
########################################################################################
|
|
#
|
|
# This programm is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# The GNU General Public License can be found at
|
|
# http://www.gnu.org/copyleft/gpl.html.
|
|
# A copy is found in the textfile GPL.txt and important notices to the license
|
|
# from the author is found in LICENSE.txt distributed with these scripts.
|
|
#
|
|
# This script is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
########################################################################################
|
|
package main;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
my %gets = (
|
|
"midnight" => "",
|
|
"cnt_midnight" => "",
|
|
"pm_midnight" => "",
|
|
"month" => ""
|
|
);
|
|
|
|
my %sets = (
|
|
"cnt_midnight" => "C",
|
|
"pm_midnight" => "P",
|
|
"pm_current" => "M",
|
|
);
|
|
|
|
#-- 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 CostD CDStart CDEnd CostN CostM ".
|
|
$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}{"energy"}{FACTOR} = 150;
|
|
$hash->{READINGS}{"energy"}{UNIT} = "Kilowattstunden";
|
|
$hash->{READINGS}{"energy"}{UNITABBR}= "kWh";
|
|
$hash->{READINGS}{"power"}{PERIOD} = "h";
|
|
$hash->{READINGS}{"power"}{UNIT} = "Kilowatt";
|
|
$hash->{READINGS}{"power"}{UNITABBR} = "kW";
|
|
CommandAttr(undef,"$name model emulator");
|
|
|
|
#-- set/ get artificial data
|
|
my $msg=EMX_emu(0,12345);
|
|
$hash->{READINGS}{"count"}{midnight} = 12345;
|
|
$hash->{READINGS}{"pmeter"}{midnight} = 0;
|
|
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}{"energy"}{FACTOR} = $a[3];
|
|
$hash->{READINGS}{"energy"}{UNIT} = "Kilowattstunden";
|
|
$hash->{READINGS}{"energy"}{UNITABBR} = "kWh";
|
|
$hash->{READINGS}{"power"}{PERIOD} = "h";
|
|
$hash->{READINGS}{"power"}{UNIT} = "Kilowatt";
|
|
$hash->{READINGS}{"power"}{UNITABBR} = "kW";
|
|
CommandAttr (undef,"$name model EMWZ");
|
|
} elsif($a[2] >= 5 && $a[2] <= 8) { # EMEM
|
|
$hash->{READINGS}{"energy"}{FACTOR} = $a[3];
|
|
$hash->{READINGS}{"energy"}{UNIT} = "Kilowattstunden";
|
|
$hash->{READINGS}{"energy"}{UNITABBR} = "kWh";
|
|
$hash->{READINGS}{"power"}{PERIOD} = "h";
|
|
$hash->{READINGS}{"power"}{UNIT} = "Kilowatt";
|
|
$hash->{READINGS}{"power"}{UNITABBR} = "kW";
|
|
CommandAttr (undef,"$name model EMEM");
|
|
} elsif($a[2] >= 9 && $a[2] <= 12) { # EMGZ
|
|
$hash->{READINGS}{"energy"}{FACTOR} = $a[3];
|
|
$hash->{READINGS}{"energy"}{UNIT} = "Kubikmeter";
|
|
$hash->{READINGS}{"energy"}{UNITABBR} = "m^3";
|
|
$hash->{READINGS}{"power"}{PERIOD} = "h";
|
|
$hash->{READINGS}{"power"}{UNIT} = "Kubikmeter/Stunde";
|
|
$hash->{READINGS}{"power"}{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,$tval,$rval,$pval,$dval,$deltim,$delcnt,$msg);
|
|
my ($svalue,$dvalue,$mvalue) = ("","","");
|
|
my $cost = 0;
|
|
|
|
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;
|
|
if( $deltim>=0 ){
|
|
$daybreak = 1;
|
|
#-- Timer data from tomorrow
|
|
my ($secn,$minn,$hourn,$dayn,$monthn,$yearn,$wdayn,$ydayn,$isdstn) = localtime(time() + 3600);
|
|
#-- 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}{"energy"}{FACTOR};
|
|
$unit = $hash->{READINGS}{"energy"}{UNITABBR};
|
|
$period = $hash->{READINGS}{"power"}{PERIOD};
|
|
$runit = $hash->{READINGS}{"power"}{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);
|
|
$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
|
|
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.
|
|
# But careful: we cannot be sure that measurement intervals are really
|
|
# 5 minutes. Differ up to a second per interval !
|
|
# Affects the rate by 0.3%, absolute count is not affected
|
|
my $fivemin = 5.0;
|
|
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/$fivemin *$delcnt;
|
|
$cval = $emx_cnt-$midnight;
|
|
#-- 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") || ($model eq "emulator") ){
|
|
$vval = int($cval/$factor*1000)/1000;
|
|
$rval = int($emx_5min*12/$factor*1000)/1000;
|
|
$pval = int($emx_peak/($factor*20)*1000)/1000;
|
|
} elsif( $model eq "EMEM" ){
|
|
$vval = int($cval/($factor*10)*1000)/1000;
|
|
$rval = int($emx_5min/$factor*1000)/1000;
|
|
$pval = int($emx_peak/($factor*20)*1000)/1000;
|
|
} elsif( $model eq "EMGZ" ){
|
|
$vval = int($cval/$factor*1000)/1000;
|
|
$rval = int($emx_5min/$factor*1000)/1000;
|
|
$pval = int($emx_peak/($factor*20)*1000)/1000;
|
|
} else {
|
|
Log 3,"EMX: Wrong device model $model";
|
|
}
|
|
#-- power meter value
|
|
$tval = $vval + $hash->{READINGS}{"pmeter"}{midnight};
|
|
|
|
#-- calculate cost
|
|
if( defined($main::attr{$name}{"CostD"}) ){
|
|
#-- single rate counter
|
|
if( !defined($main::attr{$name}{"CostN"}) ){
|
|
$cost = $vval*$main::attr{$name}{"CostD"};
|
|
#-- dual rate counter
|
|
}else{
|
|
#--determine period 1 = still night, 2 = day, 3 = night again
|
|
my @crs = split(':',$main::attr{$name}{"CDStart"});
|
|
my @cre = split(':',$main::attr{$name}{"CDEnd"});
|
|
my @tim = split(/[- :]/,$emx_cnt_tim);
|
|
#-- if one of them fails, we switch to single rate mode
|
|
if( (int(@crs) ne 2) || (int(@cre) ne 2) ){
|
|
$cost = $vval*$main::attr{$name}{"CostD"};
|
|
delete $main::attr{$name}{"CostN"};
|
|
Log 3,"EMX: $name has improper cost rate time specification";
|
|
} else {
|
|
#-- period 1
|
|
if ( (($hour-$crs[0])*60 + $min-$crs[1])<0 ){
|
|
$cost = $vval*$main::attr{$name}{"CostN"};
|
|
#-- period 2
|
|
}elsif ( (($hour-$cre[0])*60 + $min-$cre[1])<0 ){
|
|
my $delta = ($tim[3]-$crs[0])*60 + ($tim[4]-$crs[1]) + $tim[5]/60.0;
|
|
my $oldval = $hash->{READINGS}{"energy"}{VAL};
|
|
#-- previous measurement was in period 1
|
|
if( $delta < 0 ){
|
|
$cost = $hash->{READINGS}{"cost"}{VAL} +
|
|
$main::attr{$name}{"CostN"}*($vval-$oldval)*(1+$delta/$fivemin)+
|
|
$main::attr{$name}{"CostD"}*($vval-$oldval)*(-$delta/$fivemin);
|
|
} else{
|
|
$cost = $hash->{READINGS}{"cost"}{VAL} + $main::attr{$name}{"CostD"}*($vval-$oldval);
|
|
}
|
|
#-- period 3
|
|
}else{
|
|
my $delta = ($tim[3]-$cre[0])*60 + ($tim[4]-$cre[1]) +$tim[5]/60.0;
|
|
my $oldval = $hash->{READINGS}{"energy"}{VAL};
|
|
#-- previous measurement was in period 2
|
|
if( $delta < 0 ){
|
|
$cost = $hash->{READINGS}{"cost"}{VAL} +
|
|
$main::attr{$name}{"CostD"}*($vval-$oldval)*(1+$delta/$fivemin)+
|
|
$main::attr{$name}{"CostN"}*($vval-$oldval)*(-$delta/$fivemin);
|
|
} else{
|
|
$cost = $hash->{READINGS}{"cost"}{VAL} + $main::attr{$name}{"CostN"}*($vval-$oldval);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
$cost = floor($cost*10000+0.5)/10000;
|
|
}
|
|
|
|
#-- state format
|
|
$svalue = sprintf("W: %5.2f %s P: %5.2f %s Pmax: %5.3f %s",$vval,$unit,$rval,$runit,$pval,$runit);
|
|
#-- put into READINGS
|
|
readingsBulkUpdate($hash,"count",$emx_cnt);
|
|
readingsBulkUpdate($hash,"energy",$vval);
|
|
readingsBulkUpdate($hash,"pmeter",$tval);
|
|
readingsBulkUpdate($hash,"power",$rval);
|
|
readingsBulkUpdate($hash,"peak",$pval);
|
|
readingsBulkUpdate($hash,"cost",$cost);
|
|
|
|
#-- daybreak postprocessing
|
|
if( $daybreak == 1 ){
|
|
#-- store corrected counter value at midnight
|
|
$hash->{READINGS}{"count"}{midnight} = $emx_cnt;
|
|
$hash->{READINGS}{"pmeter"}{midnight} = $tval;
|
|
EMX_store($hash);
|
|
#-- daily/monthly accumulated value
|
|
my @monthv = EMX_GetMonth($hash);
|
|
my $total = $monthv[0]+$vval;
|
|
$dvalue = sprintf("D%02d Wd: %5.2f %s Wm: %6.2f %s Cd: %5.2f €",$day,$vval,$unit,$total,$unit,int($cost*100)/100);
|
|
readingsBulkUpdate($hash,"day",$dvalue);
|
|
if( $monthbreak == 1){
|
|
$mvalue = sprintf("M%02d Wm: %6.2f %s",$month+1,$total,$unit);
|
|
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;
|
|
|
|
#-- both midnight values
|
|
if($key eq "midnight"){
|
|
return "EMX_Get => midhight counter ".$hash->{READINGS}{"count"}{midnight}." (pmeter ".$hash->{READINGS}{"pmeter"}{midnight}.")";
|
|
}
|
|
|
|
#-- midnight counter value
|
|
if($key eq "cnt_midnight"){
|
|
$value = $hash->{READINGS}{"count"}{midnight};
|
|
}
|
|
|
|
#-- midnight power meter value
|
|
if($key eq "pm_midnight"){
|
|
$value = $hash->{READINGS}{"pmeter"}{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 midnight power meter reading may be set at runtime
|
|
if($key eq "pm_midnight"){
|
|
return "EMX_Set: Wrong midnight value for power meter, must be 0 <= value < 99999"
|
|
if( ($value < 0) || ($value > 99999) );
|
|
$hash->{READINGS}{"pmeter"}{midnight}=$value;
|
|
#-- store this for later usage
|
|
$ret = EMX_store($hash);
|
|
return "EMX_Set: ".$ret
|
|
if( defined($ret) );
|
|
}
|
|
|
|
#-- value of current power meter reading may be set at runtime
|
|
if($key eq "pm_current"){
|
|
return "EMX_Set: Wrong current value for power meter, must be 0 <= value < 99999"
|
|
if( ($value < 0) || ($value > 99999) );
|
|
$hash->{READINGS}{"pmeter"}{midnight}=$value-$hash->{READINGS}{"energy"}{VAL};
|
|
#-- store this for later usage
|
|
$ret = EMX_store($hash);
|
|
return "EMX_Set: ".$ret
|
|
if( defined($ret) );
|
|
}
|
|
|
|
#-- midnight counter value may be set at runtime
|
|
if( ($key eq "midnight") || ($key eq "cnt_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 %d %d",
|
|
$year+1900,$month+1,$day,$hour,$min,$sec,
|
|
$hash->{READINGS}{"count"}{midnight},
|
|
$hash->{READINGS}{"pmeter"}{midnight};
|
|
} else {
|
|
$msg = sprintf "%4d-%02d-%02d midnight %7.2f %7.2f",
|
|
$year+1900,$month+1,$day,
|
|
$hash->{READINGS}{"count"}{midnight},
|
|
$hash->{READINGS}{"pmeter"}{midnight};
|
|
}
|
|
print EMXFILE $msg;
|
|
Log 1, "EMX_store: $name $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+1,$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];
|
|
$hash->{READINGS}{"pmeter"}{midnight} = defined($a[3]) ? $a[3] : 0;
|
|
} else {
|
|
Log 1, "EMX_recall: recalled midnight value $a[2] for $name";
|
|
$hash->{READINGS}{"count"}{midnight} = $a[2];
|
|
$hash->{READINGS}{"pmeter"}{midnight} = defined($a[3]) ? $a[3] : 0;
|
|
}
|
|
} else {
|
|
Log 1, "EMX_recall: Cannot open EMX_$name.dat for reading!";
|
|
$hash->{READINGS}{"count"}{midnight}=0;
|
|
$hash->{READINGS}{"pmeter"}{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 $total2 = int(100*($total+$hash->{READINGS}{"energy"}{VAL}))/100;
|
|
#-- number of days so far, including the present day
|
|
my ($sec,$min,$hour,$day,$month,$year,$wday,$yday,$isdst) = localtime(time);
|
|
my $deltim = int(@month)+($hour+$min/60.0 + $sec/3600.0)/24.0;
|
|
my $av = int(100*$total2/$deltim)/100;
|
|
#-- output format
|
|
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 with the EM1000 WZ/EM/GZ power/gas sensors <br />
|
|
<br /> <b>NOTE:</b> This module is currently NOT registered in the client list of 00_CUL.pm.
|
|
Therefore ist must be saved under the name 15_CUL_EM.pm or entered into the client list manually.
|
|
<br /></p>
|
|
<br /><h4>Example</h4>
|
|
<p>
|
|
<code>define E_Verbrauch EMX 1 75</code>
|
|
</p>
|
|
<br />
|
|
<a name="EMXdefine"></a>
|
|
<h4>Define</h4>
|
|
<p>
|
|
<code>define <name> EMX <code> <rpunit></code> or <br/>
|
|
<code>define <name> EMX emulator</code>
|
|
<br /><br /> Define an EMX device or an emulated EM1000-WZ device <br /><br />
|
|
|
|
</p>
|
|
<ul>
|
|
<li>
|
|
<code><code></code><br /> Defines the sensor model, currently the following values are permitted:
|
|
<ul>
|
|
<li>1 .. 4: EM1000-WZ power meter sensor => unit is kWh</li>
|
|
<li>5 .. 8: EM1000-EM power sensor => unit is kWh</li>
|
|
<li>9 .. 12: EM1000-GZ gas meter sensor => unit is m<sup>3</sup></li>
|
|
</ul>
|
|
</li>
|
|
<li>
|
|
<code><rpunit></code><br/>Factor 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_cnt_midnight">
|
|
<code>set <name> cnt_midnight <int></code></a><br /> Midnight Value of internal counter </li>
|
|
<li><a name="emx_pm_midnight">
|
|
<code>set <name> pm_midnight <int></code></a><br /> Midnight value of external power meter</li>
|
|
<li><a name="emx_pmeter">
|
|
<code>set <name> pm_current <int></code></a><br /> Current value of external power meter</li>
|
|
</ul>
|
|
<br />
|
|
<a name="EMXget"></a>
|
|
<h4>Get</h4>
|
|
<ul>
|
|
<li><a name="emx_midnight">
|
|
<code>get <name> midnight</code></a>
|
|
<br /> Returns the midnight value of the counter and power meter </li>
|
|
<li><a name="emx_cnt_midnight2">
|
|
<code>get <name> cnt_midnight</code></a>
|
|
<br /> Returns the midnight value of the internal counter</li>
|
|
<li><a name="emx_pm_midnight2">
|
|
<code>get <name> pm_midnight</code></a>
|
|
<br /> Returns the midnight value of the power meter</li>
|
|
<li><a name="emx_month">
|
|
<code>get <name> month</code>
|
|
</a>
|
|
<br /> Returns a summary of the current month</li>
|
|
</ul>
|
|
<br />
|
|
<a name="EMXattr"></a>
|
|
<h4>Attributes</h4>
|
|
<ul>
|
|
<li><a name="emx_logm"><code>attr <name> <LogM>
|
|
<string></code></a>
|
|
<br />Device name (<i>not file name</i>) of the monthly logfile </li>
|
|
<li><a name="emx_logy"><code>attr <name> <LogY>
|
|
<string></code></a>
|
|
<br />Device name (<i>not file name</i>) of the yearly logfile </li>
|
|
<li><a name="emx_costm"><code>attr <name> <CostM>
|
|
<float></code></a>
|
|
<br />Cost per month</li>
|
|
<li><a name="emx_costd"><code>attr <name> <CostD>
|
|
<float></code></a>
|
|
<br />Cost per unit (during daytime, when the following attributes are given)</li>
|
|
<li><a name="emx_costn"><code>attr <name> <CostN>
|
|
<float></code></a>
|
|
<br />Cost per unit during night time</li>
|
|
<li><a name="emx_cdstart"><code>attr <name> <CDStart>
|
|
<hh:mm></code></a>
|
|
<br />Time of day when daytime cost rate starts</li>
|
|
<li><a name="emx_cdend"><code>attr <name> <CDEnd>
|
|
<hh:mm></code></a>
|
|
<br />Time of day when daytime cost rate ends</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
|
|
|