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: +
        +
      • 11221
      • +
      • 11222
      • +
      + <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.