ComfoAir [devicename|none] [interval]"
if(@a < 3);
$hash->{BUSY} = 0;
$hash->{EXPECT} = '';
$hash->{ModuleVersion} = $Module_Version;
DevIo_CloseDev($hash);
$hash->{DeviceName} = $dev;
if($dev ne "none") {
DevIo_OpenDev($hash, 0, 0);
}
if (!$interval) {
$hash->{Interval} = 0;
Log3 $name, 3, "$name: interval is 0 or not specified - not sending requests - just listening!";
}
else {
$hash->{Interval} = $interval;
UpdateTimer($hash, \&ComfoAir::GetUpdate, 'start');
}
Log3 $name, 3, "$name: Defined with device $dev" . ($interval ? ", interval $interval" : '');
return;
}
#####################################
sub UndefFn {
my ($hash, $arg) = @_;
my $name = $hash->{NAME};
DevIo_CloseDev($hash);
RemoveInternalTimer ("timeout:".$name);
StopQueueTimer($hash, {silent => 1});
UpdateTimer($hash, \&ComfoAir::GetUpdate, 'stop');
return;
}
#########################################################################
# Attr command
# if validation fails, return something so CommandAttr in fhem.pl doesn't assign a value to $attr
sub AttrFn {
my $cmd = shift; # 'set' or 'del'
my $name = shift; # the Fhem device name
my $aName = shift; # attribute name
my $aVal = shift // ''; # attribute value
my $hash = $defs{$name}; # reference to the Fhem device hash
Log3 $name, 5, "$name: attr $name $aName $aVal";
if ($cmd eq 'set') {
if ($aName eq 'alignTime') {
my ($alErr, $alHr, $alMin, $alSec, undef) = GetTimeSpec($aVal);
return "Invalid Format $aVal in $aName : $alErr" if ($alErr);
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
$hash->{'.TimeAlign'} = fhemTimeLocal($alSec, $alMin, $alHr, $mday, $mon, $year);
UpdateTimer($hash, \&ComfoAir::GetUpdate, 'start'); # change timer for alignment
}
}
elsif ($cmd eq 'del') { # Deletion of Attributes
#Log3 $name, 5, "$name: del attribute $aName";
if ($aName eq 'alignTime') {
delete $hash->{'.TimeAlign'};
}
}
return;
}
#####################################
sub GetFn {
my @getValArr = @_; # rest is optional values
my $hash = shift @getValArr; # reference to device hash
my $name = shift @getValArr; # device name
my $getName = shift @getValArr; # get option name
my $getVal = join(' ', @getValArr); # optional value after get name
return "\"get ComfoAir\" needs at least one argument" if(!$getName);
if (!defined($getHash{$getName})) { # undefined Get
Log3 $name, 5, "$name: Get $getName not found, return list @getList ";
return "Unknown argument $getName, choose one of @getList ";
}
my $msgHash = $getHash{$getName}{msgHash}; # Hash für die Nachricht aus parseInfo
Log3 $name, 5, "$name: Request found in getHash created from parseInfo data";
if (!$msgHash->{request}) {
return "Protocol doesn't provide a command to get $getName";
}
Send($hash, $msgHash->{request}, '', $msgHash->{replyCode}, 1);
my ($err, $result) = ReadAnswer($hash, $getName, $msgHash->{replyCode});
return $err if ($err);
return $result;
}
#####################################
sub SetFn {
my ($hash, @a) = @_;
return "\"set ComfoAir\" needs at least an argument" if(@a < 2);
my $name = $hash->{NAME};
my ($cmd,$fmt,$data);
my $setName = $a[1];
my $setVal = $a[2];
my $rawVal = "";
if (defined($requestHash{$setName})) {
# set Option ist Daten-Abfrage-Request aus parseInfo
Log3 $name, 5, "$name: Request found in requestHash created from parseInfo data";
Send($hash, $requestHash{$setName}{request}, "", $requestHash{$setName}{replyCode});
return "";
}
if (defined($setHash{$setName})) {
# set Option für einen einzelnen Wert, in parseInfo definiert -> generische Verarbeitung
if (!defined($setVal)) {
Log3 $name, 3, "$name: No Value given to set $setName";
return "No Value given to set $setName";
}
Log3 $name, 5, "$name: Set found option $setName in setHash created from parseInfo data";
($cmd, $fmt) = split(":", $setHash{$setName}{set});
# 1. Schritt, falls definiert per Umkehrung der Map umwandeln (z.B. Text in numerische Codes)
if (defined($setHash{$setName}{rmap})) {
if (defined($setHash{$setName}{rmap}{$setVal})) {
# reverse map für das Reading und den Wert definiert
$rawVal = $setHash{$setName}{rmap}{$setVal};
Log3 $name, 5, "$name: found $setVal in setHash rmap and converted to $rawVal";
} else {
Log3 $name, 3, "$name: Set Value $setVal did not match defined map";
return "Set Value $setVal did not match defined map";
}
} else {
# wenn keine map, dann wenigstens sicherstellen, dass numerisch.
if ($setVal !~ /^-?\d+\.?\d*$/) {
Log3 $name, 3, "$name: Set Value $setVal is not numeric";
return "Set Value $setVal is not numeric";
}
$rawVal = $setVal;
}
# 2. Schritt: falls definiert Min- und Max-Werte prüfen
if (defined($setHash{$setName}{setmin})) {
Log3 $name, 5, "$name: checking Value $rawVal against Min $setHash{$setName}{setmin}";
return "Set Value $rawVal is smaller than Min ($setHash{$setName}{setmin})"
if ($rawVal < $setHash{$setName}{setmin});
}
if (defined($setHash{$setName}{setmax})) {
Log3 $name, 5, "$name: checking Value $rawVal against Max $setHash{$setName}{setmax}";
return "Set Value $rawVal is bigger than Max ($setHash{$setName}{setmax})"
if ($rawVal > $setHash{$setName}{setmax});
}
# 3. Schritt: Konvertiere mit setexpr falls definiert
if (defined($setHash{$setName}{setexpr})) {
my $val = $rawVal;
$rawVal = eval($setHash{$setName}{setexpr}); ## no critic - expression needs to come from variable
Log3 $name, 5, "$name: converted Value $val to $rawVal using expr $setHash{$setName}{setexpr}";
}
# 4. Schritt: mit sprintf umwandeln und senden.
$data = sprintf($fmt, $rawVal); # in parseInfo angegebenes Format bei set=> - meist Konvert in Hex
Send($hash, $cmd, $data, 0);
# Nach dem Set gleich den passenden Datenblock nochmals anfordern, damit die Readings de neuen Wert haben
if ($setHash{$setName}{msgHash}{request}) {
Send($hash, $setHash{$setName}{msgHash}{request}, "",
$setHash{$setName}{msgHash}{replyCode},1);
# falls ein minDelay bei Send implementiert wäre, müsste ReadAnswer optimiert werden, sonst wird der 2. send ggf nicht vor einem Timeout gesendet ...
my ($err, $result) = ReadAnswer($hash, $setName, $setHash{$setName}{msgHash}{replyCode});
#return "$setName -> $result";
return $err if ($err);
}
return;
} elsif (defined($AddSets{$setName})) {
# Additional set option not defined in parseInfo but AddSets
if($setName eq "SendRawData") {
return "please specify data as cmd or cmd -> data in hex"
if (!defined($setVal));
($cmd, $data) = split("->",$setVal); # eingegebener Wert ist HexCmd -> HexData
$data="" if(!defined($data));
}
Send($hash, $cmd, $data, 0);
} else {
# undefiniertes Set
Log3 $name, 5, "$name: Set $setName not found, return list @setList " . join (" ", keys %AddSets);
return "Unknown argument $a[1], choose one of @setList " . join (" ", keys %AddSets);
}
return;
}
#####################################
# Called from the read functions
sub ParseFrames {
my $hash = shift;
my $name = $hash->{NAME};
my $frame = $hash->{helper}{buffer};
$hash->{RAWBUFFER} = unpack ('H*', $frame);
Log3 $name, 5, "$name: raw buffer: $hash->{RAWBUFFER}";
# check for full frame in buffer
if ($frame =~ /\x07\xf0(.{3}(?:[^\x07]|(?:\x07\x07))*)\x07\x0f(.*)/s) {
# got full frame (and maybe Ack before but that's ok)
my $framedata = $1;
$hash->{helper}{buffer} = $2; # only keep the rest after the frame
$framedata =~ s/\x07\x07/\x07/g; # remove double x07
$hash->{LASTFRAMEDATA} = unpack ('H*', $framedata);
Log3 $name, 5, "$name: ParseFrames got frame: $hash->{RAWBUFFER}" .
" data $hash->{LASTFRAMEDATA} Rest " . unpack ('H*', $hash->{helper}{buffer});
return $framedata;
}
# ACK?
elsif ($frame =~ /\x07\xf3(.*)/s) {
my $level = ($hash->{Interval} ? 4 : 5);
Log3 $name, $level, "$name: read got Ack";
$hash->{helper}{buffer} = $1; # only keep the rest after the frame
if (!$hash->{EXPECT}) {
$hash->{BUSY} = 0;
# es wird keine weitere Antwort erwartet -> gleich weiter Send Queue abarbeiten und nicht auf alten Timer warten
RemoveInternalTimer ("timeout:".$name);
HandleSendQueue ("direct:".$name); # don't wait for next regular handle queue slot
}
}
return; # continue reading, probably frame not fully received yet
}
#####################################
# Called from the read functions
sub InterpretFrame {
my $hash = shift;
my $framedata = shift;
my $name = $hash->{NAME};
my ($cmd, $hexcmd, $hexdata, $len, $data, $chk);
if (defined($framedata)) {
if ($framedata !~ /(.{2})(.)(.*)(.)/s) {
Log3 $name, 3, "$name: read: error splitting frame into fields: $hash->{LASTFRAMEDATA}";
return;
}
$cmd = $1;
$len = $2;
$data = $3;
$chk = unpack ('C', $4);
$hexcmd = unpack ('H*', $cmd);
$hexdata = unpack ('H*', $data);
Log3 $name, 5, "$name: read split frame into cmd $hexcmd, len " . unpack ('C', $len) .
", data $hexdata chk $chk";
}
# Länge prüfen
if (unpack ('C', $len) != length($data)) {
Log3 $name, 4, "$name: read: wrong length: " . length($data) .
" (calculated) != " . unpack ('C', $len) . " (header)" .
" cmd=$hexcmd, data=$hexdata, chk=$chk";
return;
}
# Checksum prüfen
my $csum = unpack ('%8C*', $cmd . $len . $data . "\xad"); # berechne csum
if($csum != $chk) {
Log3 $name, 4, "$name: read: wrong checksum: $csum (calculated) != $chk (frame) cmd $hexcmd, data $hexdata";
return;
};
# Parse Data
if ($parseInfo{$hexcmd}) {
if (!AttrVal($name, "hide-$parseInfo{$hexcmd}{name}", 0)) {
# Definition für diesen Nachrichten-Typ gefunden
my %p = %{$parseInfo{$hexcmd}};
Log3 $name, 4, "$name: read got " . $p{"name"} . " (reply code $hexcmd) with data $hexdata";
# Definition der einzelnen Felder abarbeiten
my @fields = unpack($p{"unpack"}, $data);
my $filter = 0;
if ($p{check}) {
my $result = eval($p{check}); ## no critic - expression needs to come from variable
Log3 $name, 5, "$name: cmd $hexcmd check is " . $result . ', $fields[5] = ' . $fields[5] if ($fields[5] > 15);
if (!$result) {
Log3 $name, 5, "$name: filter data for failed check: @fields";
$filter = 1;
}
}
if (!$filter) {
readingsBeginUpdate($hash);
for (my $i = 0; $i < scalar(@fields); $i++) {
# einzelne Felder verarbeiten
my $reading = $p{"readings"}[$i]{"name"};
my $val = $fields[$i];
# Exp zur Nachbearbeitung der Werte?
if ($p{"readings"}[$i]{"expr"}) {
Log3 $name, 5, "$name: read evaluate $val with expr " . $p{"readings"}[$i]{"expr"};
$val = eval($p{"readings"}[$i]{"expr"}); ## no critic - expr needs tocome from variable
}
# Map zur Nachbereitung der Werte?
if ($p{"readings"}[$i]{"map"}) {
my %map = split (/[,: ]+/, $p{"readings"}[$i]{"map"});
Log3 $name, 5, "$name: read maps value $val with " . $p{"readings"}[$i]{"map"};
$val = $map{$val} if ($map{$val});
}
Log3 $name, 5, "$name: read assign $reading with $val";
readingsBulkUpdate($hash, $reading, $val);
}
readingsEndUpdate($hash, 1);
}
}
} else {
my $level = ($hash->{Interval} ? 4 : 5);
Log3 $name, $level, "$name: read: unknown cmd $hexcmd, len " . unpack ('C', $len) .
", data $hexdata, chk $chk";
}
if ($hash->{EXPECT}) {
# der letzte Request erwartet eine Antwort -> ist sie das?
if ($hexcmd eq $hash->{EXPECT}) {
$hash->{BUSY} = 0;
$hash->{EXPECT} = '';
Log3 $name, 5, "$name: read got expected reply ($hexcmd), setting BUSY=0";
} else {
Log3 $name, 3, "$name: read did not get expected reply (" . $hash->{EXPECT} . ") but $hexcmd";
}
}
SendAck($hash) if ($hash->{Interval});
# todo: sowohl hier als auch am Ende von ParseFrames wird HandleSendQueue aufgerufen. Geht das nicht eleganter?
if (!$hash->{EXPECT}) {
# es wird keine Antwort mehr erwartet -> gleich weiter Send Queue abarbeiten und nicht auf Timer warten
$hash->{BUSY} = 0; # zur Sicherheit falls ein Ack versäumt wurde
RemoveInternalTimer ("timeout:".$name);
HandleSendQueue ("direct:".$name); # don't wait for next regular handle queue slot
}
return;
}
#####################################
# Called from the global loop, when the select for hash->{FD} reports data
sub ReadFn {
my $hash = shift;
my $name = $hash->{NAME};
my $buf;
if ($hash->{DeviceName} eq 'none') { # simulate receiving
if ($hash->{TestInput}) {
$buf = $hash->{TestInput};
delete $hash->{TestInput};
}
}
else {
$buf = DevIo_SimpleRead($hash);
return if(!defined($buf));
}
$hash->{helper}{buffer} .= $buf;
# todo: does this loop really make sense?
for (my $i = 0;$i < 2;$i++) {
my $framedata = ParseFrames($hash);
return if (!$framedata);
InterpretFrame($hash, $framedata);
}
return;
}
#####################################
# Called from get / set to get a direct answer
# todo: restructure this function (see Modbus)
# handle BUSY when timeout here!
sub ReadAnswer {
my ($hash, $arg, $expectReply) = @_;
my $name = $hash->{NAME};
return ("No FD", undef)
if(!$hash || ($^O !~ /Win/ && !defined($hash->{FD})));
my ($buf, $framedata, $cmd);
my $rin = '';
my $to = AttrVal($name, "timeout", 2); # default is 2 seconds timeout
Log3 $name, 5, "$name: ReadAnswer called for get $arg";
for(;;) {
if ($hash->{DeviceName} eq 'none') { # simulate receiving
if ($hash->{TestInput}) {
$buf = $hash->{TestInput};
delete $hash->{TestInput};
} else {
#$hash->{BUSY} = 0;
#$hash->{EXPECT} = "";
return ("Timeout reading answer for $arg", undef);
}
}
elsif($^O =~ m/Win/ && $hash->{USBDev}) {
$hash->{USBDev}->read_const_time($to*1000); # set timeout (ms)
$buf = $hash->{USBDev}->read(999);
if(length($buf) == 0) {
Log3 $name, 3, "$name: Timeout in ReadAnswer for get $arg";
return ("Timeout reading answer for $arg", undef);
}
} else {
if(!$hash->{FD}) {
Log3 $name, 3, "$name: Device lost in ReadAnswer for get $arg";
return ("Device lost when reading answer for get $arg", undef);
}
vec($rin, $hash->{FD}, 1) = 1; # setze entsprechendes Bit in rin
my $nfound = select($rin, undef, undef, $to);
if($nfound < 0) {
next if ($! == EAGAIN() || $! == EINTR() || $! == 0);
my $err = $!;
DevIo_Disconnected($hash);
Log3 $name, 3, "$name: ReadAnswer $arg: error $err";
return("ReadAnswer $arg: $err", undef);
}
if($nfound == 0) {
Log3 $name, 3, "$name: Timeout2 in ReadAnswer for $arg";
return ("Timeout reading answer for $arg", undef);
}
$buf = DevIo_SimpleRead($hash);
if(!defined($buf)) {
Log3 $name, 3, "$name: ReadAnswer for $arg got no data";
return ("No data", undef);
}
}
if($buf) {
$hash->{helper}{buffer} .= $buf;
Log3 $name, 5, "$name: ReadAnswer got: " . unpack ("H*", $hash->{helper}{buffer});
}
$framedata = ParseFrames($hash);
if ($framedata) {
InterpretFrame($hash, $framedata);
$cmd = unpack ('H4x*', $framedata);
if ($cmd eq $expectReply) {
# das war's worauf wir gewartet haben
Log3 $name, 5, "$name: ReadAnswer done with success";
return (undef, ReadingsVal($name, $arg, ""));
}
}
HandleSendQueue("direct:".$name);
}
return;
}
#####################################
sub ReadyFn {
my ($hash) = @_;
return DevIo_OpenDev($hash, 1, undef)
if($hash->{STATE} eq "disconnected");
# This is relevant for windows/USB only
my $po = $hash->{USBDev};
if ($po) {
my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags) = $po->status;
return ($InBytes>0);
}
return;
}
#####################################
sub GetUpdate {
my $arg = shift; # called with a string type:$name
my ($calltype, $name) = split(':', $arg);
my $hash = $defs{$name};
UpdateTimer($hash, \&ComfoAir::GetUpdate, 'next');
foreach my $msgHashRef (values %parseInfo) {
if (defined($msgHashRef->{request})) {
my $default = ($msgHashRef->{defaultpoll} ? 1 : 0); # verwende als Defaultwert für Attribut, falls gesetzt in %parseInfo
if (AttrVal($name, "poll-$msgHashRef->{name}", $default)) {
Log3 $name, 5, "$name: GetUpdate requests $msgHashRef->{name}, default is $default";
Send($hash, $msgHashRef->{request}, "", $msgHashRef->{replyCode});
}
}
}
return;
}
#####################################
sub Send {
my ($hash, $hexcmd, $hexdata, $expectReply, $first) = @_;
my $name = $hash->{NAME};
my $cmd = pack ('H*', $hexcmd);
my $data = pack ('H*', $hexdata);
my $len = pack ('C', length ($data));
my $csum = pack ('C', unpack ('%8C*', $cmd . $len . $data. "\xad"));
my $framedata = $data.$csum;
$framedata =~ s/\x07/\x07\x07/g; # double 07 in contents of frame including Checksum!
my $frame = "\x07\xF0".$cmd.$len.$framedata."\x07\x0F";
my $hexframe = unpack ('H*', $frame);
$expectReply = "" if (!$expectReply);
Log3 $name, 4, "$name: send adds frame to queue with cmd $hexcmd" .
($cmdHash{$hexcmd} ? " (get " . $cmdHash{$hexcmd}{name} . ")" : "") .
" / frame " . $hexframe;
my %entry;
$entry{DATA} = $frame;
$entry{EXPECT} = $expectReply;
my $qlen = ($hash->{QUEUE} ? scalar(@{$hash->{QUEUE}}) : 0);
Log3 $name, 5, "$name: send queue length : $qlen";
if(!$qlen) {
$hash->{QUEUE} = [ \%entry ];
} else {
if ($qlen > AttrVal($name, "queueMax", 20)) {
Log3 $name, 3, "$name: send queue too long, dropping request";
} else {
if ($first) {
unshift (@{$hash->{QUEUE}}, \%entry);
} else {
push(@{$hash->{QUEUE}}, \%entry);
}
}
}
HandleSendQueue("direct:".$name);
return;
}
#######################################
sub TimeoutSend {
my $param = shift;
my (undef,$name) = split(':',$param);
my $hash = $defs{$name};
Log3 $name, 3, "$name: timeout waiting for reply" .
($hash->{EXPECT} ? " expecting " . $hash->{EXPECT} : "") .
" Request was " . $hash->{LASTREQUEST};
$hash->{BUSY} = 0;
$hash->{EXPECT} = "";
return;
}
#######################################
sub HandleSendQueue {
my $param = shift;
my (undef,$name) = split(':',$param);
my $hash = $defs{$name};
my $now = gettimeofday();
my $arr = $hash->{QUEUE};
my $qlen = ($arr ? scalar(@{ $arr }) : 0 );
Log3 $name, 5, "$name: HandleSendQueue called from " . FhemCaller() . ", qlen = $qlen";
StopQueueTimer($hash, {silent => 1});
if($qlen) {
if (!$init_done) { # fhem not initialized, wait with IO
StartQueueTimer($hash, \&ComfoAir::HandleSendQueue, {log => 'init not done, delay sending from queue'});
return;
}
if ($hash->{BUSY}) { # still waiting for reply to last request
StartQueueTimer($hash, \&ComfoAir::HandleSendQueue, {log => 'send busy, delay writing from queue'});
return;
}
my $entry = $arr->[0];
my $bstring = $entry->{DATA};
my $hexcmd = unpack ('xxH4x*', $bstring);
if($bstring ne "") { # if something to send - do so
$hash->{LASTREQUEST} = unpack ('H*', $bstring);
$hash->{BUSY} = 1; # at least wait for ACK
Log3 $name, 4, "$name: handle queue sends" .
($cmdHash{$hexcmd} ? " get " . $cmdHash{$hexcmd}{name} : "") .
" code: $hexcmd" .
" frame: " . $hash->{LASTREQUEST} .
($entry->{EXPECT} ? " and wait for " . $entry->{EXPECT} : "") .
", V " . $hash->{ModuleVersion};
if ($hash->{DeviceName} eq 'none') {
Log3 $name, 4, "$name: Simulate sending to none: " . unpack ('H*', $bstring);
}
else {
DevIo_SimpleWrite($hash, $bstring, 0);
}
if ($entry->{EXPECT}) {
# we expect a reply
$hash->{EXPECT} = $entry->{EXPECT};
}
my $to = AttrVal($name, "timeout", 2); # default is 2 seconds timeout
RemoveInternalTimer ("timeout:".$name);
InternalTimer($now+$to, "ComfoAir::TimeoutSend", "timeout:".$name, 0);
}
shift(@{$arr});
if(@{$arr} == 0) { # last item was sent -> delete queue
delete($hash->{QUEUE});
} else { # more items in queue -> schedule next handle invocation
StartQueueTimer($hash, \&ComfoAir::HandleSendQueue);
}
}
return;
}
#######################################
sub SendAck {
my $hash = shift;
my $name = $hash->{NAME};
Log3 $name, 4, "$name: sending Ack";
DevIo_SimpleWrite($hash, "\x07\xf3", 0);
return;
}
1;
=pod
=item device
=item summary module for Zehnder ComfoAir, StorkAir WHR930, Wernig G90-380 and Santos 370
=item summary_DE Modul für Zehnder ComfoAir, StorkAir WHR930, Wernig G90-380 and Santos 370
=begin html
ComfoAir
ComfoAir provides a way to communicate with ComfoAir ventilation systems from Zehnder, especially the ComfoAir 350 (CA350).
It seems that many other ventilation systems use the same communication device and protocol,
e.g. WHR930 from StorkAir, G90-380 from Wernig and Santos 370 DC from Paul.
They are connected via serial line to the fhem computer.
This module is based on the protocol description at http://www.see-solutions.de/sonstiges/Protokollbeschreibung_ComfoAir.pdf
and copies some ideas from earlier modules for the same devices that were posted in the fhem forum from danhauck(Santos) and Joachim (WHR962).
The module can be used in two ways depending on how fhem and / or a vendor supplied remote control device
like CC Ease or CC Luxe are connected to the system. If a remote control device is connected it is strongly advised that
fhem does not send data to the ventilation system as well and only listens to the communication betweem the vendor equipment.
The RS232 interface used is not made to support more than two parties communicating and connecting fhem in parallel to a CC Ease or similar device can lead to
collisions when sending data which can corrupt the ventilation system.
If connected in parallel fhem should only passively listen and <Interval> is to be set to 0.
If no remote control device is connected to the ventilation systems then fhem has to take control and actively request data
in the interval to be defined. Otherwiese fhem will not see any data. In this case fhem can also send commands to modify settings.
Prerequisites
-
This module requires the Device::SerialPort or Win32::SerialPort module.
Define
define <name> ComfoAir <device> <Interval>
The module connects to the ventialation system through the given Device and either passively listens to data that is communicated
between the ventialation system and its remote control device (e.g. CC Luxe) or it actively requests data from the
ventilation system every <Interval> seconds
If <Interval> is set to 0 then no polling will be done and the module only listens to messages on the line.
Example:
define ZL ComfoAir /dev/ttyUSB1@9600 60
Configuration of the module
apart from the serial connection and the interval which both are specified in the define command there are several attributes that
can optionally be used to modify the behavior of the module.
The module internally gives names to all the protocol messages that are defined in the module and these names can be used
in attributes to define which requests are periodically sent to the ventilation device. The same nams can also be used with
set commands to manually send a request. Since all messages and readings are generically defined in a data structure in the module, it should be
quite easy to add more protocol details if needed without programming.
The names currently defined are:
Bootloader-Version
Firmware-Version
RS232-Modus
Sensordaten
KonPlatine-Version
Verzoegerungen
Ventilation-Levels
Temperaturen
Betriebsstunden
Status-Bypass
Status-Vorheizung
The attributes that control which messages are sent / which data is requested every <Interval> seconds are:
poll-Bootloader-Version
poll-Firmware-Version
poll-RS232-Modus
poll-Sensordaten
poll-KonPlatine-Version
poll-Verzoegerungen
poll-Ventilation-Levels
poll-Temperaturen
poll-Betriebsstunden
poll-Status-Bypass
poll-Status-Vorheizung
if the attribute is set to 1, the corresponding data is requested every <Interval> seconds. If it is set to 0, then the data is not requested.
by default Ventilation-Levels, Temperaturen and Status-Bypass are requested if no attributes are set.
Example:
define ZL ComfoAir /dev/ttyUSB1@9600 60
attr ZL poll-Status-Bypass 0
define FileLog_Lueftung FileLog ./log/Lueftung-%Y.log ZL
Set-Commands
like with the attributes mentioned above, set commands can be used to send a request for data manually. The following set options are available for this:
request-Status-Bypass
request-Bootloader-Version
request-Sensordaten
request-Temperaturen
request-Firmware-Version
request-KonPlatine-Version
request-Ventilation-Levels
request-Verzoegerungen
request-Betriebsstunden
request-Status-Vorheizung
additionally important fields can be set:
Temp_Komfort (target temperature for comfort)
Stufe (ventilation level)
Get-Commands
All readings that are derived from the responses to protocol requests are also available as Get commands. Internally a Get command triggers the corresponding
request to the device and then interprets the data and returns the right field value. To avoid huge option lists in FHEMWEB, only the most important Get options
are visible in FHEMWEB. However this can easily be changed since all the readings and protocol messages are internally defined in the modue in a data structure
and to make a Reading visible as Get option only a little option (e.g. showget => 1
has to be added to this data structure
Attributes
- do_not_notify
- readingFnAttributes
- poll-Bootloader-Version
- poll-Firmware-Version
- poll-RS232-Modus
- poll-Sensordaten
- poll-KonPlatine-Version
- poll-Verzoegerungen
- poll-Ventilation-Levels
- poll-Temperaturen
- poll-Betriebsstunden
- poll-Status-Bypass
- poll-Status-Vorheizung
include a request for the data belonging to the named group when sending requests every interval seconds
- hide-Bootloader-Version
- hide-Firmware-Version
- hide-RS232-Modus
- hide-Sensordaten
- hide-KonPlatine-Version
- hide-Verzoegerungen
- hide-Ventilation-Levels
- hide-Temperaturen
- hide-Betriebsstunden
- hide-Status-Bypass
- hide-Status-Vorheizung
prevent readings of the named group from being created even if used passively without polling and an external remote control requests this data.
please note that this attribute doesn't delete already existing readings.
- queueDelay
modify the delay used when sending requests to the device from the internal queue, defaults to 1 second
- queueMax
max length of the send queue, defaults to 50
- timeout
set the timeout for reads, defaults to 2 seconds
- alignTime
Aligns each periodic read request for the defined interval to this base time. This is typcally something like 00:00 (see the Fhem at command)
=end html
=cut