From f9ce311bf5c36e856942c81b2269e7c25337204c Mon Sep 17 00:00:00 2001
From: pizmus <>
Date: Sun, 31 May 2020 14:06:53 +0000
Subject: [PATCH] EseraDimmer: new module for Esera 11221 and 11222 dimmer
SolarEdgeAPI: daily readings at 23:59 instead of 22:00
git-svn-id: https://svn.fhem.de/fhem/trunk@22085 2b470e98-0d58-463d-a4d8-8e2adae1ed80
---
fhem/FHEM/66_EseraDimmer.pm | 468 +++++++++++++++++++++++++++++++++++
fhem/FHEM/66_EseraMulti.pm | 5 +-
fhem/FHEM/66_EseraOneWire.pm | 7 +-
fhem/FHEM/70_SolarEdgeAPI.pm | 16 +-
4 files changed, 485 insertions(+), 11 deletions(-)
create mode 100644 fhem/FHEM/66_EseraDimmer.pm
diff --git a/fhem/FHEM/66_EseraDimmer.pm b/fhem/FHEM/66_EseraDimmer.pm
new file mode 100644
index 000000000..e1015e426
--- /dev/null
+++ b/fhem/FHEM/66_EseraDimmer.pm
@@ -0,0 +1,468 @@
+################################################################################
+#
+# $Id$
+#
+# 66_EseraDimmer.pm
+#
+# Copyright (C) 2020 pizmus
+#
+# This program 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 3 of the License, or
+# (at your option) any later version.
+#
+# This program 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.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+################################################################################
+
+package main;
+
+use strict;
+use warnings;
+use SetExtensions;
+
+sub
+EseraDimmer_Initialize($)
+{
+ my ($hash) = @_;
+ $hash->{Match} = "11221|11222";
+ $hash->{DefFn} = "EseraDimmer_Define";
+ $hash->{UndefFn} = "EseraDimmer_Undef";
+ $hash->{ParseFn} = "EseraDimmer_Parse";
+ $hash->{SetFn} = "EseraDimmer_Set";
+ $hash->{AttrFn} = "EseraDimmer_Attr";
+ $hash->{AttrList} = "$readingFnAttributes";
+}
+
+sub
+EseraDimmer_Define($$)
+{
+ my ($hash, $def) = @_;
+ my @a = split( "[ \t][ \t]*", $def);
+
+ return "Usage: define EseraDimmer <1-wire-ID> " if(@a < 6);
+
+ my $devName = $a[0];
+ my $type = $a[1];
+ my $physicalDevice = $a[2];
+ my $oneWireId = $a[3];
+ my $deviceType = uc($a[4]);
+ my $channel = $a[5];
+
+ $hash->{STATE} = 'Initialized';
+ $hash->{NAME} = $devName;
+ $hash->{TYPE} = $type;
+ $hash->{ONEWIREID} = $oneWireId;
+ $hash->{ESERAID} = undef; # We will get this from the first reading.
+ $hash->{LAST_OUT} = undef;
+
+ if (($deviceType eq "11221") or ($deviceType eq "11222"))
+ {
+ $hash->{DEVICE_TYPE} = $deviceType;
+ }
+ else
+ {
+ Log3 $devName, 4, "EseraDimmer ($devName) - invalid device type";
+ return "Usage: define EseraDimmer <1-wire-ID> " if(@a < 6);
+ }
+
+ if (($channel eq "1") or ($channel eq "2"))
+ {
+ $hash->{CHANNEL} = $channel;
+ }
+ else
+ {
+ Log3 $devName, 4, "EseraDimmer ($devName) - invalid channel";
+ return "Usage: define EseraDimmer <1-wire-ID> " if(@a < 6);
+ }
+
+ $modules{EseraDimmer}{defptr}{$oneWireId} = $hash;
+
+ AssignIoPort($hash, $physicalDevice);
+
+ if (defined($hash->{IODev}->{NAME}))
+ {
+ Log3 $devName, 4, "EseraDimmer ($devName) - I/O device is " . $hash->{IODev}->{NAME};
+ }
+ else
+ {
+ Log3 $devName, 1, "EseraDimmer ($devName) - no I/O device";
+ }
+
+ return undef;
+}
+
+sub
+EseraDimmer_Undef($$)
+{
+ my ($hash, $arg) = @_;
+ my $oneWireId = $hash->{ONEWIREID};
+
+ RemoveInternalTimer($hash);
+ delete( $modules{EseraDimmer}{defptr}{$oneWireId} );
+
+ return undef;
+}
+
+sub
+EseraDimmer_setOutput($$$)
+{
+ my ($hash, $owId, $value) = @_;
+ my $name = $hash->{NAME};
+
+ if (($value < 0) || ($value > 31))
+ {
+ my $message = "error: value out of range";
+ Log3 $name, 1, "EseraDimmer ($name) - ".$message;
+ return $message;
+ }
+
+ # look up the ESERA ID
+ my $eseraId = $hash->{ESERAID};
+ if (!defined $eseraId)
+ {
+ my $message = "error: ESERA ID not known";
+ Log3 $name, 1, "EseraDimmer ($name) - ".$message;
+ return $message;
+ }
+
+ # set value
+ my $command = "set,owd,dim,".$eseraId.",".$hash->{CHANNEL}.",".$value;
+ IOWrite($hash, "set;$owId;$command");
+
+ return undef;
+}
+
+# interpret a string entered by the user as a number
+sub
+EseraDimmer_convertNumber($$)
+{
+ my ($hash, $numberString) = @_;
+ $numberString = lc($numberString);
+ my $number = undef;
+ if ($numberString =~ m/^(\d+)$/)
+ {
+ $number = $1;
+ }
+ elsif (($numberString =~ m/^0b([01]+)$/) || ($numberString =~ m/^0x([a-f0-9]+)$/))
+ {
+ $number = oct($numberString);
+ }
+ return $number;
+}
+
+sub
+EseraDimmer_Set($$)
+{
+ my ( $hash, @parameters ) = @_;
+ my $name = $parameters[0];
+ my $what = lc($parameters[1]);
+
+ my $oneWireId = $hash->{ONEWIREID};
+ my $iodev = $hash->{IODev}->{NAME};
+
+ my $commands = ("on:noArg off:noArg out up:noArg down:noArg");
+
+ if ($what eq "out")
+ {
+ if ((scalar(@parameters) != 3))
+ {
+ my $message = "error: unexpected number of parameters (".scalar(@parameters).")";
+ Log3 $name, 1, "EseraDimmer ($name) - ".$message;
+ return $message;
+ }
+ my $value = EseraDimmer_convertNumber($hash, $parameters[2]);
+ EseraDimmer_setOutput($hash, $oneWireId, $value);
+ $hash->{LAST_OUT} = $value;
+ }
+ elsif ($what eq "on")
+ {
+ if ((scalar(@parameters) != 2))
+ {
+ my $message = "error: unexpected number of parameters (".scalar(@parameters).")";
+ Log3 $name, 1, "EseraDimmer ($name) - ".$message;
+ return $message;
+ }
+ EseraDimmer_setOutput($hash, $oneWireId, 31);
+ $hash->{LAST_OUT} = 31;
+ }
+ elsif ($what eq "off")
+ {
+ if ((scalar(@parameters) != 2))
+ {
+ my $message = "error: unexpected number of parameters (".scalar(@parameters).")";
+ Log3 $name, 1, "EseraDimmer ($name) - ".$message;
+ return $message;
+ }
+ EseraDimmer_setOutput($hash, $oneWireId, 0);
+ $hash->{LAST_OUT} = 0;
+ }
+ elsif ($what eq "up")
+ {
+ if ((scalar(@parameters) != 2))
+ {
+ my $message = "error: unexpected number of parameters (".scalar(@parameters).")";
+ Log3 $name, 1, "EseraDimmer ($name) - ".$message;
+ return $message;
+ }
+ if ((defined $hash->{LAST_OUT}) and ($hash->{LAST_OUT} < 31))
+ {
+ $hash->{LAST_OUT} = $hash->{LAST_OUT} + 1;
+ EseraDimmer_setOutput($hash, $oneWireId, $hash->{LAST_OUT});
+ }
+ }
+ elsif ($what eq "down")
+ {
+ if ((scalar(@parameters) != 2))
+ {
+ my $message = "error: unexpected number of parameters (".scalar(@parameters).")";
+ Log3 $name, 1, "EseraDimmer ($name) - ".$message;
+ return $message;
+ }
+ if ((defined $hash->{LAST_OUT}) and ($hash->{LAST_OUT} > 0))
+ {
+ $hash->{LAST_OUT} = $hash->{LAST_OUT} - 1;
+ EseraDimmer_setOutput($hash, $oneWireId, $hash->{LAST_OUT});
+ }
+ }
+ elsif ($what eq "?")
+ {
+ my $message = "unknown argument $what, choose one of $commands";
+ return $message;
+ }
+ else
+ {
+ shift @parameters;
+ shift @parameters;
+ return SetExtensions($hash, $commands, $name, $what, @parameters);
+ }
+ return undef;
+}
+
+sub
+EseraDimmer_ParseForOneDevice($$$$$$)
+{
+ my ($rhash, $deviceType, $oneWireId, $eseraId, $readingId, $value) = @_;
+ my $rname = $rhash->{NAME};
+ Log3 $rname, 4, "EseraDimmer ($rname) - ParseForOneDevice: ".$rname;
+
+ # capture the Esera ID for later use
+ $rhash->{ESERAID} = $eseraId;
+
+ # consistency check of device type
+ if (!($rhash->{DEVICE_TYPE} eq uc($deviceType)))
+ {
+ Log3 $rname, 1, "EseraDimmer ($rname) - unexpected device type ".$deviceType;
+ }
+
+ if ($readingId eq "ERROR")
+ {
+ Log3 $rname, 1, "EseraDimmer ($rname) - error message from physical device: ".$value;
+ }
+ elsif ($readingId eq "STATISTIC")
+ {
+ Log3 $rname, 1, "EseraDimmer ($rname) - statistics message not supported yet: ".$value;
+ }
+ else
+ {
+ my $nameOfReading;
+ if (($deviceType eq "11221") || ($deviceType eq "11222"))
+ {
+ if ($rhash->{CHANNEL} == 1)
+ {
+ if ($readingId == 3)
+ {
+ $rhash->{LAST_OUT} = $value;
+ $nameOfReading = "out";
+ readingsSingleUpdate($rhash, $nameOfReading, $value, 1);
+ }
+ elsif ($readingId == 1)
+ {
+ # Tasterschnittstelle Kanal 1 = 1, Tasterschnittstelle Kanal 2 = 2, Modultaster Kanal 1 = 4, Modultaster Kanal 2 = 8
+ $nameOfReading = "button";
+ my $buttonPressed = 0;
+ if ((($value & 0x1) != 0) or (($value & 0x4) != 0))
+ {
+ $buttonPressed = 1;
+ }
+ readingsSingleUpdate($rhash, $nameOfReading, $buttonPressed, 1);
+ }
+ }
+ elsif ($rhash->{CHANNEL} == 2)
+ {
+ if ($readingId == 4)
+ {
+ $rhash->{LAST_OUT} = $value;
+ $nameOfReading = "out";
+ readingsSingleUpdate($rhash, $nameOfReading, $value, 1);
+ }
+ elsif ($readingId == 1)
+ {
+ # Tasterschnittstelle Kanal 1 = 1, Tasterschnittstelle Kanal 2 = 2, Modultaster Kanal 1 = 4, Modultaster Kanal 2 = 8
+ $nameOfReading = "button";
+ my $buttonPressed = 0;
+ if ((($value & 0x2) != 0) or (($value & 0x8) != 0))
+ {
+ $buttonPressed = 1;
+ }
+ readingsSingleUpdate($rhash, $nameOfReading, $buttonPressed, 1);
+ }
+ }
+ }
+ }
+ return $rname;
+}
+
+sub
+EseraDimmer_Parse($$)
+{
+ my ($ioHash, $msg) = @_;
+ my $ioName = $ioHash->{NAME};
+ my $buffer = $msg;
+
+ # expected message format: $deviceType."_".$oneWireId."_".$eseraId."_".$readingId."_".$value
+ my @fields = split(/_/, $buffer);
+ if (scalar(@fields) != 5)
+ {
+ return undef;
+ }
+ my $deviceType = uc($fields[0]);
+ my $oneWireId = $fields[1];
+ my $eseraId = $fields[2];
+ my $readingId = $fields[3];
+ my $value = $fields[4];
+
+ # search for logical device
+ my $rhash = undef;
+ my @list;
+ foreach my $d (keys %defs)
+ {
+ my $h = $defs{$d};
+ my $type = $h->{TYPE};
+
+ if($type eq "EseraDimmer")
+ {
+ if (defined($h->{IODev}->{NAME}))
+ {
+ my $ioDev = $h->{IODev}->{NAME};
+ my $def = $h->{DEF};
+
+ # $def has the whole definition, extract the oneWireId (which is expected as 2nd parameter)
+ my @parts = split(/ /, $def);
+ my $oneWireIdFromDef = $parts[1];
+
+ if (($ioDev eq $ioName) && ($oneWireIdFromDef eq $oneWireId))
+ {
+ $rhash = $h;
+
+ # readingId 4 is for channel 2 only. Ignore it with channel 1.
+ if (not (($rhash->{CHANNEL} == 1) and ($readingId == 4)))
+ {
+ my $rname = EseraDimmer_ParseForOneDevice($rhash, $deviceType, $oneWireId, $eseraId, $readingId, $value);
+ push(@list, $rname);
+ }
+ }
+ }
+ }
+ }
+
+ if ((scalar @list) > 0)
+ {
+ return @list;
+ }
+ elsif (($deviceType eq "11221") or ($deviceType eq "11222"))
+ {
+ my $channel = 1;
+
+ if ($readingId == 4)
+ {
+ # readingId 4 is for channel 2
+ $channel = 2;
+ }
+
+ return "UNDEFINED EseraDimmer_".$ioName."_".$oneWireId."_".$channel." EseraDimmer ".$ioName." ".$oneWireId." ".$deviceType." ".$channel;
+ }
+
+ return undef;
+}
+
+sub
+EseraDimmer_Attr(@)
+{
+}
+
+
+
+1;
+
+=pod
+=item summary Represents an Esera 1-wire dimmer.
+=item summary_DE Repraesentiert einen Esera 1-wire Dimmer.
+=begin html
+
+
+EseraDimmer
+
+
+ This module implements an Esera 1-wire dimmer. It uses 66_EseraOneWire as I/O device.
+
+
+
+ Define
+
+ define <name> EseraDimmer <ioDevice> <oneWireId> <deviceType> <channel>
+ <oneWireId> specifies the 1-wire ID of the digital input/output chip.
+ Use the "get devices" query of EseraOneWire to get a list of 1-wire IDs,
+ or simply rely on autocreate.
+ Supported values for deviceType:
+
+ <channel> specifies the channel, 1 or 2.
+
+
+
+
+ Set
+
+ -
+
set <name> out <value>
+
+ -
+
set <name> on
+ Sets the dimmer to the maximum value 31.
+
+ -
+
set <name> off
+ Sets the dimmer to the minimum value 0.
+
+ -
+
set <name> up
+ Increase dimmer value by 1.
+
+ -
+
set <name> down
+ Reduce dimmer value by 1.
+
+
+
+
+
+ Readings
+
+ - out – output state
+ - button – state of the push button, 1=pressed, 0=not pressed
+
+
+
+
+
+=end html
+=cut
diff --git a/fhem/FHEM/66_EseraMulti.pm b/fhem/FHEM/66_EseraMulti.pm
index 0b95f6b5e..ccd778108 100644
--- a/fhem/FHEM/66_EseraMulti.pm
+++ b/fhem/FHEM/66_EseraMulti.pm
@@ -236,7 +236,10 @@ EseraMulti_Parse($$)
elsif ($readingId == 3)
{
$nameOfReading = "humidity";
- readingsSingleUpdate($rhash, $nameOfReading, $value / 100.0, 1);
+ if ($value != 10000) # Esera 11133 sporadically reports 100% by mistake
+ {
+ readingsSingleUpdate($rhash, $nameOfReading, $value / 100.0, 1);
+ }
}
elsif ($readingId == 4)
{
diff --git a/fhem/FHEM/66_EseraOneWire.pm b/fhem/FHEM/66_EseraOneWire.pm
index 2b26c882f..8cf7f27f2 100644
--- a/fhem/FHEM/66_EseraOneWire.pm
+++ b/fhem/FHEM/66_EseraOneWire.pm
@@ -90,7 +90,8 @@ EseraOneWire_Initialize($)
"4:EseraAnalogInOut" => "^SYS3",
"5:EseraIButton" => "^DS2401",
"6:EseraCount" => "^DS2423",
- "7:EseraShutter" => "^11231|^11209"};
+ "7:EseraShutter" => "^11231|^11209",
+ "8:EseraDimmer" => "^11221|^11222"};
}
sub
@@ -1043,7 +1044,7 @@ EseraOneWire_Read($)
}
else
{
- Log3 $name, 5, "EseraOneWire ($name) - readings ignored because controller is not initialized (2)";
+ Log3 $name, 5, "EseraOneWire ($name) - readings ignored because controller is not initialized (3)";
}
}
elsif ($ascii =~ m/^1_SYS(\d+)/)
@@ -1054,7 +1055,7 @@ EseraOneWire_Read($)
}
else
{
- Log3 $name, 5, "EseraOneWire ($name) - readings ignored because controller is not initialized (2)";
+ Log3 $name, 5, "EseraOneWire ($name) - readings ignored because controller is not initialized (4)";
}
}
else
diff --git a/fhem/FHEM/70_SolarEdgeAPI.pm b/fhem/FHEM/70_SolarEdgeAPI.pm
index 4861e584f..061a94de5 100644
--- a/fhem/FHEM/70_SolarEdgeAPI.pm
+++ b/fhem/FHEM/70_SolarEdgeAPI.pm
@@ -154,12 +154,14 @@ eval "use JSON;1" or $solarEdgeAPI_missingModul .= "JSON ";
#
# 2.0.1 tolerate empty field in energyDetails response
#
+# 2.1.0 generate daily readings at ~23:59 instead of 22:00
+#
###############################################################################
sub SolarEdgeAPI_SetVersion($)
{
my ($hash) = @_;
- $hash->{VERSION} = "2.0.1";
+ $hash->{VERSION} = "2.1.0";
}
###############################################################################
@@ -703,7 +705,7 @@ sub SolarEdgeAPI_RestartHttpRequestTimers($)
Log3 $name, 3, "SolarEdgeAPI ($name) - restarting timer";
- # remove any active timer
+ # remove all active timers
RemoveInternalTimer($hash);
# Do the next http request now. This will start a timer for the next one.
@@ -721,9 +723,9 @@ sub SolarEdgeAPI_GetTimeOfNextDailyReading($)
my $epoch = time();
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($epoch);
- if ($hour >= 22)
+ if (($hour >= 23) and ($min >= 59))
{
- # If it is after 10pm the next reading should occur tomorrow.
+ # If it is 23:59 the next reading should occur tomorrow.
# add 24 hours to epoch to get a time during the following day
$epoch += 24 * 60 * 60;
@@ -732,8 +734,8 @@ sub SolarEdgeAPI_GetTimeOfNextDailyReading($)
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($epoch);
}
- # change hour to 10pm and convert to epoch
- $epoch = fhemTimeLocal(5, 0, 22, $mday, $mon, $year); # $sec, $min, $hour, $mday, $month, $year
+ # change hour:minute to 23:59 and convert to epoch
+ $epoch = fhemTimeLocal(0, 59, 23, $mday, $mon, $year); # $sec, $min, $hour, $mday, $month, $year
return $epoch;
}
@@ -1401,7 +1403,7 @@ EOF
All reading names start with the name of the group of readings followed by "-".
All readings that belong to the same group have the same timing: Some groups of readings are generated
periodically. The period is defined by attributes intervalAtDayTime, intervalAtNighttime, dayTimeStartHour and
- nightTimeStartHour. Other readings are generated once per day only. Reading groups which are update
+ nightTimeStartHour. Other readings are generated once per day only. Reading groups which are updated
once per day have a name starting with "daily". Each update of a group of readings requires on http
request to the SolarEdge server. The number of queries is limited to 300 per day, according to API
documentation.