mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-05-01 20:20:10 +00:00

00_MAXLAN.pm: update doc 10_MAX.pm: update doc 00_MAXLAN.pm: Fix syntax error 00_MAXLAN.pm: remove reference to hmId 00_MAXLAN.pm: also dispatch define events from metadata, because shutter does not have configdata 10_MAX.pm: allow setting temp to "on" and "off" 10_MAX.pm: fix doc formatting 00_MAXLAN.pm: Add two set commands: raw and clock 00_MAXLAN.pm: parse current until time 00_MAXLAN.pm: send quit in Undef 00_MAXLAN.pm: beautifying Display ctrl mode (auto/manual/temporary) Allow setting a temporary temperature MAX: change event delimiter to handle time Fix bug where only one room could be handled MAX: parse "S:" response List roomid, send correct roomid with desired-temp MAX: show roomname for devices MAX: add troubleshooting when device send temperature 0 00_MAXLAN: Fix parsing of "until" date: time and month were broken 00_MAXLAN: show time offset on start Show more state info Set timezone to floating because local cannot be determined on fritzbox dispatch CubeClockState only if rfaddr is known Read valve position of HeatingThermostat by gaggi (g.sturm@sturm-busse.de) MAX: notify if 1% percent rule is oversteped and if queue is full Do not crash on empty Metadata response change inital label from "initalized" to "waiting for data" Auto reconnect if connecting failed defer setting clock until after complete hello cleanup Lower polling interval to 10 seconds Remove ReadyFn, we already got a FD really defer setting the clock valveposition is in percent rename serialNr to serial check for matching magic/version, be more robust while parsing metadata implement SendMetadata Add missing devices during C: response Fill SetRoomId Make ReadAnswer really wait for one Make valveposition a reading/show possible settings in WebUI (by wollet42, wton1@gmx.de) Remove DateTime module rename room to group: fhem already has a room concept Set temperature individually, not for the whole group Implement setting of groupid Remove display of groupname, it does not say anything generate events for HeatingThermostat's readings Switched to ReadingsBulkUpdate (thanks to tobias.faust, tobias.faust@gmx.net) Implement interfaces: this changes the setter desired-temp to desiredTemperature Correct some Log messages doc: make clear that MAXLAN uses the cube exclusively Fix webinterface showing old name for desiredTemperature add temp4valve4.gplot for Max! HeatingThermostats Move SetTemperature to backend, allow eco/comfort Show eco/comfort temperature from device Support event-on-{update,change}-reading Correct computation of time difference (thanks Andy Fuchs) gplot: Corrected axis range Allow configuring of a lot of device parameters Add optional pollintervall to define (thanks to wollet42) Return message up to UI Minor fixes Fix setting desiredTemperature Allow cancel of pairmode gplot: better scaling (by wollet42) and steps instead of interpolation Continue even if L: is empty add factoryReset request config after new device paired Updated CUL_MAX Correct parsing of flags parse measured(!) temperature Unify logic for MAXLAN and CUL_MAX into MAX Show some unused bits Correctly show until time only in temporary mode Fix measured temp Fix checks for existance Return list of valid values for desiredTemperature Require a value with desiredTemperature and don't show (manual) Update doc text Update CUL_MAX MAXLAN: dispatch define before configuration event Update plot to show measured temperature, too git-svn-id: https://svn.fhem.de/fhem/trunk/fhem@2144 2b470e98-0d58-463d-a4d8-8e2adae1ed80
374 lines
15 KiB
Perl
Executable File
374 lines
15 KiB
Perl
Executable File
##############################################
|
|
# $Id$
|
|
# Written by Matthias Gehre, M.Gehre@gmx.de, 2012
|
|
package main;
|
|
|
|
use strict;
|
|
use warnings;
|
|
use MIME::Base64;
|
|
use Data::Dumper;
|
|
|
|
sub MAX_Define($$);
|
|
sub MAX_Initialize($);
|
|
sub MAX_Parse($$);
|
|
sub MAX_Set($@);
|
|
sub MAX_MD15Cmd($$$);
|
|
sub MAX_DateTime2Internal($);
|
|
|
|
my @ctrl_modes = ( "auto", "manual", "temporary" );
|
|
|
|
my %interfaces = (
|
|
"Cube" => undef,
|
|
"HeatingThermostat" => "thermostat;battery;temperature",
|
|
"HeatingThermostatPlus" => "thermostat;battery;temperature",
|
|
"WallMountedThermostat" => "thermostat;temperature;battery",
|
|
"ShutterContact" => "switch_active;battery",
|
|
"PushButton" => "switch_passive;battery"
|
|
);
|
|
|
|
sub
|
|
MAX_Initialize($)
|
|
{
|
|
my ($hash) = @_;
|
|
|
|
Log 5, "Calling MAX_Initialize";
|
|
$hash->{Match} = "^MAX";
|
|
$hash->{DefFn} = "MAX_Define";
|
|
$hash->{ParseFn} = "MAX_Parse";
|
|
$hash->{SetFn} = "MAX_Set";
|
|
$hash->{AttrList} = "IODev do_not_notify:1,0 ignore:0,1 dummy:0,1 " .
|
|
"showtime:1,0 loglevel:0,1,2,3,4,5,6 event-on-update-reading event-on-change-reading";
|
|
return undef;
|
|
}
|
|
|
|
#############################
|
|
sub
|
|
MAX_Define($$)
|
|
{
|
|
my ($hash, $def) = @_;
|
|
my @a = split("[ \t][ \t]*", $def);
|
|
my $name = $hash->{NAME};
|
|
return "wrong syntax: define <name> MAX addr"
|
|
if(int(@a)!=4 || $a[3] !~ m/^[A-F0-9]{6}$/i);
|
|
|
|
my $type = $a[2];
|
|
my $addr = $a[3];
|
|
Log 5, "Max_define $type with addr $addr ";
|
|
$hash->{type} = $type;
|
|
$hash->{addr} = $addr;
|
|
$hash->{STATE} = "waiting for data";
|
|
$modules{MAX}{defptr}{$addr} = $hash;
|
|
|
|
$hash->{internals}{interfaces} = $interfaces{$type};
|
|
|
|
AssignIoPort($hash);
|
|
return undef;
|
|
}
|
|
|
|
sub
|
|
MAX_DateTime2Internal($)
|
|
{
|
|
my($day, $month, $year, $hour, $min) = ($_[0] =~ /^(\d{2}).(\d{2})\.(\d{4}) (\d{2}):(\d{2})$/);
|
|
return (($month&0xE) << 20) | ($day << 16) | (($month&1) << 15) | (($year-2000) << 8) | ($hour*2 + int($min/30));
|
|
}
|
|
|
|
#############################
|
|
sub
|
|
MAX_Set($@)
|
|
{
|
|
my ($hash, $devname, @a) = @_;
|
|
my ($setting, @args) = @a;
|
|
|
|
return "Cannot set without IODev" if(!exists($hash->{IODev}));
|
|
|
|
if($setting eq "desiredTemperature"){
|
|
return "can only set desiredTemperature for HeatingThermostat" if($hash->{type} ne "HeatingThermostat");
|
|
return "missing a value" if(@args == 0);
|
|
|
|
my $temperature;
|
|
my $until = undef;
|
|
my $ctrlmode = 1; #0=auto, 1=manual; 2=temporary
|
|
|
|
if($args[0] eq "auto") {
|
|
#This enables the automatic/schedule mode where the thermostat follows the weekly program
|
|
$temperature = 0;
|
|
$ctrlmode = 0; #auto
|
|
#TODO: auto mode with temperature is also possible
|
|
} elsif($args[0] eq "eco") {
|
|
$temperature = $hash->{ecoTemperature};
|
|
} elsif($args[0] eq "comfort") {
|
|
$temperature = $hash->{comfortTemperature};
|
|
} elsif($args[0] eq "on") {
|
|
$temperature = 30.5;
|
|
} elsif($args[0] eq "off") {
|
|
$temperature = 4.5;
|
|
}else{
|
|
$temperature = $args[0];
|
|
}
|
|
|
|
if(@args > 1 and $args[1] eq "until" and $ctrlmode == 1) {
|
|
$ctrlmode = 2; #temporary
|
|
$until = sprintf("%06x",MAX_DateTime2Internal($args[2]." ".$args[3]));
|
|
}
|
|
|
|
my $groupid = $hash->{groupid};
|
|
$groupid = 0; #comment this line to control the whole group, no only one device
|
|
|
|
$temperature = int($temperature*2.0) | ($ctrlmode << 6); #convert to internal representation
|
|
my $payload;
|
|
if(defined($until)) {
|
|
$payload = pack("CCCCCCH6CCH6",0x00,$groupid?0x04:0,0x40,0x00,0x00,0x00,$hash->{addr},$groupid,$temperature,$until);
|
|
}else{
|
|
$payload = pack("CCCCCCH6CC" ,0x00,$groupid?0x04:0,0x40,0x00,0x00,0x00,$hash->{addr},$groupid,$temperature);
|
|
}
|
|
return ($hash->{IODev}{SendDeviceCmd})->($hash->{IODev},$payload);
|
|
|
|
}elsif($setting eq "groupid"){
|
|
return "argument needed" if(@args == 0);
|
|
|
|
return ($hash->{IODev}{SendDeviceCmd})->($hash->{IODev},pack("CCCCCCH6CC",0x00,0x00,34,0x00,0x00,0x00,$hash->{addr},0x00,$args[0]));
|
|
|
|
}elsif( $setting ~~ ["ecoTemperature", "comfortTemperature", "temperatureOffset", "maximumTemperature", "minimumTemperature", "windowOpenTemperature", "windowOpenDuration" ]) {
|
|
|
|
return "can only set configuration for HeatingThermostat" if($hash->{type} ne "HeatingThermostat");
|
|
return "Invalid comfortTemperature" if(!exists($hash->{comfortTemperature}) or $hash->{comfortTemperature} < 4.5 or $hash->{comfortTemperature} > 30.5);
|
|
return "Invalid ecoTemperature" if(!exists($hash->{ecoTemperature}) or $hash->{ecoTemperature} < 4.5 or $hash->{ecoTemperature} > 30.5);
|
|
return "Invalid maximumTemperature" if(!exists($hash->{maximumTemperature}) or $hash->{maximumTemperature} < 4.5 or $hash->{maximumTemperature} > 30.5);
|
|
return "Invalid minimumTemperature" if(!exists($hash->{minimumTemperature}) or $hash->{minimumTemperature} < 4.5 or $hash->{minimumTemperature} > 30.5);
|
|
return "Invalid windowOpenTemperature" if(!exists($hash->{windowOpenTemperature}) or $hash->{windowOpenTemperature} < 4.5 or $hash->{windowOpenTemperature} > 30.5);
|
|
return "Invalid temperatureOffset" if(!exists($hash->{temperatureOffset}) or $hash->{temperatureOffset} < -3.5 or $hash->{temperatureOffset} > 3.5);
|
|
return "Invalid windowOpenDuration" if(!exists($hash->{windowOpenDuration}) or $hash->{windowOpenDuration} < 0 or $hash->{windowOpenDuration} > 60);
|
|
|
|
$hash->{$setting} = $args[0];
|
|
|
|
my $comfort = int($hash->{comfortTemperature}*2);
|
|
my $eco = int($hash->{ecoTemperature}*2);
|
|
my $max = int($hash->{maximumTemperature}*2);
|
|
my $min = int($hash->{minimumTemperature}*2);
|
|
my $offset = int(($hash->{temperatureOffset} + 3.5)*2);
|
|
my $windowOpenTemp = int($hash->{windowOpenTemperature}*2);
|
|
my $windowOpenTime = int($hash->{windowOpenDuration}/5);
|
|
|
|
my $payload = pack("CCCCCCH6C"."CCCCCCC",0x00,0x00,17,0x00,0x00,0x00,$hash->{addr},0x00,
|
|
$comfort,$eco,$max,$min,$offset,$windowOpenTemp,$windowOpenTime);
|
|
return ($hash->{IODev}{SendDeviceCmd})->($hash->{IODev},$payload);
|
|
}else{
|
|
if($hash->{type} eq "HeatingThermostat") {
|
|
#Create numbers from 4.5 to 30.5
|
|
my $templist = join(",",map { $_/2 } (9..61));
|
|
return "Unknown argument $setting, choose one of desiredTemperature:eco,comfort,$templist ecoTemperature comfortTemperature temperatureOffset maximumTemperature minimumTemperature windowOpenTemperature windowOpenDuration groupid";
|
|
} else {
|
|
return "Unknown argument $setting";
|
|
}
|
|
}
|
|
}
|
|
|
|
#############################
|
|
sub
|
|
MAX_Parse($$)
|
|
{
|
|
my ($hash, $msg) = @_;
|
|
my ($MAX,$msgtype,$addr,@args) = split(",",$msg);
|
|
|
|
#Find the device with the given addr
|
|
my $shash = $modules{MAX}{defptr}{$addr};
|
|
|
|
if(!$shash)
|
|
{
|
|
if($msgtype eq "define"){
|
|
my $devicetype = $args[0];
|
|
return "UNDEFINED MAX_$addr MAX $devicetype $addr";
|
|
}else{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if($msgtype eq "define"){
|
|
my $devicetype = $args[0];
|
|
Log 1, "Device changed type from $shash->{type} to $devicetype" if($shash->{type} ne $devicetype);
|
|
if(@args > 1){
|
|
my $serial = $args[1];
|
|
Log 1, "Device changed serial from $shash->{serial} to $serial" if($shash->{serial} and ($shash->{serial} ne $serial));
|
|
$shash->{serial} = $serial;
|
|
}
|
|
if(@args > 2){
|
|
my $groupid = $args[2];
|
|
$shash->{groupid} = $groupid;
|
|
}
|
|
|
|
} elsif($msgtype eq "HeatingThermostatState") {
|
|
my $settemp = $args[0];
|
|
my $mode = $ctrl_modes[$args[1]];
|
|
my $until = $args[2];
|
|
my $batterylow = $args[3];
|
|
my $rferror = $args[4];
|
|
my $dstsetting = $args[5];
|
|
my $valveposition = $args[6];
|
|
my $measuredTemperature = "";
|
|
$measuredTemperature = $args[7] if(@args > 7);
|
|
|
|
$shash->{mode} = $mode;
|
|
$shash->{rferror} = $rferror;
|
|
$shash->{dstsetting} = $dstsetting;
|
|
if($mode eq "temporary"){
|
|
$shash->{until} = "$until";
|
|
}else{
|
|
delete($shash->{until});
|
|
}
|
|
|
|
readingsBeginUpdate($shash);
|
|
readingsBulkUpdate($shash, "battery", $batterylow ? "low" : "ok");
|
|
readingsBulkUpdate($shash, "desiredTemperature", $settemp);
|
|
readingsBulkUpdate($shash, "valveposition", $valveposition);
|
|
if($measuredTemperature ne "") {
|
|
readingsBulkUpdate($shash, "temperature", $measuredTemperature);
|
|
}
|
|
readingsEndUpdate($shash, 0);
|
|
|
|
}elsif($msgtype eq "ShutterContactState"){
|
|
my $isopen = $args[0];
|
|
my $batterylow = $args[1];
|
|
my $rferror = $args[2];
|
|
|
|
$shash->{rferror} = $rferror;
|
|
|
|
readingsBeginUpdate($shash);
|
|
readingsBulkUpdate($shash, "battery", $batterylow ? "low" : "ok");
|
|
readingsBulkUpdate($shash,"onoff",$isopen);
|
|
readingsEndUpdate($shash, 0);
|
|
|
|
}elsif($msgtype eq "CubeClockState"){
|
|
my $clockset = $args[0];
|
|
$shash->{clocknotset} = !$clockset;
|
|
|
|
}elsif($msgtype eq "CubeConnectionState"){
|
|
my $connected = $args[0];
|
|
|
|
readingsSingleUpdate($shash,"connection",$connected,0);
|
|
} elsif($msgtype eq "HeatingThermostatConfig") {
|
|
|
|
$shash->{ecoTemperature} = $args[0];
|
|
$shash->{comfortTemperature} = $args[1];
|
|
$shash->{boostValveposition} = $args[2];
|
|
$shash->{boostDuration} = $args[3];
|
|
$shash->{temperatureOffset} = $args[4];
|
|
$shash->{maximumTemperature} = $args[5];
|
|
$shash->{minimumTemperature} = $args[6];
|
|
$shash->{windowOpenTemperature} = $args[7];
|
|
$shash->{windowOpenDuration} = $args[8];
|
|
}
|
|
|
|
#Build $shash->{STATE}
|
|
$shash->{STATE} = "waiting for data";
|
|
if(exists($shash->{READINGS})) {
|
|
$shash->{STATE} = $shash->{READINGS}{connection}{VAL} ? "connected" : "not connected" if(exists($shash->{READINGS}{connection}));
|
|
$shash->{STATE} = "$shash->{READINGS}{desiredTemperature}{VAL} °C" if(exists($shash->{READINGS}{desiredTemperature}));
|
|
$shash->{STATE} = $shash->{READINGS}{onoff}{VAL} ? "opened" : "closed" if(exists($shash->{READINGS}{onoff}));
|
|
}
|
|
|
|
$shash->{STATE} .= " (clock not set)" if($shash->{clocknotset});
|
|
$shash->{STATE} .= " (auto)" if(exists($shash->{mode}) and $shash->{mode} eq "auto");
|
|
#Don't print this: it's the standard mode
|
|
#$shash->{STATE} .= " (manual)" if(exists($shash->{mode}) and $shash->{mode} eq "manual");
|
|
$shash->{STATE} .= " (until ".$shash->{until}.")" if(exists($shash->{mode}) and $shash->{mode} eq "temporary" );
|
|
$shash->{STATE} .= " (battery low)" if($shash->{batterylow});
|
|
$shash->{STATE} .= " (rf error)" if($shash->{rferror});
|
|
|
|
return $shash->{NAME}
|
|
}
|
|
|
|
1;
|
|
|
|
=pod
|
|
=begin html
|
|
|
|
<a name="MAX"></a>
|
|
<h3>MAX</h3>
|
|
<ul>
|
|
Devices from the eQ-3 MAX! group.<br>
|
|
When heating thermostats show a temperature of zero degrees, they didn't yet send any data to the cube. You can
|
|
force the device to send data to the cube by physically setting a temperature directly at the device (not through fhem).
|
|
<br><br>
|
|
<a name="MAXdefine"></a>
|
|
<b>Define</b>
|
|
<ul>
|
|
<code>define <name> MAX <type> <addr></code>
|
|
<br><br>
|
|
|
|
Define an MAX device of type <type> and rf address <addr>.
|
|
The <type> is one of Cube, HeatingThermostat, HeatingThermostatPlus, WallMountedThermostat, ShutterContact, PushButton.
|
|
The <addr> is a 6 digit hex number.
|
|
You should never need to specify this by yourself, the <a href="#autocreate">autocreate</a> module will do it for you.<br>
|
|
It's advisable to set event-on-change-reading, like
|
|
<code>attr Heater_0 event-on-change-reading battery,desiredTemperature,valveposition</code>
|
|
because the polling mechanism will otherwise create events every 10 seconds.
|
|
|
|
Example:
|
|
<ul>
|
|
<code>define switch1 MAX PushButton ffc545</code><br>
|
|
</ul>
|
|
</ul>
|
|
<br>
|
|
|
|
<a name="MAXset"></a>
|
|
<b>Set</b>
|
|
<ul>
|
|
<li>desiredTemperature <value> [until <date>]<br>
|
|
For devices of type HeatingThermostat only. <value> maybe one of
|
|
<ul>
|
|
<li>degree celcius between 3.5 and 30.5 in 0.5 degree steps</li>
|
|
<li>"on" or "off" correspondig to 30.5 and 4.5 degree celcius</li>
|
|
<li>"eco" or "comfort" using the eco/comfort temperature set on the device (just as the right-most physical button on the device itself does)</li>
|
|
<li>"auto", where the weekly program saved on the thermostat is processed</li>
|
|
</ul>
|
|
All values but "auto" maybe accompanied by the "until" clause, with <data> in format "dd.mm.yyyy HH:MM" (minutes may only be "30" or "00"!)
|
|
to set a temporary temperature until that date/time. Make sure that the cube has valid system time!</li>
|
|
<li>groupid <id><br>
|
|
For devices of type HeatingThermostat only.
|
|
Writes the given group id the device's memory. It is usually not necessary to change this.</li>
|
|
<li>ecoTemperature <value><br>
|
|
For devices of type HeatingThermostat only. Writes the given eco temperature to the device's memory. It can be activated by pressing the rightmost physical button on the device.</li>
|
|
<li>comfortTemperature <value><br>
|
|
For devices of type HeatingThermostat only. Writes the given comfort temperature to the device's memory. It can be activated by pressing the rightmost physical button on the device.</li>
|
|
<li>temperatureOffset <value><br>
|
|
For devices of type HeatingThermostat only. Writes the given temperature offset to the device's memory. The thermostat tries to match desiredTemperature to (measuredTemperature+temperatureOffset). Usually, the measured temperature is a bit higher than the overall room temperature (due to closeness to the heater), so one uses a small negative offset. Must be between -3.5 and 3.5 degree.</li>
|
|
<li>minimumTemperature <value><br>
|
|
For devices of type HeatingThermostat only. Writes the given minimum temperature to the device's memory. It confines the temperature that can be manually set on the device.</li>
|
|
<li>maximumTemperature <value><br>
|
|
For devices of type HeatingThermostat only. Writes the given maximum temperature to the device's memory. It confines the temperature that can be manually set on the device.</li>
|
|
<li>windowOpenTemperature <value><br>
|
|
For devices of type HeatingThermostat only. Writes the given window open temperature to the device's memory. That is the temperature the heater will temporarily set if an open window is detected.</li>
|
|
<li>windowOpenDuration <value><br>
|
|
For devices of type HeatingThermostat only. Writes the given window open duration to the device's memory. That is the duration the heater will temporarily set the window open temperature if an open window is detected by a rapid temperature decrease. (Not used if open window is detected by ShutterControl. Must be between 0 and 60 minutes in multiples of 5.</li>
|
|
</ul>
|
|
<br>
|
|
|
|
<a name="MAXget"></a>
|
|
<b>Get</b> <ul>N/A</ul><br>
|
|
|
|
<a name="MAXattr"></a>
|
|
<b>Attributes</b>
|
|
<ul>
|
|
<li><a href="#event-on-update-reading">event-on-update-reading</a></li>
|
|
<li><a href="#event-on-change-reading">event-on-change-reading</a></li>
|
|
<li><a href="#eventMap">eventMap</a></li>
|
|
<li><a href="#IODev">IODev</a></li>
|
|
<li><a href="#loglevel">loglevel</a></li>
|
|
<li><a href="#do_not_notify">do_not_notify</a></li>
|
|
<li><a href="#ignore">ignore</a></li>
|
|
</ul>
|
|
<br>
|
|
|
|
<a name="MAXevents"></a>
|
|
<b>Generated events:</b>
|
|
<ul>
|
|
<li>desiredTemperature<br>Only for HeatingThermostat</li>
|
|
<li>valveposition<br>Only for HeatingThermostat</li>
|
|
<li>battery</li>
|
|
<li>temperature<br>The measured(!) temperature, only for HeatingThermostat</li>
|
|
</ul>
|
|
</ul>
|
|
|
|
=end html
|
|
=cut
|