";
FW_pO "";
- foreach my $d (sort { lc(AttrVal($a,"alias",$a)) cmp
- lc(AttrVal($b,"alias",$b)) } keys %{$group{$g}}) {
+ foreach my $d (sort { lc(AttrVal($a, "sortby", AttrVal($a,"alias",$a))) cmp
+ lc(AttrVal($b, "sortby", AttrVal($b,"alias",$b))) }
+ keys %{$group{$g}}) {
my $type = $defs{$d}{TYPE};
- pF "\n", ($row&1)?"odd":"even";
+ FW_pF "\n ", ($row&1)?"odd":"even";
my $devName = AttrVal($d, "alias", $d);
my $icon = AttrVal($d, "icon", "");
- if($icon =~ m/^(.*)\.($ICONEXTENSION)$/) {
- $icon= $1; # silently remove the extension
- }
$icon = FW_makeImage($icon) . " " if($icon);
if($FW_hiddenroom{detail}) {
@@ -1155,14 +1094,15 @@ FW_showRoom()
my $srf = $FW_room ? "&room=$FW_room" : "";
my $cv = ReadingsVal($d, $cmd, Value($d));
$cmd = "" if($cmd eq "state");
- $cv =~ s/[^\d\.]//g;
+ $cv =~ s/.*?(\d+).*/$1/; # get first number
+ $cv = 0 if($cv !~ m/\d/);
FW_pO "| ".
- " | ";
$firstIdx=1;
@@ -1226,13 +1166,9 @@ FW_showRoom()
# Now the weblinks
my $buttons = 1;
- $FW_room = "" if(!defined($FW_room));
- my @list = ($FW_room eq "all" ? keys %defs : keys %{$FW_rooms{$FW_room}});
- foreach my $d (sort @list) {
- next if(IsIgnored($d));
- my $type = $defs{$d}{TYPE};
- next if(!$type || $type ne "weblink" || AttrVal($d, "group", undef));
-
+ foreach my $d (sort { lc(AttrVal($a, "sortby", AttrVal($a,"alias",$a))) cmp
+ lc(AttrVal($b, "sortby", AttrVal($b,"alias",$b))) }
+ @weblinks) {
$buttons = FW_showWeblink($d, $defs{$d}{LINK}, $defs{$d}{WLTYPE}, $buttons);
}
FW_pO "";
@@ -1248,7 +1184,8 @@ FW_fileList($)
$fname =~ m,^(.*)/([^/]*)$,; # Split into dir and file
my ($dir,$re) = ($1, $2);
return if(!$re);
- $re =~ s/%./[A-Za-z0-9]*/g;
+ $dir =~ s/%L/$attr{global}{logdir}/g if($dir =~ m/%/ && $attr{global}{logdir}); # %L present and log directory defined
+ $re =~ s/%./[A-Za-z0-9]*/g; # logfile magic (%Y, etc)
my @ret;
return @ret if(!opendir(DH, $dir));
while(my $f = readdir(DH)) {
@@ -1289,7 +1226,6 @@ FW_returnFileAsStream($$$$$)
}
$etag = (stat($path))[9]; #mtime
-
if(defined($etag) && defined($if_none_match) && $etag eq $if_none_match) {
print $c "HTTP/1.1 304 Not Modified\r\n",
$FW_headercors, "\r\n";
@@ -1298,13 +1234,14 @@ FW_returnFileAsStream($$$$$)
}
if(!open(FH, $path)) {
+ Log 2, "FHEMWEB $FW_wname $path: $!";
FW_pO "$path: $! ";
return 0;
}
binmode(FH) if($type !~ m/text/); # necessary for Windows
$etag = defined($etag) ? "ETag: \"$etag\"\r\n" : "";
- my $expires = $cacheable ? ("Expires: ".localtime(time()+900)." GMT\r\n"): "";
+ my $expires = $cacheable ? ("Expires: ".gmtime(time()+900)." GMT\r\n"): "";
my $compr = ((int(@FW_enc) == 1 && $FW_enc[0] =~ m/gzip/) && $FW_use_zlib) ?
"Content-Encoding: gzip\r\n" : "";
print $c "HTTP/1.1 200 OK\r\n",
@@ -1345,6 +1282,7 @@ FW_logWrapper($)
if(defined($type) && $type eq "text") {
$defs{$d}{logfile} =~ m,^(.*)/([^/]*)$,; # Dir and File
my $path = "$1/$file";
+ $path =~ s/%L/$attr{global}{logdir}/g if($path =~ m/%/ && $attr{global}{logdir}); # %L present and log directory defined
$path = AttrVal($d,"archivedir","") . "/$file" if(!-f $path);
FW_pO "";
@@ -1622,7 +1560,8 @@ FW_showLog($)
Log 5, "plotcommand: get $d $file INT $f $t " . join(" ", @{$flog});
$ret = FW_fC("get $d $file INT $f $t " . join(" ", @{$flog}));
($cfg, $plot) = FW_substcfg(1, $wl, $cfg, $plot, $file, " ");
- FW_pO SVG_render($wl, $f, $t, $cfg, $internal_data, $plot, $FW_wname, $FW_cssdir);
+ FW_pO SVG_render($wl, $f, $t, $cfg,
+ $internal_data, $plot, $FW_wname, $FW_cssdir);
$FW_RETTYPE = "image/svg+xml";
}
@@ -1712,6 +1651,10 @@ FW_zoomLink($$$)
if($d eq "zoom") {
+ my $n = 0;
+ my @FW_zoom = ("hour","qday","day","week","month","year");
+ my %FW_zoom = map { $_, $n++ } @FW_zoom;
+
$val = "day" if(!$val);
$val = $FW_zoom{$val};
return if(!defined($val) || $val+$off < 0 || $val+$off >= int(@FW_zoom) );
@@ -1721,8 +1664,11 @@ FW_zoomLink($$$)
# Approximation of the next offset.
my $w_off = $FW_pos{off};
$w_off = 0 if(!$w_off);
- if($val eq "qday") {
- $w_off = $w_off*4;
+
+ if ($val eq "hour") {
+ $w_off = $w_off*6;
+ } elsif($val eq "qday") {
+ $w_off = ($off < 0) ? $w_off*4 : int($w_off/6);
} elsif($val eq "day") {
$w_off = ($off < 0) ? $w_off*7 : int($w_off/4);
} elsif($val eq "week") {
@@ -1745,8 +1691,7 @@ FW_zoomLink($$$)
}
FW_pO " ";
- FW_pHPlain "$cmd", " ";
+ FW_pHPlain "$cmd", FW_makeImage($img, $alt);
}
##################
@@ -1787,8 +1732,16 @@ FW_calcWeblink($$)
$zoom = "day" if(!$zoom);
$zoom = $frx if ($frx); #for fixedrange {day|week|...} klaus
- if($zoom eq "qday") {
+ if($zoom eq "hour") {
+ my $t = $now + $off*3600;
+ my @l = localtime($t);
+ $FW_devs{$d}{from}
+ = sprintf("%04d-%02d-%02d_%02d",$l[5]+1900,$l[4]+1,$l[3],$l[2]);
+ $FW_devs{$d}{to}
+ = sprintf("%04d-%02d-%02d_%02d",$l[5]+1900,$l[4]+1,$l[3],$l[2]+1);;
+
+ } elsif($zoom eq "qday") {
my $t = $now + $off*21600;
my @l = localtime($t);
$l[2] = int($l[2]/6)*6;
@@ -1798,25 +1751,21 @@ FW_calcWeblink($$)
= sprintf("%04d-%02d-%02d_%02d",$l[5]+1900,$l[4]+1,$l[3],$l[2]+6);
} elsif($zoom eq "day") {
-
my $t = $now + $off*86400;
my @l = localtime($t);
$FW_devs{$d}{from} = sprintf("%04d-%02d-%02d",$l[5]+1900,$l[4]+1,$l[3]);
$FW_devs{$d}{to} = sprintf("%04d-%02d-%02d",$l[5]+1900,$l[4]+1,$l[3]+1);
} elsif($zoom eq "week") {
-
my @l = localtime($now);
my $start = (AttrVal($FW_wname, "endPlotToday", undef) ? 6 : $l[6]);
my $t = $now - ($start*86400) + ($off*86400)*7;
@l = localtime($t);
$FW_devs{$d}{from} = sprintf("%04d-%02d-%02d",$l[5]+1900,$l[4]+1,$l[3]);
-
@l = localtime($t+7*86400);
$FW_devs{$d}{to} = sprintf("%04d-%02d-%02d",$l[5]+1900,$l[4]+1,$l[3]);
} elsif($zoom eq "month") {
-
my ($endDay, @l);
if(AttrVal($FW_wname, "endPlotToday", undef)) {
@l = localtime($now+86400);
@@ -1838,7 +1787,6 @@ FW_calcWeblink($$)
$FW_devs{$d}{to} = sprintf("%04d-%02d-%02d", $l[5]+1900, $l[4]+1,$endDay);
} elsif($zoom eq "year") {
-
my @l = localtime($now);
$l[5] += $off;
$FW_devs{$d}{from} = sprintf("%04d", $l[5]+1900);
@@ -1910,17 +1858,14 @@ FW_style($$)
FW_fileList("$FW_gplotdir/^.*gplot\$"));
FW_pO $end;
-
} elsif($a[1] eq "select") {
-
my @fl = FW_fileList("$FW_cssdir/.*style.css");
-
FW_pO "$start";
my $row = 0;
foreach my $file (@fl) {
- next if($file =~ m/(svg_|smallscreen|touchpad)style.css/);
+ next if($file =~ m/svg_/);
$file =~ s/style.css//;
- $file = "Default" if($file eq "");
+ $file = "default" if($file eq "");
FW_pO "";
FW_pH "cmd=style set $file", "$file", 1;
FW_pO " ";
@@ -1929,16 +1874,14 @@ FW_style($$)
FW_pO " $end";
} elsif($a[1] eq "set") {
- if($a[2] eq "Default") {
- delete($attr{$FW_wname}{stylesheetPrefix});
+ if($a[2] eq "default") {
+ CommandDeleteAttr(undef, "$FW_wname stylesheetPrefix");
} else {
- $attr{$FW_wname}{stylesheetPrefix} = $a[2];
+ CommandAttr(undef, "$FW_wname stylesheetPrefix $a[2]");
}
- FW_readIcons($defs{$FW_wname});
FW_pO "${start}Reload the page in the browser.$end";
} elsif($a[1] eq "edit") {
-
my $fileName = $a[2];
$fileName =~ s,.*/,,g; # Little bit of security
my $filePath = FW_fileNameToPath($fileName);
@@ -1960,7 +1903,7 @@ FW_style($$)
FW_pO FW_hidden("cmd", "style save $fileName");
FW_pO "";
- FW_pO "";
+ FW_pO "";
FW_pO " ";
} elsif($a[1] eq "save") {
@@ -1986,15 +1929,21 @@ FW_style($$)
$ret = "";
} elsif($a[1] eq "iconFor") {
- FW_pO "";
- foreach my $i (sort grep {/^ico/} keys %FW_icons) {
- FW_pO "| ";
- FW_pO "$i";
- FW_pO " | ";
- FW_pO FW_makeImage($i);
- FW_pO " | ";
- }
- FW_pO " ";
+ FW_iconTable("iconFor", "^ico", "style setIF $a[2] %s", undef);
+
+ } elsif($a[1] eq "setIF") {
+ FW_fC("attr $a[2] icon $a[3]");
+ FW_doDetail($a[2]);
+
+ } elsif($a[1] eq "showDSI") {
+ FW_iconTable("devStateIcon", '^((?!(weather|_big|fhemicon|darklogo)).)*$',
+ "style addDSI $a[2] %s", "Enter value/regexp for STATE");
+
+ } elsif($a[1] eq "addDSI") {
+ my $dsi = AttrVal($a[2], "devStateIcon", "");
+ $dsi .= " " if($dsi);
+ FW_fC("attr $a[2] devStateIcon $dsi$FW_data:$a[3]");
+ FW_doDetail($a[2]);
} elsif($a[1] eq "eventMonitor") {
FW_pO "";
@@ -2004,74 +1953,36 @@ FW_style($$)
FW_pO "";
FW_pO "";
- } elsif($a[1] eq "addDef") {
- my $cnt = 0;
- my %isHelper;
- my $colCnt = ($FW_ss ? 2 : 8);
- FW_pO "";
-
- FW_pO "Helpers:";
- FW_pO " ";
- foreach my $mn ( "at", "notify", "average", "dummy", "holiday", "sequence",
- "structure", "watchdog", "weblink", "FileLog", "PID", "Twilight") {
- $isHelper{$mn} = 1;
- FW_pH "cmd=style addDef $mn", "$mn", 1;
- FW_pO " " if(++$cnt % $colCnt == 0);
- }
- FW_pO " " if($cnt % $colCnt);
- FW_pO "
";
-
- $cnt = 0;
- FW_pO " Other Modules:";
- FW_pO " ";
- foreach my $mn (sort keys %modules) {
- my $mp = $modules{$mn};
- next if($isHelper{$mn});
- # If it is not loaded, read it through to check if it has a Define
- # Function
- if(!$mp->{LOADED} && !$mp->{defChecked}) {
- $mp->{defChecked} = 1;
- if(open(FH,"$attr{global}{modpath}/FHEM/$modules{$mn}{ORDER}_$mn.pm")) {
- while(my $l = ) {
- $mp->{DefFn} = 1 if(index($l, "{DefFn}") > 0);
- }
- close(FH);
- }
- }
-
- next if(!$mp->{DefFn});
- FW_pH "cmd=style addDef $mn", "$mn", 1;
- FW_pO " " if(++$cnt % $colCnt == 0);
- }
- FW_pO " " if($cnt % $colCnt);
- FW_pO "
";
-
- if($a[2]) {
- if(!open(FH, "$FW_commandref")) {
- FW_pO " comandref.html is missing";
- } else {
- my $inDef;
- while(my $l = ) {
- if($l =~ m/$a[2]) {
- $inDef = 1;
- } else {
- next if (!$inDef);
- last if($l =~ m//);
- }
- chomp($l);
- $l =~ s/href="#/href="$FW_commandref#/g;
- FW_pO $l;
- }
- close(FH);
- }
- }
-
- FW_pO " ";
-
}
}
+sub
+FW_iconTable($$$$)
+{
+ my ($name, $re, $cmdFmt, $textfield) = @_;
+ my %icoList = ();
+ foreach my $style ("default", $FW_sp) {
+ foreach my $imgName (sort grep {/^$re/} keys %{$FW_icons{$style}}) {
+ $imgName =~ s/\.[^.]*$//; # Cut extension
+ next if(!$FW_icons{$style}{$imgName}); # Dont cut it twice: FS20.on.png
+ $icoList{$imgName} = 1;
+ }
+ }
+
+ FW_pO "";
+ FW_pO "";
+ FW_pO " ";
+}
+
##################
# print (append) to output
sub
@@ -2095,7 +2006,9 @@ FW_pH(@)
$class = "" if(!defined($class));
$class = " class=\"$class\"" if($class);
- if($FW_ss || $FW_tp) { # No pointer change if using onClick
+ # Using onclick, as href start safari in a webapp.
+ # Known issue: the pointer won't change
+ if($FW_ss || $FW_tp) {
FW_pO "$txt ";
} else {
@@ -2119,11 +2032,27 @@ FW_pHPlain(@)
}
+##############################
+sub
+FW_makeImage(@)
+{
+ my ($name, $txt)= @_;
+ $txt = $name if(!defined($txt));
+ return " ";
+}
+
+####
+sub
+FW_IconURL($)
+{
+ my ($name)= @_;
+ return "$FW_ME/icons/$name";
+}
##################
# print formatted
sub
-pF($@)
+FW_pF($@)
{
my $fmt = shift;
$FW_RET .= sprintf $fmt, @_;
@@ -2145,6 +2074,17 @@ FW_fC($@)
}
##################
+sub
+FW_showWeblinkDetail($)
+{
+ my ($d)= @_;
+ my $alias= AttrVal($d, "alias", $d);
+
+ FW_pO " ";
+ FW_pHPlain("detail=$d", $alias) if(!$FW_subdir);
+ FW_pO " ";
+}
+
sub
FW_showWeblink($$$$)
{
@@ -2157,20 +2097,15 @@ FW_showWeblink($$$$)
FW_pO $v;
} elsif($t eq "link") {
- FW_pO "$d"; # no FW_pH, want to open extra browser
+ FW_pO "$d"; # no FW_pH, want to open extra browser
} elsif($t eq "image") {
FW_pO " ";
- FW_pO " ";
- FW_pHPlain "detail=$d", $d if(!$FW_subdir);
- FW_pO " ";
+ FW_showWeblinkDetail($d);
} elsif($t eq "iframe") {
FW_pO "";
- FW_pO " ";
- FW_pHPlain "detail=$d", $d if(!$FW_subdir);
- FW_pO " ";
-
+ FW_showWeblinkDetail($d);
} elsif($t eq "fileplot" || $t eq "dbplot" ) {
@@ -2188,10 +2123,13 @@ FW_showWeblink($$$$)
}
my @va = split(":", $v, 3);
- if($defs{$d}{WLTYPE} eq "fileplot" && (@va != 3 || !$defs{$va[0]} || !$defs{$va[0]}{currentlogfile})) {
+ if($defs{$d}{WLTYPE} eq "fileplot" &&
+ (@va != 3 || !$defs{$va[0]} || !$defs{$va[0]}{currentlogfile})) {
FW_pO "Broken definition for fileplot $d: $v ";
+
} elsif ($defs{$d}{WLTYPE} eq "dbplot" && (@va != 2 || !$defs{$va[0]})) {
FW_pO "Broken definition for dbplot $d: $v ";
+
} else {
if(defined($va[2]) && $va[2] eq "CURRENT") {
$defs{$va[0]}{currentlogfile} =~ m,([^/]*)$,;
@@ -2214,9 +2152,7 @@ FW_showWeblink($$$$)
FW_pO " ";
}
- FW_pO " ";
- FW_pHPlain "detail=$d", $d if(!$FW_subdir);
- FW_pO " ";
+ FW_showWeblinkDetail($d);
}
}
@@ -2229,60 +2165,63 @@ FW_Attr(@)
my @a = @_;
my $hash = $defs{$a[1]};
my $name = $hash->{NAME};
+ my $sP = "stylesheetPrefix";
+ my $retMsg;
if($a[0] eq "set" && $a[2] eq "HTTPS") {
TcpServer_SetSSL($hash);
}
- if($a[2] eq "stylesheetPrefix" ||
- $a[2] eq "smallscreen") {
-
+ if($a[0] eq "set") { # Converting styles
+ if($a[2] eq "smallscreen" || $a[2] eq "touchpad") {
+ $attr{$name}{$sP} = $a[2];
+ $retMsg="$name: attribute $a[2] deprecated, converted to $sP";
+ $a[3] = $a[2]; $a[2] = $sP;
+ }
+ }
+ if($a[2] eq $sP) {
# AttrFn is called too early, we have to set/del the attr here
if($a[0] eq "set") {
- $attr{$name}{$a[2]} = (defined($a[3]) ? $a[3] : 1);
+ $attr{$name}{$sP} = (defined($a[3]) ? $a[3] : "default");
+ FW_readIcons($attr{$name}{$sP});
} else {
- delete $attr{$name}{$a[2]};
+ delete $attr{$name}{$sP};
}
- FW_readIcons($hash);
}
-
- return undef;
+ return $retMsg;
}
-# recursively reads .gif .ico .jpg .png files and returns filenames as array
# recursion starts at $FW_icondir/$dir
# filenames are relative to $FW_icondir
sub
FW_readIconsFrom($$)
{
- my ($prepend,$dir)= @_;
- return if($dir =~ m,/\.svn,);
+ my ($dir,$subdir)= @_;
- #Debug "read icons from \"${FW_icondir}/${dir}\", prepend \"$prepend\"";
-
- my (@entries, @filenames);
- if(opendir(DH, "${FW_icondir}/${dir}")) {
+ #Log 1, "OPENDIR: $dir $subdir";
+ my $ldir = ($subdir ? "$dir/$subdir" : $dir);
+ my @entries;
+ if(opendir(DH, "$FW_icondir/$ldir")) {
@entries= sort readdir(DH); # assures order: .gif .ico .jpg .png
closedir(DH);
}
- #Debug "$#entries entries found.";
+
foreach my $entry (@entries) {
- my $filename= "$dir/$entry";
- #Debug " entry: \"$entry\", filename= \"$filename\"";
+ if( -d "$FW_icondir/$ldir/$entry" ) { # directory -> recurse
+ FW_readIconsFrom($dir, $subdir ? "$subdir/$entry" : $entry)
+ unless($entry eq "." || $entry eq ".." || $entry eq ".svn");
- if( -d "${FW_icondir}/${filename}" ) { # entry is a directory
- FW_readIconsFrom("${prepend}${entry}/", $filename)
- unless($entry eq "." || $entry eq "..");
+ } else {
+ my $filename = $subdir ? "$subdir/$entry" : $entry;
+ $FW_icons{$dir}{$filename} = $filename;
+
+ my $tag = $filename; # Add it without extension too
+ $tag =~ s/\.[^.]*$//;
+ #Log 1, "Adding $dir $tag -> $filename";
+ $FW_icons{$dir}{$tag} = $filename;
- } elsif( -f "${FW_icondir}/${filename}") { # entry is a regular file
- if($entry =~ m/^(.*)\.($ICONEXTENSION)$/i) {
- my $logicalname= $1;
- my $iconname= "${prepend}${logicalname}";
- #Debug " icon: $iconname / $filename";
- $FW_icons{$iconname}= $filename;
- }
}
}
}
@@ -2290,72 +2229,36 @@ FW_readIconsFrom($$)
sub
FW_readIcons($)
{
- my ($hash)= @_;
- my $name = $hash->{NAME};
-
- %FW_icons = ();
-
- # read icons from default directory
- FW_readIconsFrom("", "default");
-
- # read icons from stylesheet specific directory, icons found here supersede
- # default icons with same name. Smallscreen a special "stylesheet"
- my $prefix = AttrVal($name, "smallscreen", "") ? "smallscreen" : "";
- $prefix = AttrVal($name, "stylesheetPrefix", $prefix);
- FW_readIconsFrom("", "$prefix") unless($prefix eq "");
-
- # read icons from explicit directory, icons found here supersede all other
- # icons with same name
- my $iconpath= AttrVal($name, "iconpath", "");
- FW_readIconsFrom("", "$iconpath") unless($iconpath eq "");
-
- # if now icons were found so far, read icons from icondir itself
- FW_readIconsFrom("", "") unless(%FW_icons);
-
- my %icons = %FW_icons;
- $hash->{fhemIcons} = \%icons;
-
- my $dumpLevel = 5;
- if($attr{global}{verbose} >= $dumpLevel) {
- Log $dumpLevel, "$name Icon dictionary for $FW_icondir follows...";
- foreach my $k (sort keys %FW_icons) {
- Log $dumpLevel, "$name $k => " . $FW_icons{$k};
- }
- }
+ my ($dir)= @_;
+ return if($FW_icons{$dir});
+ FW_readIconsFrom($dir, "");
}
+# check if the icon exists, and if yes, returns its "logical" name;
sub
-FW_getIcon($)
+FW_iconName($)
{
my ($name)= @_;
- return $FW_icons{$name} ? $name : undef;
+ return $name if($FW_icons{$FW_sp} && $FW_icons{$FW_sp}{$name});
+ return $name if($FW_icons{default}{$name});
+ return undef;
}
# returns the physical absolute path relative for the logical path
# examples:
-# FS20.on -> $FW_icondir/dark/FS20.on.png
-# weather/sunny -> $FW_icondir/default/weather/sunny.gif
+# FS20.on -> dark/FS20.on.png
+# weather/sunny -> default/weather/sunny.gif
sub
-FW_IconPath($)
+FW_iconPath($)
{
- my ($name)= @_;
- my $path= $FW_icons{$name};
- return $path ? $FW_icondir . "/" . $path : undef;
+ my ($name) = @_;
+ my $sp = ($FW_sp && $FW_icons{$FW_sp} ? $FW_icons{$FW_sp} : undef);
+ return "$FW_sp/$sp->{$name}" if($sp && $sp->{$name});
+ return "default/$FW_icons{default}{$name}" if($FW_icons{default}{$name});
+ return undef;
}
-# returns the URL for the logical path
-# examples:
-# FS20.on -> /icons/FS20.on
-# weather/sunny -> /icons/sunny
-sub
-FW_IconURL($)
-{
- my ($name)= @_;
- return "$FW_ME/icons/${name}";
-}
-
-
sub
FW_dev2image($)
{
@@ -2370,31 +2273,42 @@ FW_dev2image($)
my (undef, $rstate) = ReplaceEventMap($name, [undef, $state], 0);
- my $icon;
+ my ($icon, $rlink);
my $devStateIcon = AttrVal($name, "devStateIcon", undef);
if(defined($devStateIcon)) {
+ if($devStateIcon =~ m/^{.*}$/) {
+ my ($html, $link) = eval $devStateIcon;
+ Log 1, "devStateIcon $name: $@" if($@);
+ return ($link, undef, 1) if(!$html); # only one value returned by the {}
+ return ($html, $link, 1);
+ }
+
my @list = split(" ", $devStateIcon);
foreach my $l (@list) {
- my ($re,$iconName) = split(":", $l);
+ my ($re, $iconName, $link) = split(":", $l, 3);
if(defined($re) && $state =~ m/^$re$/) {
- return FW_getIcon($iconName); # Can be used to preserve text
+ if($iconName eq "") {
+ $rlink = $link;
+ last;
+ }
+ return ($iconName, $link, (defined(FW_iconName($iconName)) ? 0 : 1));
}
}
}
$state =~ s/ .*//; # Want to be able to have icons for "on-for-timer xxx"
- $icon = FW_getIcon("$name.$state") if(!$icon); # lamp.Aus.png
- $icon = FW_getIcon("$name.$rstate") if(!$icon); # lamp.on.png
- $icon = FW_getIcon($name) if(!$icon); # lamp.png
- $icon = FW_getIcon("$model.$state") if(!$icon && $model); # HM-OU-LED16.off.png
- $icon = FW_getIcon($model) if(!$icon && $model); # HM-OU-LED16.png
- $icon = FW_getIcon("$type.$state") if(!$icon); # FS20.Aus.png
- $icon = FW_getIcon("$type.$rstate") if(!$icon); # FS20.on.png
- $icon = FW_getIcon($type) if(!$icon); # FS20.png
- $icon = FW_getIcon($state) if(!$icon); # Aus.png
- $icon = FW_getIcon($rstate) if(!$icon); # on.png
- return $icon;
+ $icon = FW_iconName("$name.$state") if(!$icon); # lamp.Aus.png
+ $icon = FW_iconName("$name.$rstate") if(!$icon); # lamp.on.png
+ $icon = FW_iconName($name) if(!$icon); # lamp.png
+ $icon = FW_iconName("$model.$state") if(!$icon && $model); # fs20st.off.png
+ $icon = FW_iconName($model) if(!$icon && $model); # fs20st.png
+ $icon = FW_iconName("$type.$state") if(!$icon); # FS20.Aus.png
+ $icon = FW_iconName("$type.$rstate") if(!$icon); # FS20.on.png
+ $icon = FW_iconName($type) if(!$icon); # FS20.png
+ $icon = FW_iconName($state) if(!$icon); # Aus.png
+ $icon = FW_iconName($rstate) if(!$icon); # on.png
+ return ($icon, $rlink, 0);
}
sub
@@ -2439,15 +2353,15 @@ FW_dumpFileLog($$$)
my $nr;
if($oneRow) {
- pF " ", ($row&1)?"odd":"even";
- pF "$f | ";
+ FW_pF " ", ($row&1)?"odd":"even";
+ FW_pO "$f | ";
}
foreach my $ln (split(",", AttrVal($d, "logtype", "text"))) {
my ($lt, $name) = split(":", $ln);
$name = $lt if(!$name);
if(!$oneRow) {
- pF " ", ($row&1)?"odd":"even";
- pF "%s | ", ($nr ? "" : $f);
+ FW_pF " ", ($row&1)?"odd":"even";
+ FW_pF "%s | ", ($nr ? "" : $f);
}
FW_pH "cmd=logwrapper $d $lt $f",
"$name ", 1, "dval";
@@ -2475,7 +2389,8 @@ FW_roomStatesForInform($)
my @rl = devspec2array("room=$room");
foreach my $dn (@rl) {
my ($allSet, $cmdlist, $txt) = FW_devState($dn, "");
- $data .= "$dn<<$defs{$dn}{STATE}<<$txt\r\n";
+ $data .= "$dn<<$defs{$dn}{STATE}<<$txt\r\n"
+ if($defs{$dn} && $defs{$dn}{STATE});
}
return $data;
}
@@ -2499,8 +2414,10 @@ FW_Notify($$)
$FW_wname = $ntfy->{SNAME};
$FW_ME = "/" . AttrVal($FW_wname, "webname", "fhem");
$FW_subdir = "";
- $FW_ss = AttrVal($FW_wname, "smallscreen", 0);
- $FW_tp = AttrVal($FW_wname, "touchpad", $FW_ss);
+ $FW_sp = AttrVal($FW_wname, "stylesheetPrefix", 0);
+ $FW_ss = ($FW_sp =~ m/smallscreen/);
+ $FW_tp = ($FW_sp =~ m/smallscreen|touchpad/);
+
my ($allSet, $cmdlist, $txt) = FW_devState($dn, "");
($FW_wname, $FW_ME, $FW_ss, $FW_tp, $FW_subdir) = @old;
$data = "$dn<<$dev->{STATE}<<$txt\n";
@@ -2562,52 +2479,50 @@ FW_devState($$)
{
my ($d, $rf) = @_;
- my ($hasOnOff, $cmdlist, $link);
+ my ($hasOnOff, $link);
- my $webCmd = AttrVal($d, "webCmd", "");
+ my $cmdList = AttrVal($d, "webCmd", "");
my $allSets = getAllSets($d);
my $state = $defs{$d}{STATE};
$state = "" if(!defined($state));
- $hasOnOff = (!$webCmd && $allSets =~ m/\bon\b/ && $allSets =~ m/\boff\b/);
+ $hasOnOff = ($allSets =~ m/(^| )on( |$)/ &&
+ $allSets =~ m/(^| )off( |$)/);
my $txt = $state;
if(defined(AttrVal($d, "showtime", undef))) {
my $v = $defs{$d}{READINGS}{state}{TIME};
$txt = $v if(defined($v));
} elsif($allSets =~ m/\bdesired-temp:/) {
- $txt = ReadingsVal($d, "measured-temp", "");
- $txt =~ s/ .*//;
- $txt .= "°C";
- $cmdlist = "desired-temp";
+ $txt = "$1 °C" if($txt =~ m/^measured-temp: (.*)/); # FHT fix
+ $cmdList = "desired-temp" if(!$cmdList);
+
} elsif($allSets =~ m/\bdesiredTemperature:/) {
$txt = ReadingsVal($d, "temperature", "");
$txt =~ s/ .*//;
$txt .= "°C";
- $cmdlist = "desiredTemperature";
+ $cmdList = "desiredTemperature" if(!$cmdList);
+
} else {
- my $icon;
- $icon = FW_dev2image($d);
- #Debug "Dev2Image returned $icon for $d";
- $txt = " " if($icon);
+ my ($icon, $isHtml);
+ ($icon, $link, $isHtml) = FW_dev2image($d);
+ $txt = ($isHtml ? $icon : FW_makeImage($icon, $state)) if($icon);
+ $link = "cmd.$d=set $d $link" if($link);
+
}
$txt = "$txt ";
- if($webCmd) {
- my @a = split(":", $webCmd);
- $link = "cmd.$d=set $d $a[0]";
- $cmdlist = $webCmd;
- } elsif($hasOnOff && !$cmdlist) {
+ if($hasOnOff) {
# Have to cover: "on:An off:Aus", "A0:Aus AI:An Aus:off An:on"
my $on = ReplaceEventMap($d, "on", 1);
my $off = ReplaceEventMap($d, "off", 1);
- $link = "cmd.$d=set $d " . ($state eq $on ? $off : $on);
- $cmdlist = "$on:$off";
+ $link = "cmd.$d=set $d " . ($state eq $on ? $off : $on) if(!$link);
+ $cmdList = "$on:$off" if(!$cmdList);
}
- if($link) {
+ if($link) { # Have command to execute
my $room = AttrVal($d, "room", undef);
if($room) {
if($FW_room && $room =~ m/\b$FW_room\b/) {
@@ -2628,19 +2543,7 @@ FW_devState($$)
}
}
- return ($allSets, $cmdlist, $txt);
-}
-
-#####################################
-sub
-FW_pathList()
-{
- return "web server root: $FW_dir\n".
- "icon directory: $FW_icondir\n".
- "doc directory: $FW_docdir\n".
- "css directory: $FW_cssdir\n".
- "gplot directory: $FW_gplotdir\n".
- "javascript directory: $FW_jsdir";
+ return ($allSets, $cmdList, $txt);
}
@@ -2649,16 +2552,19 @@ FW_Get($@)
{
my ($hash, @a) = @_;
$FW_wname= $hash->{NAME};
- %FW_icons= %{$hash->{fhemIcons}};
my $arg = (defined($a[1]) ? $a[1] : "");
if($arg eq "icon") {
return "need one icon as argument" if(int(@a) != 3);
- my $icon= FW_IconPath($a[2]);
- return defined($icon) ? $icon : "no such icon";
+ my $icon = FW_iconPath($a[2]);
+ return defined($icon) ? "$FW_icondir/$icon" : "no such icon";
} elsif($arg eq "pathlist") {
- return FW_pathList();
+ return "web server root: $FW_dir\n".
+ "icon directory: $FW_icondir\n".
+ "css directory: $FW_cssdir\n".
+ "gplot directory: $FW_gplotdir\n".
+ "javascript directory: $FW_jsdir";
} else {
return "Unknown argument $arg choose one of icon pathlist";
@@ -2677,7 +2583,11 @@ FW_Set($@)
return "Unknown argument $a[1], choose one of " . "rereadicons"
unless($a[1] eq "rereadicons");
- FW_readIcons($hash);
+ my @dirs = keys %FW_icons;
+ %FW_icons = ();
+ foreach my $d (@dirs) {
+ FW_readIcons($d);
+ }
return undef;
}
@@ -2837,21 +2747,6 @@ FW_htmlEscape($)
month will be shown.
-
-
- smallscreen, touchpad
- Optimize for small screen size (i.e. smartphones) or for touchpad
- devices (i.e. tablets)
- Note: The default configuration installed with make install-pgm2
- installs 2 FHEMWEB instances: port 8083 for desktop browsers and
- port 8084 for smallscreen browsers, both using SVG rendering.
- On Android SVG is supported by Opera/Firefox.
-
- WebApp suppport if specifying one of the above options: After viewing
- the site on the iPhone or iPad in Safari, add it to the home-screen to
- get full-screen support.
-
-
plotfork
If set, generate the logs in a parallel process. Note: do not use it
@@ -2880,10 +2775,12 @@ FW_htmlEscape($)
Example:
attr WEB basicAuth { "$user:$password" eq "admin:secret" }
- attr WEB basicAuth {use FritzBoxUtils;;FB_checkPw("localhost","$password") }
+ attr WEB basicAuth {use FritzBoxUtils;;FB_checkPw("localhost","$password") }
+
+ or if you defined multiple users on the Fritzbox:
+
+ attr WEB basicAuth {use FritzBoxUtils;;FB_checkPw("localhost","$user", "$password") }
-
-
@@ -2911,10 +2808,10 @@ FW_htmlEscape($)
stylesheetPrefix
- prefix for the files style.css, svg_style.css and svg_defs.svg. If the file
- with the prefix is missing, the default file (without prefix) will be used.
- These files have to be placed into the FHEM directory, and can be selected
- directly from the "Select style" FHEMWEB menu entry. Example:
+ prefix for the files style.css, svg_style.css and svg_defs.svg. If the
+ file with the prefix is missing, the default file (without prefix) will
+ be used. These files have to be placed into the FHEM directory, and can
+ be selected directly from the "Select style" FHEMWEB menu entry. Example:
attr WEB stylesheetPrefix dark
@@ -2926,6 +2823,18 @@ FW_htmlEscape($)
+ Note:if the argument contains the string smallscreen or touchpad,
+ then FHEMWEB will optimize the layout/access for small screen size (i.e.
+ smartphones) or touchpad devices (i.e. tablets)
+
+ The default configuration installs 3 FHEMWEB instances: port 8083 for
+ desktop browsers, port 8084 for smallscreen, and 8085 for touchpad.
+
+ If touchpad or smallscreen is specified, then WebApp support is
+ activated: After viewing the site on the iPhone or iPad in Safari, you
+ can add a link to the home-screen to get full-screen support. Links are
+ rendered differently in this mode to avoid switching back to the "normal"
+ browser.
@@ -2947,6 +2856,14 @@ FW_htmlEscape($)
+
+ menuEntries
+ Comma separated list of name,html-link pairs to display in the
+ left-side list. Example:
+ attr WEB menuEntries fhem.de,http://fhem.de,culfw.de,http://culfw.de
+ attr WEB menuEntries AlarmOn,http://fhemhost:8083/fhem?cmd=set%20alarm%20on
+
+
longpoll
@@ -2967,57 +2884,12 @@ FW_htmlEscape($)
-
- webCmd
- Colon separated list of commands to be shown in the room overview for a
- certain device. On smallscreen devices only the first value is
- accessible.
- If the first value references a command, for which "set
- device ?" lists a number possible choices (e.g. desired-temp for FHT
- devices), then a select widget will be displayed. If the values are
- "slider,min,step,max", then a javascript driven slider is displayed.
- if the value is "time", then a javascript timepicker is displayed.
- If the command is state, then the value will be used as a command.
- Examples:
-
- attr lamp webCmd on:off:on-for-timer 10
- define d1 dummy
- attr d1 webCmd state
- attr d1 set setList state:on,off
- define d2 dummy
- attr d2 webCmd state
- attr d2 set setList state:slider,0,1,10
- define d3 dummy
- attr d3 webCmd state
- attr d3 set setList state:time
-
- Note: this is an attribute for the displayed device, not for the FHEMWEB
- instance.
-
-
-
-
- fwmodpath
- Set the "modpath" for this intance of FHEMWEB. Used to search .gplot
- files, pictures, etc. Default is the modpath/FHEM directory.
-
-
-
fwcompress
Enable compressing the HTML data (default is 1, i.e. yes, use 0 to switch it off).
-
- icon
- Set the icon for a device in the room overview. There is an
- icon-chooser in FHEMWEB to ease this task. Setting icons for the room
- itself is indirect: there must exist an icon with the name
- ico.png in the modpath/fwmodpath directory.
-
-
-
reverseLogs
Display the lines from the logfile in a reversed order, newest on the
@@ -3035,21 +2907,78 @@ FW_htmlEscape($)
+
+ icon
+ Set the icon for a device in the room overview. There is an
+ icon-chooser in FHEMWEB to ease this task. Setting icons for the room
+ itself is indirect: there must exist an icon with the name
+ ico.png in the modpath directory.
+
+
+
+
+ sortby
+ Take the value of this attribute when sorting the devices in the room
+ overview instead of the alias, or if that is missing the devicename
+ itself.
+
+
+
devStateIcon
- Space separated list of regexp/icon-name pairs. If the state of the
- device matches regexp, then the corresponding icon-name will be
- displayed. If icon-name does not exist in the fhem/www/images
- directory, then the status text will be displayed. Note: the icon-name
- must be specified without the trailing .png/.jpg suffix.
+ First form:
+
+ Space separated list of regexp:icon-name:cmd triples, icon-name and cmd
+ may be empty.
+ If the state of the device matches regexp, then icon-name will be
+ displayed as the status icon in the room, and (if specified) clicking
+ on the icon executes cmd. If fhem cannot find icon-name, then the
+ status text will be displayed.
Example:
attr lamp devStateIcon on:closed off:open
+ attr lamp devStateIcon on::A0 off::AI
attr lamp devStateIcon .*:noIcon
+
+ Second form:
+
+ Perl regexp enclosed in {}. Example:
+ {'<div style="width:32px;height:32px;background-color:green"></div>'}
+
+
+ webCmd
+ Colon separated list of commands to be shown in the room overview for a
+ certain device. On smallscreen devices only the first value is
+ accessible.
+ If the first value references a command, for which "set
+ device ?" lists a number possible choices (e.g. desired-temp for FHT
+ devices), then a select widget will be displayed. If the values are
+ "slider,min,step,max", then a javascript driven slider is displayed.
+ if the value is "time", then a javascript timepicker is displayed.
+ If the command is state, then the value will be used as a command.
+ Examples:
+
+ attr lamp webCmd on:off:on-for-timer 10
+ define d1 dummy
+ attr d1 webCmd state
+ attr d1 setList state:on,off
+ define d2 dummy
+ attr d2 webCmd state
+ attr d2 setList state:slider,0,1,10
+ define d3 dummy
+ attr d3 webCmd state
+ attr d3 setList state:time
+
+ Note: this is an attribute for the displayed device, not for the FHEMWEB
+ instance.
+
+
+
+
See also room and group attributes.
diff --git a/FHEM/02_RSS.pm b/FHEM/02_RSS.pm
index eb1257bd9..61c99b628 100644
--- a/FHEM/02_RSS.pm
+++ b/FHEM/02_RSS.pm
@@ -368,8 +368,9 @@ RSS_returnJPEG($) {
#
# create the image
#
- my $S= GD::Image->newTrueColor($width,$height);
- $S->colorAllocate(0,0,0); # black is the background
+ my $S;
+# my $S= GD::Image->newTrueColor($width,$height);
+# $S->colorAllocate(0,0,0); # black is the background
# wrap to make problems with GD non-lethal
@@ -410,16 +411,29 @@ RSS_returnJPEG($) {
my $bgfile= $bgdir . "/" . $bgfiles[$bgnr];
my $bg= newFromJpeg GD::Image($bgfile);
my ($bgwidth,$bgheight)= $bg->getBounds();
- my ($w,$h);
- my ($u,$v)= ($bgwidth/$width, $bgheight/$height);
- if($u>$v) {
- $w= $width;
- $h= $bgheight/$u;
+ if($bgwidth != $width or $bgheight != $height) {
+ # we need to resize
+ my ($w,$h);
+ my ($u,$v)= ($bgwidth/$width, $bgheight/$height);
+ if($u>$v) {
+ $w= $width;
+ $h= $bgheight/$u;
+ } else {
+ $h= $height;
+ $w= $bgwidth/$v;
+ }
+ # create empty image
+ $S= GD::Image->newTrueColor($width,$height);
+ $S->colorAllocate(0,0,0); # black is the background
+ $S->copyResized($bg,($width-$w)/2,($height-$h)/2,0,0,$w,$h,$bgwidth,$bgheight);
} else {
- $h= $height;
- $w= $bgwidth/$v;
+ # size is as required, we take the original
+ $S= $bg;
}
- $S->copyResized($bg,($width-$w)/2,($height-$h)/2,0,0,$w,$h,$bgwidth,$bgheight);
+ } else {
+ # no background, we create an empty background
+ $S= GD::Image->newTrueColor($width,$height);
+ $S->colorAllocate(0,0,0); # black is the background
}
SKIPBG:
diff --git a/FHEM/10_CUL_HM.pm b/FHEM/10_CUL_HM.pm
index cfdd388e7..7ebc2bc60 100755
--- a/FHEM/10_CUL_HM.pm
+++ b/FHEM/10_CUL_HM.pm
@@ -1,5265 +1,4680 @@
-##############################################
-# CUL HomeMatic handler
-# $Id$
-
-package main;
-
-# update regRaw warnings "#todo Updt2 remove"
-# the lines can be removed after some soak time - around version 2600
-use strict;
-use warnings;
-
-sub CUL_HM_Initialize($);
-sub CUL_HM_Define($$);
-sub CUL_HM_Undef($$);
-sub CUL_HM_Parse($$);
-sub CUL_HM_Get($@);
-sub CUL_HM_fltCvT($);
-sub CUL_HM_Set($@);
-sub CUL_HM_infoUpdtDevData($$$);
-sub CUL_HM_Pair(@);
-sub CUL_HM_getConfig($$$$$);
-sub CUL_HM_SndCmd($$);
-sub CUL_HM_responseSetup($$);
-sub CUL_HM_eventP($$);
-sub CUL_HM_respPendRm($);
-sub CUL_HM_respPendTout($);
-sub CUL_HM_PushCmdStack($$);
-sub CUL_HM_ProcessCmdStack($);
-sub CUL_HM_Resend($);
-sub CUL_HM_Id($);
-sub CUL_HM_name2Hash($);
-sub CUL_HM_name2Id(@);
-sub CUL_HM_id2Name($);
-sub CUL_HM_getDeviceHash($);
-sub CUL_HM_DumpProtocol($$@);
-sub CUL_HM_parseCommon(@);
-sub CUL_HM_encodeTime8($);
-sub CUL_HM_decodeTime8($);
-sub CUL_HM_encodeTime16($);
-sub CUL_HM_convTemp($);
-sub CUL_HM_updtRegDisp($$$);
-sub CUL_HM_decodeTime16($);
-sub CUL_HM_pushConfig($$$$$$$$);
-sub CUL_HM_maticFn($$$$$);
-sub CUL_HM_secSince2000();
-# ----------------modul globals-----------------------
-my $respRemoved; # used to control trigger of stach processing
- # need to take care that ACK is first
-
-my %culHmDevProps=(
- "01" => { st => "AlarmControl", cl => " " }, # by peterp
- "12" => { st => "outputUnit", cl => "receiver" }, # Test Pending
- "10" => { st => "switch", cl => "receiver" }, # Parse,Set
- "20" => { st => "dimmer", cl => "receiver" }, # Parse,Set
- "30" => { st => "blindActuator", cl => "receiver" }, # Parse,Set
- "39" => { st => "ClimateControl", cl => "sender" },
- "40" => { st => "remote", cl => "sender" }, # Parse
- "41" => { st => "sensor", cl => "sender" },
- "42" => { st => "swi", cl => "sender" }, # e.g. HM-SwI-3-FM
- "43" => { st => "pushButton", cl => "sender" },
- "58" => { st => "thermostat", cl => "receiver" },
- "60" => { st => "KFM100", cl => "sender" }, # Parse,unfinished
- "70" => { st => "THSensor", cl => "sender" }, # Parse,unfinished
- "80" => { st => "threeStateSensor",cl => "sender" }, # e.g.HM-SEC-RHS
- "81" => { st => "motionDetector", cl => "sender" },
- "C0" => { st => "keyMatic", cl => "receiver" },
- "C1" => { st => "winMatic", cl => "receiver" },
- "CD" => { st => "smokeDetector", cl => "receiver" }, # Parse,set unfinished
-);
-# chan supports autocreate of channels for the device
-# Syntax ::
-# chn=>{btn:1:3,disp:4,aux:5:7} wil create
-# _btn1,_btn2,_btn3 as channel 1 to 3
-# _disp as channel 4
-# _aux1,_aux2,_aux7 as channel 5 to 7
-# autocreate for single channel devices is possible not recommended
-#rxt - receivetype of the device------
-# c: receive on config
-# w: receive in wakeup
-# b: receive on burst
-#register list definition - identifies valid register lists
-# 1,3,5:3p.4.5 => list 1 valid for all channel
-# => list 3 for all channel
-# => list 5 only for channel 3 but assotiated with peers
-# => list 5 for channel 4 and 5 with peer=00000000
-#
-my %culHmModel=(
- "0001" => {name=>"HM-LC-SW1-PL-OM54" ,cyc=>'' ,rxt=>'' ,lst=>'3' ,chn=>"",},
- "0002" => {name=>"HM-LC-SW1-SM" ,cyc=>'' ,rxt=>'' ,lst=>'3' ,chn=>"",},
- "0003" => {name=>"HM-LC-SW4-SM" ,cyc=>'' ,rxt=>'' ,lst=>'3' ,chn=>"Sw:1:4",},
- "0004" => {name=>"HM-LC-SW1-FM" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",},
- "0005" => {name=>"HM-LC-BL1-FM" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",},
- "0006" => {name=>"HM-LC-BL1-SM" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",},
- "0007" => {name=>"KS550" ,cyc=>'00:10' ,rxt=>'' ,lst=>'1' ,chn=>"",},
- "0008" => {name=>"HM-RC-4" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"Btn:1:4",},
- "0009" => {name=>"HM-LC-SW2-FM" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"Sw:1:2",},
- "000A" => {name=>"HM-LC-SW2-SM" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"Sw:1:2",},
- "000B" => {name=>"HM-WDC7000" ,cyc=>'' ,rxt=>'' ,lst=>'' ,chn=>"",},
- "000D" => {name=>"ASH550" ,cyc=>'' ,rxt=>'c:w' ,lst=>'' ,chn=>"",},
- "000E" => {name=>"ASH550I" ,cyc=>'' ,rxt=>'c:w' ,lst=>'' ,chn=>"",},
- "000F" => {name=>"S550IA" ,cyc=>'00:10' ,rxt=>'c:w' ,lst=>'' ,chn=>"",},
- "0011" => {name=>"HM-LC-SW1-PL" ,cyc=>'' ,rxt=>'' ,lst=>'3' ,chn=>"",},
- "0012" => {name=>"HM-LC-DIM1L-CV" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",},
- "0013" => {name=>"HM-LC-DIM1L-PL" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",},
- "0014" => {name=>"HM-LC-SW1-SM-ATMEGA168" ,cyc=>'' ,rxt=>'' ,lst=>'3' ,chn=>"",},
- "0015" => {name=>"HM-LC-SW4-SM-ATMEGA168" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"Sw:1:4",},
- "0016" => {name=>"HM-LC-DIM2L-CV" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"Sw:1:2",},
- "0018" => {name=>"CMM" ,cyc=>'' ,rxt=>'' ,lst=>'3' ,chn=>"",},
- "0019" => {name=>"HM-SEC-KEY" ,cyc=>'' ,rxt=>'b' ,lst=>'3' ,chn=>"",},
- "001A" => {name=>"HM-RC-P1" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"",},
- "001B" => {name=>"HM-RC-SEC3" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"Btn:1:3",},
- "001C" => {name=>"HM-RC-SEC3-B" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"Btn:1:3",},
- "001D" => {name=>"HM-RC-KEY3" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"Btn:1:3",},
- "001E" => {name=>"HM-RC-KEY3-B" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"Btn:1:3",},
- "0022" => {name=>"WS888" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",},
- "0026" => {name=>"HM-SEC-KEY-S" ,cyc=>'' ,rxt=>'b' ,lst=>'3' ,chn=>"",},
- "0027" => {name=>"HM-SEC-KEY-O" ,cyc=>'' ,rxt=>'b' ,lst=>'3' ,chn=>"",},
- "0028" => {name=>"HM-SEC-WIN" ,cyc=>'' ,rxt=>'b' ,lst=>'1,3' ,chn=>"Win:1:1,Akku:2:2",},
- "0029" => {name=>"HM-RC-12" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"Btn:1:12",},
- "002A" => {name=>"HM-RC-12-B" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"Btn:1:12",},
- "002D" => {name=>"HM-LC-SW4-PCB" ,cyc=>'' ,rxt=>'' ,lst=>'3' ,chn=>"Sw:1:4",},
- "002E" => {name=>"HM-LC-DIM2L-SM" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"Sw:1:2",},
- "002F" => {name=>"HM-SEC-SC" ,cyc=>'28:00' ,rxt=>'c:w' ,lst=>'1,4' ,chn=>"",},
- "0030" => {name=>"HM-SEC-RHS" ,cyc=>'28:00' ,rxt=>'c:w' ,lst=>'1,4' ,chn=>"",},
- "0034" => {name=>"HM-PBI-4-FM" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"Btn:1:4",},
- "0035" => {name=>"HM-PB-4-WM" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"Btn:1:4",},
- "0036" => {name=>"HM-PB-2-WM" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"Btn:1:2",},
- "0037" => {name=>"HM-RC-19" ,cyc=>'' ,rxt=>'c:b' ,lst=>'1,4' ,chn=>"Btn:1:17,Disp:18:18",},
- "0038" => {name=>"HM-RC-19-B" ,cyc=>'' ,rxt=>'c:b' ,lst=>'1,4' ,chn=>"Btn:1:17,Disp:18:18",},
- "0039" => {name=>"HM-CC-TC" ,cyc=>'00:10' ,rxt=>'c:w' ,lst=>'5:2.3p,6:2' ,chn=>"Weather:1:1,Climate:2:2,WindowRec:3:3",},
- "003A" => {name=>"HM-CC-VD" ,cyc=>'28:00' ,rxt=>'c:w' ,lst=>'5' ,chn=>"",},
- "003B" => {name=>"HM-RC-4-B" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"Btn:1:4",},
- "003C" => {name=>"HM-WDS20-TH-O" ,cyc=>'' ,rxt=>'c:w' ,lst=>'' ,chn=>"",},
- "003D" => {name=>"HM-WDS10-TH-O" ,cyc=>'' ,rxt=>'c:w' ,lst=>'' ,chn=>"",},
- "003E" => {name=>"HM-WDS30-T-O" ,cyc=>'00:10' ,rxt=>'c:w' ,lst=>'' ,chn=>"",},
- "003F" => {name=>"HM-WDS40-TH-I" ,cyc=>'' ,rxt=>'c:w' ,lst=>'' ,chn=>"",},
- "0040" => {name=>"HM-WDS100-C6-O" ,cyc=>'00:10' ,rxt=>'c:w' ,lst=>'1' ,chn=>"",},
- "0041" => {name=>"HM-WDC7000" ,cyc=>'' ,rxt=>'' ,lst=>'1,4' ,chn=>"",},
- "0042" => {name=>"HM-SEC-SD" ,cyc=>'99:00' ,rxt=>'b' ,lst=>'' ,chn=>"",},
- "0043" => {name=>"HM-SEC-TIS" ,cyc=>'28:00' ,rxt=>'c:w' ,lst=>'1,4' ,chn=>"",},
- "0044" => {name=>"HM-SEN-EP" ,cyc=>'' ,rxt=>'c:w' ,lst=>'1,4' ,chn=>"",},
- "0045" => {name=>"HM-SEC-WDS" ,cyc=>'28:00' ,rxt=>'c:w' ,lst=>'1,4' ,chn=>"",},
- "0046" => {name=>"HM-SWI-3-FM" ,cyc=>'' ,rxt=>'c' ,lst=>'4' ,chn=>"Sw:1:3",},
- "0047" => {name=>"KFM-Sensor" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",},
- "0048" => {name=>"IS-WDS-TH-OD-S-R3" ,cyc=>'' ,rxt=>'c:w' ,lst=>'1,3' ,chn=>"",},
- "0049" => {name=>"KFM-Display" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",},
- "004A" => {name=>"HM-SEC-MDIR" ,cyc=>'00:10' ,rxt=>'c:w' ,lst=>'1,4' ,chn=>"",},
- "004B" => {name=>"HM-Sec-Cen" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",},
- "004C" => {name=>"HM-RC-12-SW" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"Btn:1:12",},
- "004D" => {name=>"HM-RC-19-SW" ,cyc=>'' ,rxt=>'c:b' ,lst=>'1,4' ,chn=>"Btn:1:17,Disp:18:18",},
- "004E" => {name=>"HM-LC-DDC1-PCB" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",},
- "004F" => {name=>"HM-SEN-MDIR-SM" ,cyc=>'' ,rxt=>'c:w' ,lst=>'1,4' ,chn=>"",},
- "0050" => {name=>"HM-SEC-SFA-SM" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"Siren:1:1,Flash:2:2",},
- "0051" => {name=>"HM-LC-SW1-PB-FM" ,cyc=>'' ,rxt=>'' ,lst=>'3' ,chn=>"",},
- "0052" => {name=>"HM-LC-SW2-PB-FM" ,cyc=>'' ,rxt=>'' ,lst=>'3' ,chn=>"Sw:1:2",},
- "0053" => {name=>"HM-LC-BL1-PB-FM" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",},
- "0054" => {name=>"DORMA_RC-H" ,cyc=>'' ,rxt=>'c' ,lst=>'1,3' ,chn=>"",},
- "0056" => {name=>"HM-CC-SCD" ,cyc=>'28:00' ,rxt=>'c:w' ,lst=>'1,4' ,chn=>"",},
- "0057" => {name=>"HM-LC-DIM1T-PL" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",},
- "0058" => {name=>"HM-LC-DIM1T-CV" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",},
- "0059" => {name=>"HM-LC-DIM1T-FM" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",},
- "005A" => {name=>"HM-LC-DIM2T-SM" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"Sw:1:2",},
- "005C" => {name=>"HM-OU-CF-PL" ,cyc=>'' ,rxt=>'' ,lst=>'3' ,chn=>"Led:1:1,Sound:2:2",},
- "005D" => {name=>"HM-Sen-MDIR-O" ,cyc=>'00:10' ,rxt=>'c:w' ,lst=>'1,4' ,chn=>"",},
- "005F" => {name=>"HM-SCI-3-FM" ,cyc=>'28:00' ,rxt=>'c:w' ,lst=>'1,4' ,chn=>"Sw:1:3",},
- "0060" => {name=>"HM-PB-4DIS-WM" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"Btn:1:20",},
- "0061" => {name=>"HM-LC-SW4-DR" ,cyc=>'' ,rxt=>'' ,lst=>'3' ,chn=>"Sw:1:4",},
- "0062" => {name=>"HM-LC-SW2-DR" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"Sw:1:2",},
- "0064" => {name=>"DORMA_atent" ,cyc=>'' ,rxt=>'c' ,lst=>'1,3' ,chn=>"",},
- "0065" => {name=>"DORMA_BRC-H" ,cyc=>'' ,rxt=>'c' ,lst=>'1,3' ,chn=>"",},
- "0066" => {name=>"HM-LC-SW4-WM" ,cyc=>'' ,rxt=>'b' ,lst=>'3' ,chn=>"Sw:1:4",},
- "0067" => {name=>"HM-LC-Dim1PWM-CV" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",},
- "0068" => {name=>"HM-LC-Dim1TPBU-FM" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",},
- "0069" => {name=>"HM-LC-Sw1PBU-FM" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",},
- "006A" => {name=>"HM-LC-Bl1PBU-FM" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",},
- "006B" => {name=>"HM-PB-2-WM55" ,cyc=>'' ,rxt=>'c:w' ,lst=>'1,4' ,chn=>"Btn:1:2",},
- "006C" => {name=>"HM-LC-SW1-BA-PCB" ,cyc=>'' ,rxt=>'b' ,lst=>'3' ,chn=>"",},
- "006D" => {name=>"HM-OU-LED16" ,cyc=>'' ,rxt=>'' ,lst=>'' ,chn=>"Led:1:16",},
- "006E" => {name=>"HM-LC-Dim1L-CV" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",},
- "006F" => {name=>"HM-LC-Dim1L-Pl" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",},
- "0070" => {name=>"HM-LC-Dim2L-SM" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"Sw:1:2",},
- "0071" => {name=>"HM-LC-Dim1T-Pl" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",},
- "0072" => {name=>"HM-LC-Dim1T-CV" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",},
- "0073" => {name=>"HM-LC-Dim1T-FM" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",},
- "0074" => {name=>"HM-LC-Dim2T-SM" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"Sw:1:2",},
- "0075" => {name=>"HM-OU-CFM-PL" ,cyc=>'' ,rxt=>'' ,lst=>'3' ,chn=>"Led:1:1,Mp3:2:2",},
- "0076" => {name=>"HM-Sys-sRP-Pl" ,cyc=>'' ,rxt=>'' ,lst=>'2' ,chn=>"",},
- "0078" => {name=>"HM-Dis-TD-T" ,cyc=>'' ,rxt=>'b' ,lst=>'3' ,chn=>"",}, #
- "0079" => {name=>"ROTO_ZEL-STG-RM-FWT" ,cyc=>'' ,rxt=>'c:w' ,lst=>'1,3' ,chn=>"",}, #
- "0x7A" => {name=>"ROTO_ZEL-STG-RM-FSA" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",}, #
- "007B" => {name=>"ROTO_ZEL-STG-RM-FEP-230V",cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",}, #
- "007C" => {name=>"ROTO_ZEL-STG-RM-FZS" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",}, # radio-controlled socket adapter switch actuator 1-channel
- "007D" => {name=>"ROTO_ZEL-STG-RM-WT-2" ,cyc=>'' ,rxt=>'c:w' ,lst=>'1,4' ,chn=>"",}, # HM Push Button 2
- "007E" => {name=>"ROTO_ZEL-STG-RM-DWT-10" ,cyc=>'00:10' ,rxt=>'c' ,lst=>'1,4' ,chn=>"",}, #
- "007F" => {name=>"ROTO_ZEL-STG-RM-FST-UP4" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"",}, # HM Push Button Interface
- "0080" => {name=>"ROTO_ZEL-STG-RM-HS-4" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"",}, # HM Remote 4 buttons
- "0081" => {name=>"ROTO_ZEL-STG-RM-FDK" ,cyc=>'28:00' ,rxt=>'c:w' ,lst=>'1,3' ,chn=>"",}, # HM Rotary Handle Sensor
- "0082" => {name=>"Roto_ZEL-STG-RM-FFK" ,cyc=>'28:00' ,rxt=>'c:w' ,lst=>'1,4' ,chn=>"",}, # HM Shutter Contact
- "0083" => {name=>"Roto_ZEL-STG-RM-FSS-UP3" ,cyc=>'' ,rxt=>'c' ,lst=>'4' ,chn=>"",}, # HM Switch Interface 3 switches
- "0084" => {name=>"Schueco_263-160" ,cyc=>'' ,rxt=>'c:w' ,lst=>'1,4' ,chn=>"",}, # HM SENSOR_FOR_CARBON_DIOXIDE
- "0086" => {name=>"Schueco_263-146" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",}, # radio-controlled blind actuator 1-channel (flush-mount)
- "0087" => {name=>"Schueco_263-147" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",}, # radio-controlled blind actuator 1-channel (flush-mount)
- "0088" => {name=>"Schueco_263-132" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",}, # 1 channel dimmer L (ceiling voids)
- "0089" => {name=>"Schueco_263-134" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",}, # 1 channel dimmer T (ceiling voids)
- "008A" => {name=>"Schueco_263-133" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",}, # 1 channel dimmer TPBU (flush mount)
- "008B" => {name=>"Schueco_263-130" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",}, # radio-controlled switch actuator 1-channel (flush-mount)
- "008C" => {name=>"Schueco_263-131" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",}, # radio-controlled switch actuator 1-channel (flush-mount)
- "008D" => {name=>"Schueco_263-135" ,cyc=>'' ,rxt=>'c:w' ,lst=>'1,3' ,chn=>"",}, # HM Push Button 2
- "008E" => {name=>"Schueco_263-155" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"",}, # HM Remote Display 4 buttons
- "008F" => {name=>"Schueco_263-145" ,cyc=>'' ,rxt=>'c' ,lst=>'1,4' ,chn=>"",}, # HM Push Button Interface
- "0090" => {name=>"Schueco_263-162" ,cyc=>'00:30' ,rxt=>'c:w' ,lst=>'1,3' ,chn=>"",}, # HM radio-controlled motion detector
- "0092" => {name=>"Schueco_263-144" ,cyc=>'' ,rxt=>'c' ,lst=>'4' ,chn=>"",}, # HM Switch Interface 3 switches
- "0093" => {name=>"Schueco_263-158" ,cyc=>'' ,rxt=>'c:w' ,lst=>'' ,chn=>"",}, #
- "0094" => {name=>"Schueco_263-157" ,cyc=>'' ,rxt=>'c:w' ,lst=>'' ,chn=>"",}, #
- "00A1" => {name=>"HM-LC-SW1-PL2" ,cyc=>'' ,rxt=>'' ,lst=>'3' ,chn=>"",}, #
- "00A2" => {name=>"ROTO_ZEL-STG-RM-FZS-2" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",}, #radio-controlled socket adapter switch actuator 1-channel
- "00A3" => {name=>"HM-LC-Dim1L-Pl-2" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",},
- "00A4" => {name=>"HM-LC-Dim1T-Pl-2" ,cyc=>'' ,rxt=>'' ,lst=>'1,3' ,chn=>"",},
- "00A7" => {name=>"HM-Sen-RD-O" ,cyc=>'' ,rxt=>'' ,lst=>'1:1,4:1' ,chn=>"Rain:1:1,Sw:2:2",},
- #263 167 HM Smoke Detector Schueco
- #BRC-H Dorma Remote 4 single buttons
- #RC-H DORMA Remote 4 buttons
- #atent DORMA Remote 3 buttons
-);
-sub
-CUL_HM_Initialize($)
-{
- my ($hash) = @_;
-
- $hash->{Match} = "^A....................";
- $hash->{DefFn} = "CUL_HM_Define";
- $hash->{UndefFn} = "CUL_HM_Undef";
- $hash->{ParseFn} = "CUL_HM_Parse";
- $hash->{SetFn} = "CUL_HM_Set";
- $hash->{GetFn} = "CUL_HM_Get";
- $hash->{RenameFn} = "CUL_HM_Rename";
- $hash->{AttrFn} = "CUL_HM_Attr";
- $hash->{AttrList} = "IODev do_not_notify:1,0 ignore:1,0 dummy:1,0 ".
- "showtime:1,0 loglevel:0,1,2,3,4,5,6 ".
- "hmClass:receiver,sender serialNr firmware devInfo ".
- "rawToReadable unit ".
- "peerIDs ".
- "actCycle actStatus autoReadReg:1,0 ".
- "expert:0_off,1_on,2_full ".
- $readingFnAttributes;
- my @modellist;
- foreach my $model (keys %culHmModel){
- push @modellist,$culHmModel{$model}{name};
- }
- $hash->{AttrList} .= " model:" .join(",", sort @modellist);
- $hash->{AttrList} .= " subType:".join(",", sort
- map { $culHmDevProps{$_}{st} } keys %culHmDevProps);
- CUL_HM_initRegHash();
-
-}
-sub
-CUL_HM_autoReadConfig($){
- # will trigger a getConfig and statusrequest for each device assigned.
- #
- while(@{$modules{CUL_HM}{helper}{updtCfgLst}}){
- my $name = shift(@{$modules{CUL_HM}{helper}{updtCfgLst}});
- my $hash = CUL_HM_name2Hash($name);
-
- if (1 == AttrVal($name,"autoReadReg","0")){
- CUL_HM_Set($hash,$name,"getConfig");
- CUL_HM_Set($hash,$name,"statusRequest");
- InternalTimer(gettimeofday()+15,"CUL_HM_autoReadConfig","updateConfig",0);
- last;
- }
- }
-}
-sub
-CUL_HM_updateConfig($){
- # this routine is called 5 sec after the last define of a restart
- # this gives FHEM sufficient time to fill in attributes
- # it will also be called after each manual definition
- # Purpose is to parse attributes and read config
- my @getConfList;
- while(@{$modules{CUL_HM}{helper}{updtCfgLst}}){
- my $name = shift(@{$modules{CUL_HM}{helper}{updtCfgLst}});
- my $hash = CUL_HM_name2Hash($name);
-
- CUL_HM_ID2PeerList ($name,"",1); # update peerList out of peerIDs
- if ($name ne "ActionDetector"){ # need to start actiondetector?
- my $actCycle = AttrVal($name,"actCycle",undef);
- CUL_HM_Set($hash,$name,"actiondetect",$actCycle) if ($actCycle);
- }
-
- # add default web-commands
- my $webCmd;
- my $st = AttrVal(($hash->{device}?$hash->{device}:$name), "subType", "");
-
- $webCmd = AttrVal($name,"webCmd","");
- if (!$webCmd){
- if((length (CUL_HM_hash2Id($hash)) == 6)&&
- $hash->{channel_01} &&
- $st ne "thermostat" ){$webCmd = "getConfig";
- }elsif($st eq "blindActuator"){$webCmd="on:off:stop:statusRequest";
- }elsif($st eq "dimmer" ){$webCmd="on:off:toggle:statusRequest";
- }elsif($st eq "switch" ){$webCmd="on:off:toggle:statusRequest";
- }elsif($st eq "virtual" ){$webCmd="press short:press long";
- }elsif($st eq "smokeDetector"){$webCmd="test:alarmOn:alarmOff";
- }elsif($st eq "keyMatic" ){$webCmd="lock:inhibit on:inhibit off";
- }
- }
- $attr{$name}{webCmd} = $webCmd if ($webCmd);
- push @getConfList,$name if (1 == AttrVal($name,"autoReadReg","0"));
- }
- $modules{CUL_HM}{helper}{updtCfgLst} = \@getConfList;
- CUL_HM_autoReadConfig("updateConfig");
-}
-#############################
-sub
-CUL_HM_Define($$)
-{
- my ($hash, $def) = @_;
- my @a = split("[ \t][ \t]*", $def);
- my $name = $hash->{NAME};
- return "wrong syntax: define CUL_HM 6-digit-hex-code [Raw-Message]"
- if(!(int(@a)==3 || int(@a)==4) || $a[2] !~ m/^[A-F0-9]{6,8}$/i);
-
- my $HMid = uc($a[2]);
- return "HMid DEF already used by " . CUL_HM_id2Name($HMid)
- if ($modules{CUL_HM}{defptr}{$HMid});
- if(length($a[2]) == 8) {# define a channel
- my $devHmId = uc(substr($a[2], 0, 6));
- my $chn = substr($a[2], 6, 2);
- my $devHash = $modules{CUL_HM}{defptr}{$devHmId};
- return "please define a device with hmId:".$devHmId." first" if(!$devHash);
-
- $modules{CUL_HM}{defptr}{$HMid} = $hash;
- AssignIoPort($hash);
- my $devName = $devHash->{NAME};
- $hash->{device} = $devName;
- $hash->{chanNo} = $chn;
- $attr{$name}{model} = $attr{$devName}{model} if ($attr{$devName}{model});
- $devHash->{"channel_$chn"} = $name;
- }
- else{# define a device
- $modules{CUL_HM}{defptr}{$HMid} = $hash;
- AssignIoPort($hash);
- }
- CUL_HM_ActGetCreateHash() if($HMid eq '000000');#startTimer
- if(int(@a) == 4) {
- $hash->{DEF} = $a[2];
- CUL_HM_Parse($hash, $a[3]);
- }
- RemoveInternalTimer("updateConfig");
- InternalTimer(gettimeofday()+5,"CUL_HM_updateConfig", "updateConfig", 0);
-
- my @arr;
- if(!$modules{CUL_HM}{helper}{updtCfgLst}){
- $modules{CUL_HM}{helper}{updtCfgLst} = \@arr;
- }
- push(@{$modules{CUL_HM}{helper}{updtCfgLst}}, $name);
-
- return undef;
-}
-#############################
-sub
-CUL_HM_Undef($$)
-{
- my ($hash, $name) = @_;
- my $devName = $hash->{device};
- my $HMid = $hash->{DEF};
- my $chn = substr($HMid,6,2);
- if ($chn){# delete a channel
- my $devHash = CUL_HM_name2Hash($devName);
- delete $devHash->{"channel_$chn"} if ($devName);
- }
- else{# delete a device
- foreach my $channel (keys %{$hash}){
- CommandDelete(undef,$hash->{$channel})
- if ($channel =~ m/^channel_/);
- }
- }
- delete($modules{CUL_HM}{defptr}{$HMid});
- return undef;
-}
-#############################
-sub
-CUL_HM_Rename($$$)
-{
- my ($name, $oldName) = @_;
- my $HMid = CUL_HM_name2Id($name);
- my $hash = CUL_HM_name2Hash($name);
- if (length($HMid) == 8){# we are channel, inform the device
- $hash->{chanNo} = substr($HMid,6,2);
- my $device = $hash->{device}?$hash->{device}:"";
- my $devHash = CUL_HM_name2Hash($device);
- $devHash->{"channel_".$hash->{chanNo}} = $name if ($device);
- }
- else{# we are a device - inform channels if exist
- for (my$chn = 1; $chn <25;$chn++){
- my $chnName = $hash->{sprintf("channel_%02X",$chn)};
- my $chnHash = CUL_HM_name2Hash($chnName);
- $chnHash->{device} = $name if ($chnName);
- }
- }
- return;
-}
-#############################
-sub
-CUL_HM_Parse($$)
-{
- my ($iohash, $msg) = @_;
- my $id = CUL_HM_Id($iohash);
- # Msg format: Allnnffttssssssddddddpp...
- $msg =~ m/A(..)(..)(..)(..)(......)(......)(.*)/;
- my ($len,$msgcnt,$msgFlag,$msgType,$src,$dst,$p) = ($1,$2,$3,$4,$5,$6,$7);
- $p = "" if(!defined($p));
- my $cmd = "$msgFlag$msgType"; #still necessary to maintain old style
- my $lcm = "$len$cmd";
- # $shash will be replaced for multichannel commands
- my $shash = $modules{CUL_HM}{defptr}{$src};
- my $dhash = $modules{CUL_HM}{defptr}{$dst};
- my $dname = ($dst eq "000000") ? "broadcast" :
- ($dhash ? $dhash->{NAME} :
- ($dst eq $id ? $iohash->{NAME} :
- $dst));
- my $target = " (to $dname)";
- my $msgStat;
- ($p,$msgStat) = split(":",$p,2);
- return "" if($msgStat && $msgStat eq 'NACK');#discard TCP errors from HMlan. Resend will cover it
- return "" if($src eq $id);#discard mirrored messages
-
- $respRemoved = 0; #set to 'no response in this message' at start
- if(!$shash) { # Unknown source
- # Generate an UNKNOWN event for pairing requests, ignore everything else
- if($msgType eq "00") {
- my $sname = "CUL_HM_$src";
- # prefer subType over model to make autocreate easier
- # model names are quite cryptic anyway
- my $model = substr($p, 2, 4);
- my $stc = substr($p, 26, 2); # subTypeCode
- if($culHmDevProps{$stc}) {
- $sname = "CUL_HM_".$culHmDevProps{$stc}{st} . "_" . $src;
- }
- elsif($culHmModel{$model}{name}) {
- $sname = "CUL_HM_".$culHmModel{$model}{name} . "_" . $src;
- $sname =~ s/-/_/g;
- }
- Log 3, "CUL_HM Unknown device $sname, please define it";
- return "UNDEFINED $sname CUL_HM $src $msg";
- }
- return "";
- }
- CUL_HM_eventP($shash,"Rcv");
- my $name = $shash->{NAME};
- my @event;
- my $st = AttrVal($name, "subType", "");
- my $model = AttrVal($name, "model", "");
- my $tn = TimeNow();
-
- # return if duplicate
- my $msgX = "No:$msgcnt - t:$msgType s:$src d:$dst ".($p?$p:"");
-
- if($shash->{lastMsg} && $shash->{lastMsg} eq $msgX) {
- Log GetLogLevel($name,4), "CUL_HM $name dup mesg";
- if(($id eq $dst)&& (hex($msgFlag)&0x20)){
-# CUL_HM_SndCmd($shash, $msgcnt."8002".$id.$src."00"); # Send Ack
- Log GetLogLevel($name,4), "CUL_HM $name dup mesg - ack and ignore";
- }
- else{
- Log GetLogLevel($name,4), "CUL_HM $name dup mesg - ignore";
- }
-
- return ""; #return something to please dispatcher
- }
- $shash->{lastMsg} = $msgX;
- $iohash->{HM_CMDNR} = hex($msgcnt) if($dst eq $id);# update messag counter to receiver
-
- CUL_HM_DumpProtocol("RCV",$iohash,$len,$msgcnt,$msgFlag,$msgType,$src,$dst,$p);
-
- #----------start valid messages parsing ---------
- my $parse = CUL_HM_parseCommon($msgcnt,$msgFlag,$msgType,$src,$dst,$p);
- push @event, "powerOn" if($parse eq "powerOn");
-
- my $sendAck = "yes";# if yes Ack will be determined automatically
-
- if ($parse eq "ACK"){# remember - ACKinfo will be passed on
- push @event, "";
- }
- elsif($parse eq "NACK"){
- push @event, "state:NACK";
- }
- elsif($parse eq "done"){
- push @event, "";
- }
- elsif($lcm eq "09A112") { #### Another fhem wants to talk (HAVE_DATA)
- ;
- }
- elsif($msgType eq "00" ){ #### DEVICE_INFO, Pairing-Request
- CUL_HM_ProcessCmdStack($shash) if(CUL_HM_getRxType($shash) & 0x04);#config
- CUL_HM_infoUpdtDevData($name, $shash,$p);#update data
-
- if($shash->{cmdStack} && (CUL_HM_getRxType($shash) & 0x04)) {
- push @event,"";
- }
- else {
- push @event, CUL_HM_Pair($name, $shash,$cmd,$src,$dst,$p);
- }
- }
- elsif($model eq "KS550" || $model eq "HM-WDS100-C6-O") { ####################
-
- if($msgType eq "70" && $p =~ m/^(....)(..)(....)(....)(..)(..)(..)/) {
-
- my ( $t, $h, $r, $w, $wd, $s, $b ) =
- (hex($1), hex($2), hex($3), hex($4), hex($5), hex($6), hex($7));
- my $tsgn = ($t & 0x4000);
- $t = ($t & 0x3fff)/10;
- $t = sprintf("%0.1f", $t-1638.4) if($tsgn);
- my $ir = $r & 0x8000;
- $r = ($r & 0x7fff) * 0.295;
- my $wdr = ($w>>14)*22.5;
- $w = ($w & 0x3fff)/10;
- $wd = $wd * 5;
-
- push @event,
- "state:T: $t H: $h W: $w R: $r IR: $ir WD: $wd WDR: $wdr S: $s B: $b";
- push @event, "temperature:$t";
- push @event, "humidity:$h";
- push @event, "windSpeed:$w";
- push @event, "windDirection:$wd";
- push @event, "windDirRange:$wdr";
- push @event, "rain:$r";
- push @event, "isRaining:$ir";
- push @event, "sunshine:$s";
- push @event, "brightness:$b";
- }
- else {
- push @event, "unknown:$p";
- }
- }
- elsif($model eq "HM-CC-TC") { ###############################################
- my ($sType,$chn) = ($1,$2) if($p && $p =~ m/^(..)(..)/);
- if($msgType eq "70" && $p =~ m/^(....)(..)/) { # weather event
- $chn = '01'; # fix definition
- my ( $t, $h) = (hex($1), hex($2));# temp is 15 bit signed
- $t = ($t & 0x3fff)/10*(($t & 0x4000)?-1:1);
- my $chnHash = $modules{CUL_HM}{defptr}{$src.$chn};
- CUL_HM_UpdtReadBulk($chnHash,1,"state:T: $t H: $h", # update weather channel
- "measured-temp:$t",
- "humidity:$h")
- if ($chnHash);
- push @event, "state:T: $t H: $h";
- push @event, "measured-temp:$t";
- push @event, "humidity:$h";
- }
- elsif($msgType eq "58" && $p =~ m/^(..)(..)/) {# climate event
- $chn = '02'; # fix definition
- my ( $d1, $vp) = # adjust_command[0..4] adj_data[0..250]
- ( $1, hex($2));
- $vp = int($vp/2.56+0.5); # valve position in %
- my $chnHash = $modules{CUL_HM}{defptr}{$src.$chn};
- readingsSingleUpdate($chnHash,"state","$vp %",1) if($chnHash);
- push @event, "actuator:$vp %";
-
- # Set the valve state too, without an extra trigger
- readingsSingleUpdate($dhash,"state","set_$vp %",1) if($dhash);
- }
- elsif(($msgType eq '02' &&$sType eq '01')|| # ackStatus
- ($msgType eq '10' &&$sType eq '06')){ # infoStatus
- $chn = substr($p,2,2);
-
-
- my $temp = substr($p,4,2);
- my $dTemp = ($temp eq '00')?'off':
- (($temp eq 'C8')?'on' :
- sprintf("%0.1f", hex($temp)/2));
- my $chnHash = $modules{CUL_HM}{defptr}{$src.$chn};
- if($chnHash){
- my $chnName = $chnHash->{NAME};
- my $mode = ReadingsVal($chnName,"R-MdTempReg","");
- readingsSingleUpdate($chnHash,"desired-temp",$dTemp,1);
- readingsSingleUpdate($chnHash,"desired-temp-manu",$dTemp,1) if($mode eq 'manual ' && $msgType eq '10');
-# readingsSingleUpdate($chnHash,"desired-temp-cent",$dTemp,1) if($mode eq 'central ' && $msgType eq '02');
-# removed - shall not be changed automatically - change is only temporary
-# CUL_HM_Set($chnHash,$chnName,"desired-temp",$dTemp) if($mode eq 'central ' && $msgType eq '10');
- }
- push @event, "desired-temp:" .$dTemp;
- }
- elsif($msgType eq "10"){ # Config change report
- $chn = substr($p,2,2);
- if( $p =~ m/^0403(......)(..)0505(..)0000/) {# param change
- # change of chn 3(window) list 5 register 5 - a peer window changed!
- my ( $tdev, $tchan, $v1) = (($1), hex($2), hex($3));
- push @event, sprintf("windowopen-temp-%d: %.1f (sensor:%s)"
- ,$tchan, $v1/2, $tdev);
- #todo: This will never cleanup if a peer is deleted
- }
- elsif($p =~ m/^0402000000000(.)(..)(..)(..)(..)(..)(..)(..)(..)/) {
- # param list 5 or 6, 4 value pairs.
- my ($plist, $o1, $v1, $o2, $v2, $o3, $v3, $o4, $v4) =
- (hex($1),hex($2),hex($3),hex($4),hex($5),hex($6),hex($7),hex($8),hex($9));
-
- my ($dayoff, $maxdays, $basevalue);
- my @days = ("Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri");
-
- if($plist == 5 || $plist == 6) {
- if($plist == 5) {
- $dayoff = 0; $maxdays = 5; $basevalue = hex("0B");
- }
- else {
- $dayoff = 5; $maxdays = 2; $basevalue = hex("01");
- }
- my $idx = ($o1-$basevalue);
- my $dayidx = int($idx/48);
- if($idx % 4 == 0 && $dayidx < $maxdays) {
- $idx -= 48*$dayidx;
- $idx /= 2;
- my $ptr = $shash->{TEMPLIST}{$days[$dayidx+$dayoff]};
- $ptr->{$idx}{HOUR} = int($v1/6);
- $ptr->{$idx}{MINUTE} = ($v1%6)*10;
- $ptr->{$idx}{TEMP} = $v2/2;
- $ptr->{$idx+1}{HOUR} = int($v3/6);
- $ptr->{$idx+1}{MINUTE} = ($v3%6)*10;
- $ptr->{$idx+1}{TEMP} = $v4/2;
- }
- }
- foreach my $wd (@days) {
- my $twentyfour = 0;
- my $msg = 'tempList'.$wd.':';
- foreach(my $idx=0; $idx<24; $idx++) {
- my $ptr = $shash->{TEMPLIST}{$wd}{$idx};
- if(defined ($ptr->{TEMP}) && $ptr->{TEMP} ne "") {
- if($twentyfour == 0) {
- $msg .= sprintf(" %02d:%02d %.1f",
- $ptr->{HOUR}, $ptr->{MINUTE}, $ptr->{TEMP});
- } else {
- $ptr->{HOUR} = $ptr->{MINUTE} = $ptr->{TEMP} = "";
- }
- }
- if($ptr->{HOUR} && 0+$ptr->{HOUR} == 24) {
- $twentyfour = 1; # next value uninteresting, only first counts.
- }
- }
- push @event, $msg; # generate one event per day entry
- }
- }
- elsif($p =~ m/^04020000000005(..)(..)/) { # paramchanged L5
- my ( $o1, $v1) = (hex($1),hex($2));# only parse list 5 for chn 2
- my $msg;
- my @days = ("Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri");
- if($o1 == 1) { ### bitfield containing multiple values...
- # MUST be IDENTICAL to the set commands assotiated
- my %mode = (0 => "manual",1 => "auto",2 => "central",3 => "party");
- push @event,'displayMode:temp-'.(($v1 & 1)?"hum" :"only");
- push @event,'displayTemp:' .(($v1 & 2)?"setpoint" :"actual");
- push @event,'displayTempUnit:' .(($v1 & 4)?"fahrenheit":"celsius");
- push @event,'controlMode:' .($mode{(($v1 & 0x18)>>3)});
- push @event,'decalcDay:' .$days[($v1 & 0xE0)>>5];
- my $chnHash = $modules{CUL_HM}{defptr}{$src.$chn};
- my $dTemp;
- if($chnHash){
- my $chnName = $chnHash->{NAME};
- my $mode = ReadingsVal($chnName,"R-MdTempReg","");
- $dTemp = ReadingsVal($chnName,"desired-temp","21.0");
- if (!$chnHash->{helper}{oldMode} || $chnHash->{helper}{oldMode} ne $mode){
- $dTemp = ReadingsVal($chnName,"desired-temp-manu",$dTemp)if ($mode eq 'manual ');
- $dTemp = ReadingsVal($chnName,"desired-temp-cent",$dTemp)if ($mode eq 'central ');
- $chnHash->{helper}{oldMode} = $mode;
- }
- readingsSingleUpdate($chnHash,"desired-temp",$dTemp,1);
- }
- push @event, "desired-temp:" .$dTemp;
- }
- elsif($o1 == 2) {
- my %pos = (0=>"Auto",1=>"Closed",2=>"Open",3=>"unknown");
- push @event,"tempValveMode:".$pos{(($v1 & 0xC0)>>6)};
- }
- else{
- push @event,'param-change: offset='.$o1.', value='.$v1;
- }
- }
- elsif($p =~ m/^0[23]/){ # param response
- push @event,'';#cannot be handled here as request missing
- }
- }
- elsif($msgType eq "01"){ # status reports
- if($p =~ m/^010809(..)0A(..)/) { # TC set valve for VD => post events to VD
- my ( $of, $vep) = (hex($1), hex($2));
- push @event, "ValveErrorPosition_for_$dname: $vep %";
- push @event, "ValveOffset_for_$dname: $of %";
- CUL_HM_UpdtReadBulk($dhash,1,'ValveErrorPosition:set_'.$vep.' %',
- 'ValveOffset:set_'.$of.' %');
- }
- elsif($p =~ m/^010[56]/){ # 'prepare to set' or 'end set'
- push @event,""; #
- }
- }
- elsif($cmd eq "A03F" && $id eq $dst) { # Timestamp request
- my $s2000 = sprintf("%02X", CUL_HM_secSince2000());
- CUL_HM_SndCmd($shash, "++803F$id${src}0204$s2000");
- push @event, "time-request";
- $sendAck = "";
- }
- }
- elsif($model eq "HM-CC-VD") { ###############################################
- # CMD:8202 SRC:13F251 DST:15B50D 010100002A
- # status ACK to controlling HM-CC-TC
- if($msgType eq "02" && $p =~ m/^(..)(..)(..)(..)/) {#subtype+chn+value+err
- my ($chn,$vp, $err) = ($2,hex($3), hex($4));
- $vp = int($vp)/2; # valve position in %
- push @event, "ValvePosition:$vp %";
- push @event, "state:$vp %";
- $shash = $modules{CUL_HM}{defptr}{"$src$chn"}
- if($modules{CUL_HM}{defptr}{"$src$chn"});
-
- # Status-Byte Auswertung
-
- my $stErr = ($err >>1) & 0x7;
- push @event,"battery:".(($stErr == 4)?"critical":($err&0x80?"low":"ok"));
- if (!$stErr){#remove both conditions
- push @event, "motorErr:ok";
- }
- else{
- push @event, "motorErr:blocked" if($stErr == 1);
- push @event, "motorErr:loose" if($stErr == 2);
- push @event, "motorErr:adjusting range too small" if($stErr == 3);
-# push @event, "battery:critical" if($stErr == 4);
- }
- push @event, "motor:opening" if(($err&0x30) == 0x10);
- push @event, "motor:closing" if(($err&0x30) == 0x20);
- push @event, "motor:stop" if(($err&0x30) == 0x00);
- push @event, ""; # just in case - mark message as confirmed
- }
-
-
- # CMD:A010 SRC:13F251 DST:5D24C9 0401 00000000 05 09:00 0A:07 00:00
- # status change report to paired central unit
- #read List5 reg 09 (offset) and 0A (err-pos)
- #list 5 is channel-dependant not link dependant
- # => Link discriminator (00000000) is fixed
- elsif($msgType eq "10" && $p =~ m/^04..........0509(..)0A(..)/) {
- my ( $of, $vep) = (hex($1), hex($2));
- push @event, "ValveErrorPosition:$vep %";
- push @event, "ValveOffset:$of %";
- }
- }
- elsif($model eq "HM-CC-SCD"){ ###############################################
- if (($msgType eq "02" && $p =~ m/^01/) || # handle Ack_Status
- ($msgType eq "10" && $p =~ m/^06/) || #or Info_Status message here
- ($msgType eq "41")) {
- my $co2Lvl = substr($p,4,2);
- my %lvl=("00"=>"normal","64"=>"added","C8"=>"addedStrong");
- push @event, "state:".$lvl{$co2Lvl};
- }
- }
- elsif($st eq "KFM100" && $model eq "KFM-Sensor") { ##########################
-
- if($p =~ m/.14(.)0200(..)(..)(..)/) {# todo very risky - no start...
- my ($k_cnt, $k_v1, $k_v2, $k_v3) = ($1,$2,$3,$4);
- my $v = 128-hex($k_v2); # FIXME: calibrate
- # $v = 256+$v if($v < 0);
- $v += 256 if(!($k_v3 & 1));
- push @event, "rawValue:$v";
-
- my $seq = hex($k_cnt);
- push @event, "Sequence:$seq";
-
- my $r2r = AttrVal($name, "rawToReadable", undef);
- if($r2r) {
- my @r2r = split("[ :]", $r2r);
- foreach(my $idx = 0; $idx < @r2r-2; $idx+=2) {
- if($v >= $r2r[$idx] && $v <= $r2r[$idx+2]) {
- my $f = (($v-$r2r[$idx])/($r2r[$idx+2]-$r2r[$idx]));
- my $cv = ($r2r[$idx+3]-$r2r[$idx+1])*$f + $r2r[$idx+1];
- my $unit = AttrVal($name, "unit", "");
- $unit = " $unit" if($unit);
- push @event, sprintf("state:%.1f %s",$cv,$unit);
- push @event, sprintf("content:%.1f %s",$cv,$unit);
- last;
- }
- }
- } else {
- push @event, "state:$v";
- }
- }
-
- }
- elsif($st eq "switch" || ####################################################
- $st eq "dimmer" ||
- $st eq "blindActuator") {
-
- if (($msgType eq "02" && $p =~ m/^01/) || # handle Ack_Status
- ($msgType eq "10" && $p =~ m/^06/)) { # or Info_Status message here
-
- my ($subType,$chn,$level,$err) = ($1,$2,$3,hex($4))
- if($p =~ m/^(..)(..)(..)(..)/);
- # Multi-channel device: Use channel if defined
- $shash = $modules{CUL_HM}{defptr}{"$src$chn"}
- if($modules{CUL_HM}{defptr}{"$src$chn"});
- my $val = hex($level)/2;
- $val = ($val == 100 ? "on" : ($val == 0 ? "off" : "$val %"));
- push @event, "deviceMsg:$val$target" if($chn ne "00");
-
- my $eventName = "unknown"; # different names for events
- $eventName = "switch" if($st eq "switch");
- $eventName = "motor" if($st eq "blindActuator");
- $eventName = "dim" if($st eq "dimmer");
- my $action; #determine action
- if ($st ne "switch"){
- push @event, "$eventName:up:$val" if(($err&0x30) == 0x10);
- push @event, "$eventName:down:$val" if(($err&0x30) == 0x20);
- push @event, "$eventName:stop:$val" if(($err&0x30) == 0x00);
- }
- if ($st eq "dimmer"){
- push @event,"overload:".(($err&0x02)?"on":"off");
- push @event,"overheat:".(($err&0x04)?"on":"off");
- push @event,"reduced:" .(($err&0x08)?"on":"off");
- #hack for blind - other then behaved devices blind does not send
- # a status info for chan 0 at power on
- # chn3 (virtual chan) and not used up to now
- # info from it is likely a power on!
- push @event,"powerOn" if($chn eq "03");
- }
- elsif ($model eq "HM-SEC-SFA-SM"){ # && $chn eq "00")
- CUL_HM_UpdtReadBulk(CUL_HM_getDeviceHash($shash),1,
- "powerError:" .(($err&0x02) ? "on":"off"),
- "sabotageError:".(($err&0x04) ? "on":"off"),
- "battery:".(($err&0x08)?"critical":($err&0x80?"low":"ok")));
- }
- elsif ($model eq "HM-LC-SW1-BA-PCB"){
- push @event, "battery:" . (($err&0x80) ? "low" : "ok" );
- }
- push @event, "state:$val";
- }
- }
- elsif($st eq "remote" || $st eq "pushButton" || $st eq "swi") { #############
- if($msgType =~ m/^4./ && $p =~ m/^(..)(..)$/) {
- my ($buttonField, $bno) = (hex($1), hex($2));# button number/event count
- my $buttonID = $buttonField&0x3f;# only 6 bit are valid
- my $btnName;
- my $state = "";
- my $chnHash = $modules{CUL_HM}{defptr}{$src.uc(sprintf("%02x",$buttonID))};
-
- if ($chnHash){# use userdefined name - ignore this irritating on-off naming
- $btnName = $chnHash->{NAME};
- }
- else{# Button not defined, use default naming
- $chnHash = $shash;
- if ($st eq "swi"){#maintain history for event naming
- $btnName = "Btn$buttonField";
- }
- else{
- my $btn = int((($buttonField&0x3f)+1)/2);
- $btnName = "Btn$btn";
- $state = ($buttonField&1 ? "off" : "on")
- }
- }
-
- if($buttonField & 0x40){
- if(!$shash->{BNO} || $shash->{BNO} ne $bno){#bno = event counter
- $shash->{BNO}=$bno;
- $shash->{BNOCNT}=1; # message counter reest
- }
- $shash->{BNOCNT}+=1;
- $state .= "Long" .($msgFlag eq "A0" ? "Release" : "").
- " ".$shash->{BNOCNT}."-".$cmd."-";
- }
- else{
- $state .= ($st eq "swi")?"toggle":"Short";#swi only support toggle
- }
- $shash->{helper}{addVal} = $buttonField; #store to handle changes
- readingsSingleUpdate($chnHash,"state",$state.$target,1);#trig chan also
- push @event,"battery:". (($buttonField&0x80)?"low":"ok");
- push @event,"state:$btnName $state$target";
- }
- }
- elsif($st eq "virtual"){#####################################################
- # possibly add code to count all acks that are paired.
- if($msgType eq "02") {# this must be a reflection from what we sent, ignore
- push @event, "";
- }
- }
- elsif($st eq "outputUnit"){##################################################
- if($msgType eq "40" && $p =~ m/^(..)(..)$/){
- my ($button, $bno) = (hex($1), hex($2));
- if(!(exists($shash->{BNO})) || $shash->{BNO} ne $bno){
- $shash->{BNO}=$bno;
- $shash->{BNOCNT}=1;
- }
- else{
- $shash->{BNOCNT}+=1;
- }
- my $btn = int($button&0x3f);
- push @event, "state:Btn$btn on$target";
- }
- elsif(($msgType eq "02" && $p =~ m/^01/) || # handle Ack_Status
- ($msgType eq "10" && $p =~ m/^06/)){ # or Info_Status message
- my ($msgChn,$msgState) = ($1,$2) if ($p =~ m/..(..)(..)/);
- my $chnHash = $modules{CUL_HM}{defptr}{$src.$msgChn};
- if ($model eq "HM-OU-LED16") {
- #special: all LEDs map to device state
- my $devState = ReadingsVal($name,"color","00000000");
- if($parse eq "powerOn"){# reset LEDs after power on
- CUL_HM_PushCmdStack($shash,'++A011'.$id.$src."8100".$devState);
- CUL_HM_ProcessCmdStack($shash);
- # no event necessary, all the same as before
- }
- else {# just update datafields in storage
- my $bitLoc = ((hex($msgChn)-1)*2);#calculate bit location
- my $mask = 3<<$bitLoc;
- my $value = sprintf("%08X",(hex($devState) &~$mask)|($msgState<<$bitLoc));
- CUL_HM_UpdtReadBulk($shash,1,"color:".$value,
- "state:".$value);
- if ($chnHash){
- $shash = $chnHash;
- my %colorTable=("00"=>"off","01"=>"red","02"=>"green","03"=>"orange");
- my $actColor = $colorTable{$msgState};
- $actColor = "unknown" if(!$actColor);
- push @event, "color:$actColor"; #todo duplicate
- push @event, "state:$actColor";
- }
- }
- }
- elsif ($model eq "HM-OU-CFM-PL"){
- if ($chnHash){
- $shash = $chnHash;
- my $val = hex($msgState)/2;
- $val = ($val == 100 ? "on" : ($val == 0 ? "off" : "$val %"));
- push @event, "state:$val";
- }
- }
- }
- }
- elsif($st eq "motionDetector") { ############################################
- # Code with help of Bassem
- my $state;
- if(($msgType eq "10" ||$msgType eq "02") && $p =~ m/^0601(..)(..)/) {
- my $err;
- ($state, $err) = ($1, hex($2));
- my $bright = hex($state);
- push @event, "brightness:".$bright;
- push @event, "cover:". (($err&0x0E)?"open" :"closed");
- push @event, "battery:". (($err&0x80)?"low" :"ok" );
- }
- elsif($msgType eq "41" && $p =~ m/^01(..)(..)(..)/) {#01 is channel
- my($cnt,$bright,$nextTr);
- ($cnt,$state,$nextTr) = (hex($1),$2,(hex($3)>>4));
- $bright = hex($state);
- my @nextVal = ("0x0","0x1","0x2","0x3","15" ,"30" ,"60" ,"120",
- "240","0x9","0xa","0xb","0xc","0xd","0xe","0xf");
- push @event, "state:motion";
- push @event, "motion:on$target"; #added peterp
- push @event, "motionCount:".$cnt."_next:".$nextTr."-".$nextVal[$nextTr];
- push @event, "brightness:".$bright;
- }
- elsif($msgType eq "70" && $p =~ m/^7F(..)(.*)/) {
- my($d1, $d2) = ($1, $2);
- push @event, 'devState_raw'.$d1.':'.$d2;
- }
-
- if($id eq $dst && $cmd ne "8002" && $state){
- CUL_HM_SndCmd($shash, $msgcnt."8002".$id.$src."0101${state}00");
- $sendAck = ""; #todo why is this special?
- }
- }
- elsif($st eq "smokeDetector") { #############################################
- #Info Level: msgType=0x10 p(..)(..)(..) subtype=06, channel, state (1 byte)
- #Event: msgType=0x41 p(..)(..)(..) channel , unknown, state (1 byte)
-
- if ($msgType eq "10" && $p =~ m/^06..(..)/) {
- my $state = hex($1);
- push @event, "battery:". (($state&0x04)?"low" :"ok" );
- push @event, "state:alive";
- }
- elsif ($msgType eq "40"){ #autonomous event
- if($dhash){ # the source is in dst
- my ($state,$trgCnt) = (hex(substr($p,0,2)),hex(substr($p,2,2)));
- readingsSingleUpdate($dhash,'test',"from $dname:$state",1)
- if (!($state & 1));
- readingsSingleUpdate($dhash,'battery',(($state & 0x04)?"low":"ok"),1)
- if($state&0x80);
- }
- push @event, "";
- }
- elsif ($msgType eq "41"){ #Alarm detected
- my ($No,$state) = (substr($p,2,2),substr($p,4,2));
- if($dhash && $dname ne $name){ # update source(ID is reported in $dst...)
- if (!$dhash->{helper}{alarmNo} || $dhash->{helper}{alarmNo} ne $No){
- $dhash->{helper}{alarmNo} = $No;
- readingsSingleUpdate($dhash,'state',
- (($state eq "01")?"off":
- (($state eq "C7")?"smoke-forward":
- "smoke-alarm")),1);
- }
- }
- # - - - - - - now handle the team - - - - - -
- $shash->{helper}{alarmList} = "" if (!$dhash->{helper}{alarmList});
- $shash->{helper}{alarmFwd} = "" if (!$dhash->{helper}{alarmFwd});
- if ($state eq "01") { # clear Alarm for one sensor
- $shash->{helper}{alarmList} =~ s/",".$dst//;
- }
- elsif($state eq "C7"){# add alarm forwarding
- $shash->{helper}{alarmFwd} .= ",".$dst;
- }
- else{ # add alarm for Sensor
- $shash->{helper}{alarmList} .= ",".$dst;
- }
- my $alarmList; # make alarm ID list readable
- foreach(split(",",$shash->{helper}{alarmList})){
- $alarmList .= CUL_HM_id2Name($_)."," if ($_);
- }
- if (!$alarmList){# all alarms are gone - clear forwarding
- foreach(split(",",$shash->{helper}{alarmFwd})){
- my $fHash = CUL_HM_id2Hash($1) if ($1);
- readingsSingleUpdate($fHash,'state',"off",1)if ($fHash);
- }
- $shash->{helper}{alarmList} = "";
- $shash->{helper}{alarmFwd} = "";
- }
- my $alarmFwd; # make forward ID list readable
- foreach(split(",",$shash->{helper}{alarmFwd})){
- $alarmFwd .= CUL_HM_id2Name($_)."," if ($_);
- }
- push @event,"state:" .($alarmList?"smoke-Alarm":"off" );
- push @event,"smoke_detect:" .($alarmList?$alarmList :"none");
- push @event,"smoke_forward:".($alarmFwd ?$alarmFwd :"none");
- }
- elsif ($msgType eq "01"){ #Configs
- my $sType = substr($p,0,2);
- if($sType eq "01"){#add peer to group
- push @event,"SDteam:add_".$dname;
- }
- elsif($sType eq "02"){# remove from group
- push @event,"SDteam:remove_".$dname;
- }
- elsif($sType eq "05"){# set param List 3 and 4
- push @event,"";
- }
- }
- else{
- push @event, "SDunknownMsg:$p" if(!@event);
- }
-
- if($id eq $dst && (hex($msgFlag)&0x20)){ # Send Ack/Nack
- CUL_HM_SndCmd($shash, $msgcnt."8002".$id.$src.($cmd eq "A001" ? "80":"00"));
- $sendAck = ""; #todo why is this special?
- }
- }
- elsif($st eq "threeStateSensor") { ##########################################
- #todo: check for correct msgType, see below
- #Event: msgType=0x41 p(..)(..)(..) channel , unknown, state
- #Info Level: msgType=0x10 p(..)(..)(..)(..) subty=06, chn, state,err (3bit)
- #AckStatus: msgType=0x02 p(..)(..)(..)(..) subty=01, chn, state,err (3bit)
- my ($chn,$state,$err,$cnt); #define locals
- if($msgType eq "10" || $msgType eq "02"){
- my $mT = $msgType.substr($p,0,2);
- if ($mT eq "1006" ||$$mT eq "0201"){
- $p =~ m/^..(..)(..)(..)?$/;
- ($chn,$state,$err) = ($1, $2, hex($3));
- $shash = $modules{CUL_HM}{defptr}{"$src$chn"}
- if($modules{CUL_HM}{defptr}{"$src$chn"});
- push @event, "alive:yes";
- push @event, "battery:". (($err&0x80)?"low" :"ok" );
- if ($model ne "HM-SEC-WDS"){
- push @event, "cover:". (($err&0x0E)?"open" :"closed");
- }
- }
- }
- elsif($msgType eq "41"){
- ($chn,$cnt,$state)=($1,$2,$3) if($p =~ m/^(..)(..)(..)/);
- $shash = $modules{CUL_HM}{defptr}{"$src$chn"}
- if($modules{CUL_HM}{defptr}{"$src$chn"});
- }
-
- if (defined($state)){# if state was detected post events
- my %txt;
- %txt = ("C8"=>"open", "64"=>"tilted", "00"=>"closed");
- %txt = ("C8"=>"wet", "64"=>"damp", "00"=>"dry")
- if($model eq "HM-SEC-WDS");
- my $txt = $txt{$state};
- $txt = "unknown:$state" if(!$txt);
- push @event, "state:$txt";
- push @event, "contact:$txt$target";
-
- }
- else{push @event, "3SSunknownMsg:$p" if(!@event);}
- }
- elsif($model eq "HM-WDC7000" ||$st eq "THSensor") { #########################
- my $t = hex(substr($p,0,4));
- $t -= 32768 if($t > 1638.4);
- $t = sprintf("%0.1f", $t/10);
- my $h = hex(substr($p,4,2));
- my $ap = hex(substr($p,6,4));
- my $statemsg = "state:T: $t";
- $statemsg .= " H: $h" if ($h);
- $statemsg .= " AP: $ap" if ($ap);
- push @event, $statemsg;
- push @event, "temperature:$t";#temp is always there
- push @event, "humidity:$h" if ($h);
- push @event, "airpress:$ap" if ($ap);
- }
- elsif($st eq "winMatic") { #################################################
- my($sType,$chn,$lvl,$stat) = ($1,$2,$3,$4) if ($p =~ m/^(..)(..)(..)(..)/);
- if(($msgType eq "10" && $sType eq "06") ||
- ($msgType eq "02" && $sType eq "01")){
- $shash = $modules{CUL_HM}{defptr}{"$src$chn"}
- if($modules{CUL_HM}{defptr}{"$src$chn"});
- # stateflag meaning unknown
- push @event, "state:".(($lvl eq "FF")?"locked":((hex($lvl)/2)." %"));
- if ($chn eq "01"){
- my %err = (0=>"no",1=>"TurnError",2=>"TiltError");
- my %dir = (0=>"no",1=>"up",2=>"down",3=>"undefined");
- push @event, "motorError:".$err{(hex($stat)>>1)&0x02};
- push @event, "direction:".$dir{(hex($stat)>>4)&0x02};
-# CUL_HM_SndCmd($shash, $msgcnt."8002".$id.$src."0101".$lst."00")
-# if($id eq $dst);# Send AckStatus
-# $sendAck = "";
- }
- else{ #should be akku
- my %statF = (0=>"trickleCharge",1=>"charge",2=>"dischange",3=>"unknown");
- push @event, "charge:".$statF{(hex($stat)>>4)&0x02};
- }
- }
- if ($p =~ m/^0287(..)89(..)8B(..)/) {
- my ($air, undef, $course) = ($1, $2, $3);
- push @event, "airing:".($air eq "FF" ? "inactiv" : CUL_HM_decodeTime8($air));
- push @event, "course:".($course eq "FF" ? "tilt" : "close");
- }
- elsif($p =~ m/^0201(..)03(..)04(..)05(..)07(..)09(..)0B(..)0D(..)/) {
- my ($flg1, $flg2, $flg3, $flg4, $flg5, $flg6, $flg7, $flg8) =
- ($1, $2, $3, $4, $5, $6, $7, $8);
- push @event, "airing:".($flg5 eq "FF" ? "inactiv" : CUL_HM_decodeTime8($flg5));
- push @event, "contact:tesed";
- }
- }
- elsif($st eq "keyMatic") { #################################################
- #Info Level: msgType=0x10 p(..)(..)(..)(..) subty=06, chn, state,err (3bit)
- #AckStatus: msgType=0x02 p(..)(..)(..)(..) subty=01, chn, state,err (3bit)
-
- if(($msgType eq "10" && $p =~ m/^06/) ||
- ($msgType eq "02" && $p =~ m/^01/)) {
- $p =~ m/^..(..)(..)(..)/;
- my ($chn,$val, $err) = ($1,hex($2), hex($3));
- $shash = $modules{CUL_HM}{defptr}{"$src$chn"}
- if($modules{CUL_HM}{defptr}{"$src$chn"});
-
- my $stErr = ($err >>1) & 0x7;
- my $error = 'unknown_'.$stErr;
- $error = 'motor aborted' if ($stErr == 2);
- $error = 'clutch failure' if ($stErr == 1);
- $error = 'none' if ($stErr == 0);
-
- push @event, "unknown:40" if($err&0x40);
- push @event, "battery:". (($err&0x80) ? "low":"ok");
- push @event, "uncertain:" .(($err&0x30) ? "yes":"no");
- push @event, "error:" . ($error);
- my $state = ($err & 0x30) ? " (uncertain)" : "";
- push @event, "lock:" . (($val == 1) ? "unlocked" : "locked");
- push @event, "state:" . (($val == 1) ? "unlocked" : "locked") . $state;
- }
- }
- else{########################################################################
- ; # no one wants the message
- }
-
- #------------ parse if FHEM or virtual actor is destination ---------------
-
- if(AttrVal($dname, "subType", "none") eq "virtual"){# see if need for answer
- if($msgType =~ m/^4./ && $p =~ m/^(..)(..)/) { #Push Button event
- my ($recChn,$trigNo) = (hex($1),hex($2));# button number/event count
- my $longPress = ($recChn & 0x40)?"long":"short";
- my $recId = $src.sprintf("%02X",($recChn&0x3f));
- foreach my $dChId (CUL_HM_getAssChnIds($dname)){# need to check all chan
- next if (!$modules{CUL_HM}{defptr}{$dChId});
- my $dChNo = substr($dChId,6,2);
- my $dChName = CUL_HM_id2Name($dChId);
- my @peerIDs = split(',',AttrVal($dChName,"peerIDs",""));
-
- if (AttrVal($dChName,"peerIDs","") =~m/$recId/){# is in peerlist?
- my $dChHash = CUL_HM_name2Hash($dChName);
- $dChHash->{helper}{trgLgRpt} = 0
- if (!defined($dChHash->{helper}{trgLgRpt}));
- $dChHash->{helper}{trgLgRpt} +=1;
-
- my $state = ReadingsVal($dChName,"virtActState","OFF");
- my $tNoOld = ReadingsVal($dChName,"virtActTrigNo","0");
- $state = ($state eq "OFF")?"ON":"OFF" if ($trigNo ne $tNoOld);
- if (hex($msgFlag)&0x20){
- $longPress .= "_Release";
- $dhash->{helper}{trgLgRpt}=0;
- CUL_HM_SndCmd($dChHash,$msgcnt."8002".$dst.$src.'01'.$dChNo.
- (($state eq "ON")?"C8":"00")."00");
- $sendAck = "";
- }
- CUL_HM_UpdtReadBulk($dChHash,1,"state:".$state,
- "virtActState:".$state,
- "virtActTrigger:".CUL_HM_id2Name($recId),
- "virtActTrigType:".$longPress,
- "virtActTrigRpt:".$dChHash->{helper}{trgLgRpt},
- "virtActTrigNo:".$trigNo );
- }
- }
- }
- elsif($msgType eq "58" && $p =~ m/^(..)(..)/) {# climate event
- my ($d1,$vp) =($1,hex($2)); # adjust_command[0..4] adj_data[0..250]
- $vp = int($vp/2.56+0.5); # valve position in %
- my $chnHash = $modules{CUL_HM}{defptr}{$dst."01"};
- CUL_HM_UpdtReadBulk($chnHash,1,"ValvePosition:$vp %",
- "ValveAdjCmd:".$d1);
- CUL_HM_SndCmd($chnHash,$msgcnt."8002".$dst.$src.'0101'.
- sprintf("%02X",$vp*2)."0000");#$vp, $err,$??
- $sendAck = "";
- }
- elsif($msgType eq "02"){
- if ($dhash->{helper}{respWait}{msgId} &&
- $dhash->{helper}{respWait}{msgId} eq $msgcnt ){
- #ack we waited for - stop Waiting
- CUL_HM_respPendRm($dhash);
- }
- }
- if (hex($msgFlag)&0x20 && ($sendAck eq "yes")){
- CUL_HM_SndCmd($dhash, $msgcnt."8002".$dst.$src."00");#virtual must ack
- }
- }
- elsif($id eq $dst){# if fhem is destination check if we need to react
- if($msgType =~ m/^4./ && $p =~ m/^(..)/ && #Push Button event
- (hex($msgFlag)&0x20)){ #response required Flag
- my ($recChn) = ($1);# button number/event count
- # fhem CUL shall ack a button press
- CUL_HM_SndCmd($shash, $msgcnt."8002".$dst.$src."0101".
- ((hex($recChn)&1)?"C8":"00")."00");#Actor simulation
- $sendAck = "";
- }
- }
-
- #------------ send default ACK if not applicable------------------
- # ack if we are destination, anyone did accept the message (@event)
- # parser did not supress
- CUL_HM_SndCmd($shash, $msgcnt."8002".$id.$src."00") # Send Ack
- if( ($id eq $dst) #are we adressee
- && (hex($msgFlag)&0x20) #response required Flag
- && @event #only ack of we identified it
- && ($sendAck eq "yes") #sender requested ACK
- );
-
- CUL_HM_ProcessCmdStack($shash) if ($respRemoved); # cont stack if a response is complete
- #------------ process events ------------------
-
- push @event, "noReceiver:src:$src ($cmd) $p" if(!@event);
- CUL_HM_UpdtReadBulk($shash,1,@event);
- return $shash->{NAME} ;# shash could have changed to support channel
-}
-
-##----------definitions for register settings-----------------
- # definition of Register for all devices
- # a: address, incl bits 13.4 4th bit in reg 13
- # s: size 2.0 = 2 byte, 0.5 = 5 bit. Max is 4.0!!
- # l: list number. List0 will be for channel 0
- # List 1 will set peer to 00000000
- # list 3 will need the input of a peer!
- # min: minimal input value
- # max: maximal input value
- # c: conversion, will point to a routine for calculation
- # f: factor to be used if c = 'factor'
- # u: unit for description
- # t: txt description
- # lit: if the command is a literal options will be entered here
- # d: if '1' the register will appear in Readings
- #
-my %culHmRegDefShLg = (# register that are available for short AND long button press. Will be merged to rgister list at init
-#blindActuator mainly
- ActionType =>{a=> 10.0,s=>0.2,l=>3,min=>0 ,max=>3 ,c=>'lit' ,f=>'' ,u=>'' ,d=>1,t=>"" ,lit=>{off=>0,jmpToTarget=>1,toggleToCnt=>2,toggleToCntInv=>3}},
- OffTimeMode =>{a=> 10.6,s=>0.1,l=>3,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"off time mode",lit=>{absolut=>0,minimal=>1}},
- OnTimeMode =>{a=> 10.7,s=>0.1,l=>3,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"on time mode" ,lit=>{absolut=>0,minimal=>1}},
- MaxTimeF =>{a=> 29.0,s=>1.0,l=>3,min=>0 ,max=>25.4 ,c=>'factor' ,f=>10 ,u=>'s' ,d=>0,t=>"max time first direction"},
- DriveMode =>{a=> 31.0,s=>1.0,l=>3,min=>0 ,max=>3 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"" ,lit=>{direct=>0,viaUpperEnd=>1,viaLowerEnd=>2,viaNextEnd=>3}},
-#dimmer mainly
- OnDly =>{a=> 6.0,s=>1.0,l=>3,min=>0 ,max=>111600 ,c=>'fltCvT' ,f=>'' ,u=>'s' ,d=>0,t=>"on delay "},
- OnTime =>{a=> 7.0,s=>1.0,l=>3,min=>0 ,max=>111600 ,c=>'fltCvT' ,f=>'' ,u=>'s' ,d=>0,t=>"on time"},
- OffDly =>{a=> 8.0,s=>1.0,l=>3,min=>0 ,max=>111600 ,c=>'fltCvT' ,f=>'' ,u=>'s' ,d=>0,t=>"off delay"},
- OffTime =>{a=> 9.0,s=>1.0,l=>3,min=>0 ,max=>111600 ,c=>'fltCvT' ,f=>'' ,u=>'s' ,d=>0,t=>"off time"},
-
- ActionTypeDim =>{a=> 10.0,s=>0.4,l=>3,min=>0 ,max=>8 ,c=>'lit' ,f=>'' ,u=>'' ,d=>1,t=>"" ,lit=>{off=>0,jmpToTarget=>1,toggleToCnt=>2,toggleToCntInv=>3,upDim=>4,downDim=>5,toggelDim=>6,toggelDimToCnt=>7,toggelDimToCntInv=>8}},
- OffDlyBlink =>{a=> 14.5,s=>0.1,l=>3,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"" ,lit=>{off=>0,on=>1}},
- OnLvlPrio =>{a=> 14.6,s=>0.1,l=>3,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"" ,lit=>{high=>0,low=>1}},
- OnDlyMode =>{a=> 14.7,s=>0.1,l=>3,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"" ,lit=>{setToOff=>0,NoChange=>1}},
- OffLevel =>{a=> 15.0,s=>1.0,l=>3,min=>0 ,max=>100 ,c=>'factor' ,f=>2 ,u=>'%' ,d=>0,t=>"PowerLevel Off"},
- OnMinLevel =>{a=> 16.0,s=>1.0,l=>3,min=>0 ,max=>100 ,c=>'factor' ,f=>2 ,u=>'%' ,d=>0,t=>"minimum PowerLevel"},
- OnLevel =>{a=> 17.0,s=>1.0,l=>3,min=>0 ,max=>100 ,c=>'factor' ,f=>2 ,u=>'%' ,d=>1,t=>"PowerLevel on"},
-
- OffLevelKm =>{a=> 15.0,s=>1.0,l=>3,min=>0 ,max=>127.5 ,c=>'factor' ,f=>2 ,u=>'%' ,d=>0,t=>"OnLevel 127.5=locked"},
- OnLevelKm =>{a=> 17.0,s=>1.0,l=>3,min=>0 ,max=>127.5 ,c=>'factor' ,f=>2 ,u=>'%' ,d=>0,t=>"OnLevel 127.5=locked"},
- OnRampOnSp =>{a=> 34.0,s=>1.0,l=>3,min=>0 ,max=>1 ,c=>'factor' ,f=>200 ,u=>'s' ,d=>0,t=>"Ramp On speed"},
- OnRampOffSp =>{a=> 35.0,s=>1.0,l=>3,min=>0 ,max=>1 ,c=>'factor' ,f=>200 ,u=>'s' ,d=>0,t=>"Ramp Off speed"},
-
- RampSstep =>{a=> 18.0,s=>1.0,l=>3,min=>0 ,max=>100 ,c=>'factor' ,f=>2 ,u=>'%' ,d=>0,t=>"rampStartStep"},
- RampOnTime =>{a=> 19.0,s=>1.0,l=>3,min=>0 ,max=>111600 ,c=>'fltCvT' ,f=>'' ,u=>'s' ,d=>0,t=>"rampOnTime"},
- RampOffTime =>{a=> 20.0,s=>1.0,l=>3,min=>0 ,max=>111600 ,c=>'fltCvT' ,f=>'' ,u=>'s' ,d=>0,t=>"rampOffTime"},
- DimMinLvl =>{a=> 21.0,s=>1.0,l=>3,min=>0 ,max=>100 ,c=>'factor' ,f=>2 ,u=>'%' ,d=>0,t=>"dimMinLevel"},
- DimMaxLvl =>{a=> 22.0,s=>1.0,l=>3,min=>0 ,max=>100 ,c=>'factor' ,f=>2 ,u=>'%' ,d=>0,t=>"dimMaxLevel"},
- DimStep =>{a=> 23.0,s=>1.0,l=>3,min=>0 ,max=>100 ,c=>'factor' ,f=>2 ,u=>'%' ,d=>0,t=>"dimStep"},
-
- OffDlyNewTime =>{a=> 25.0,s=>1.0,l=>3,min=>0.1,max=>25.6 ,c=>'factor' ,f=>10 ,u=>'s' ,d=>0,t=>"off delay new time"},
- OffDlyNewTime =>{a=> 26.0,s=>1.0,l=>3,min=>0.1,max=>25.6 ,c=>'factor' ,f=>10 ,u=>'s' ,d=>0,t=>"off delay old time"},
- DimElsOffTimeMd =>{a=> 38.6,s=>0.1,l=>3,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"" ,lit=>{absolut=>0,minimal=>1}},
- DimElsOnTimeMd =>{a=> 38.7,s=>0.1,l=>3,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"" ,lit=>{absolut=>0,minimal=>1}},
- DimElsActionType=>{a=> 38.0,s=>0.4,l=>3,min=>0 ,max=>8 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"" ,lit=>{off=>0,jmpToTarget=>1,toggleToCnt=>2,toggleToCntInv=>3,upDim=>4,downDim=>5,toggelDim=>6,toggelDimToCnt=>7,toggelDimToCntInv=>8}},
-#output Unit
- ActType =>{a=> 36 ,s=>1 ,l=>3,min=>0 ,max=>255 ,c=>'' ,f=>'' ,u=>'' ,d=>0,t=>"Action type(LED or Tone)"},
- ActNum =>{a=> 37 ,s=>1 ,l=>3,min=>1 ,max=>255 ,c=>'' ,f=>'' ,u=>'' ,d=>0,t=>"Action Number"},
- Intense =>{a=> 43 ,s=>1 ,l=>3,min=>10 ,max=>255 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Volume",lit=>{vol_0=>255,vol_1=>250,vol_2=>246,vol_3=>240,vol_4=>234,vol_5=>227,vol_6=>218,vol_7=>207,vol_8=>190,vol_9=>162,vol_00=>10}},
-# statemachines
- BlJtOn =>{a=> 11.0,s=>0.4,l=>3,min=>0 ,max=>9 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jump from On" ,lit=>{no=>0,dlyOn=>1,refOn=>2,on=>3,dlyOff=>4,refOff=>5,off=>6,rampOn=>8,rampOff=>9}},
- BlJtOff =>{a=> 11.4,s=>0.4,l=>3,min=>0 ,max=>9 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jump from Off" ,lit=>{no=>0,dlyOn=>1,refOn=>2,on=>3,dlyOff=>4,refOff=>5,off=>6,rampOn=>8,rampOff=>9}},
- BlJtDlyOn =>{a=> 12.0,s=>0.4,l=>3,min=>0 ,max=>9 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jump from delayOn" ,lit=>{no=>0,dlyOn=>1,refOn=>2,on=>3,dlyOff=>4,refOff=>5,off=>6,rampOn=>8,rampOff=>9}},
- BlJtDlyOff =>{a=> 12.4,s=>0.4,l=>3,min=>0 ,max=>9 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jump from delayOff",lit=>{no=>0,dlyOn=>1,refOn=>2,on=>3,dlyOff=>4,refOff=>5,off=>6,rampOn=>8,rampOff=>9}},
- BlJtRampOn =>{a=> 13.0,s=>0.4,l=>3,min=>0 ,max=>9 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jump from rampOn" ,lit=>{no=>0,dlyOn=>1,refOn=>2,on=>3,dlyOff=>4,refOff=>5,off=>6,rampOn=>8,rampOff=>9}},
- BlJtRampOff =>{a=> 13.4,s=>0.4,l=>3,min=>0 ,max=>9 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jump from rampOff" ,lit=>{no=>0,dlyOn=>1,refOn=>2,on=>3,dlyOff=>4,refOff=>5,off=>6,rampOn=>8,rampOff=>9}},
- BlJtRefOn =>{a=> 28.0,s=>0.4,l=>3,min=>0 ,max=>9 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jump from refOn" ,lit=>{no=>0,dlyOn=>1,refOn=>2,on=>3,dlyOff=>4,refOff=>5,off=>6,rampOn=>8,rampOff=>9}},
- BlJtRefOff =>{a=> 28.4,s=>0.4,l=>3,min=>0 ,max=>9 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jump from refOff" ,lit=>{no=>0,dlyOn=>1,refOn=>2,on=>3,dlyOff=>4,refOff=>5,off=>6,rampOn=>8,rampOff=>9}},
-
- DimJtOn =>{a=> 11.0,s=>0.4,l=>3,min=>0 ,max=>6 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jump from On" ,lit=>{no=>0,dlyOn=>1,rampOn=>2,on=>3,dlyOff=>4,rampOff=>5,Off=>6}},
- DimJtOff =>{a=> 11.4,s=>0.4,l=>3,min=>0 ,max=>6 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jump from Off" ,lit=>{no=>0,dlyOn=>1,rampOn=>2,on=>3,dlyOff=>4,rampOff=>5,Off=>6}},
- DimJtDlyOn =>{a=> 12.0,s=>0.4,l=>3,min=>0 ,max=>6 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jump from delayOn" ,lit=>{no=>0,dlyOn=>1,rampOn=>2,on=>3,dlyOff=>4,rampOff=>5,Off=>6}},
- DimJtDlyOff =>{a=> 12.4,s=>0.4,l=>3,min=>0 ,max=>6 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jump from delayOff",lit=>{no=>0,dlyOn=>1,rampOn=>2,on=>3,dlyOff=>4,rampOff=>5,Off=>6}},
- DimJtRampOn =>{a=> 13.0,s=>0.4,l=>3,min=>0 ,max=>6 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jump from rampOn" ,lit=>{no=>0,dlyOn=>1,rampOn=>2,on=>3,dlyOff=>4,rampOff=>5,Off=>6}},
- DimJtRampOff =>{a=> 13.4,s=>0.4,l=>3,min=>0 ,max=>6 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jump from rampOff" ,lit=>{no=>0,dlyOn=>1,rampOn=>2,on=>3,dlyOff=>4,rampOff=>5,Off=>6}},
-
- DimElsJtOn =>{a=> 39.0,s=>0.4,l=>3,min=>0 ,max=>6 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"else Jump from On" ,lit=>{no=>0,dlyOn=>1,rampOn=>2,on=>3,dlyOff=>4,rampOff=>5,Off=>6}},
- DimElsJtOff =>{a=> 39.4,s=>0.4,l=>3,min=>0 ,max=>6 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"else Jump from Off" ,lit=>{no=>0,dlyOn=>1,rampOn=>2,on=>3,dlyOff=>4,rampOff=>5,Off=>6}},
- DimElsJtDlyOn =>{a=> 40.0,s=>0.4,l=>3,min=>0 ,max=>6 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"else Jump from delayOn" ,lit=>{no=>0,dlyOn=>1,rampOn=>2,on=>3,dlyOff=>4,rampOff=>5,Off=>6}},
- DimElsJtDlyOff =>{a=> 40.4,s=>0.4,l=>3,min=>0 ,max=>6 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"else Jump from delayOff",lit=>{no=>0,dlyOn=>1,rampOn=>2,on=>3,dlyOff=>4,rampOff=>5,Off=>6}},
- DimElsJtRampOn =>{a=> 41.0,s=>0.4,l=>3,min=>0 ,max=>6 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"else Jump from rampOn" ,lit=>{no=>0,dlyOn=>1,rampOn=>2,on=>3,dlyOff=>4,rampOff=>5,Off=>6}},
- DimElsJtRampOff =>{a=> 41.4,s=>0.4,l=>3,min=>0 ,max=>6 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"else Jump from rampOff" ,lit=>{no=>0,dlyOn=>1,rampOn=>2,on=>3,dlyOff=>4,rampOff=>5,Off=>6}},
-
- SwJtOn =>{a=> 11.0,s=>0.4,l=>3,min=>0 ,max=>6 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jump from On" ,lit=>{no=>0,dlyOn=>1,on=>3,dlyOff=>4,off=>6}},
- SwJtOff =>{a=> 11.4,s=>0.4,l=>3,min=>0 ,max=>6 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jump from Off" ,lit=>{no=>0,dlyOn=>1,on=>3,dlyOff=>4,off=>6}},
- SwJtDlyOn =>{a=> 12.0,s=>0.4,l=>3,min=>0 ,max=>6 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jump from delayOn" ,lit=>{no=>0,dlyOn=>1,on=>3,dlyOff=>4,off=>6}},
- SwJtDlyOff =>{a=> 12.4,s=>0.4,l=>3,min=>0 ,max=>6 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jump from delayOff",lit=>{no=>0,dlyOn=>1,on=>3,dlyOff=>4,off=>6}},
-
- KeyJtOn =>{a=> 11.0,s=>0.4,l=>3,min=>0 ,max=>7 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jump from On" ,lit=>{no=>0,dlyUnlock=>1,rampUnlock=>2,lock=>3,dlyLock=>4,rampLock=>5,lock=>6,open=>8}},
- KeyJtOff =>{a=> 11.4,s=>0.4,l=>3,min=>0 ,max=>7 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jump from Off" ,lit=>{no=>0,dlyUnlock=>1,rampUnlock=>2,lock=>3,dlyLock=>4,rampLock=>5,lock=>6,open=>8}},
-
- WinJtOn =>{a=> 11.0,s=>0.4,l=>3,min=>0 ,max=>9 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jump from Off" ,lit=>{no=>0,rampOnDly=>1,rampOn=>2,on=>3,ramoOffDly=>4,rampOff=>5,Off=>6,rampOnFast=>8,rampOffFast=>9}},
- WinJtOff =>{a=> 11.4,s=>0.4,l=>3,min=>0 ,max=>9 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jump from Off" ,lit=>{no=>0,rampOnDly=>1,rampOn=>2,on=>3,ramoOffDly=>4,rampOff=>5,Off=>6,rampOnFast=>8,rampOffFast=>9}},
- WinJtRampOn =>{a=> 13.0,s=>0.4,l=>3,min=>0 ,max=>9 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jump from Off" ,lit=>{no=>0,rampOnDly=>1,rampOn=>2,on=>3,ramoOffDly=>4,rampOff=>5,Off=>6,rampOnFast=>8,rampOffFast=>9}},
- WinJtRampOff =>{a=> 13.4,s=>0.4,l=>3,min=>0 ,max=>9 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jump from Off" ,lit=>{no=>0,rampOnDly=>1,rampOn=>2,on=>3,ramoOffDly=>4,rampOff=>5,Off=>6,rampOnFast=>8,rampOffFast=>9}},
-
- CtRampOn =>{a=> 1.0,s=>0.4,l=>3,min=>0 ,max=>5 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jmp on condition from rampOn" ,lit=>{geLo=>0,geHi=>1,ltLo=>2,ltHi=>3,between=>4,outside=>5}},
- CtRampOff =>{a=> 1.4,s=>0.4,l=>3,min=>0 ,max=>5 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jmp on condition from rampOff" ,lit=>{geLo=>0,geHi=>1,ltLo=>2,ltHi=>3,between=>4,outside=>5}},
- CtDlyOn =>{a=> 2.0,s=>0.4,l=>3,min=>0 ,max=>5 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jmp on condition from delayOn" ,lit=>{geLo=>0,geHi=>1,ltLo=>2,ltHi=>3,between=>4,outside=>5}},
- CtDlyOff =>{a=> 2.4,s=>0.4,l=>3,min=>0 ,max=>5 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jmp on condition from delayOff" ,lit=>{geLo=>0,geHi=>1,ltLo=>2,ltHi=>3,between=>4,outside=>5}},
- CtOn =>{a=> 3.0,s=>0.4,l=>3,min=>0 ,max=>5 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jmp on condition from On" ,lit=>{geLo=>0,geHi=>1,ltLo=>2,ltHi=>3,between=>4,outside=>5}},
- CtOff =>{a=> 3.4,s=>0.4,l=>3,min=>0 ,max=>5 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jmp on condition from Off" ,lit=>{geLo=>0,geHi=>1,ltLo=>2,ltHi=>3,between=>4,outside=>5}},
- CtValLo =>{a=> 4.0,s=>1 ,l=>3,min=>0 ,max=>255 ,c=>'' ,f=>'' ,u=>'' ,d=>0,t=>"Condition value low for CT table" },
- CtValHi =>{a=> 5.0,s=>1 ,l=>3,min=>0 ,max=>255 ,c=>'' ,f=>'' ,u=>'' ,d=>0,t=>"Condition value high for CT table" },
- CtRefOn =>{a=> 28.0,s=>0.4,l=>3,min=>0 ,max=>5 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jmp on condition from refOn" ,lit=>{geLo=>0,geHi=>1,ltLo=>2,ltHi=>3,between=>4,outside=>5}},
- CtRefOff =>{a=> 28.4,s=>0.4,l=>3,min=>0 ,max=>5 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Jmp on condition from refOff" ,lit=>{geLo=>0,geHi=>1,ltLo=>2,ltHi=>3,between=>4,outside=>5}},
-);
-
-my %culHmRegDefine = (
- intKeyVisib =>{a=> 2.7,s=>0.1,l=>0,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>'visibility of internal channel',lit=>{invisib=>0,visib=>1}},
- pairCentral =>{a=> 10.0,s=>3.0,l=>0,min=>0 ,max=>16777215,c=>'hex' ,f=>'' ,u=>'' ,d=>1,t=>'pairing to central'},
-#blindActuator mainly
- lgMultiExec =>{a=> 10.5,s=>0.1,l=>3,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'' ,d=>1,t=>"multiple execution per repeat of long trigger" ,lit=>{off=>0,on=>1}},
- driveDown =>{a=> 11.0,s=>2.0,l=>1,min=>0 ,max=>6000.0 ,c=>'factor' ,f=>10 ,u=>'s' ,d=>1,t=>"drive time up"},
- driveUp =>{a=> 13.0,s=>2.0,l=>1,min=>0 ,max=>6000.0 ,c=>'factor' ,f=>10 ,u=>'s' ,d=>1,t=>"drive time up"},
- driveTurn =>{a=> 15.0,s=>1.0,l=>1,min=>0 ,max=>6000.0 ,c=>'factor' ,f=>10 ,u=>'s' ,d=>1,t=>"fliptime up <=>down"},
- refRunCounter =>{a=> 16.0,s=>1.0,l=>1,min=>0 ,max=>255 ,c=>'' ,f=>'' ,u=>'' ,d=>0,t=>"reference run counter"},
-
-#repeater
- compMode =>{a=> 23.0,s=>0.1,l=>0,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'' ,d=>1,t=>"compatibility moden" ,lit=>{off=>0,on=>1}},
-#remote mainly
- language =>{a=> 7.0,s=>1.0,l=>0,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'' ,d=>1,t=>"Language" ,lit=>{English=>0,German=>1}},
- backAtKey =>{a=> 13.7,s=>0.1,l=>0,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'' ,d=>1,t=>"Backlight at keystroke",lit=>{off=>0,on=>1}},
- backAtMotion =>{a=> 13.6,s=>0.1,l=>0,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'' ,d=>1,t=>"Backlight at motion" ,lit=>{off=>0,on=>1}},
- backAtCharge =>{a=> 13.5,s=>0.1,l=>0,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'' ,d=>1,t=>"Backlight at Charge" ,lit=>{off=>0,on=>1}},
- stbyTime =>{a=> 14.0,s=>1.0,l=>0,min=>1 ,max=>99 ,c=>'' ,f=>'' ,u=>'s' ,d=>1,t=>"Standby Time"},
- backOnTime =>{a=> 14.0,s=>1.0,l=>0,min=>0 ,max=>255 ,c=>'' ,f=>'' ,u=>'s' ,d=>1,t=>"Backlight On Time"},
-
- longPress =>{a=> 4.4,s=>0.4,l=>1,min=>0.3,max=>1.8 ,c=>'m10s3' ,f=>'' ,u=>'s' ,d=>0,t=>"time to detect key long press"},
- dblPress =>{a=> 9.0,s=>0.4,l=>1,min=>0 ,max=>1.5 ,c=>'factor' ,f=>10 ,u=>'s' ,d=>0,t=>"time to detect double press"},
- msgShowTime =>{a=> 45.0,s=>1.0,l=>1,min=>0.0,max=>120 ,c=>'factor' ,f=>2 ,u=>'s' ,d=>1,t=>"Message show time(RC19). 0=always on"},
- beepAtAlarm =>{a=> 46.0,s=>0.2,l=>1,min=>0 ,max=>3 ,c=>'lit' ,f=>'' ,u=>'' ,d=>1,t=>"Beep Alarm" ,lit=>{none=>0,tone1=>1,tone2=>2,tone3=>3}},
- beepAtService =>{a=> 46.2,s=>0.2,l=>1,min=>0 ,max=>3 ,c=>'lit' ,f=>'' ,u=>'' ,d=>1,t=>"Beep Service" ,lit=>{none=>0,tone1=>1,tone2=>2,tone3=>3}},
- beepAtInfo =>{a=> 46.4,s=>0.2,l=>1,min=>0 ,max=>3 ,c=>'lit' ,f=>'' ,u=>'' ,d=>1,t=>"Beep Info" ,lit=>{none=>0,tone1=>1,tone2=>2,tone3=>3}},
- backlAtAlarm =>{a=> 47.0,s=>0.2,l=>1,min=>0 ,max=>3 ,c=>'lit' ,f=>'' ,u=>'' ,d=>1,t=>"Backlight Alarm" ,lit=>{off=>0,on=>1,blinkSlow=>2,blinkFast=>3}},
- backlAtService =>{a=> 47.2,s=>0.2,l=>1,min=>0 ,max=>3 ,c=>'lit' ,f=>'' ,u=>'' ,d=>1,t=>"Backlight Service" ,lit=>{off=>0,on=>1,blinkSlow=>2,blinkFast=>3}},
- backlAtInfo =>{a=> 47.4,s=>0.2,l=>1,min=>0 ,max=>3 ,c=>'lit' ,f=>'' ,u=>'' ,d=>1,t=>"Backlight Info" ,lit=>{off=>0,on=>1,blinkSlow=>2,blinkFast=>3}},
-
- peerNeedsBurst =>{a=> 1.0,s=>0.1,l=>4,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'' ,d=>1,t=>"peer expects burst",lit=>{off=>0,on=>1}},
- expectAES =>{a=> 1.7,s=>0.1,l=>4,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'' ,d=>1,t=>"expect AES" ,lit=>{off=>0,on=>1}},
- lcdSymb =>{a=> 2.0,s=>0.1,l=>4,min=>0 ,max=>255 ,c=>'hex' ,f=>'' ,u=>'' ,d=>0,t=>"bitmask which symbol to display on message"},
- lcdLvlInterp =>{a=> 3.0,s=>0.1,l=>4,min=>0 ,max=>255 ,c=>'hex' ,f=>'' ,u=>'' ,d=>0,t=>"bitmask fro symbols"},
-#dimmer mainly
- loadErrCalib =>{a=> 18.0,s=>1.0,l=>1,min=>0 ,max=>255 ,c=>'' ,f=>'' ,u=>"" ,d=>0,t=>"Load Error Calibration"},
- transmitTryMax =>{a=> 48.0,s=>1.0,l=>1,min=>1 ,max=>10 ,c=>'' ,f=>'' ,u=>"" ,d=>0,t=>"max message re-transmit"},
- loadAppearBehav =>{a=> 49.0,s=>0.2,l=>1,min=>0 ,max=>3 ,c=>'lit' ,f=>'' ,u=>"" ,d=>1,t=>"behavior on load appearence at restart",lit=>{off=>0,last=>1,btnPress=>2,btnPressIfWasOn=>3}},
- ovrTempLvl =>{a=> 50.0,s=>1.0,l=>1,min=>30 ,max=>100 ,c=>'' ,f=>'' ,u=>"C" ,d=>0,t=>"overtemperatur level"},
- fuseDelay =>{a=> 51.0,s=>1.0,l=>1,min=>0 ,max=>2.55 ,c=>'factor' ,f=>100 ,u=>"s" ,d=>0,t=>"fuse delay"},
- redTempLvl =>{a=> 52.0,s=>1.0,l=>1,min=>30 ,max=>100 ,c=>'' ,f=>'' ,u=>"C" ,d=>0,t=>"reduced temperatur recover"},
- redLvl =>{a=> 53.0,s=>1.0,l=>1,min=>0 ,max=>100 ,c=>'factor' ,f=>2 ,u=>"%" ,d=>0,t=>"reduced power level"},
- powerUpAction =>{a=> 86.0,s=>0.1,l=>1,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>"" ,d=>1,t=>"behavior on power up" ,lit=>{off=>0,on=>1}},
- statusInfoMinDly=>{a=> 87.0,s=>0.5,l=>1,min=>0.5,max=>15.5 ,c=>'factor' ,f=>2 ,u=>"s" ,d=>0,t=>"status message min delay"},
- statusInfoRandom=>{a=> 87.5,s=>0.3,l=>1,min=>0 ,max=>7 ,c=>'' ,f=>'' ,u=>"s" ,d=>0,t=>"status message random delay"},
- characteristic =>{a=> 88.0,s=>0.1,l=>1,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>"" ,d=>1,t=>"" ,lit=>{linear=>0,square=>1}},
- logicCombination=>{a=> 89.0,s=>0.5,l=>1,min=>0 ,max=>16 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"" ,lit=>{inactive=>0,or=>1,and=>2,xor=>3,nor=>4,nand=>5,orinv=>6,andinv=>7,plus=>8,minus=>9,mul=>10,plusinv=>11,minusinv=>12,mulinv=>13,invPlus=>14,invMinus=>15,invMul=>16}},
-#CC-TC
- backlOnTime =>{a=> 5.0,s=>0.6,l=>0,min=>1 ,max=>25 ,c=>"" ,f=>'' ,u=>'s' ,d=>0,t=>"Backlight ontime"},
- backlOnMode =>{a=> 5.6,s=>0.2,l=>0,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Backlight mode" ,lit=>{off=>0,auto=>1}},
- btnLock =>{a=> 15 ,s=>1 ,l=>0,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Button Lock" ,lit=>{unlock=>0,lock=>1}},
-
- dispTempHum =>{a=> 1.0,s=>0.1,l=>5,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'' ,d=>1,t=>"" ,lit=>{temp=>0,tempHumidity=>1}},
- dispTempInfo =>{a=> 1.1,s=>0.1,l=>5,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'' ,d=>1,t=>"" ,lit=>{actual=>0,setPoint=>1}},
- dispTempUnit =>{a=> 1.2,s=>0.1,l=>5,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'' ,d=>1,t=>"" ,lit=>{Celsius=>0,Fahrenheit=>1}},
- mdTempReg =>{a=> 1.3,s=>0.2,l=>5,min=>0 ,max=>3 ,c=>'lit' ,f=>'' ,u=>'' ,d=>1,t=>"" ,lit=>{manual=>0,auto=>1,central=>2,party=>3}},
- decalDay =>{a=> 1.5,s=>0.3,l=>5,min=>0 ,max=>7 ,c=>'lit' ,f=>'' ,u=>'' ,d=>1,t=>"Decalc weekday" ,lit=>{sat=>0,sun=>1,mon=>2,tue=>3,wed=>4,thu=>5,fri=>6}},
- mdTempValve =>{a=> 2.6,s=>0.2,l=>5,min=>0 ,max=>2 ,c=>'lit' ,f=>'' ,u=>'' ,d=>1,t=>"" ,lit=>{auto=>0,close=>1,open=>2}},
- tempComfort =>{a=> 3 ,s=>0.6,l=>5,min=>6 ,max=>30 ,c=>'factor' ,f=>2 ,u=>'C' ,d=>1,t=>"comfort temp value"},
- tempLower =>{a=> 4 ,s=>0.6,l=>5,min=>6 ,max=>30 ,c=>'factor' ,f=>2 ,u=>'C' ,d=>1,t=>"comfort temp value"},
- tempWinOpen =>{a=> 5 ,s=>0.6,l=>5,min=>6 ,max=>30 ,c=>'factor' ,f=>2 ,u=>'C' ,d=>1,t=>"Temperature for Win open !chan 3 only!"},
- tempParty =>{a=> 6 ,s=>0.6,l=>5,min=>6 ,max=>30 ,c=>'factor' ,f=>2 ,u=>'C' ,d=>1,t=>"Temperature for Party"},
- decalMin =>{a=> 8 ,s=>0.3,l=>5,min=>0 ,max=>50 ,c=>'factor' ,f=>0.1 ,u=>'min' ,d=>1,t=>"Decalc min"},
- decalHr =>{a=> 8.3,s=>0.5,l=>5,min=>0 ,max=>23 ,c=>'' ,f=>'' ,u=>'h' ,d=>1,t=>"Decalc hour"},
- partyEndHr =>{a=> 97 ,s=>0.6,l=>6,min=>0 ,max=>23 ,c=>'' ,f=>'' ,u=>'h' ,d=>1,t=>"Party end Hour"},
- partyEndMin =>{a=> 97.7,s=>0.1,l=>6,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'min' ,d=>1,t=>"Party end min",lit=>{"00"=>0,"30"=>1}},
- partyEndDay =>{a=> 98 ,s=>1 ,l=>6,min=>0 ,max=>200 ,c=>'' ,f=>'' ,u=>'d' ,d=>1,t=>"Party end Day"},
-#Thermal-cc-VD
- valveOffset =>{a=> 9 ,s=>0.5,l=>5,min=>0 ,max=>25 ,c=>'' ,f=>'' ,u=>'%' ,d=>1,t=>"Valve offset"}, # size actually 0.5
- valveError =>{a=> 10 ,s=>1 ,l=>5,min=>0 ,max=>99 ,c=>'' ,f=>'' ,u=>'%' ,d=>1,t=>"Valve position when error"},# size actually 0.7
-#SCD
- msgScdPosA =>{a=> 32.6,s=>0.2,l=>1,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Message for position A",lit=>{noMsg=>0,lvlNormal=>1}},
- msgScdPosB =>{a=> 32.4,s=>0.2,l=>1,min=>0 ,max=>3 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Message for position B",lit=>{noMsg=>0,lvlNormal=>1,lvlAddStrong=>2,lvlAdd=>3}},
- msgScdPosC =>{a=> 32.2,s=>0.2,l=>1,min=>0 ,max=>3 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Message for position C",lit=>{noMsg=>0,lvlNormal=>1,lvlAddStrong=>2,lvlAdd=>3}},
- msgScdPosD =>{a=> 32.0,s=>0.2,l=>1,min=>0 ,max=>3 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Message for position D",lit=>{noMsg=>0,lvlNormal=>1,lvlAddStrong=>2,lvlAdd=>3}},
- evtFltrTime =>{a=> 35.0,s=>1 ,l=>1,min=>600,max=>1200 ,c=>'' ,f=>1.6 ,u=>'s' ,d=>0,t=>"Event filter time",},#todo check calculation
-#rhs - different literals
- msgRhsPosA =>{a=> 32.6,s=>0.2,l=>1,min=>0 ,max=>3 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Message for position A",lit=>{noMsg=>0,closed=>1,open=>2,tilted=>3}},
- msgRhsPosB =>{a=> 32.4,s=>0.2,l=>1,min=>0 ,max=>3 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Message for position B",lit=>{noMsg=>0,closed=>1,open=>2,tilted=>3}},
- msgRhsPosC =>{a=> 32.2,s=>0.2,l=>1,min=>0 ,max=>3 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Message for position C",lit=>{noMsg=>0,closed=>1,open=>2,tilted=>3}},
- evtDly =>{a=> 33 ,s=>1 ,l=>1,min=>0 ,max=>7620 ,c=>'factor' ,f=>1.6 ,u=>'s' ,d=>0,t=>"Event delay time",},#todo check calculation
-# keymatic/winmatic secific register
- signal =>{a=> 3.4,s=>0.1,l=>0,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Confirmation beep" ,lit=>{off=>0,on=>1}},
- signalTone =>{a=> 3.6,s=>0.2,l=>0,min=>0 ,max=>3 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"" ,lit=>{low=>0,mid=>1,high=>2,veryHigh=>3}},
- keypressSignal =>{a=> 3.0,s=>0.1,l=>0,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Keypress beep" ,lit=>{off=>0,on=>1}},
- holdTime =>{a=> 20 ,s=>1, l=>1,min=>0 ,max=>8.16 ,c=>'factor' ,f=>31.25 ,u=>'s' ,d=>0,t=>"Holdtime for door opening"},
- holdPWM =>{a=> 21 ,s=>1, l=>1,min=>0 ,max=>255 ,c=>'' ,f=>'' ,u=>'' ,d=>0,t=>"Holdtime pulse wide modulation"},
- setupDir =>{a=> 22 ,s=>0.1,l=>1,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Rotation direction for locking",lit=>{right=>0,left=>1}},
- setupPosition =>{a=> 23 ,s=>1 ,l=>1,min=>0 ,max=>3000 ,c=>'factor' ,f=>0.06666 ,u=>'deg' ,d=>1,t=>"Rotation angle neutral position"},
- angelOpen =>{a=> 24 ,s=>1 ,l=>1,min=>0 ,max=>3000 ,c=>'factor' ,f=>0.06666 ,u=>'deg' ,d=>1,t=>"Door opening angle"},
- angelMax =>{a=> 25 ,s=>1 ,l=>1,min=>0 ,max=>3000 ,c=>'factor' ,f=>0.06666 ,u=>'deg' ,d=>1,t=>"Angle maximum"},
- angelLocked =>{a=> 26 ,s=>1 ,l=>1,min=>0 ,max=>3000 ,c=>'factor' ,f=>0.06666 ,u=>'deg' ,d=>1,t=>"Angle Locked position"},
- pullForce =>{a=> 28 ,s=>1 ,l=>1,min=>0 ,max=>100 ,c=>'factor' ,f=>2 ,u=>'%' ,d=>1,t=>"pull force level"},
- pushForce =>{a=> 29 ,s=>1 ,l=>1,min=>0 ,max=>100 ,c=>'factor' ,f=>2 ,u=>'%' ,d=>1,t=>"push force level"},
- tiltMax =>{a=> 30 ,s=>1 ,l=>1,min=>0 ,max=>255 ,c=>'' ,f=>'' ,u=>'' ,d=>1,t=>"maximum tilt level"},
- ledFlashUnlocked=>{a=> 31.3,s=>0.1,l=>1,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"LED blinks when not locked",lit=>{off=>0,on=>1}},
- ledFlashLocked =>{a=> 31.6,s=>0.1,l=>1,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"LED blinks when locked" ,lit=>{off=>0,on=>1}},
-# sec_mdir
- cyclicInfoMsg =>{a=> 9 ,s=>1 ,l=>0,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'' ,d=>1,t=>"cyclic message",lit=>{off=>0,on=>1}},
- sabotageMsg =>{a=> 16.0,s=>1 ,l=>0,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'' ,d=>1,t=>"enable sabotage message" ,lit=>{off=>0,on=>1}},
- lowBatLimit =>{a=> 18.0,s=>1 ,l=>0,min=>10 ,max=>12 ,c=>'factor' ,f=>10 ,u=>'V' ,d=>1,t=>"low batterie limit"},
- batDefectLimit =>{a=> 19.0,s=>1 ,l=>0,min=>0.1,max=>2 ,c=>'factor' ,f=>100 ,u=>'Ohm' ,d=>1,t=>"batterie defect detection"},
- transmDevTryMax =>{a=> 20.0,s=>1.0,l=>0,min=>1 ,max=>10 ,c=>'' ,f=>'' ,u=>'' ,d=>0,t=>"max message re-transmit"},
-
- evtFltrPeriod =>{a=> 1.0,s=>0.4,l=>1,min=>0.5,max=>7.5 ,c=>'factor' ,f=>2 ,u=>'s' ,d=>1,t=>"event filter period"},
- evtFltrNum =>{a=> 1.4,s=>0.4,l=>1,min=>1 ,max=>15 ,c=>'' ,f=>'' ,u=>'' ,d=>1,t=>"sensitivity - read sach n-th puls"},
- minInterval =>{a=> 2.0,s=>0.3,l=>1,min=>0 ,max=>4 ,c=>'lit' ,f=>'' ,u=>'' ,d=>1,t=>"minimum interval in sec" ,lit=>{15=>0,30=>1,60=>2,120=>3,240=>4}},
- captInInterval =>{a=> 2.3,s=>0.1,l=>1,min=>0 ,max=>1 ,c=>'lit' ,f=>'' ,u=>'' ,d=>1,t=>"capture within interval" ,lit=>{off=>0,on=>1}},
- brightFilter =>{a=> 2.4,s=>0.4,l=>1,min=>0 ,max=>7 ,c=>'' ,f=>'' ,u=>'' ,d=>1,t=>"brightness filter - ignore light at night"},
- msgScPosA =>{a=> 32.6,s=>0.2,l=>1,min=>0 ,max=>2 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Message for position A",lit=>{noMsg=>0,closed=>1,open=>2}},
- msgScPosB =>{a=> 32.4,s=>0.2,l=>1,min=>0 ,max=>2 ,c=>'lit' ,f=>'' ,u=>'' ,d=>0,t=>"Message for position B",lit=>{noMsg=>0,closed=>1,open=>2}},
- eventDlyTime =>{a=> 33 ,s=>1 ,l=>1,min=>0 ,max=>7620 ,c=>'fltCvT' ,f=>'' ,u=>'s' ,d=>1,t=>"event delay time"},
- ledOnTime =>{a=> 34 ,s=>1 ,l=>1,min=>0 ,max=>1.275 ,c=>'factor' ,f=>200 ,u=>'s' ,d=>0,t=>"LED ontime"},
- eventFilterTime =>{a=> 35 ,s=>1 ,l=>1,min=>0 ,max=>7620 ,c=>'fltCvT' ,f=>'' ,u=>'s' ,d=>0,t=>"evetn filter time"},
-
-# weather units
- stormUpThresh =>{a=> 6 ,s=>1 ,l=>1,min=>0 ,max=>255 ,c=>'' ,f=>'' ,u=>'' ,d=>1,t=>"Storm upper threshold"},
- stormLowThresh =>{a=> 7 ,s=>1 ,l=>1,min=>0 ,max=>255 ,c=>'' ,f=>'' ,u=>'' ,d=>1,t=>"Storm lower threshold"},
-# others
- localResetDis =>{a=> 7 ,s=>1 ,l=>1,min=>0 ,max=>255 ,c=>'lit' ,f=>'' ,u=>'' ,d=>1,t=>"LocalReset disable",lit=>{off=>0,on=>1}},
- );
-
-my %culHmRegGeneral = (
- intKeyVisib=>1,pairCentral=>1,
- );
-my %culHmRegType = (
- remote=> {expectAES=>1,peerNeedsBurst=>1,dblPress=>1,longPress=>1},
- blindActuator=> {driveUp =>1,driveDown =>1,driveTurn =>1,refRunCounter =>1,
- transmitTryMax =>1,statusInfoMinDly=>1,statusInfoRandom=>1, # nt present in all files
- MaxTimeF =>1,
- OnDly =>1,OnTime =>1,OffDly =>1,OffTime =>1,
- OffLevel =>1,OnLevel =>1,
- ActionType =>1,OnTimeMode =>1,OffTimeMode =>1,DriveMode =>1,
- BlJtOn =>1,BlJtOff =>1,BlJtDlyOn =>1,BlJtDlyOff =>1,
- BlJtRampOn =>1,BlJtRampOff =>1,BlJtRefOn =>1,BlJtRefOff =>1,
- CtValLo =>1,CtValHi =>1,
- CtOn =>1,CtDlyOn =>1,CtRampOn =>1,CtRefOn =>1,
- CtOff =>1,CtDlyOff =>1,CtRampOff =>1,CtRefOff =>1,
- lgMultiExec =>1,
- },
- dimmer=> {transmitTryMax =>1,statusInfoMinDly=>1,statusInfoRandom=>1,powerUpAction =>1,
- ovrTempLvl =>1,redTempLvl =>1,redLvl =>1,fuseDelay =>1,#not dim.L
- OnDly =>1,OnTime =>1,OffDly =>1,OffTime =>1,
- OffDlyBlink =>1,OnLvlPrio =>1,OnDlyMode =>1,
- ActionTypeDim =>1,OnTimeMode =>1,OffTimeMode =>1,
- OffLevel =>1,OnMinLevel =>1,OnLevel =>1,
- RampSstep =>1,RampOnTime =>1,RampOffTime =>1,
- DimMinLvl =>1,DimMaxLvl =>1,DimStep =>1,
- DimJtOn =>1,DimJtOff =>1,DimJtDlyOn =>1,
- DimJtDlyOff =>1,DimJtRampOn =>1,DimJtRampOff =>1,
- CtValLo =>1,CtValHi =>1,
- CtOn =>1,CtDlyOn =>1,CtRampOn =>1,
- CtOff =>1,CtDlyOff =>1,CtRampOff =>1,
- OffDlyNewTime =>1,OffDlyNewTime =>1,
- DimElsOffTimeMd =>1,DimElsOnTimeMd =>1,
- DimElsActionType=>1,
- DimElsJtOn =>1,DimElsJtOff =>1,DimElsJtDlyOn =>1,
- DimElsJtDlyOff =>1,DimElsJtRampOn =>1,DimElsJtRampOff =>1,
- lgMultiExec =>1,
- logicCombination=>1,
- },
- switch=> {OnTime =>1,OffTime =>1,OnDly =>1,OffDly =>1,
- SwJtOn =>1,SwJtOff =>1,SwJtDlyOn =>1,SwJtDlyOff =>1,
- CtValLo =>1,CtValHi =>1,
- CtOn =>1,CtDlyOn =>1,CtOff =>1,CtDlyOff =>1,
- ActionType =>1,OnTimeMode =>1,OffTimeMode =>1,
- lgMultiExec =>1,
- },
- winMatic=>{
- signal =>1,signalTone =>1,keypressSignal =>1,
- },
- keyMatic=>{
- signal =>1,signalTone =>1,keypressSignal =>1,
- holdTime =>1,holdPWM =>1,setupDir =>1,setupPosition =>1,
- angelOpen =>1,angelMax =>1,angelLocked =>1,
- ledFlashUnlocked=>1,ledFlashLocked =>1,
- CtValLo =>1,CtValHi =>1,
- CtOn =>1,CtOff =>1,
- KeyJtOn =>1,KeyJtOff =>1,
- },
- motionDetector=>{
- evtFltrPeriod =>1,evtFltrNum =>1,minInterval =>1,
- captInInterval=>1,brightFilter =>1,ledOnTime =>1,
- },
-);
-
-my %culHmRegModel = (
- "HM-RC-12" => {backAtKey =>1, backAtMotion =>1, backOnTime =>1},
- "HM-RC-12-B" => {backAtKey =>1, backAtMotion =>1, backOnTime =>1},
- "HM-RC-12-SW"=> {backAtKey =>1, backAtMotion =>1, backOnTime =>1},
-
- "HM-RC-19" => {backAtKey =>1, backAtMotion =>1, backOnTime =>1,backAtCharge =>1, language =>1,},
- "HM-RC-19-B" => {backAtKey =>1, backAtMotion =>1, backOnTime =>1,backAtCharge =>1, language =>1,},
- "HM-RC-19-SW"=> {backAtKey =>1, backAtMotion =>1, backOnTime =>1,backAtCharge =>1, language =>1,},
-
- "HM-LC-Dim1PWM-CV"=>{characteristic =>1},
- "HM-LC-Dim1L-P" =>{loadAppearBehav =>1,loadErrCalib =>1},
- "HM-LC-Dim1L-CV" =>{loadAppearBehav =>1,loadErrCalib =>1},
- "HM-LC-Dim2L-SM" =>{loadAppearBehav =>1,loadErrCalib =>1},
-
- "HM-CC-VD" =>{valveOffset =>1,valveError =>1},
- "HM-PB-4DIS-WM" =>{peerNeedsBurst =>1,expectAES =>1,language =>1,stbyTime =>1},
- "HM-WDS100-C6-O" =>{stormUpThresh =>1,stormLowThresh =>1},
- "KS550" =>{stormUpThresh =>1,stormLowThresh =>1},
- "HM-OU-CFM-PL" =>{localResetDis =>1,
- OnTime =>1,OffTime =>1, OnDly =>1,OffDly =>1,
- SwJtOn =>1,SwJtOff =>1,SwJtDlyOn =>1,SwJtDlyOff =>1,
- CtValLo =>1,CtValHi =>1,
- CtOn =>1,CtDlyOn =>1,CtOff =>1,CtDlyOff =>1,
- OnTimeMode =>1,OffTimeMode =>1,
- ActType =>1,ActNum =>1},
- "HM-SEC-MDIR" =>{sabotageMsg =>1,},
- "HM-CC-TC" =>{backlOnTime =>1,backlOnMode =>1,btnLock =>1},
- "HM-CC-SCD" =>{peerNeedsBurst =>1,expectAES =>1,
- transmitTryMax =>1,evtFltrTime =>1,
- msgScdPosA =>1,msgScdPosB =>1,msgScdPosC =>1,msgScdPosD =>1,},
- "HM-SEC-RHS" =>{peerNeedsBurst =>1,expectAES =>1,
- cyclicInfoMsg =>1,transmDevTryMax =>1,
- msgRhsPosA =>1,msgRhsPosB =>1,msgRhsPosC =>1,
- evtDly =>1,ledOnTime =>1,transmitTryMax =>1,},
- "HM-SEC-SC" =>{cyclicInfoMsg =>1,sabotageMsg =>1,transmDevTryMax =>1,
- msgScPosA =>1,msgScPosB =>1,
- ledOnTime =>1,transmitTryMax =>1,eventDlyTime =>1,
- peerNeedsBurst =>1,expectAES =>1,},
- "HM-SCI-3-FM" =>{cyclicInfoMsg =>1 ,transmDevTryMax =>1,
- msgScPosA =>1,msgScPosB =>1,
- transmitTryMax =>1,eventDlyTime =>1,
- peerNeedsBurst =>1,expectAES =>1,},
- "HM-SEC-TIS" =>{cyclicInfoMsg =>1,sabotageMsg =>1,transmDevTryMax =>1,
- msgScPosA =>1,msgScPosB =>1,
- ledOnTime =>1,transmitTryMax =>1,eventFilterTime =>1,
- peerNeedsBurst =>1,expectAES =>1,},
- "HM-SEC-SFA-SM" =>{cyclicInfoMsg =>1,sabotageMsg =>1,transmDevTryMax =>1,
- lowBatLimit =>1,batDefectLimit =>1,
- transmitTryMax =>1,},
- "HM-Sys-sRP-Pl" =>{compMode =>1,},
-);
-my %culHmRegChan = (# if channelspecific then enter them here
- "HM-CC-TC02" => {
- dispTempHum =>1,dispTempInfo =>1,dispTempUnit =>1,mdTempReg =>1,
- mdTempValve =>1,tempComfort =>1,tempLower =>1,partyEndDay =>1,
- partyEndMin =>1,partyEndHr =>1,tempParty =>1,decalDay =>1,
- decalHr =>1,decalMin =>1,
- },
- "HM-CC-TC03" =>{tempWinOpen =>1, }, #window channel
- "HM-RC-1912" =>{msgShowTime =>1, beepAtAlarm =>1, beepAtService =>1,beepAtInfo =>1,
- backlAtAlarm =>1, backlAtService =>1, backlAtInfo =>1,
- lcdSymb =>1, lcdLvlInterp =>1},
- "HM-RC-19-B12" =>{msgShowTime =>1, beepAtAlarm =>1, beepAtService =>1,beepAtInfo =>1,
- backlAtAlarm =>1, backlAtService =>1, backlAtInfo =>1,
- lcdSymb =>1, lcdLvlInterp =>1},
- "HM-RC-19-SW12" =>{msgShowTime =>1, beepAtAlarm =>1, beepAtService =>1,beepAtInfo =>1,
- backlAtAlarm =>1, backlAtService =>1, backlAtInfo =>1,
- lcdSymb =>1, lcdLvlInterp =>1},
- "HM-OU-CFM-PL02"=>{Intense=>1},
- "HM-SEC-WIN01" =>{setupDir =>1,pullForce =>1,pushForce =>1,tiltMax =>1,
- CtValLo =>1,CtValHi =>1,
- CtOn =>1,CtOff =>1,CtRampOn =>1,CtRampOff =>1,
- WinJtOn =>1,WinJtOff =>1,WinJtRampOn =>1,WinJtRampOff =>1,
- OnTime =>1,OffTime =>1,OffLevelKm =>1,
- OnLevelKm =>1,OnRampOnSp =>1,OnRampOffSp =>1
- }
- );
-
-##--------------- Conversion routines for register settings
-my %fltCvT = (0.1=>3.1,1=>31,5=>155,10=>310,60=>1860,300=>9300,
- 600=>18600,3600=>111600);
-#############################
-sub
-CUL_HM_Attr($$$)
-{
- my ($cmd,$name, $attrName,$attrVal) = @_;
- my @hashL;
- if ($attrName eq "expert"){
- $attr{$name}{expert} = $attrVal;
- my $eHash = CUL_HM_name2Hash($name);
- foreach my $chId (CUL_HM_getAssChnIds($name)){
- my $cHash = CUL_HM_id2Hash($chId);
- push(@hashL,$cHash) if ($eHash ne $cHash);
- }
- push(@hashL,$eHash);
- foreach my $hash (@hashL){
- my $exLvl = CUL_HM_getExpertMode($hash);
- if ($exLvl eq "0"){# off
- foreach my $rdEntry (keys %{$hash->{READINGS}}){
- my $rdEntryNew;
- $rdEntryNew = ".".$rdEntry if ($rdEntry =~m /^RegL_/);
- if ($rdEntry =~m /^R-/){
- my $reg = $rdEntry;
- $reg =~ s/.*-//;
- $rdEntryNew = ".".$rdEntry if($culHmRegDefine{$reg}{d} eq '0' );
- }
- next if (!defined($rdEntryNew)); # no change necessary
- delete $hash->{READINGS}{$rdEntryNew};
- $hash->{READINGS}{$rdEntryNew} = $hash->{READINGS}{$rdEntry};
- delete $hash->{READINGS}{$rdEntry};
- }
- }
- elsif ($exLvl eq "1"){# on: Only register values, no raw data
- # move register to visible if available
- foreach my $rdEntry (keys %{$hash->{READINGS}}){
- my $rdEntryNew;
- $rdEntryNew = substr($rdEntry,1) if ($rdEntry =~m /^\.R-/);
- $rdEntryNew = ".".$rdEntry if ($rdEntry =~m /^RegL_/);
- next if (!$rdEntryNew); # no change necessary
- delete $hash->{READINGS}{$rdEntryNew};
- $hash->{READINGS}{$rdEntryNew} = $hash->{READINGS}{$rdEntry};
- delete $hash->{READINGS}{$rdEntry};
- }
- }
- elsif ($exLvl eq "2"){# full - incl raw data
- foreach my $rdEntry (keys %{$hash->{READINGS}}){
- my $rdEntryNew;
- $rdEntryNew = substr($rdEntry,1) if (($rdEntry =~m /^\.RegL_/) ||
- ($rdEntry =~m /^\.R-/));
- next if (!$rdEntryNew); # no change necessary
- delete $hash->{READINGS}{$rdEntryNew};
- $hash->{READINGS}{$rdEntryNew} = $hash->{READINGS}{$rdEntry};
- delete $hash->{READINGS}{$rdEntry};
- }
- }
- else{;
- }
- }
- }
- return;
-}
-
-sub
-CUL_HM_initRegHash()
-{ #duplicate short and long press register
- foreach my $reg (keys %culHmRegDefShLg){ #update register list
- %{$culHmRegDefine{"sh".$reg}} = %{$culHmRegDefShLg{$reg}};
- %{$culHmRegDefine{"lg".$reg}} = %{$culHmRegDefShLg{$reg}};
- $culHmRegDefine{"lg".$reg}{a} +=0x80;
- }
- foreach my $type(sort(keys %culHmRegType)){ #update references to register
- foreach my $reg (sort(keys %{$culHmRegType{$type}})){
- if ($culHmRegDefShLg{$reg}){
- delete $culHmRegType{$type}{$reg};
- $culHmRegType{$type}{"sh".$reg} = 1;
- $culHmRegType{$type}{"lg".$reg} = 1;
- }
- }
- }
- foreach my $type(sort(keys %culHmRegModel)){ #update references to register
- foreach my $reg (sort(keys %{$culHmRegModel{$type}})){
- if ($culHmRegDefShLg{$reg}){
- delete $culHmRegModel{$type}{$reg};
- $culHmRegModel{$type}{"sh".$reg} = 1;
- $culHmRegModel{$type}{"lg".$reg} = 1;
- }
- }
- }
- foreach my $type(sort(keys %culHmRegChan)){ #update references to register
- foreach my $reg (sort(keys %{$culHmRegChan{$type}})){
- if ($culHmRegDefShLg{$reg}){
- delete $culHmRegChan{$type}{$reg};
- $culHmRegChan{$type}{"sh".$reg} = 1;
- $culHmRegChan{$type}{"lg".$reg} = 1;
- }
- }
- }
-}
-
-sub
-CUL_HM_fltCvT($) # float -> config time
-{
- my ($inValue) = @_;
- my $exp = 0;
- my $div2;
- foreach my $div(sort{$a <=> $b} keys %fltCvT){
- $div2 = $div;
- last if ($inValue < $fltCvT{$div});
- $exp++;
- }
- return ($exp << 5)+int($inValue/$div2);
-}
-sub
-CUL_HM_CvTflt($) # config time -> float
-{
- my ($inValue) = @_;
- return ($inValue & 0x1f)*((sort {$a <=> $b} keys(%fltCvT))[$inValue >> 5]);
-}
-
-
-#define gets - try use same names as for set
-my %culHmGlobalGets = (
- param => "",
- reg => " ... ",
- regList => "",
- saveConfig => "",
-);
-my %culHmSubTypeGets = (
- none4Type =>
- { "test"=>"" },
-);
-my %culHmModelGets = (
- none4Mod=>
- { "none" => "",
- },
-);
-sub
-CUL_HM_TCtempReadings($)
-{
- my ($hash)=@_;
- my $name = $hash->{NAME};
- my $regLN = ((CUL_HM_getExpertMode($hash) eq "2")?"":".")."RegL_";
- my $reg5 = ReadingsVal($name,$regLN."05:" ,"");
- my $reg6 = ReadingsVal($name,$regLN."06:" ,"");
- my @days = ("Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri");
- $reg5 =~ s/.* 0B://; #remove register up to addr 11 from list 5
- my $tempRegs = $reg5.$reg6; #one row
- $tempRegs =~ s/ 00:00/ /g; #remove regline termination
- $tempRegs =~ s/ ..:/,/g; #remove addr Info
- $tempRegs =~ s/ //g; #blank
- my @Tregs = split(",",$tempRegs);
- my @time = @Tregs[grep !($_ % 2), 0..$#Tregs]; # even-index =time
- my @temp = @Tregs[grep $_ % 2, 0..$#Tregs]; # odd-index =data
- return "reglist incomplete\n" if (scalar( @time )<168);
- foreach (@time){$_=hex($_)*10};
- foreach (@temp){$_=hex($_)/2};
- my $setting;
- my @changedRead;
- push (@changedRead,"tempList_State:".
- (($hash->{helper}{shadowReg}{$regLN."05:"} ||
- $hash->{helper}{shadowReg}{$regLN."06:"} )?"set":"verified"));
- for (my $day = 0;$day<7;$day++){
- my $tSpan = 0;
- my $dayRead = "";
- for (my $entry = 0;$entry<24;$entry++){
- my $reg = $day *24 + $entry;
- last if ($tSpan > 1430);
- $tSpan = $time[$reg];
- my $entry = sprintf("%02d:%02d %3.01f",($tSpan/60),($tSpan%60),$temp[$reg]);
- $setting .= "Temp set: ".$days[$day]." ".$entry." C\n";
- $dayRead .= " ".$entry;
- $tSpan = $time[$reg];
- }
- push (@changedRead,"tempList".$days[$day].":".$dayRead);
- }
- CUL_HM_UpdtReadBulk($hash,1,@changedRead) if (@changedRead);
- return $setting;
-}
-sub
-CUL_HM_repReadings($)
-{
- my ($hash)=@_;
- my $name = $hash->{NAME};
- my $regLN = ((CUL_HM_getExpertMode($hash) eq "2")?"":".")."RegL_";
- my $reg2 = ReadingsVal($name,$regLN."02:" ,"");
- $reg2 =~ s/..://g;
- $reg2 =~ s/ //g;
- my @array = $reg2 =~ /(.{14})/g; # entry is sendID,recID and bdcast
- my $entry = 0;
- my $ret;
- foreach (@array){
- my @arr2 = $_ =~ /(.{6})(.{6})(.{2})/;
- $entry++;
- next if ($arr2[1] eq '000000' && $arr2[0] eq '000000');
- $ret .= $entry.
- " Bdcast:".((hex($arr2[2])& 0x01)?"on":"off").
- " sendID:".(($arr2[0] ne "000000")?CUL_HM_id2Name($arr2[0]):"none ").
- " recID:" .(($arr2[1] ne "000000")?CUL_HM_id2Name($arr2[1]):"none ")."\n";
- }
- return $ret;
-}
-###################################
-sub
-CUL_HM_Get($@)
-{
- my ($hash, @a) = @_;
- return "no get value specified" if(@a < 2);
-
- my $name = $hash->{NAME};
- my $devName = $hash->{device}?$hash->{device}:$name;
- my $st = AttrVal($devName, "subType", "");
- my $md = AttrVal($devName, "model", "");
- my $mId = CUL_HM_getMId($hash);
- my $rxType = CUL_HM_getRxType($hash);
-
- my $class = AttrVal($devName, "hmClass", "");#relevant is the device
- my $cmd = $a[1];
- my $dst = $hash->{DEF};
- my $isChannel = (length($dst) == 8)?"true":"";
- my $chn = ($isChannel)?substr($dst,6,2):"01";
- $dst = substr($dst,0,6);
-
- my $devHash = CUL_HM_getDeviceHash($hash);
- my $h = $culHmGlobalGets{$cmd};
- $h = $culHmSubTypeGets{$st}{$cmd} if(!defined($h) && $culHmSubTypeGets{$st});
- $h = $culHmModelGets{$md}{$cmd} if(!defined($h) && $culHmModelGets{$md});
- my @h;
- @h = split(" ", $h) if($h);
-
- if(!defined($h)) {
- my @arr = keys %culHmGlobalGets;
- push @arr, keys %{$culHmSubTypeGets{$st}} if($culHmSubTypeGets{$st});
- push @arr, keys %{$culHmModelGets{$md}} if($culHmModelGets{$md});
- my $usg = "Unknown argument $cmd, choose one of ".join(" ",sort @arr);
-
- return $usg;
- }elsif($h eq "" && @a != 2) {
- return "$cmd requires no parameters";
-
- } elsif($h !~ m/\.\.\./ && @h != @a-2) {
- return "$cmd requires parameter: $h";
- }
- my $id = CUL_HM_Id($hash->{IODev});
-
- #----------- now start processing --------------
- if($cmd eq "param") { ######################################################
- return $attr{$name}{$a[2]} if ($attr{$name}{$a[2]});
- return $hash->{READINGS}{$a[2]}{VAL} if ($hash->{READINGS}{$a[2]});
- return $attr{$devName}{$a[2]} if ($attr{$devName}{$a[2]});
- return $devHash->{READINGS}{$a[2]}{VAL} if ($devHash->{READINGS}{$a[2]});
- return $hash->{$a[2]} if ($hash->{$a[2]});
- return $devHash->{$a[2]} if ($devHash->{$a[2]});
- return $hash->{helper}{$a[2]} if ($hash->{helper}{$a[2]} && ref($hash->{helper}{$a[2]}) ne "HASH");
- return $devHash->{helper}{$a[2]} if ($devHash->{helper}{$a[2]});
- return "undefined";
- }
- elsif($cmd eq "reg") { #####################################################
- my (undef,undef,$regReq,$list,$peerId) = @a;
- if ($regReq eq 'all'){
- my @regArr = keys %culHmRegGeneral;
- push @regArr, keys %{$culHmRegType{$st}} if($culHmRegType{$st});
- push @regArr, keys %{$culHmRegModel{$md}} if($culHmRegModel{$md});
- push @regArr, keys %{$culHmRegChan{$md.$chn}} if($culHmRegChan{$md.$chn});
-
- my @peers; # get all peers we have a reglist
- my @listWp; # list that require peers
- foreach my $readEntry (keys %{$hash->{READINGS}}){
- if ($readEntry =~m /^[\.]?RegL_(.*)/){ #reg Reading "RegL_:peerN
- my $peer = substr($1,3);
- next if (!$peer);
- push(@peers,$peer);
- push(@listWp,substr($1,1,1));
- }
- }
- my @regValList; #storage of results
- my $regHeader = "list:peer\tregister :value\n";
- foreach my $regName (@regArr){
- my $regL = $culHmRegDefine{$regName}->{l};
- my @peerExe = (grep (/$regL/,@listWp))?@peers:("00000000");
- foreach my $peer(@peerExe){
- next if($peer eq "");
- my $regVal= CUL_HM_getRegFromStore($name,$regName,0,$peer);#determine
- my $peerN = CUL_HM_id2Name($peer);
- $peerN = " " if ($peer eq "00000000");
- push @regValList,sprintf(" %d:%s\t%-16s :%s\n",
- $regL,$peerN,$regName,$regVal)
- if ($regVal ne 'invalid');
- }
- }
- my $addInfo = ""; #todo - find a generic way to handle special devices
- $addInfo = CUL_HM_TCtempReadings($hash)
- if ($md eq "HM-CC-TC" && $chn eq "02");
-
- $addInfo = CUL_HM_repReadings($hash) if ($md eq "HM-Sys-sRP-Pl");
-
- return $name." type:".$st." - \n".
- $regHeader.join("",sort(@regValList)).
- $addInfo;
- }
- else{
- my $regVal = CUL_HM_getRegFromStore($name,$regReq,$list,$peerId);
- return ($regVal eq "invalid")? "Value not captured"
- : $regVal;
- }
- }
- elsif($cmd eq "regList") { #################################################
- my @regArr = keys %culHmRegGeneral ;
- push @regArr, keys %{$culHmRegType{$st}} if($culHmRegType{$st});
- push @regArr, keys %{$culHmRegModel{$md}} if($culHmRegModel{$md});
-
- if ($isChannel){
- push @regArr, keys %{$culHmRegChan{$md.$chn}} if($culHmRegChan{$md.$chn});
- }
- else{# add all ugly channel register to device view
- for my $chnId (CUL_HM_getAssChnIds($name)){
- my $chnN = substr($chnId,6,2);
- push @regArr, keys %{$culHmRegChan{$md.$chnN}}
- if($culHmRegChan{$md.$chnN});
- }
- }
-
- my @rI;
- foreach my $regName (@regArr){
- my $reg = $culHmRegDefine{$regName};
- my $help = $reg->{t};
- my ($min,$max) = ($reg->{min},$reg->{max});
- if (defined($reg->{lit})){
- $help .= " options:".join(",",keys%{$reg->{lit}});
- $min =$max ="-";
- }
- push @rI,sprintf("%4d: %-16s | %3s to %-11s | %8s |%-3s| %s\n",
- $reg->{l},$regName,$min,$max.$reg->{u},
- ((($reg->{l} == 3)||($reg->{l} == 4))?"required":""),
- (($reg->{d} != 1)?"exp":""),
- $help)
- if (!($isChannel && $reg->{l} == 0));
- }
-
- my $info = sprintf("list: %16s | %-18s | %-8s |%-3s| %s\n",
- "register","range","peer","exp","description");
- foreach(sort(@rI)){$info .= $_;}
- return $info;
- }
- elsif($cmd eq "saveConfig"){ ###############################################
- my $fName = $a[2];
- open(aSave, ">>$fName") || return("Can't open $fName: $!");
-
- print aSave "\n\n#======== store device data:".$devName." === from: ".TimeNow();
- my @eNames;
- push @eNames,$devName;
- foreach my $e (CUL_HM_getAssChnIds($name)){
- my $eName = CUL_HM_id2Name($e);
- push @eNames, $eName if($eName !~ m/_chn:/);
- }
-
- foreach my $eName (@eNames){
- print aSave "\n#--- entity:".$eName;
- my $pIds = AttrVal($eName, "peerIDs", "");
- my $timestamps = "\n# timestamp of the readings for reference";
- if ($pIds){
- print aSave "\n# Peer Names:".ReadingsVal($eName,"peerList","");
- $timestamps .= "\n# ".ReadingsTimestamp($eName,"peerList","")." :peerList";
- print aSave "\nset ".$eName." peerBulk ".$pIds;
- }
- my $ehash = CUL_HM_name2Hash($eName);
- foreach my $read (sort keys %{$ehash->{READINGS}}){
- next if ($read !~ m/^[\.]?RegL_/);
- print aSave "\nset ".$eName." regBulk ".$read." ".ReadingsVal($eName,$read,"");
- $timestamps .= "\n# ".ReadingsTimestamp($eName,$read,"")." :".$read;
- }
- print aSave $timestamps;
- }
- print aSave "\n======= finished ===\n";
- close(aSave);
- }
-
- Log GetLogLevel($name,4), "CUL_HM get $name " . join(" ", @a[1..$#a]);
-
- CUL_HM_ProcessCmdStack($devHash) if ($rxType & 0x03);#burst/all
- return "";
-}
-###################################
-my %culHmGlobalSets = (
- raw => "data ...",
- reset => "",
- pair => "",
- unpair => "",
- sign => "[on|off]",
- regRaw => "[List0|List1|List2|List3|List4|List5|List6] ... ", #todo Updt2 remove
- regBulk => ": ...",
- peerBulk => "",
- statusRequest => "",
- getpair => "",
- getdevicepair => "",
- getRegRaw =>"[List0|List1|List2|List3|List4|List5|List6] ... ",
- getConfig => "",
- regSet =>" ... ",
- virtual =>"",
- actiondetect =>"",
- clear =>"[readings|msgEvents]",
-);
-my %culHmSubTypeSets = (
- switch =>{ "on-for-timer"=>"sec", "on-till"=>"time",
- on=>"", off=>"", toggle=>"" },
- dimmer =>{ "on-for-timer"=>"sec", "on-till"=>"time",
- on=>"", off=>"", toggle=>"", pct=>"", stop=>""},
- blindActuator =>{ on=>"", off=>"", toggle=>"", pct=>"", stop=>""},
- remote =>{ devicepair => " device ... [single|dual] [set|unset] [actor|remote|both]",},
- pushButton =>{ devicepair => " device ... [single|dual] [set|unset] [actor|remote|both]",},
- threeStateSensor =>{ devicepair => " device ... single [set|unset] [actor|remote|both]",},
- motionDetector =>{ devicepair => " device ... single [set|unset] [actor|remote|both]",},
- virtual =>{ raw => "data ...",
- devicepair => " device ... [single|dual] [set|unset] [actor|remote|both]",
- press => "[long|short]...",
- valvePos => "position",#acting as TC
- virtual =>"",}, #redef necessary for virtual
- smokeDetector =>{ test => "", alarmOn=>"", alarmOff=>"",
- devicepair => " device ... single [set|unset] actor",},
- winMatic =>{ matic => "",
- keydef => " ",
- create => "" },
- keyMatic =>{ lock =>"",
- unlock =>"[sec] ...",
- open =>"[sec] ...",
- inhibit =>"[on|off]"},
-);
-my %culHmModelSets = (
- "HM-CC-VD"=>{
- valvePos => "position"},
- "HM-RC-19"=> {
- service => "",
- alarm => "",
- display => " [comma,no] [unit] [off|1|2|3] [off|on|slow|fast] "},
- "HM-RC-19-B"=> {
- service => "",
- alarm => "",
- display => " [comma,no] [unit] [off|1|2|3] [off|on|slow|fast] "},
- "HM-RC-19-SW"=> {
- service => "",
- alarm => "",
- display => " [comma,no] [unit] [off|1|2|3] [off|on|slow|fast] "},
- "HM-PB-4DIS-WM"=>{
- text => " [on|off] "},
- "HM-OU-LED16" =>{
- led =>"[off|red|green|orange]" ,
- ilum =>"[0-15] [0-127]" },
- "HM-OU-CFM-PL"=>{
- led => "[,..]",
- playTone => "[,..]"},
- "HM-Sys-sRP-Pl"=>{
- setRepeat => "[no1..36] [bdcast-yes|no]"},
-);
-
-my %culHmChanSets = (
- "HM-CC-TC00"=>{
- devicepair => " device ... [single|dual] [set|unset] [actor|remote|both]",
- "day-temp" => "[on,off,6.0..30.0]",
- "night-temp" => "[on,off,6.0..30.0]",
- "party-temp" => "[on,off,6.0..30.0]",
- "desired-temp" => "[on,off,6.0..30.0]",
- tempListSat => "HH:MM temp ...",
- tempListSun => "HH:MM temp ...",
- tempListMon => "HH:MM temp ...",
- tempListTue => "HH:MM temp ...",
- tempListThu => "HH:MM temp ...",
- tempListWed => "HH:MM temp ...",
- tempListFri => "HH:MM temp ...",
- displayMode => "[temp-only|temp-hum]",
- displayTemp => "[actual|setpoint]",
- displayTempUnit => "[celsius|fahrenheit]",
- controlMode => "[manual|auto|central|party]",
- decalcDay => "day", },
- "HM-CC-TC02"=>{
- devicepair => " device ... [single|dual] [set|unset] [actor|remote|both]",
- "day-temp" => "[on,off,6.0..30.0]",
- "night-temp" => "[on,off,6.0..30.0]",
- "party-temp" => "[on,off,6.0..30.0]",
- "desired-temp" => "[on,off,6.0..30.0]",
- tempListSat => "HH:MM temp ...",
- tempListSun => "HH:MM temp ...",
- tempListMon => "HH:MM temp ...",
- tempListTue => "HH:MM temp ...",
- tempListThu => "HH:MM temp ...",
- tempListWed => "HH:MM temp ...",
- tempListFri => "HH:MM temp ...",
- displayMode => "[temp-only|temp-hum]",
- displayTemp => "[actual|setpoint]",
- displayTempUnit => "[celsius|fahrenheit]",
- controlMode => "[manual|auto|central|party]",
- decalcDay => "day", },
- "HM-SEC-WIN01"=>{ stop =>"",
- level =>" ..."},
-);
-
-##############################################
-sub
-CUL_HM_getMId($)
-{#in: hash(chn or dev) out:model key (key for %culHmModel).
- # Will store result in device helper
- my ($hash) = @_;
- $hash = CUL_HM_getDeviceHash($hash);
- my $mId = $hash->{helper}{mId};
- if (!$mId){
- my $model = AttrVal($hash->{NAME}, "model", "");
- foreach my $mIdKey(keys%culHmModel){
- if ($culHmModel{$mIdKey}{name} && $culHmModel{$mIdKey}{name} eq $model){
- $mId = $hash->{helper}{mId} = $mIdKey;
- return $mIdKey;
- }
- }
- }
- return $mId;
-}
-##############################################
-sub
-CUL_HM_getRxType($)
-{ #in:hash(chn or dev) out:binary coded Rx type
- # Will store result in device helper
- my ($hash) = @_;
- $hash = CUL_HM_getDeviceHash($hash);
- no warnings; #convert regardless of content
- my $rxtEntity = int($hash->{helper}{rxType});
- use warnings;
- if (!$rxtEntity){ #at least one bit must be set
- my $MId = CUL_HM_getMId($hash);
- my $rxtOfModel = $culHmModel{$MId}{rxt} if ($MId && $culHmModel{$MId}{rxt});
- if ($rxtOfModel){
- $rxtEntity |= ($rxtOfModel =~ m/b/)?0x02:0;#burst
- $rxtEntity |= ($rxtOfModel =~ m/c/)?0x04:0;#config
- $rxtEntity |= ($rxtOfModel =~ m/w/)?0x08:0;#wakeup
- }
- $rxtEntity = 1 if (!$rxtEntity);#always
- $hash->{helper}{rxType} = $rxtEntity;
- }
- return $rxtEntity;
-}
-##############################################
-sub
-CUL_HM_getFlag($)
-{#msgFlag set to 'A0' for normal and 'B0' for burst devices
- # currently not supported is the wakeupflag since it is hardly used
- my ($hash) = @_;
- return (CUL_HM_getRxType($hash) & 0x02)?"B0":"A0"; #set burst flag
-}
-
-sub
-CUL_HM_Set($@)
-{
- my ($hash, @a) = @_;
- my ($ret, $tval, $rval); #added rval for ramptime by unimatrix
-
- return "no set value specified" if(@a < 2);
-
- my $name = $hash->{NAME};
- my $devName = $hash->{device}?$hash->{device}:$name;
- my $st = AttrVal($devName, "subType", "");
- my $md = AttrVal($devName, "model" , "");
- my $class = AttrVal($devName, "hmClass", "");#relevant is the device
-
- my $rxType = CUL_HM_getRxType($hash);
- my $flag = CUL_HM_getFlag($hash); #set burst flag
- my $cmd = $a[1];
- my $dst = $hash->{DEF};
- my $isChannel = (length($dst) == 8)?"true":"";
- my $chn = ($isChannel)?substr($dst,6,2):"01";
- $dst = substr($dst,0,6);
- my $devHash = CUL_HM_getDeviceHash($hash);
-
- my $mdCh = $md.($isChannel?$chn:"00"); # chan specific commands?
- my $h = $culHmGlobalSets{$cmd} if($st ne "virtual");
- $h = $culHmSubTypeSets{$st}{$cmd} if(!defined($h) && $culHmSubTypeSets{$st});
- $h = $culHmModelSets{$md}{$cmd} if(!defined($h) && $culHmModelSets{$md} );
- $h = $culHmChanSets{$mdCh}{$cmd} if(!defined($h) && $culHmChanSets{$mdCh} );
-
- my @h;
- @h = split(" ", $h) if($h);
-
- if(!defined($h) && defined($culHmSubTypeSets{$st}{pct}) && $cmd =~ m/^\d+/) {
- $cmd = "pct";
- }
- elsif(!defined($h)) {
- my @arr;
- @arr = keys %culHmGlobalSets if($st ne "virtual");
- push @arr, keys %{$culHmSubTypeSets{$st}} if($culHmSubTypeSets{$st});
- push @arr, keys %{$culHmModelSets{$md}} if($culHmModelSets{$md});
- push @arr, keys %{$culHmChanSets{$mdCh}} if($culHmChanSets{$mdCh});
- my $usg = "Unknown argument $cmd, choose one of ".join(" ",sort @arr);
-
- if($usg =~ m/ pct/) {
- $usg =~ s/ pct/ pct:slider,0,1,100/;
- }
- elsif($md eq "HM-CC-TC") {
- my @list = map { ($_.".0", $_+0.5) } (6..30);
- pop @list;
- my $list = "on,off," . join(",",@list);
- $usg =~ s/-temp/-temp:$list/g;
- }
- return $usg;
- }
- elsif($cmd eq "pct") {
- splice @a, 1, 1;
- }
- elsif($h eq "" && @a != 2) {
- return "$cmd requires no parameters";
- }
- elsif($h !~ m/\.\.\./ && @h != @a-2) {
- return "$cmd requires parameter: $h";
- }
-
- my $id = CUL_HM_Id($hash->{IODev});
- my $state = "set_".join(" ", @a[1..(int(@a)-1)]);
-
- if($cmd eq "raw") { ########################################################
- return "Usage: set $a[0] $cmd data [data ...]" if(@a < 3);
- $state = "";
- for (my $i = 2; $i < @a; $i++) {
- CUL_HM_PushCmdStack($hash, $a[$i]);
- }
- }
- elsif($cmd eq "clear") { ####################################################
- my (undef,undef,$sect) = @a;
- if ($sect eq "readings"){
- delete $hash->{READINGS};
- }
- elsif($sect eq "msgEvents"){
- CUL_HM_respPendRm($hash);
- delete ($hash->{helper}{burstEvtCnt});
- delete ($hash->{cmdStack});
- foreach my $var (keys %{$attr{$name}}){ # can be removed versions later
- delete ($attr{$name}{$var}) if ($var =~ m/^prot/);
- }
- foreach my $var (keys %{$hash}){
- delete ($hash->{$var}) if ($var =~ m/^prot/);
- }
- $hash->{protState} = "Info_Cleared" ;
- }
- else{
- return "unknown section. User readings or msgEvents";
- }
- $state = "";
- }
- elsif($cmd eq "reset") { ####################################################
- CUL_HM_PushCmdStack($hash,"++".$flag."11".$id.$dst."0400");
- }
- elsif($cmd eq "pair") { #####################################################
- $state = "";
- return "pair is not enabled for this type of device, ".
- "use set hmPairForSec"
- if($class eq "sender");
- my $serialNr = AttrVal($name, "serialNr", undef);
- return "serialNr is not set" if(!$serialNr);
- CUL_HM_PushCmdStack($hash,"++A401".$id."000000010A".unpack("H*",$serialNr));
- $hash->{hmPairSerial} = $serialNr;
- }
- elsif($cmd eq "unpair") { ###################################################
- CUL_HM_pushConfig($hash, $id, $dst, 0,0,0,0, "02010A000B000C00");
- $state = "";
- }
- elsif($cmd eq "sign") { #####################################################
- CUL_HM_pushConfig($hash, $id, $dst, $chn,0,0,$chn,
- "08" . ($a[2] eq "on" ? "01":"02"));
- $state = "";
- }
- elsif($cmd eq "statusRequest") { ############################################
- my @chnIdList = CUL_HM_getAssChnIds($name);
- foreach my $channel (@chnIdList){
- my $chnNo = substr($channel,6,2);
- CUL_HM_PushCmdStack($hash,"++".$flag.'01'.$id.$dst.$chnNo.'0E');
- }
- $state = "";
- }
- elsif($cmd eq "getpair") { ##################################################
- CUL_HM_PushCmdStack($hash,'++'.$flag.'01'.$id.$dst.'00040000000000');
- $state = "";
- }
- elsif($cmd eq "getdevicepair") { ############################################
- CUL_HM_PushCmdStack($hash,'++'.$flag.'01'.$id.$dst.$chn.'03');
- $state = "";
- }
- elsif($cmd eq "getConfig") { ################################################
- my $chFound = 0;
- CUL_HM_PushCmdStack($hash,'++'.$flag.'01'.$id.$dst.'00040000000000')
- if (!$isChannel);
- my @chnIdList = CUL_HM_getAssChnIds($name);
- foreach my $channel (@chnIdList){
- my $chnHash = CUL_HM_id2Hash($channel);
- CUL_HM_getConfig($hash,$chnHash,$id,$dst,substr($channel,6,2));
- }
- $state = "";
- }
- elsif($cmd eq "peerBulk") { #################################################
- $state = "";
- my $pL = $a[2];
- foreach my $peer (split(',',$pL)){
- next if ($peer =~ m/^self/);
- my $pID = CUL_HM_peerChId($peer,$dst,$id);
- return "unknown peer".$peer if (length($pID) != 8);# peer only to channel
- my $pCh1 = substr($pID,6,2);
- my $pCh2 = $pCh1;
- if($culHmSubTypeSets{$st}{devicepair}||
- $culHmModelSets{$md}{devicepair}||
- $culHmChanSets{$mdCh}{devicepair}){
- $pCh2 = "00";
- }
- CUL_HM_PushCmdStack($hash,'++'.$flag.'01'.$id.$dst.$chn.'01'.
- substr($pID,0,6).$pCh1.$pCh2);
- }
- }
- elsif($cmd eq "regRaw" ||$cmd eq "regBulk"||$cmd eq "getRegRaw") { ##########
- my ($list,$addr,$data,$peerID);
- $state = "";
- ($list,$addr,$data,$peerID) = ($a[2],hex($a[3]),hex($a[4]),$a[5])
- if ($cmd eq "regRaw");
- if ($cmd eq "regBulk"){
- ($list) = ($a[2]);
- $list =~ s/[\.]?RegL_//;
- ($list,$peerID) = split(":",$list);
- return "unknown list Number:".$list if(hex($list)>6);
- }
-
- ($list,$peerID) = ($a[2],$a[3])if ($cmd eq "getRegRaw");#todo Updt2 remove
- $list =~ s/List/0/;# convert Listy to 0y
- # as of now only hex value allowed check range and convert
-
- $peerID = CUL_HM_peerChId(($peerID?$peerID:"00000000"),$dst,$id);
- my $peerChn = ((length($peerID) == 8)?substr($peerID,6,2):"01");# have to split chan and id
- $peerID = substr($peerID,0,6);
-
- if($cmd eq "getRegRaw"){
- if ($list eq "00"){
- CUL_HM_PushCmdStack($hash,'++'.$flag.'01'.$id.$dst.'00040000000000');
- }
- else{# other lists are per channel
- my @chnIdList = CUL_HM_getAssChnIds($name);
- foreach my $channel (@chnIdList){
- my $chnNo = substr($channel,6,2);
- if ($list =~m /0[34]/){#getPeers to see if list3 is available
- CUL_HM_PushCmdStack($hash,'++'.$flag.'01'.$id.$dst.$chnNo.'03');
- my $chnHash = CUL_HM_id2Hash($channel);
- $chnHash->{helper}{getCfgList} = $peerID.$peerChn;#list3 regs
- $chnHash->{helper}{getCfgListNo} = int($list);
- }
- else{
- CUL_HM_PushCmdStack($hash,'++'.$flag.'01'.$id.$dst.$chnNo.'04'
- .$peerID.$peerChn.$list);
- }
- }
- }
- }
- elsif($cmd eq "regBulk"){;
- my @adIn = @a;
- shift @adIn;shift @adIn;shift @adIn;
- my $adList;
- foreach my $ad (sort @adIn){
- ($addr,$data) = split(":",$ad);
- $adList .= sprintf("%02X%02X",hex($addr),hex($data)) if ($addr ne "00");
- return "wrong addr or data:".$ad if (hex($addr)>255 || hex($data)>255);
- }
- CUL_HM_pushConfig($hash,$id,$dst,$chn,$peerID,$peerChn,$list,$adList);
- }
- else{ #todo Updt2 remove
- return "outdated - use regBulk with changed format"; #todo Updt2 remove
- } #todo Updt2 remove
- }
- elsif($cmd eq "regSet") { ###################################################
- #set regSet
- my ($regName,$data,$peerChnIn) = ($a[2],$a[3],$a[4]);
- $state = "";
- if (!$culHmRegType{$st}{$regName} &&
- !$culHmRegGeneral{$regName} &&
- !$culHmRegModel{$md}{$regName} &&
- !$culHmRegChan{$md.$chn}{$regName} ){
- my @regArr = keys %culHmRegGeneral ;
- push @regArr, keys %{$culHmRegType{$st}} if($culHmRegType{$st});
- push @regArr, keys %{$culHmRegModel{$md}} if($culHmRegModel{$md});
- push @regArr, keys %{$culHmRegChan{$md.$chn}} if($culHmRegChan{$md.$chn});
- return "supported register are ".join(" ",sort @regArr);
- }
-
- my $reg = $culHmRegDefine{$regName};
- return $st." - ".$regName # give some help
- ." range:". $reg->{min}." to ".$reg->{max}.$reg->{u}
- .(($reg->{l} == 3)?" peer required":"")." : ".$reg->{t}."\n"
- if ($data eq "?");
- return "value:".$data." out of range for Reg \"".$regName."\""
- if (!($reg->{c} eq 'lit'||$reg->{c} eq 'hex')&&
- ($data < $reg->{min} ||$data > $reg->{max})); # none number
- return"invalid value. use:". join(",",keys%{$reg->{lit}})
- if ($reg->{c} eq 'lit' && !defined($reg->{lit}{$data}));
-
- my $conversion = $reg->{c};
- if (!$conversion){;# do nothing
- }elsif($conversion eq "factor"){$data *= $reg->{f};# use factor
- }elsif($conversion eq "fltCvT"){$data = CUL_HM_fltCvT($data);
- }elsif($conversion eq "m10s3") {$data = $data*10-3;
- }elsif($conversion eq "hex") {$data = hex($data);
- }elsif($conversion eq "lit") {$data = $reg->{lit}{$data};
- }else{return " conversion undefined - please contact admin";
- }
-
- my $addr = int($reg->{a}); # bit location later
- my $list = $reg->{l};
- my $bit = ($reg->{a}*10)%10; # get fraction
-
- my $dLen = $reg->{s}; # datalength in bit
- $dLen = int($dLen)*8+(($dLen*10)%10);
- # only allow it level if length less then one byte!!
- return "partial Word error: ".$dLen if($dLen != 8*int($dLen/8) && $dLen>7);
- no warnings qw(overflow portable);
- my $mask = (0xffffffff>>(32-$dLen));
- use warnings qw(overflow portable);
- my $dataStr = substr(sprintf("%08X",($data & $mask) << $bit),
- 8-int($reg->{s}+0.99)*2,);
-
- my ($lChn,$peerID,$peerChn) = ($chn,"000000","00");
- if (($list == 3) ||($list == 4)){ # peer is necessary for list 3/4
- return "Peer not specified" if (!$peerChnIn);
- $peerID = CUL_HM_peerChId($peerChnIn,$dst,$id);
- $peerChn = ((length($peerID) == 8)?substr($peerID,6,2):"01");
- $peerID = substr($peerID,0,6);
- return "Peer not specified" if (!$peerID);
- }
- elsif($list == 0){
- $lChn = "00";
- }
- else{ #if($list == 1/5/6){
- $lChn = "01" if ($chn eq "00"); #by default select chan 01 for device
- }
-
- my $addrData;
- if ($dLen < 8){# fractional byte see whether we have stored the register
- #read full 8 bit!!!
- my $rName = CUL_HM_id2Name($dst.$lChn);
- $rName =~ s/_chn:.*//;
- my $curVal = CUL_HM_getRegFromStore($rName,
- $addr,$list,$peerID.$peerChn);
- return "cannot read current value for Bitfield - retrieve Data first"
- if (!$curVal);
- $curVal =~ s/set_//; # set is not relevant, we take it as given
- $data = ($curVal & (~($mask<<$bit)))|($data<<$bit);
- $addrData.=sprintf("%02X%02X",$addr,$data);
- }
- else{
- for (my $cnt = 0;$cnt{s}+0.99);$cnt++){
- $addrData.=sprintf("%02X",$addr+$cnt).substr($dataStr,$cnt*2,2);
- }
- }
- CUL_HM_pushConfig($hash,$id,$dst,$lChn,$peerID,$peerChn,$list,$addrData);
- }
- elsif($cmd eq "level") { ####################################################
- #level =>" ..."
- my (undef,undef,$lvl,$rLocDly,$speed) = @a;
- return "please enter level 0 to 100" if (!defined($lvl) || $lvl>100);
- return "reloclDelay range 0..65535 or ignore"
- if (defined($rLocDly) &&
- ($rLocDly > 65535 ||
- ($rLocDly < 0.1 && $rLocDly ne 'ignore' && $rLocDly ne '0' )));
- return "select speed range 0 to 100" if (defined($speed) && $speed>100);
- $rLocDly = 111600 if (!defined($rLocDly)||$rLocDly eq "ignore");# defaults
- $speed = 30 if (!defined($rLocDly));
- $rLocDly = CUL_HM_encodeTime8($rLocDly);# calculate hex value
- CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'81'.$chn.
- sprintf("%02X%02s%02X",$lvl*2,$rLocDly,$speed*2));
- }
- elsif($cmd eq "on") { #######################################################
- CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'02'.$chn.'C80000');
- }
- elsif($cmd eq "off") { ######################################################
- CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'02'.$chn.'000000');
- }
- elsif($cmd eq "on-for-timer"||$cmd eq "on-till") { ##########################
- my (undef,undef,$duration,$edate) = @a; #date prepared extention to entdate
- if ($cmd eq "on-till"){
- # to be extended to handle end date as well
- my ($eH,$eM,$eSec) = split(':',$duration);
- $eSec += $eH*3600 + $eM*60;
- my @lt = localtime;
- my $ltSec = $lt[2]*3600+$lt[1]*60+$lt[0];# actually strip of date
- $eSec += 3600*24 if ($ltSec > $eSec); # go for the next day
- $duration = $eSec - $ltSec;
- }
- return "please enter the duration in seconds" if (!defined ($duration));
- $tval = CUL_HM_encodeTime16($duration);# onTime 0.0..85825945.6, 0=forever
- CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'02'.$chn.'C80000'.$tval);
- }
- elsif($cmd eq "toggle") { ###################################################
- if($st eq "dimmer"){;
- CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'02'.$chn.
- (ReadingsVal($name,"state","on") eq "off" ?"C80000":"000000"));
- }
- else{
- $hash->{toggleIndex} = 1 if(!$hash->{toggleIndex});
- $hash->{toggleIndex} = (($hash->{toggleIndex}+1) % 128);
- CUL_HM_PushCmdStack($hash, sprintf("++%s3E%s%s%s40%s%02X",$flag,$id,
- $dst,$dst, $chn, $hash->{toggleIndex}));
- }
- }
- elsif($cmd eq "lock") { #####################################################
- CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'800100FF'); # LEVEL_SET
- }
- elsif($cmd eq "unlock") { ###################################################
- $tval = (@a > 2) ? int($a[2]) : 0;
- my $delay = ($tval > 0) ? CUL_HM_encodeTime8($tval) : "FF"; # RELOCK_DELAY (FF=never)
- CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'800101'.$delay);# LEVEL_SET
- }
- elsif($cmd eq "open") { #####################################################
- $tval = (@a > 2) ? int($a[2]) : 0;
- my $delay = ($tval > 0) ? CUL_HM_encodeTime8($tval) : "FF"; # RELOCK_DELAY (FF=never)
- CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'8001C8'.$delay);# OPEN
- $state = "";
- }
- elsif($cmd eq "inhibit") { ##################################################
- return "$a[2] is not on or off" if($a[2] !~ m/^(on|off)$/);
- my $val = ($a[2] eq "on") ? "01" : "00";
- CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.$val.'01'); # SET_LOCK
- $state = "";
- }
- elsif($cmd eq "pct") { ######################################################
- $a[1] = 100 if ($a[1] > 100);
- $tval = CUL_HM_encodeTime16(((@a > 2)&&$a[2]!=0)?$a[2]:6709248);# onTime 0.0..6709248, 0=forever
- $rval = CUL_HM_encodeTime16((@a > 3)?$a[3]:2.5); # rampTime 0.0..6709248, 0=immediate
- CUL_HM_PushCmdStack($hash,
- sprintf("++%s11%s%s02%s%02X%s%s",$flag,$id,$dst,$chn,$a[1]*2,$rval,$tval));
- }
- elsif($cmd eq "stop") { #####################################################
- CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'03'.$chn);
- }
- elsif($cmd eq "text") { #####################################################
- $state = "";
- return "$a[2] is not a button number" if($a[2] !~ m/^\d$/ || $a[2] < 1);
- return "$a[3] is not on or off" if($a[3] !~ m/^(on|off)$/);
- my $bn = $a[2]*2-($a[3] eq "on" ? 0 : 1);
-
- my ($l1, $l2, $s); # Create CONFIG_WRITE_INDEX string
- $l1 = $a[4] . "\x00";
- $l1 = substr($l1, 0, 13);
- $s = 54;
- $l1 =~ s/(.)/sprintf("%02X%02X",$s++,ord($1))/ge;
-
- $l2 = $a[5] . "\x00";
- $l2 = substr($l2, 0, 13);
- $s = 70;
- $l2 =~ s/(.)/sprintf("%02X%02X",$s++,ord($1))/ge;
- $l1 .= $l2;
-
- CUL_HM_pushConfig($hash, $id, $dst, $bn,0,0,1, $l1);
- }
- elsif($cmd eq "setRepeat") { ################################################
- # setRepeat => "[no1..36] [bdcast-yes|no]"}
- $state = "";
- return "entry must be between 1 and 36" if ($a[2] < 1 || $a[2] > 36);
- my $sndID = CUL_HM_name2Id($a[3]);
- my $recID = CUL_HM_name2Id($a[4]);
- return "sender ID unknown:".$sndID if ($sndID !~ m/(^[0-9A-F]{6})$/);
- return "receiver ID unknown:".$recID if ($recID !~ m/(^[0-9A-F]{6})$/);
- return "broadcast must be yes or now" if ($a[5] ne "yes" && $a[5] ne "no");
- my $pattern = $sndID.$recID.(($a[5] eq "no")?"00":"01");
- my $cnt = ($a[2]-1)*7+1;
- my $addrData;
- foreach ($pattern =~ /(.{2})/g){
- $addrData .= sprintf("%02X%s",$cnt++,$_);
- }
- CUL_HM_pushConfig($hash, $id, $dst, 1,0,0,2, $addrData);
-
- }
- elsif($cmd eq "display") { ##################################################
- my (undef,undef,undef,$t,$c,$u,$snd,$blk,$symb) = @_;
- return "cmd only possible for device or its display channel"
- if ($isChannel && $chn ne "12");
- my %symbol=(off => 0x0000,
- bulb =>0x0100,switch =>0x0200,window =>0x0400,door=>0x0800,
- blind=>0x1000,scene =>0x2000,phone =>0x4000,bell=>0x8000,
- clock=>0x0001,arrowUp=>0x0002,arrowDown=>0x0004);
- my %light=(off=>0,on=>1,slow=>2,fast=>3);
- my %unit=(off =>0,Proz=>1,Watt=>2,x3=>3,C=>4,x5=>5,x6=>6,x7=>7,
- F=>8,x9=>9,x10=>10,x11=>11,x12=>12,x13=>13,x14=>14,x15=>15);
-
- my @symbList = split(',',$symb);
- my $symbAdd = "";
- foreach my $symb (@symbList){
- if (!defined($symbol{$symb})){# wrong parameter
- return "'$symb ' unknown. Select one of ".join(" ",sort keys(%symbol));
- }
- $symbAdd |= $symbol{$symb};
- }
-
- return "$c not specified. Select one of [comma|no]"
- if ($c ne "comma" && $c ne "no");
- return "'$u' unknown. Select one of ".join(" ",sort keys(%unit))
- if (!defined($unit{$u}));
- return "'$snd' unknown. Select one of [off|1|2|3]"
- if ($snd ne "off" && $snd > 3);
- return "'$blk' unknown. Select one of ".join(" ",sort keys(%light))
- if (!defined($light{$blk}));
- my $beepBack = $snd | $light{$blk}*4;
-
- $symbAdd |= 0x0004 if ($c eq "comma");
- $symbAdd |= $unit{$u};
-
- my $text = sprintf("%5.5s",$t);#pad left with space
- $text = uc(unpack("H*",$text));
-
- CUL_HM_PushCmdStack($hash,sprintf("++%s11%s%s8012%s%04X%02X",
- $flag,$id,$dst,$text,$symbAdd,$beepBack));
- }
- elsif($cmd eq "alarm"||$cmd eq "service") { #################################
- return "$a[2] must be below 255" if ($a[2] >255 );
- $chn = 18 if ($chn eq "01");
- my $subtype = ($cmd eq "alarm")?"81":"82";
- CUL_HM_PushCmdStack($hash,
- sprintf("++%s11%s%s%s%s%02X",$flag,$id,$dst,$subtype,$chn, $a[2]));
- }
- elsif($cmd eq "led") { ######################################################
- if ($md eq "HM-OU-LED16"){
- my %color=(off=>0,red=>1,green=>2,orange=>3);
- if (length($hash->{DEF}) == 6){# command called for a device, not a channel
- my $col4all;
- if (defined($color{$a[2]})){
- $col4all = sprintf("%02X",$color{$a[2]}*85);#Color for 4 LEDS
- $col4all = $col4all.$col4all.$col4all.$col4all;#and now for 16
- }
- elsif ($a[2] =~ m/^[A-Fa-f0-9]{1,8}$/i){
- $col4all = sprintf("%08X",hex($a[2]));
- }
- else{
- return "$a[2] unknown. use hex or: ".join(" ",sort keys(%color));
- }
- CUL_HM_UpdtReadBulk($hash,1,"color:".$col4all,
- "state:set_".$col4all);
- CUL_HM_PushCmdStack($hash,"++".$flag."11".$id.$dst."8100".$col4all);
- }else{# operating on a channel
- return "$a[2] unknown. use: ".join(" ",sort keys(%color))
- if (!defined($color{$a[2]}) );
- CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'80'.$chn.'0'.$color{$a[2]});
- }
- }
- elsif($md eq "HM-OU-CFM-PL"){
- return "use channel 1 of the device for LED" if ($chn != 1);
- my %color = (redL =>18,greenL =>34,orangeL =>50,
- redS =>17,greenS =>33,orangeS =>49);
- my @ledList = split(',',$a[2]);
- my $ledBytes;
- foreach my $led (@ledList){
- if (!$color{$led} ){# wrong parameter
- return "'$led' unknown. use: ".join(" ",sort keys(%color));
- }
- $ledBytes .= sprintf("%02X",$color{$led});
- }
- CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'80'.$chn.'0101'.$ledBytes);
- }
- else{
- return "device for command cannot be identified";
- }
- }
- elsif($cmd eq "playTone") { #################################################
- $chn = "02" if (length($hash->{DEF}) == 6);# be nice, select implicite
- return "use channel 2 of the device to play MP3" if ($chn != 2);
- my @mp3List = split(',',$a[2]);
- my $mp3Bytes;
- foreach my $mp3 (@mp3List){
- $mp3Bytes .= sprintf("%02X",$mp3);
- }
- CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'80'.$chn.'0202'.$mp3Bytes);
- }
- elsif($cmd eq "ilum") { #####################################################
- return "$a[2] not specified. choose 0-15 for brightness" if ($a[2]>15);
- return "$a[3] not specified. choose 0-127 for duration" if ($a[3]>127);
- return "unsupported for HMid:".$hash->{DEF}.", use HMId:".substr($hash->{DEF},0,6)
- if (length($hash->{DEF}) != 6);
- my $addrData = sprintf("04%02X08%02X",$a[2],$a[3]*2);
- # write list0,
- CUL_HM_pushConfig($hash,$id,$dst,0,0,0,0,$addrData);
- }
- elsif(($cmd eq "displayMode")||($cmd eq "displayTemp")||
- ($cmd eq "controlMode")||($cmd eq "decalcDay") ||
- ($cmd eq "displayTempUnit") ){ ########################################
- my %regs = (displayTemp =>{actual=>0,setpoint=>2},
- displayMode =>{"temp-only"=>0,"temp-hum"=>1},
- displayTempUnit =>{celsius=>0,fahrenheit=>4},
- controlMode =>{manual=>0,auto=>8,central=>16,party=>24},
- decalcDay =>{Sat=>0 ,Sun=>32 ,Mon=>64,Tue=>96,
- Wed=>128,Thu=>160,Fri=>192});
- return $a[2]."invalid for ".$cmd." select one of ".
- join (" ",sort keys %{$regs{$cmd}}) if(!defined($regs{$cmd}{$a[2]}));
- readingsSingleUpdate($hash,$cmd,$a[2],1);
- my $tcnf = 0;
- my $missingEntries;
- foreach my $entry (keys %regs){
- if (!$hash->{READINGS}{$entry}){
- $missingEntries .= $entry." ";
- }
- else{
- $tcnf |= $regs{$entry}{$hash->{READINGS}{$entry}{VAL}};
- }
- }
- return "please complete settings for ".$missingEntries if($missingEntries);
-
- CUL_HM_pushConfig($hash, $id, $dst, 2,0,0,5, "01".sprintf("%02X",$tcnf));
- }
- elsif($cmd eq "desired-temp") { #############################################
- CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'0202'.
- CUL_HM_convTemp($a[2]));
- my $chnHash = CUL_HM_id2Hash($dst."02");
- my $mode = ReadingsVal($chnHash->{NAME},"R-MdTempReg","");
- readingsSingleUpdate($chnHash,"desired-temp-cent",$a[2],1)
- if($mode eq 'central ');
- }
- elsif($cmd =~ m/^(day|night|party)-temp$/) { ################################
- my %tt = (day=>"03", night=>"04", party=>"06");
- my $tt = $tt{$1};
- CUL_HM_pushConfig($hash, $id, $dst, 2,0,0,5, "$tt".CUL_HM_convTemp($a[2]));
- }
- elsif($cmd =~ m/^tempList(...)/) { ##########################################
- my %day2off = ( "Sat"=>"5 0B", "Sun"=>"5 3B", "Mon"=>"5 6B",
- "Tue"=>"5 9B", "Wed"=>"5 CB", "Thu"=>"6 01",
- "Fri"=>"6 31");
- my $wd = $1;
- my ($list,$addr) = split(" ", $day2off{$wd});
- $addr = hex($addr);
-
- return "To few arguments" if(@a < 4);
- return "To many arguments, max is 24 pairs" if(@a > 50);
- return "Bad format, use HH:MM TEMP ..." if(@a % 2);
- return "Last time spec must be 24:00" if($a[@a-2] ne "24:00");
- my $data = "";
- my $msg = "";
- for(my $idx = 2; $idx < @a; $idx += 2) {
- return "$a[$idx] is not in HH:MM format"
- if($a[$idx] !~ m/^([0-2]\d):([0-5]\d)/);
- my ($h, $m) = ($1, $2);
- $data .= sprintf("%02X%02X%02X%s", $addr, $h*6+($m/10), $addr+1,
- CUL_HM_convTemp($a[$idx+1]));
- $addr += 2;
- $hash->{TEMPLIST}{$wd}{($idx-2)/2}{HOUR} = $h;
- $hash->{TEMPLIST}{$wd}{($idx-2)/2}{MINUTE} = $m;
- $hash->{TEMPLIST}{$wd}{($idx-2)/2}{TEMP} = $a[$idx+1];
- $msg .= sprintf(" %02d:%02d %.1f", $h, $m, $a[$idx+1]);
- }
- CUL_HM_pushConfig($hash, $id, $dst, 2,0,0,$list, $data);
- readingsSingleUpdate($hash,"tempList$wd",$msg,0);
- }
- elsif($cmd eq "valvePos") { #################################################
- return "only number <= 100 or 'off' allowed"
- if (!($a[2] eq "off" ||$a[2]+0 ne $a[2] ||$a[2] <100 ));
- if ($a[2] eq "off"){
- $state = "ValveAdjust:stopped";
- RemoveInternalTimer("valvePos:$dst$chn");# remove responsePending timer
- delete($hash->{helper}{virtTC});
- }
- else {
- my $vp = $a[2];
- readingsSingleUpdate($hash,"valvePosTC","$vp %",0);
- CUL_HM_valvePosUpdt("valvePos:$dst$chn") if (!$hash->{helper}{virtTC});
- $hash->{helper}{virtTC} = "03";
- $state = "ValveAdjust:$vp %";
- }
- }
- elsif($cmd eq "matic") { ####################################################
- # Trigger pre-programmed action in the winmatic. These actions must be
- # programmed via the original software.
- CUL_HM_PushCmdStack($hash,
- sprintf("++%s3E%s%s%s40%02X%s", $flag,$id, $dst, $id, $a[2], $chn));
- }
- elsif($cmd eq "create") { ###################################################
- CUL_HM_PushCmdStack($hash,
- sprintf("++%s01%s%s0101%s%02X%s",$flag,$id, $dst, $id, $a[2], $chn));
- CUL_HM_PushCmdStack($hash,
- sprintf("++A001%s%s0104%s%02X%s", $id, $dst, $id, $a[2], $chn));
- }
- elsif($cmd eq "keydef") { ###################################################
- if ( $a[3] eq "tilt") {CUL_HM_pushConfig($hash,$id,$dst,1,$id,$a[2],3,"0B220D838B228D83");#JT_ON/OFF/RAMPON/RAMPOFF short and long
- } elsif ($a[3] eq "close") {CUL_HM_pushConfig($hash,$id,$dst,1,$id,$a[2],3,"0B550D838B558D83");#JT_ON/OFF/RAMPON/RAMPOFF short and long
- } elsif ($a[3] eq "closed") {CUL_HM_pushConfig($hash,$id,$dst,1,$id,$a[2],3,"0F008F00"); #offLevel (also thru register)
- } elsif ($a[3] eq "bolt") {CUL_HM_pushConfig($hash,$id,$dst,1,$id,$a[2],3,"0FFF8FFF"); #offLevel (also thru register)
- } elsif ($a[3] eq "speedclose"){CUL_HM_pushConfig($hash,$id,$dst,1,$id,$a[2],3,sprintf("23%02XA3%02X",$a[4]*2,$a[4]*2));#RAMPOFFspeed (also in reg)
- } elsif ($a[3] eq "speedtilt") {CUL_HM_pushConfig($hash,$id,$dst,1,$id,$a[2],3,sprintf("22%02XA2%02X",$a[4]*2,$a[4]*2));#RAMPOFFspeed (also in reg)
- } elsif ($a[3] eq "delete") {CUL_HM_PushCmdStack($hash,sprintf("++%s01%s%s0102%s%02X%s",$flag,$id, $dst, $id, $a[2], $chn));#unlearn key
- } else {
- return 'unknown argument '.$a[3];
- }
- }
- elsif($cmd eq "test") { #####################################################
- my $testnr = $hash->{TESTNR} ? ($hash->{TESTNR} +1) : 1;
- $hash->{TESTNR} = $testnr;
- my $msg = sprintf("++9440%s%s00%02X",$dst,$dst,$testnr);
- CUL_HM_SndCmd($hash, $msg);# repeat non-ack messages - delivery uncertain
- CUL_HM_SndCmd($hash, $msg);
- CUL_HM_SndCmd($hash, $msg);
- }
- elsif($cmd =~ m/alarm(.*)/) { ###############################################
- my $msg = sprintf("++9441%s%s01%s",$dst,$dst,(($1 eq "On")?"0BC8":"0C01"));
- CUL_HM_SndCmd($hash, $msg);# repeat non-ack messages - delivery uncertain
- CUL_HM_SndCmd($hash, $msg);
- CUL_HM_SndCmd($hash, $msg);
- }
- elsif($cmd eq "virtual") { ##################################################
- $state = "";
- my (undef,undef,$maxBtnNo) = @a;
- return "please give a number between 1 and 255"
- if ($maxBtnNo < 1 ||$maxBtnNo > 255);# arbitrary - 255 should be max
- return $name." already defines as ".$attr{$name}{subType}
- if ($attr{$name}{subType} && $attr{$name}{subType} ne "virtual");
- $attr{$name}{subType} = "virtual";
- $attr{$name}{hmClass} = "sender";
- $attr{$name}{model} = "virtual_".$maxBtnNo;
- my $devId = $hash->{DEF};
- for (my $btn=1;$btn <= $maxBtnNo;$btn++){
- my $chnName = $name."_Btn".$btn;
- my $chnId = $devId.sprintf("%02X",$btn);
- DoTrigger("global", "UNDEFINED $chnName CUL_HM $chnId")
- if (!$modules{CUL_HM}{defptr}{$chnId});
- }
- foreach my $channel (keys %{$hash}){# remove higher numbers
- my $chNo = $1 if($channel =~ m/^channel_(.*)/);
- next if (!defined($chNo));
- CommandDelete(undef,$hash->{$channel})
- if (hex($chNo) > $maxBtnNo);
- }
- }
- elsif($cmd eq "actiondetect"){###############################################
- $state = "";
- my (undef,undef,$cyctime) = @a;
- return ($cyctime eq 'off')?CUL_HM_ActDel($dst):CUL_HM_ActAdd($dst,$cyctime);
- }
- elsif($cmd eq "press") { ####################################################
- my (undef,undef,$mode) = @a;
- my ($srcId,$srcChn) = ($1,$2) if ($hash->{DEF} =~ m/(......)(..)/);
- return "invalid channel:".$srcId.$srcChn if (!$srcChn);
- my $rcvId = "000000"; #may have to change
- my $btn = sprintf("%02X",$srcChn+(($mode && $mode eq "long")?64:0));
- my $pressCnt = (!$hash->{helper}{count})?1:$hash->{helper}{count}+1;
- $pressCnt %= 256;
- my @peerList;
- foreach my $peer (sort(split(',',AttrVal($name,"peerIDs","")))) {
- push (@peerList,substr($peer,0,6));
- }
-
- my $oldPeer = " "; # only once to device, not channel!
- foreach my $peer (sort @peerList){
- next if ($oldPeer eq $peer);
-
- my $peerHash = $modules{CUL_HM}{defptr}{$peer};
- my $peerSt = AttrVal($peerHash->{NAME}, "subType", "");
- my $peerFlag = ($peerSt ne "keyMatic") ? "A4" : "B4";
- CUL_HM_PushCmdStack($hash, sprintf("++%s40%s%s%s%02X",
- $peerFlag,$srcId,$peer,$btn,$pressCnt));
- $oldPeer = $peer;
- }
-
- CUL_HM_PushCmdStack($hash, sprintf("++%s40%s000000%s%02X",
- $flag,$srcId,$btn,$pressCnt))if (!@peerList);
- $hash->{helper}{count}=$pressCnt;
- }
- elsif($cmd eq "devicepair") { ###############################################
- #devicepair device ... [single|dual] [set|unset] [actor|remote|both]
- my ($bNo,$peerN,$single,$set,$target) = ($a[2],$a[3],$a[4],$a[5],$a[6]);
- $state = "";
- return "$bNo is not a button number" if(($bNo < 1) && !$chn);
- my $peerDst = CUL_HM_name2Id($peerN);
- $peerDst .= "01" if( length($peerDst)==6);
- return "please enter peer" if(!$peerDst);
- my $peerChn = substr($peerDst,6,2);
- $peerDst = substr($peerDst,0,6);
- my $peerHash;
- $peerHash = $modules{CUL_HM}{defptr}{$peerDst.$peerChn}if ($modules{CUL_HM}{defptr}{$peerDst.$peerChn});
- $peerHash = $modules{CUL_HM}{defptr}{$peerDst} if (!$peerHash);
- return "$peerN not a CUL_HM device" if($target && ($target ne "remote") &&(!$peerHash ||$peerHash->{TYPE} ne "CUL_HM"));
- return "$single must be single or dual" if(defined($single) && (($single ne"single") &&($single ne"dual")));
- return "$set must be set or unset" if(defined($set) && (($set ne"set") &&($set ne"unset")));
- return "$target must be [actor|remote|both]" if(defined($target) && (($target ne"actor")&&($target ne"remote")&&($target ne"both")));
- return "use climate chan to pair TC" if( $md eq "HM-CC-TC" &&$chn ne "02");
- return "use - single [set|unset] actor - for smoke detector" if( $st eq "smokeDetector" && (!$single || $single ne "single" || $target ne "actor"));
- return "use - single - for ".$st if(($st eq "threeStateSensor"||
- $st eq "motionDetector" ) && (!$single || $single ne "single"));
-
- $single = ($single eq "single")?1:"";#default to dual
- $set = ($set eq "unset")?0:1;
-
- my ($b1,$b2,$nrCh2Pair);
- $b1 = ($isChannel) ? hex($chn):(!$bNo?"01":$bNo);
- $b1 = $b1*2 - 1 if(!$single && !$isChannel);
- if ($single){
- $b2 = $b1;
- $b1 = 0 if ($st eq "smokeDetector");
- $nrCh2Pair = 1;
- }
- else{
- $b2 = $b1 + 1;
- $nrCh2Pair = 2;
- }
- my $cmdB = ($set)?"01":"02";# do we set or remove?
-
- # First the remote (one loop for on, one for off)
- if (!$target || $target eq "remote" || $target eq "both"){
- for(my $i = 1; $i <= $nrCh2Pair; $i++) {
- my $b = ($i==1 ? $b1 : $b2);
- if ($st eq "virtual"){
- my $btnName = CUL_HM_id2Name($dst.sprintf("%02X",$b));
- return "button ".$b." not defined for virtual remote ".$name
- if (!defined $attr{$btnName});
- CUL_HM_ID2PeerList ($btnName,$peerDst.$peerChn,$set); #update peerlist
- }
- else{
- my $bStr = sprintf("%02X",$b);
- CUL_HM_PushCmdStack($hash,
- "++".$flag."01${id}${dst}${bStr}$cmdB${peerDst}${peerChn}00");
- CUL_HM_pushConfig($hash,$id, $dst,$b,$peerDst,hex($peerChn),4,"0100")
- if($md ne "HM-CC-TC");
- }
- }
- }
- if (!$target || $target eq "actor" || $target eq "both"){
- if (AttrVal( CUL_HM_id2Name($peerDst), "subType", "") eq "virtual"){
- CUL_HM_ID2PeerList ($peerN,$dst.sprintf("%02X",$b2),$set); #update peerlist
- CUL_HM_ID2PeerList ($peerN,$dst.sprintf("%02X",$b1),$set) if ($b1 & !$single); #update peerlist
- }
- else{
- my $peerFlag = CUL_HM_getFlag($peerHash);
- CUL_HM_PushCmdStack($peerHash, sprintf("++%s01%s%s%s%s%s%02X%02X",
- $peerFlag,$id,$peerDst,$peerChn,$cmdB,$dst,$b2,$b1 ));
- }
- }
- return ("",1) if ($target && $target eq "remote");#Nothing to transmit for actor
- $devHash = $peerHash; # Exchange the hash, as the switch is always alive.
- }
-
- readingsSingleUpdate($hash,"state",$state,1) if($state);
-
- $rxType = CUL_HM_getRxType($devHash);
- Log GetLogLevel($name,2), "CUL_HM set $name " .
- join(" ", @a[1..$#a])." rxt:".$rxType;
- CUL_HM_ProcessCmdStack($devHash) if($rxType & 0x03);#all/burst
- return ("",1);# no not generate trigger outof command
-}
-
-###################################
-my $updtValveCnt = 0;
-
-sub
-CUL_HM_valvePosUpdt(@)
-{# update valve position periodically to please valve
- my($in ) = @_;
- my(undef,$vId) = split(':',$in);
- my $hash = CUL_HM_id2Hash($vId);
- my $vDevId = substr($vId,0,6);
- my $nextTimer = 150;
-
-# if ($updtValveCnt++ %2){
-# $nextTimer = 20;
-# CUL_HM_PushCmdStack($hash,"++8670".$vDevId."00000000D036");# some weather event -
-# }
-# else{
- my $name = $hash->{NAME};
- my $vp = ReadingsVal($name,"valvePosTC","15 %");
- $vp =~ s/ %//;
- $vp *=2.56;
- foreach my $peer (sort(split(',',AttrVal($name,"peerIDs","")))) {
- next if (length($peer) != 8);
- $peer = substr($peer,0,6);
- CUL_HM_PushCmdStack($hash,sprintf("++A258%s%s%s%02X",$vDevId,$peer,$hash->{helper}{virtTC},$vp));
- }
-# }
- $hash->{helper}{virtTC} = "00";
- CUL_HM_ProcessCmdStack($hash);
- InternalTimer(gettimeofday()+$nextTimer,"CUL_HM_valvePosUpdt","valvePos:$vId",0);
-}
-sub
-CUL_HM_infoUpdtDevData($$$){
- my($name,$hash,$p) = @_;
- my($fw,$mId,$serNo,$stc,$devInfo) = ($1,$2,$3,$4,$5)
- if($p =~ m/(..)(.{4})(.{20})(.{2})(.*)/);
- my $model = $culHmModel{$mId}{name} ? $culHmModel{$mId}{name}:"unknown";
- $attr{$name}{model} = $model;
- my $dp = $culHmDevProps{$stc};
- $attr{$name}{subType} = $dp ? $dp->{st} : "unknown";
- $attr{$name}{hmClass} = $dp ? $dp->{cl} : "unknown";
- $attr{$name}{serialNr} = pack('H*',$serNo);
- $attr{$name}{firmware} =
- sprintf("%d.%d", hex(substr($p,0,1)),hex(substr($p,1,1)));
- $attr{$name}{devInfo} = $devInfo;
-
- delete $hash->{helper}{rxType};
- CUL_HM_getRxType($hash); #will update rxType
- $mId = CUL_HM_getMId($hash);# set helper valiable and use result
-
- # autocreate undefined channels
- my @chanTypesList = split(',',$culHmModel{$mId}{chn});
- my $startime = gettimeofday()+1;
- foreach my $chantype (@chanTypesList){
- my ($chnTpName,$chnStart,$chnEnd) = split(':',$chantype);
- my $chnNoTyp = 1;
- for (my $chnNoAbs = $chnStart; $chnNoAbs <= $chnEnd;$chnNoAbs++){
- my $chnId = $hash->{DEF}.sprintf("%02X",$chnNoAbs);
- if (!$modules{CUL_HM}{defptr}{$chnId}){
- my $chnName = $name."_".$chnTpName.(($chnStart == $chnEnd)?
- '':'_'.sprintf("%02d",$chnNoTyp));
- InternalTimer($startime++,"CUL_HM_infoUpdtChanData",
- "$chnName,$chnId,$model",0);
- }
- $attr{CUL_HM_id2Name($chnId)}{model} = $model;
- $chnNoTyp++;
- }
- }
- if ($culHmModel{$mId}{cyc}){
- CUL_HM_ActAdd($hash->{DEF},$culHmModel{$mId}{cyc});
- }
-
-}
-sub #---------------------------------
-CUL_HM_infoUpdtChanData(@)
-{# delay this to ensure the device is already available
- my($in ) = @_;
- my($chnName,$chnId,$model ) = split(',',$in);
- DoTrigger("global", 'UNDEFINED '.$chnName.' CUL_HM '.$chnId);
- $attr{CUL_HM_id2Name($chnId)}{model} = $model;
-}
-sub #---------------------------------
-CUL_HM_Pair(@)
-{
- my ($name, $hash,$cmd,$src,$dst,$p) = @_;
- my $iohash = $hash->{IODev};
- my $id = CUL_HM_Id($iohash);
- my $serNo = $attr{$name}{serialNr};
-
- Log GetLogLevel($name,3),
- "CUL_HM pair: $name $attr{$name}{subType}, model $attr{$name}{model} serialNr $serNo";
-
- # Abort if we are not authorized
- if($dst eq "000000") {
- if(!$iohash->{hmPair} &&
- (!$iohash->{hmPairSerial} || $iohash->{hmPairSerial} ne $serNo)) {
- Log GetLogLevel($name,3),
- $iohash->{NAME}. " pairing (hmPairForSec) not enabled";
- return "";
- }
- }
- elsif($dst ne $id) {
- return "" ;
- }
- elsif($cmd eq "0400") { # WDC7000
- return "" ;
- }
- elsif($iohash->{hmPairSerial}) {
- delete($iohash->{hmPairSerial});
- }
-
- my ($idstr, $s) = ($id, 0xA);
- $idstr =~ s/(..)/sprintf("%02X%s",$s++,$1)/ge;
- CUL_HM_pushConfig($hash, $id, $src,0,0,0,0, "0201$idstr");
- CUL_HM_ProcessCmdStack($hash); # start processing immediately
-
- return "";
-}
-sub #---------------------------------
-CUL_HM_getConfig($$$$$){
- my ($hash,$chnhash,$id,$dst,$chn) = @_;
- my $flag = CUL_HM_getFlag($hash);
-
- foreach my $readEntry (keys %{$chnhash->{READINGS}}){
- delete $chnhash->{READINGS}{$readEntry} if ($readEntry =~ m/^[\.]?RegL_/);
- }
- #get Peer-list in any case - it is part of config
- CUL_HM_PushCmdStack($hash,sprintf("++%s01%s%s%s03",$flag,$id,$dst,$chn));
- my $lstAr = $culHmModel{CUL_HM_getMId($hash)}{lst};
- my @list = split(",",$lstAr); #get valid lists e.g."1, 5:2:3.p ,6:2"
- foreach my$listEntry (@list){# each list that is define for this channel
- my ($peerReq,$chnValid)= (0,0);
- my ($listNo,$chnLst1) = split(":",$listEntry);
- if (!$chnLst1){
- $chnValid = 1; #if no entry channel is valid
- $peerReq = 1 if($listNo==3 ||$listNo==4); #default
- }
- else{
- my @chnLst = split('\.',$chnLst1);
- foreach my $lchn (@chnLst){
- $peerReq = 1 if ($lchn =~ m/p/);
- no warnings;#know that lchan may be followed by a 'p' causing a warning
- $chnValid = 1 if (int($lchn) == hex($chn));
- use warnings;
- last if ($chnValid);
- }
- }
- if ($chnValid){# yes, we will go for a list
- if ($peerReq){# need to get the peers first
- $chnhash->{helper}{getCfgList} = "all"; # peers first
- $chnhash->{helper}{getCfgListNo} = $listNo;
- }
- else{
- CUL_HM_PushCmdStack($hash,sprintf("++%s01%s%s%s0400000000%02X",$flag,$id,$dst,$chn,$listNo));
- }
- }
- }
- }
-###################-------send related --------################
-sub #---------------------------------
-CUL_HM_SndCmd($$)
-{
- my ($hash, $cmd) = @_;
- my $io = $hash->{IODev};
-
- $cmd =~ m/^(..)(.*)$/;
- my ($mn, $cmd2) = ($1, $2);
-
- if($mn eq "++") {
- $mn = $io->{HM_CMDNR} ? (($io->{HM_CMDNR} +1)&0xff) : 1;
- }
- elsif($cmd =~ m/^[+-]/){; #continue pure
- IOWrite($hash, "", $cmd);
- return;
- }
- else {
- $mn = hex($mn);
- }
- $io->{HM_CMDNR} = $mn;
- $cmd = sprintf("As%02X%02X%s", length($cmd2)/2+1, $mn, $cmd2);
- IOWrite($hash, "", $cmd);
- CUL_HM_responseSetup($hash,$cmd);
- $cmd =~ m/As(..)(..)(..)(..)(......)(......)(.*)/;
- CUL_HM_DumpProtocol("SND", $io, ($1,$2,$3,$4,$5,$6,$7));
-}
-sub #---------------------------------
-CUL_HM_responseSetup($$)
-{#store all we need to handle the response
- #setup repeatTimer and cmdStackControll
- my ($hash,$cmd) = @_;
- my ($msgId, $msgFlag,$msgType,$dst,$p) = ($2,hex($3),$4,$6,$7)
- if ($cmd =~ m/As(..)(..)(..)(..)(......)(......)(.*)/);
- my ($chn,$subType) = ($1,$2) if($p =~ m/^(..)(..)/);
- my $rTo = rand(20)/10+2; #default response timeout
- if ($msgType eq "01" && $subType){
- if ($subType eq "03"){ #PeerList-------------
- #--- remember request params in device level
- $hash->{helper}{respWait}{Pending} = "PeerList";
- $hash->{helper}{respWait}{PendCmd} = $cmd;
- $hash->{helper}{respWait}{forChn} = substr($p,0,2);#channel info we await
-
- # define timeout - holdup cmdStack until response complete or timeout
- InternalTimer(gettimeofday()+$rTo, "CUL_HM_respPendTout", "respPend:$dst", 0);
-
- #--- remove readings in channel
- my $chnhash = $modules{CUL_HM}{defptr}{"$dst$chn"};
- $chnhash = $hash if (!$chnhash);
- $chnhash->{READINGS}{peerList}{VAL}="";#empty old list
- $attr{$chnhash->{NAME}}{peerIDs} = '';
- return;
- }
- elsif($subType eq "04"){ #RegisterRead-------
- my ($peer, $list) = ($1,$2) if ($p =~ m/..04(........)(..)/);
- $peer = ($peer ne "00000000")?CUL_HM_peerChName($peer,$dst,""):"";
- #--- set messaging items
- $hash->{helper}{respWait}{Pending}= "RegisterRead";
- $hash->{helper}{respWait}{PendCmd}= $cmd;
- $hash->{helper}{respWait}{forChn} = $chn;
- $hash->{helper}{respWait}{forList}= $list;
- $hash->{helper}{respWait}{forPeer}= $peer;
-
- # define timeout - holdup cmdStack until response complete or timeout
- InternalTimer(gettimeofday()+$rTo,"CUL_HM_respPendTout","respPend:$dst", 0);
- #--- remove channel entries that will be replaced
- my $chnhash = $modules{CUL_HM}{defptr}{"$dst$chn"};
- $chnhash = $hash if(!$chnhash);
-
- $peer ="" if($list !~ m/^0[34]$/);
- #empty val since reading will be cumulative
- my $rlName = ((CUL_HM_getExpertMode($hash) eq "2")?"":".")."RegL_".$list.":".$peer;
- $chnhash->{READINGS}{$rlName}{VAL}="";
- delete ($chnhash->{READINGS}{$rlName}{TIME});
- return;
- }
-# elsif($subType eq "0A"){ #Pair Serial----------
-# #--- set messaging items
-# $hash->{helper}{respWait}{Pending} = "PairSerial";
-# $hash->{helper}{respWait}{PendCmd} = $cmd;
-# $hash->{helper}{respWait}{forChn} = substr($p,4,20);
-#
-# # define timeout - holdup cmdStack until response complete or timeout
-# InternalTimer(gettimeofday()+$rTo, "CUL_HM_respPendTout", "respPend:$dst", 0);
-# return;
-# }
- elsif($subType eq "0E"){ #StatusReq----------
- #--- set messaging items
- $hash->{helper}{respWait}{Pending}= "StatusReq";
- $hash->{helper}{respWait}{PendCmd}= $cmd;
- $hash->{helper}{respWait}{forChn} = $chn;
-
- # define timeout - holdup cmdStack until response complete or timeout
- InternalTimer(gettimeofday()+$rTo, "CUL_HM_respPendTout", "respPend:$dst", 0);
- return;
- }
- }
-
- if (($msgFlag & 0x20) && ($dst ne '000000')){
- my $iohash = $hash->{IODev};
- $hash->{helper}{respWait}{cmd} = $cmd;
- $hash->{helper}{respWait}{msgId} = $msgId; #msgId we wait to ack
- $hash->{helper}{respWait}{reSent} = 1;
-
- my $off = 2;
- #$off += 0.15*int(@{$iohash->{QUEUE}}) if($iohash->{QUEUE});
- InternalTimer(gettimeofday()+$off, "CUL_HM_Resend", $hash, 0);
- }
-}
-sub #---------------------------------
-CUL_HM_eventP($$)
-{ # handle protocol events
- #todo: add severity, counter, history and acknowledge
- my ($hash, $evntType) = @_;
- my $name = $hash->{NAME};
- my $nAttr = $hash;
- return if (!$name);
- if ($evntType eq "Rcv"){
- $nAttr->{"protLastRcv"} = TimeNow();
- return;
- }
-
- my $evnt = $nAttr->{"prot".$evntType}?$nAttr->{"prot".$evntType}:"0 > x";
- my ($evntCnt,undef) = split(' last_at:',$evnt);
- $nAttr->{"prot".$evntType} = ++$evntCnt." last_at:".TimeNow();
-
- if ($evntType ne "Snd"){#count unusual events
- if ($hash->{helper}{burstEvtCnt}){
- $hash->{helper}{burstEvtCnt}++;
- }else {$hash->{helper}{burstEvtCnt}=1;};
- }
- if ($evntType eq "Nack" ||$evntType eq "ResndFail"){
- my $delMsgSum;
- $nAttr->{protCmdDel} = 0 if(!$nAttr->{protCmdDel});
- $nAttr->{protCmdDel} += scalar @{$hash->{cmdStack}} if ($hash->{cmdStack});
- }
-}
-sub #---------------------------------
-CUL_HM_respPendRm($)
-{ # delete all response related entries in messageing entity
- my ($hash) = @_;
- delete ($hash->{helper}{respWait});
- RemoveInternalTimer($hash); # remove resend-timer
- RemoveInternalTimer("respPend:$hash->{DEF}");# remove responsePending timer
- $respRemoved = 1;
-}
-sub #---------------------------------
-CUL_HM_respPendTout($)
-{
- my ($HMid) = @_;
- $HMid =~ s/.*://; #remove timer identifier
- my $hash = $modules{CUL_HM}{defptr}{$HMid};
- if ($hash && $hash->{DEF} ne '000000'){
- my $pendCmd = $hash->{helper}{respWait}{Pending};# secure before remove
-
- my $pendRsndCnt = $hash->{helper}{respWait}{PendingRsend};
- $pendRsndCnt = 1 if (!$pendRsndCnt);
- if ($pendRsndCnt <7 && # some retries
- (CUL_HM_getRxType($hash) & 0x03) != 0){# to slow for wakeup and config
- my $name = $hash->{NAME};
- Log GetLogLevel($name,4),"CUL_HM_Resend: ".$name. " nr ".$pendRsndCnt;
- $hash->{helper}{respWait}{PendingRsend} = $pendRsndCnt + 1;
- CUL_HM_SndCmd($hash,substr($hash->{helper}{respWait}{PendCmd},4));
- CUL_HM_eventP($hash,"Resnd") if ($pendCmd);
- }
- else{
- CUL_HM_eventP($hash,"ResndFail") if ($pendCmd);
- CUL_HM_respPendRm($hash);
- CUL_HM_ProcessCmdStack($hash); # continue processing commands
- readingsSingleUpdate($hash,"state","RESPONSE TIMEOUT:".$pendCmd,1);
- }
- }
-}
-sub #---------------------------------
-CUL_HM_respPendToutProlong($)
-{#used when device sends part responses
- my ($hash) = @_;
-
- RemoveInternalTimer("respPend:$hash->{DEF}");
- InternalTimer(gettimeofday()+1, "CUL_HM_respPendTout", "respPend:$hash->{DEF}", 0);
-}
-sub #---------------------------------
-CUL_HM_PushCmdStack($$)
-{
- my ($chnhash, $cmd) = @_;
- my @arr = ();
- my $hash = CUL_HM_getDeviceHash($chnhash);
- my $name = $hash->{NAME};
- if(!$hash->{cmdStack}){
- $hash->{cmdStack} = \@arr;
- delete ($hash->{helper}{burstEvtCnt}) if (!$hash->{helper}{respWait});
- }
- push(@{$hash->{cmdStack}}, $cmd);
- my $entries = scalar @{$hash->{cmdStack}};
- $hash->{protCmdPend} = $entries." CMDs_pending";
- $hash->{protState} = "CMDs_pending"
- if (!$hash->{helper}{respWait}{cmd} &&
- !$hash->{helper}{respWait}{Pending});
-}
-sub #---------------------------------
-CUL_HM_ProcessCmdStack($)
-{
- my ($chnhash) = @_;
- my $hash = CUL_HM_getDeviceHash($chnhash);
- my $name = $hash->{NAME};
- my $sent;
- if($hash->{cmdStack} && !$hash->{helper}{respWait}{cmd} &&
- !$hash->{helper}{respWait}{Pending}){
- if(@{$hash->{cmdStack}}) {
- CUL_HM_SndCmd($hash, shift @{$hash->{cmdStack}});
- $sent = 1;
- $hash->{protCmdPend} = scalar @{$hash->{cmdStack}}." CMDs pending";
- $hash->{protState} = "CMDs_processing...";
- CUL_HM_eventP($hash,"Snd");
- }
- if(!@{$hash->{cmdStack}}) {
- delete($hash->{cmdStack});
- delete($hash->{protCmdPend});
- #-- update info ---
- my $burstEvt = ($hash->{helper}{burstEvtCnt})?
- $hash->{helper}{burstEvtCnt}:0;
- $hash->{protState} = "CMDs_done".
- (($burstEvt)?("_events:".$burstEvt):"");
- }
- }
- return $sent;
-}
-sub #---------------------------------
-CUL_HM_Resend($)
-{#resend a message if there is no answer
- my $hash = shift;
- my $name = $hash->{NAME};
- return if(!$hash->{helper}{respWait}{reSent}); # Double timer?
- if($hash->{helper}{respWait}{reSent} >= 3) {
- CUL_HM_eventP($hash,"ResndFail");
- delete($hash->{cmdStack});
- delete($hash->{protCmdPend});
- CUL_HM_respPendRm($hash);
- my $burstEvt = ($hash->{helper}{burstEvtCnt})?
- $hash->{helper}{burstEvtCnt}:0;
- $hash->{protState} = "CMDs_done".
- (($burstEvt)?("_events:".$burstEvt):"");
- readingsSingleUpdate($hash,"state","MISSING ACK",1);
- }
- else {
- CUL_HM_eventP($hash,"Resnd");
- IOWrite($hash, "", $hash->{helper}{respWait}{cmd});
- $hash->{helper}{respWait}{reSent}++;
- Log GetLogLevel($name,4),"CUL_HM_Resend: ".$name. " nr ".$hash->{helper}{respWait}{reSent};
- InternalTimer(gettimeofday()+rand(40)/10+1, "CUL_HM_Resend", $hash, 0);
- }
-}
-###################-----------helper and shortcuts--------################
-################### Peer Handling ################
-sub
-CUL_HM_ID2PeerList ($$$)
-{
- my($name,$peerID,$set) = @_;
- my $peerIDs = AttrVal($name,"peerIDs","");
- my $hash = CUL_HM_name2Hash($name);
- $peerIDs =~ s/$peerID//g;#avoid duplicate, support unset
- $peerIDs.= $peerID."," if($set);
-
- my %tmpHash = map { $_ => 1 } split(",",$peerIDs);#remove duplicates
- $peerIDs = ""; #clear list
- my $peerNames = ""; #prepare names
- my $dId = substr(CUL_HM_name2Id($name),0,6); #get own device ID
- foreach my $pId (sort(keys %tmpHash)){
- next if ($pId !~ m/^[0-9A-F]{8}$/); #ignore non-channel IDs
- $peerIDs .= $pId.","; #append ID
- $peerNames .= (($dId eq substr($pId,0,6))? #is own channel?
- ("self".substr($pId,6,2)): #yes, name it 'self'
- (CUL_HM_id2Name($pId))) #find name otherwise
- .","; # dont forget separator
- }
- $attr{$name}{peerIDs} = $peerIDs; # make it public
- readingsSingleUpdate($hash,"peerList",$peerNames,0);
-}
-################### Conversions ################
-sub #---------------------------------
-CUL_HM_getExpertMode($)
-{ # get expert level for the entity.
- # if expert level is not set try to get it for device
- my ($hash) = @_;
- my $expLvl = AttrVal($hash->{NAME},"expert","");
- my $dHash = CUL_HM_getDeviceHash($hash);
- $expLvl = AttrVal($dHash->{NAME},"expert","0")
- if ($expLvl eq "");
- return substr($expLvl,0,1);
-}
-sub #---------------------------------
-CUL_HM_getAssChnIds($)
-{ # will return the list of assotiated channel of a device
- # if it is a channel only return itself
- # if device and no channel
- my ($name) = @_;
- my @chnIdList;
- my $hash = CUL_HM_name2Hash($name);
- foreach my $channel (keys %{$hash}){
- next if ($channel !~ m/^channel_/);
- my $chnHash = CUL_HM_name2Hash($hash->{$channel});
- push @chnIdList,$chnHash->{DEF} if ($chnHash);
- }
- my $dId = CUL_HM_name2Id($name);
-
- push @chnIdList,$dId."01" if (length($dId) == 6 && !$hash->{channel_01});
- push @chnIdList,$dId if (length($dId) == 8);
- return sort(@chnIdList);
-}
-sub #---------------------------------
-CUL_HM_Id($)
-{#in ioHash out ioHMid
- my ($io) = @_;
- my $fhtid = defined($io->{FHTID}) ? $io->{FHTID} : "0000";
- return AttrVal($io->{NAME}, "hmId", "F1$fhtid");
-}
-sub #---------------------------------
-CUL_HM_hash2Id($)
-{# in: id, out:hash
- my ($hash) = @_;
- return $hash->{DEF};
-}
-sub #---------------------------------
-CUL_HM_id2Hash($)
-{# in: id, out:hash
- my ($id) = @_;
- return $modules{CUL_HM}{defptr}{$id} if ($modules{CUL_HM}{defptr}{$id});
- return $modules{CUL_HM}{defptr}{substr($id,0,6)}; # could be chn 01 of dev
-}
-sub #---------------------------------
-CUL_HM_name2Hash($)
-{# in: name, out:hash
- my ($name) = @_;
- return $defs{$name};
-}
-sub #---------------------------------
-CUL_HM_name2Id(@)
-{ # in: name or HMid ==>out: HMid, "" if no match
- my ($name,$idHash) = @_;
- my $hash = $defs{$name};
- return $hash->{DEF} if ($hash); #name is entity
- return "000000" if($name eq "broadcast"); #broadcast
- return $defs{$1}->{DEF}.$2 if($name =~ m/(.*)_chn:(..)/); # chn:xx
- return $name if($name =~ m/^[A-F0-9]{6,8}$/i);#was already HMid
- return $idHash->{DEF}.sprintf("%02X",$1)
- if($idHash && ($name =~ m/self(.*)/));
- return "";
-}
-sub #---------------------------------
-CUL_HM_peerChId($$$)
-{# peer Channel name from/for user entry.
- my($pId,$dId,$iId)=@_;
- my $pSc = substr($pId,0,4); #helper for shortcut spread
- return $dId.sprintf("%02X",'0'.substr($pId,4)) if ($pSc eq 'self');
- return $iId.sprintf("%02X",'0'.substr($pId,4)) if ($pSc eq 'fhem');
- return "all" if ($pId eq 'all');#used by getRegList
- my $repID = CUL_HM_name2Id($pId);
- $repID .= '01' if (length( $repID) == 6);# add default 01 if this is a device
- return $repID;
-}
-sub #---------------------------------
-CUL_HM_peerChName($$$)
-{# peer Channel ID to user entry.
- my($pId,$dId,$iId)=@_;
- my($pDev,$pChn) = ($1,$2) if ($pId =~ m/(......)(..)/);
- return 'self'.$pChn if ($pDev eq $dId);
- return 'fhem'.$pChn if ($pDev eq $iId);
- return CUL_HM_id2Name($pId);
-}
-sub #---------------------------------
-CUL_HM_id2Name($)
-{ # in: name or HMid out: name
- my ($p) = @_;
- return $p if($attr{$p}); # is already name
- return $p if ($p =~ m/_chn:/);
- my $devId= substr($p, 0, 6);
- return "broadcast" if($devId eq "000000");
- my ($chn,$chnId);
- if (length($p) == 8){
- $chn = substr($p, 6, 2);;
- $chnId = $p;
- }
- my $defPtr = $modules{CUL_HM}{defptr};
- return $defPtr->{$chnId}{NAME} if( $chnId && $defPtr->{$chnId});#channel
- return $defPtr->{$devId}{NAME} if(!$chnId && $defPtr->{$devId});#device only
-
- return $defPtr->{$devId}{NAME}."_chn:".$chn
- if( $chnId && $defPtr->{$devId});#device, add chn
- return $devId. ($chn ? ("_chn:".$chn):""); #not defined, return ID only
-}
-sub #---------------------------------
-CUL_HM_getDeviceHash($)
-{#in: hash (chn or dev) out: hash of the device (used e.g. for send messages)
- my ($hash) = @_;
- return $hash if(!$hash->{DEF});
- my $devHash = $modules{CUL_HM}{defptr}{substr($hash->{DEF},0,6)};
- return ($devHash)?$devHash:$hash;
-}
-
-
-#############################
-my %culHmBits = (
- "00" => { txt => "DEVICE_INFO", params => {
- FIRMWARE => '00,2',
- TYPE => "02,4",
- SERIALNO => '06,20,$val=pack("H*",$val)',
- CLASS => "26,2",
- PEER_CHANNEL_A => "28,2",
- PEER_CHANNEL_B => "30,2",
- UNKNOWN => "32,2", }},
-
- "01;p11=01" => { txt => "CONFIG_PEER_ADD", params => {
- CHANNEL => "00,2",
- PEER_ADDRESS => "04,6",
- PEER_CHANNEL_A => "10,2",
- PEER_CHANNEL_B => "12,2", }},
- "01;p11=02" => { txt => "CONFIG_PEER_REMOVE", params => {
- CHANNEL => "00,2",
- PEER_ADDRESS => '04,6,$val=CUL_HM_id2Name($val)',
- PEER_CHANNEL_A => "10,2",
- PEER_CHANNEL_B => "12,2", } },
- "01;p11=03" => { txt => "CONFIG_PEER_LIST_REQ", params => {
- CHANNEL => "0,2", },},
- "01;p11=04" => { txt => "CONFIG_PARAM_REQ", params => {
- CHANNEL => "00,2",
- PEER_ADDRESS => "04,6",
- PEER_CHANNEL => "10,2",
- PARAM_LIST => "12,2", },},
- "01;p11=05" => { txt => "CONFIG_START", params => {
- CHANNEL => "00,2",
- PEER_ADDRESS => "04,6",
- PEER_CHANNEL => "10,2",
- PARAM_LIST => "12,2", } },
- "01;p11=06" => { txt => "CONFIG_END", params => {
- CHANNEL => "0,2", } },
- "01;p11=08" => { txt => "CONFIG_WRITE_INDEX", params => {
- CHANNEL => "0,2",
- DATA => '4,,$val =~ s/(..)(..)/ $1:$2/g', } },
- "01;p11=0A" => { txt => "PAIR_SERIAL", params => {
- SERIALNO => '04,,$val=pack("H*",$val)', } },
- "01;p11=0E" => { txt => "CONFIG_STATUS_REQUEST", params => {
- CHANNEL => "0,2", } },
-
- "02;p01=00" => { txt => "ACK"},
- "02;p01=01" => { txt => "ACK_STATUS", params => {
- CHANNEL => "02,2",
- STATUS => "04,2",
- DOWN => '06,02,$val=(hex($val)&0x20)?1:0',
- UP => '06,02,$val=(hex($val)&0x10)?1:0',
- LOWBAT => '06,02,$val=(hex($val)&0x80)?1:0',
- RSSI => '08,02,$val=(-1)*(hex($val))', }},
- "02;p01=02" => { txt => "ACK2"}, # smokeDetector pairing only?
- "02;p01=04" => { txt => "ACK-proc", params => {
- Para1 => "02,4",
- Para2 => "06,4",
- Para3 => "10,4",
- Para4 => "14,2",}}, # remote?
- "02;p01=80" => { txt => "NACK"},
- "02;p01=84" => { txt => "NACK_TARGET_INVALID"},
- "02" => { txt => "ACK/NACK_UNKNOWN "},
-
- "02" => { txt => "Request AES", params => { #todo check data
- DATA => "0," } },
-
- "03" => { txt => "AES reply", params => {
- DATA => "0," } },
-
- "10;p01=01" => { txt => "INFO_PEER_LIST", params => {
- PEER1 => '02,8,$val=CUL_HM_id2Name($val)',
- PEER2 => '10,8,$val=CUL_HM_id2Name($val)',
- PEER3 => '18,8,$val=CUL_HM_id2Name($val)',
- PEER4 => '26,8,$val=CUL_HM_id2Name($val)'},},
- "10;p01=02" => { txt => "INFO_PARAM_RESPONSE_PAIRS", params => {
- DATA => "2,", },},
- "10;p01=03" => { txt => "INFO_PARAM_RESPONSE_SEQ", params => {
- OFFSET => "2,2",
- DATA => "4,", },},
- "10;p01=04" => { txt => "INFO_PARAMETER_CHANGE", params => {
- CHANNEL => "2,2",
- PEER => '4,8,$val=CUL_HM_id2Name($val)',
- PARAM_LIST => "12,2",
- DATA => '14,,$val =~ s/(..)(..)/ $1:$2/g', } },
- "10;p01=06" => { txt => "INFO_ACTUATOR_STATUS", params => {
- CHANNEL => "2,2",
- STATUS => '4,2',
- UNKNOWN => "6,2",
- RSSI => '08,02,$val=(-1)*(hex($val))' } },
- "11;p02=0400" => { txt => "RESET" },
- "11;p01=02" => { txt => "SET" , params => {
- CHANNEL => "02,2",
- VALUE => "04,2",
- RAMPTIME => '06,4,$val=CUL_HM_decodeTime16($val)',
- DURATION => '10,4,$val=CUL_HM_decodeTime16($val)', } },
- "11;p01=80" => { txt => "LED" , params => {
- CHANNEL => "02,2",
- COLOR => "04,2", } },
- "11;p01=81" => { txt => "LEDall" , params => {
- Led1To16 => '04,8,$val= join(":",sprintf("%b",hex($val))=~ /(.{2})/g)',
- } },
- "12" => { txt => "HAVE_DATA"},
- "3E" => { txt => "SWITCH", params => {
- DST => "00,6",
- UNKNOWN => "06,2",
- CHANNEL => "08,2",
- COUNTER => "10,2", } },
- "3F" => { txt => "TimeStamp", params => {
- UNKNOWN => "00,4",
- TIME => "04,2", } },
- "40" => { txt => "REMOTE", params => {
- BUTTON => '00,2,$val=(hex($val)&0x3F)',
- LONG => '00,2,$val=(hex($val)&0x40)?1:0',
- LOWBAT => '00,2,$val=(hex($val)&0x80)?1:0',
- COUNTER => "02,2", } },
- "41" => { txt => "Sensor_event", params => {
- BUTTON => '00,2,$val=(hex($val)&0x3F)',
- LONG => '00,2,$val=(hex($val)&0x40)?1:0',
- LOWBAT => '00,2,$val=(hex($val)&0x80)?1:0',
- VALUE => '02,2,$val=(hex($val))',
- NEXT => '04,2,$val=(hex($val))',} },
- "58" => { txt => "ClimateEvent", params => {
- CMD => "00,2",
- ValvePos => '02,2,$val=(hex($val))', } },
- "70" => { txt => "WeatherEvent", params => {
- TEMP => '00,4,$val=((hex($val)&0x3FFF)/10)*((hex($val)&0x4000)?-1:1)',
- HUM => '04,2,$val=(hex($val))', } },
-
-);
-# RC send BCAST to specific address. Is the meaning understood?
-my @culHmCmdFlags = ("WAKEUP", "WAKEMEUP", "CFG", "Bit3",
- "BURST", "BIDI", "RPTED", "RPTEN");
- #RPTEN 0x80: set in every message. Meaning?
- #RPTED 0x40: ???
- #BIDI 0x20: response is expected
- #Burst 0x10: set if burst is required by device
- #Bit3 0x08:
- #CFG 0x04: Device in Config mode
- #WAKEMEUP 0x02: awake - hurry up to send messages
- #WAKEUP 0x01: send initially to keep the device awake
-
-sub #------------------------
-CUL_HM_DumpProtocol($$@)
-{
- my ($prefix, $iohash, $len,$cnt,$msgFlags,$msgType,$src,$dst,$p) = @_;
- my $iname = $iohash->{NAME};
- no warnings;# conv 2 number would cause a warning - which is ok
- my $hmProtocolEvents = int(AttrVal($iname, "hmProtocolEvents", 0));
- use warnings;
- return if(!$hmProtocolEvents);
-
- my $p01 = substr($p,0,2);
- my $p02 = substr($p,0,4);
- my $p11 = (length($p) > 2 ? substr($p,2,2) : "");
-
- # decode message flags for printing
- my $msgFlLong="";
- my $msgFlagsHex = hex($msgFlags);
- for(my $i = 0; $i < @culHmCmdFlags; $i++) {
- $msgFlLong .= ",$culHmCmdFlags[$i]" if($msgFlagsHex & (1<<$i));
- }
-
- my $ps;
- $ps = $culHmBits{"$msgType;p11=$p11"} if(!$ps);
- $ps = $culHmBits{"$msgType;p01=$p01"} if(!$ps);
- $ps = $culHmBits{"$msgType;p02=$p02"} if(!$ps);
- $ps = $culHmBits{"$msgType"} if(!$ps);
- my $txt = "";
- if($ps) {
- $txt = $ps->{txt};
- if($ps->{params}) {
- $ps = $ps->{params};
- foreach my $k (sort {$ps->{$a} cmp $ps->{$b} } keys %{$ps}) {
- my ($o,$l,$expr) = split(",", $ps->{$k}, 3);
- last if(length($p) <= $o);
- my $val = $l ? substr($p,$o,$l) : substr($p,$o);
- eval $expr if($hmProtocolEvents > 1 && $expr);
- $txt .= " $k:".(($hmProtocolEvents > 1 && $expr)?"":"0x")."$val";
- }
- }
- $txt = " ($txt)" if($txt);
- }
- $src=CUL_HM_id2Name($src);
- $dst=CUL_HM_id2Name($dst);
- my $msg ="$prefix L:$len N:$cnt F:$msgFlags CMD:$msgType SRC:$src DST:$dst $p$txt ($msgFlLong)";
- Log GetLogLevel($iname, 4), $msg;
- DoTrigger($iname, $msg) if($hmProtocolEvents > 2);
-}
-#############################
-sub
-CUL_HM_parseCommon(@){
- # parsing commands that are device independant
- my ($msgId,$msgFlag,$msgType,$src,$dst,$p) = @_;
- my $shash = $modules{CUL_HM}{defptr}{$src};
- my $dhash = $modules{CUL_HM}{defptr}{$dst};
- return "" if(!$shash->{DEF});# this should be from ourself
-
- my $pendType = $shash->{helper}{respWait}{Pending}?
- $shash->{helper}{respWait}{Pending}:"";
- #------------ parse message flag for start processing command Stack
- # TC wakes up with 8270, not with A258
- # VD wakes up with 8202
- if( $shash->{cmdStack} &&
- ((hex($msgFlag) & 0xA2) == 0x82) &&
- (CUL_HM_getRxType($shash) & 0x08)){ #wakeup #####
- #send wakeup and process command stack
- CUL_HM_SndCmd($shash, '++A112'.CUL_HM_Id($shash->{IODev}).$src);
- CUL_HM_ProcessCmdStack($shash);
- }
-
- if ($msgType eq "02"){# Ack/Nack #######################################
- if ($shash->{helper}{respWait}{msgId} &&
- $shash->{helper}{respWait}{msgId} eq $msgId ){
- #ack we waited for - stop Waiting
- CUL_HM_respPendRm($shash);
- }
- if ($pendType eq "StatusReq"){#possible answer for status request
- my $chnSrc = $src.$shash->{helper}{respWait}{forChn};
- my $chnhash = $modules{CUL_HM}{defptr}{$chnSrc};
- $chnhash = $shash if (!$chnhash);
- CUL_HM_respPendRm($shash);
- }
-
- #see if the channel is defined separate - otherwise go for chief
- my $subType = substr($p,0,2);
- my $chn = substr($p,2,2);
- #mark timing on the channel, not the device
- my $HMid = $chn?$src.$chn:$src;
- my $chnhash = $modules{CUL_HM}{defptr}{$HMid};
- $chnhash = $shash if(!$chnhash);
-
- my $reply;
- my $success;
-
- if ($subType =~ m/^8/){ #NACK
- $success = "no";
- CUL_HM_eventP($shash,"Nack");
- delete($shash->{cmdStack});
- delete($shash->{protCmdPend});
- CUL_HM_respPendRm($shash);
- $reply = "NACK";
- }
- else{ #ACK
- $reply = ($subType eq "01")?"ACKStatus":"ACK";
- $success = "yes";
- }
- readingsSingleUpdate($chnhash,"CommandAccepted",$success,1);
- CUL_HM_ProcessCmdStack($shash)
- if($dhash->{DEF} && (CUL_HM_Id($shash->{IODev}) eq $dhash->{DEF}));
- return $reply;
- }
- elsif($msgType eq "00"){
- if ($pendType eq "PairSerial"){
- if($shash->{helper}{respWait}{forChn} = substr($p,6,20)){
- CUL_HM_respPendRm($shash);
- }
- }
- }
- elsif($msgType eq "10"){
- my $subtype = substr($p,0,2);
- if($subtype eq "01"){ #storePeerList#################
- if ($pendType eq "PeerList"){
- my $chn = $shash->{helper}{respWait}{forChn};
- my $chnhash = $modules{CUL_HM}{defptr}{$src.$chn};
- $chnhash = $shash if (!$chnhash);
- my $chnNname = $chnhash->{NAME};
- my @peers = substr($p,2,) =~ /(.{8})/g;
- foreach my $peer(@peers){
- CUL_HM_ID2PeerList ($chnNname,$peer,1) if ($peer !~ m/^000000../);
- }
-
- if ($p =~ m/000000..$/) {# last entry, peerList is complete
- CUL_HM_respPendRm($shash);
- # check for request to get List3 data
- my $reqPeer = $chnhash->{helper}{getCfgList};
- if ($reqPeer){
- my $flag = CUL_HM_getFlag($shash);
- my $id = CUL_HM_Id($shash->{IODev});
- my $listNo = "0".$chnhash->{helper}{getCfgListNo};
- my @peerID = split(",", AttrVal($chnNname,"peerIDs",""));
- foreach my $peer (@peerID){
- $peer .="01" if (length($peer) == 6); # add the default
- if ($peer &&($peer eq $reqPeer || $reqPeer eq "all")){
- CUL_HM_PushCmdStack($shash,sprintf("++%s01%s%s%s04%s%s",
- $flag,$id,$src,$chn,$peer,$listNo));# List3 or 4
- }
- }
- }
- delete $chnhash->{helper}{getCfgList};
- delete $chnhash->{helper}{getCfgListNo};
- }
- else{
- CUL_HM_respPendToutProlong($shash);#wasn't last - reschedule timer
- }
- return "done";
- }
- }
- elsif($subtype eq "02" ||$subtype eq "03"){ #ParamResp##################
- if ($pendType eq "RegisterRead"){
- my $chnSrc = $src.$shash->{helper}{respWait}{forChn};
- my $chnHash = $modules{CUL_HM}{defptr}{$chnSrc};
- $chnHash = $shash if (!$chnHash);
- my $chnName = $chnHash->{NAME};
- my ($format,$data) = ($1,$2) if ($p =~ m/^(..)(.*)/);
- my $list = $shash->{helper}{respWait}{forList};
- $list = "00" if (!$list); #use the default
- if ($format eq "02"){ # list 2: format aa:dd aa:dd ...
- $data =~ s/(..)(..)/ $1:$2/g;
- }
- elsif ($format eq "03"){ # list 3: format aa:dddd
- my $addr;
- my @dataList;
- ($addr,$data) = (hex($1),$2) if ($data =~ m/(..)(.*)/);
- if ($addr == 0){
- $data = "00:00";
- }
- else{
- $data =~s/(..)/$1:/g;
- foreach my $d1 (split(":",$data)){
- push (@dataList,sprintf("%02X:%s",$addr++,$d1));
- }
- $data = join(" ",@dataList);
- }
- }
- my $peer = $shash->{helper}{respWait}{forPeer};
- my $regLN = ((CUL_HM_getExpertMode($chnHash) eq "2")?"":".")."RegL_".$list.":".$peer;
- readingsSingleUpdate($chnHash,$regLN,
- ReadingsVal($chnName,$regLN,"")." ".$data,0);
- if ($data =~m/00:00$/){ # this was the last message in the block
- if($list eq "00"){
- my $name = CUL_HM_id2Name($src);
- readingsSingleUpdate($shash,"PairedTo",
- CUL_HM_getRegFromStore($name,"pairCentral",0,"00000000"),0);
- }
- CUL_HM_respPendRm($shash);
- delete $chnHash->{helper}{shadowReg}{$regLN};#remove shadowhash
- # peer Channel name from/for user entry.
- CUL_HM_updtRegDisp($chnHash,$list,
- CUL_HM_peerChId($peer,
- substr(CUL_HM_hash2Id($chnHash),0,6),"00000000"));
- }
- else{
- CUL_HM_respPendToutProlong($shash);#wasn't last - reschedule timer
- }
- return "done";
- }
- }
- elsif($subtype eq "04"){ #ParamChange###################
- my($chn,$peerID,$list,$data) = ($1,$2,$3,$4) if($p =~ m/^04(..)(........)(..)(.*)/);
- my $chnHash = $modules{CUL_HM}{defptr}{$src.$chn};
- $chnHash = $shash if(!$chnHash); # will add param to dev if no chan
- my $regLN = ((CUL_HM_getExpertMode($chnHash) eq "2")?"":".")."RegL_".$list.":".CUL_HM_id2Name($peerID);
- $regLN =~ s/broadcast//;
- $regLN =~ s/ /_/g; #remove blanks
-
- $data =~ s/(..)(..)/ $1:$2/g;
-
- my $lN = ReadingsVal($chnHash->{NAME},$regLN,"");
- my $shdwReg = $chnHash->{helper}{shadowReg}{$regLN};
- foreach my $entry(split(' ',$data)){
- my ($a,$d) = split(":",$entry);
- last if ($a eq "00");
- if ($lN =~m/$a:/){$lN =~ s/$a:../$a:$d/;
- }else{ $lN .= " ".$entry;}
- $shdwReg =~ s/ $a:..// if ($shdwReg);# confirmed: remove from shadow
- }
- $chnHash->{helper}{shadowReg}{$regLN} = $shdwReg;
- $lN = join(' ',sort(split(' ',$lN)));# re-order
- if ($lN =~ s/00:00//){$lN .= " 00:00"};
- readingsSingleUpdate($chnHash,$regLN,$lN,0);
- CUL_HM_updtRegDisp($chnHash,$list,$peerID);
- }
- elsif($subtype eq "06"){ #reply to status request#######
- #todo = what is the answer to a status request
- if ($pendType eq "StatusReq"){#it is the answer to our request
- my $chnSrc = $src.$shash->{helper}{respWait}{forChn};
- my $chnhash = $modules{CUL_HM}{defptr}{$chnSrc};
- $chnhash = $shash if (!$chnhash);
- CUL_HM_respPendRm($shash);
- return "STATresp";
- }
- else{
- my ($chn) = ($1) if($p =~ m/^..(..)/);
- return "powerOn" if ($chn eq "00");# check dst eq "000000" as well?
- }
- }
- }
- elsif($msgType eq "70"){ #Time to trigger TC##################
- #send wakeup and process command stack
-# CUL_HM_SndCmd($shash, '++A112'.CUL_HM_Id($shash->{IODev}).$src);
-# CUL_HM_ProcessCmdStack($shash);
- }
- return "";
-}
-#############################
-sub
-CUL_HM_getRegFromStore($$$$)
-{#read a register from backup data
- my($name,$regName,$list,$peerId)=@_;
- my $hash = CUL_HM_name2Hash($name);
- my ($size,$pos,$conversion,$factor,$unit) = (8,0,"",1,""); # default
- my $addr = $regName;
- my $dId = substr(CUL_HM_name2Id($name),0,6);#id of device
- my $iId = CUL_HM_Id($hash->{IODev}); #id of IO device
- my $reg = $culHmRegDefine{$regName};
- if ($reg) { # get the register's information
- $addr = $reg->{a};
- $pos = ($addr*10)%10;
- $addr = int($addr);
- $list = $reg->{l};
- $size = $reg->{s};
- $size = int($size)*8 + ($size*10)%10;
- $conversion = $reg->{c}; #unconvert formula
- $factor = $reg->{f};
- $unit = $reg->{u};
- }
- else{
- ;# use address instead of
- }
- $peerId = CUL_HM_peerChId(($peerId?$peerId:"00000000"),$dId,$iId);
-
- my $regLN = ((CUL_HM_getExpertMode($hash) eq "2")?"":".").
- "RegL_".sprintf("%02X",$list).":".CUL_HM_peerChName($peerId,$dId,$iId);
- $regLN =~ s/broadcast//;
-
- my $data=0;
- my $convFlg = "";# confirmation flag - indicates data not confirmed by device
- for (my $size2go = $size;$size2go>0;$size2go -=8){
- my $addrS = sprintf("%02X",$addr);
-
- my $dReadS;
- if ($hash->{helper}{shadowReg}&&$hash->{helper}{shadowReg}{$regLN}){
- $dReadS = $1 if($hash->{helper}{shadowReg}{$regLN} =~ m/$addrS:(..)/);
- }
- my $dReadR = " ";
- if ($hash->{READINGS}{$regLN}) {
- $dReadR = $1 if($hash->{READINGS}{$regLN}{VAL} =~ m/$addrS:(..)/);
- }
- $convFlg = "set_" if ($dReadS && $dReadR ne $dReadS);
- my $dRead = $dReadS?$dReadS:$dReadR;
- return "invalid" if (!defined($dRead) || $dRead eq ""|| $dRead eq " ");
-
- $data = ($data<< 8)+hex($dRead);
- $addr++;
- }
-
- $data = ($data>>$pos) & (0xffffffff>>(32-$size));
- if (!$conversion){ ;# do nothing
- } elsif($conversion eq "factor"){ $data /= $factor;
- } elsif($conversion eq "fltCvT"){ $data = CUL_HM_CvTflt($data);
- } elsif($conversion eq "m10s3") { $data = ($data+3)/10;
- } elsif($conversion eq "hex" ) { $data = sprintf("0x%X",$data);
- } elsif(defined($reg->{lit})) {
- foreach (keys%{$reg->{lit}}){
- if ($data == $reg->{lit}{$_}){ $data = $_; last; }
- }
- } else { return " conversion undefined - please contact admin";
- }
- return $convFlg.$data.' '.$unit;
-
-}
-#----------------------
-sub
-CUL_HM_updtRegDisp($$$)
-{
- my $starttime = gettimeofday();
- my($hash,$list,$peerId)=@_;
- my $listNo = $list+0;
- my $name = $hash->{NAME};
- my $peer = ($peerId && $peerId ne '00000000' )?
- CUL_HM_peerChName($peerId,substr($hash->{DEF},0,6),"")."-":"";
- $peer=~s/:/-/;
- my $devName =CUL_HM_getDeviceHash($hash)->{NAME};# devName as protocol entity
- my $st = AttrVal($devName, "subType", "");
- my $md = AttrVal($devName, "model", "");
- my $chn = $hash->{DEF};
- $chn = (length($chn) == 8)?substr($chn,6,2):"";
- my @regArr = keys %culHmRegGeneral;
- push @regArr, keys %{$culHmRegType{$st}} if($culHmRegType{$st});
- push @regArr, keys %{$culHmRegModel{$md}} if($culHmRegModel{$md});
- push @regArr, keys %{$culHmRegChan{$md.$chn}} if($culHmRegChan{$md.$chn});
- my @changedRead;
- my $expLvl = (CUL_HM_getExpertMode($hash) ne "0")?1:0;
- foreach my $regName (@regArr){
- next if ($culHmRegDefine{$regName}->{l} ne $listNo);
- my $rgVal = CUL_HM_getRegFromStore($name,$regName,$list,$peerId);
- next if (!$rgVal || $rgVal eq "invalid");
- my $readName = "R-".$peer.$regName;
- $readName = ($culHmRegDefine{$regName}->{d}?"":".").$readName if (!$expLvl); #expert?
- push (@changedRead,$readName.":".$rgVal)
- if (ReadingsVal($name,$readName,"") ne $rgVal);
- }
-
- # --- handle specifics - no general approach so far.
- CUL_HM_TCtempReadings($hash)
- if (($list == 5 ||$list == 6) &&
- substr($hash->{DEF},6,2) eq "02" &&
- CUL_HM_Get($hash,$name,"param","model") eq "HM-CC-TC");
-
- CUL_HM_UpdtReadBulk($hash,1,@changedRead) if (@changedRead);
-}
-
-#############################
-my @culHmTimes8 = ( 0.1, 1, 5, 10, 60, 300, 600, 3600 );
-sub
-CUL_HM_encodeTime8($)
-{
- my $v = shift;
- return "00" if($v < 0.1);
- for(my $i = 0; $i < @culHmTimes8; $i++) {
- if($culHmTimes8[$i] * 32 > $v) {
- for(my $j = 0; $j < 32; $j++) {
- if($j*$culHmTimes8[$i] >= $v) {
- return sprintf("%X", $i*32+$j);
- }
- }
- }
- }
- return "FF";
-}
-#############################
-sub
-CUL_HM_decodeTime8($)
-{
- my $v = hex(shift);
- return "undef" if($v > 255);
- my $v1 = int($v/32);
- my $v2 = $v%32;
- return $v2 * $culHmTimes8[$v1];
-}
-#############################
-sub
-CUL_HM_encodeTime16($)
-{
- my $v = shift;
- return "0000" if($v < 0.05);
-
- my $ret = "FFFF";
- my $mul = 10;
- for(my $i = 0; $i < 32; $i++) {
- if($v*$mul < 0x7ff) {
- $ret=sprintf("%04X", ((($v*$mul)<<5)+$i));
- last;
- }
- $mul /= 2;
- }
- my $v2 = CUL_HM_decodeTime16($ret);
- return ($ret);
-}
-sub
-CUL_HM_convTemp($)
-{
- my ($val) = @_;
-
- if(!($val eq "on" || $val eq "off" ||
- ($val =~ m/^\d*\.?\d+$/ && $val >= 6 && $val <= 30))) {
- my @list = map { ($_.".0", $_+0.5) } (6..30);
- pop @list;
- return "Invalid temperature $val, choose one of on off " . join(" ",@list);
- }
- $val = 100 if($val eq "on");
- $val = 0 if($val eq "off");
- return sprintf("%02X", $val*2);
-}
-#############################
-sub
-CUL_HM_decodeTime16($)
-{
- my $v = hex(shift);
- my $m = int($v>>5);
- my $e = $v & 0x1f;
- my $mul = 0.1;
- return 2^$e*$m*0.1;
-}
-#############################
-sub
-CUL_HM_pushConfig($$$$$$$$)
-{#routine will generate messages to write cnfig data to register
- my ($hash,$src,$dst,$chn,$peerAddr,$peerChn,$list,$content) = @_;
- my $flag = CUL_HM_getFlag($hash);
- my $tl = length($content);
- $chn = sprintf("%02X",$chn);
- $peerChn = sprintf("%02X",$peerChn);
- $list = sprintf("%02X",$list);
-
- # --store pending changes in shadow to handle bit manipulations cululativ--
- $peerAddr = "000000" if(!$peerAddr);
- my $peerN = ($peerAddr ne "000000")?CUL_HM_id2Name($peerAddr.$peerChn):"";
- $peerN =~ s/broadcast//;
- $peerN =~ s/ /_/g;#remote blanks
- my $regLN = ((CUL_HM_getExpertMode($hash) eq "2")?"":".").
- "RegL_".$list.":".$peerN;
- #--- copy data from readings to shadow
- my $chnhash = $modules{CUL_HM}{defptr}{$dst.$chn};
- $chnhash = $hash if (!$chnhash);
- if (!$chnhash->{helper}{shadowReg} ||
- !$chnhash->{helper}{shadowReg}{$regLN}){
- $chnhash->{helper}{shadowReg}{$regLN} =
- ReadingsVal($chnhash->{NAME},$regLN,"");
- }
- #--- update with ne value
- my $regs = $chnhash->{helper}{shadowReg}{$regLN};
- for(my $l = 0; $l < $tl; $l+=4) { #substitute changed bytes in shadow
- my $addr = substr($content,$l,2);
- my $data = substr($content,$l+2,2);
- if(!$regs || !($regs =~ s/$addr:../$addr:$data/)){
- $regs .= " ".$addr.":".$data;
- }
- }
- $chnhash->{helper}{shadowReg}{$regLN} = $regs;
- CUL_HM_updtRegDisp($hash,$list,$peerAddr.$peerChn);
- CUL_HM_PushCmdStack($hash, "++".$flag.'01'.$src.$dst.$chn.'05'.
- $peerAddr.$peerChn.$list);
- for(my $l = 0; $l < $tl; $l+=28) {
- my $ml = $tl-$l < 28 ? $tl-$l : 28;
- CUL_HM_PushCmdStack($hash, "++A001".$src.$dst.$chn."08".
- substr($content,$l,$ml));
- }
- CUL_HM_PushCmdStack($hash,"++A001".$src.$dst.$chn."06");
-}
-sub
-CUL_HM_secSince2000()
-{
- # Calculate the local time in seconds from 2000.
- my $t = time();
- my @l = localtime($t);
- my @g = gmtime($t);
- $t += 60*(($l[2]-$g[2] + ((($l[5]<<9)|$l[7]) <=> (($g[5]<<9)|$g[7])) * 24 + $l[8]) * 60 + $l[1]-$g[1])
- # timezone and daylight saving...
- - 946684800 # seconds between 01.01.2000, 00:00 and THE EPOCH (1970)
- - 7200; # HM Special
- return $t;
-}
-
-############### Activity supervision section ################
-# verify that devices are seen in a certain period of time
-# It will generate events if no message is seen sourced by the device during
-# that period.
-# ActionDetector will use the fixed HMid 000000
-sub
-CUL_HM_ActGetCreateHash()
-{# return hash of ActionDetector - create one if not existant
- if (!$modules{CUL_HM}{defptr}{"000000"}){
- DoTrigger("global", "UNDEFINED ActionDetector CUL_HM 000000");
- $attr{ActionDetector}{actCycle} = 600;
- }
- my $defPtr = $modules{CUL_HM}{defptr};
- my $actName = $defPtr->{"000000"}{NAME} if($defPtr->{"000000"});
- my $actHash = $modules{CUL_HM}{defptr}{"000000"};
- if (!$actHash->{helper}{first}){ # if called first time attributes are no yet
- #recovered
- InternalTimer(gettimeofday()+3, "CUL_HM_ActGetCreateHash", "ActionDetector", 0);
- $actHash->{helper}{first} = 1;
- return;
- }
- if (!$actHash->{helper}{actCycle} ){ #This is the first call
- my $peerIDs = AttrVal($actName,"peerIDs","");
- my $tn = TimeNow();
- foreach my $devId (split(",",$peerIDs)){
- $actHash->{helper}{$devId}{start} = $tn;
- my $devName = CUL_HM_id2Name($devId);
- readingsSingleUpdate($actHash,"status_".$devName,"unknown",1);
- $attr{$devName}{actStatus}=""; # force trigger
- CUL_HM_setAttrIfCh($devName,"actStatus","unknown","Activity");
- }
- }
- if (!$actHash->{helper}{actCycle} ||
- $actHash->{helper}{actCycle} != $attr{$actName}{actCycle}){
- $attr{$actName}{actCycle} = 30 if(!$attr{$actName}{actCycle} ||
- $attr{$actName}{actCycle}<30);
- $actHash->{helper}{actCycle} = $attr{$actName}{actCycle};
- RemoveInternalTimer("ActionDetector");
- $actHash->{STATE} = "active";
-
- InternalTimer(gettimeofday()+$attr{$actName}{actCycle},
- "CUL_HM_ActCheck", "ActionDetector", 0);
- }
- return $actHash;
-}
-sub
-CUL_HM_time2sec($)
-{
- my ($timeout) = @_;
- my ($h,$m) = split(":",$timeout);
- no warnings;
- $h = int($h);
- $m = int($m);
- use warnings;
- return ((sprintf("%03s:%02d",$h,$m)),((int($h)*60+int($m))*60));
-}
-sub
-CUL_HM_ActAdd($$)
-{# add an HMid to list for activity supervision
- my ($devId,$timeout) = @_; #timeout format [hh]h:mm
-
- return $devId." is not an HM device - action detection cannot be added"
- if (length($devId) != 6);
- my ($cycleString,undef)=CUL_HM_time2sec($timeout);
- my $devName = CUL_HM_id2Name($devId);
- $attr{$devName}{actCycle} = $cycleString;
- $attr{$devName}{actStatus}=""; # force trigger
- CUL_HM_setAttrIfCh($devName,"actStatus","unknown","Activity");
- my $actHash = CUL_HM_ActGetCreateHash();
- my $actName = $actHash->{NAME}; # could have been renamed
-
- my $peerIDs = AttrVal($actName,"peerIDs","");
- $peerIDs .= $devId."," if($peerIDs !~ m/$devId,/);#add if not in
- $attr{$actName}{peerIDs} = $peerIDs;
- my $tn = TimeNow();
- $actHash->{helper}{$devId}{start} = $tn;
- readingsSingleUpdate($actHash,"status_".$devName,"unknown",1);
- Log GetLogLevel($actName,3),"Device ".$devName." added to ActionDetector with "
- .$cycleString." time";
-}
-sub
-CUL_HM_ActDel($)
-{# delete HMid for activity supervision
- my ($devId) = @_;
-
- return $devId." is not an HM device - action detection cannot be added"
- if (length($devId) != 6);
-
- my $devName = CUL_HM_id2Name($devId);
- delete ($attr{$devName}{actCycle});
- CUL_HM_setAttrIfCh($devName,"actStatus","deleted","Activity");#post trigger
- delete ($attr{$devName}{actStatus});
- my $acthash = CUL_HM_ActGetCreateHash();
- my $actName = $acthash->{NAME};
-
- delete ($acthash->{helper}{$devId});
-
- $attr{$actName}{peerIDs} = "" if (!defined($attr{$actName}{peerIDs}));
- $attr{$actName}{peerIDs} =~ s/$devId,//g;
- Log GetLogLevel($actName,3),"Device ".$devName." removed from ActionDetector";
-}
-sub
-CUL_HM_ActCheck()
-{# perform supervision
- my $actHash = CUL_HM_ActGetCreateHash();
- my $tod = int(gettimeofday());
- my $actName = $actHash->{NAME};
- my $peerIDs = AttrVal($actName,"peerIDs","none");
-# delete ($actHash->{READINGS}); #cleansweep
- my @event;
- my ($noUnkn,$noAlive,$noDead,$noOff) =(0,0,0,0);
-
- foreach my $devId (split(",",$peerIDs)){
- $noUnkn++;
- my $devName = CUL_HM_id2Name($devId);
- if(!$devName || !defined($attr{$devName}{actCycle})){
- CUL_HM_ActDel($devId);
- next;
- }
- my $rdName = "status_".$devName;
- my $state;
- my (undef,$tSec)=CUL_HM_time2sec($attr{$devName}{actCycle});
- if ($tSec == 0){# detection switched off
- $noOff++;$noUnkn--;
- $state = "switchedOff";
- }
- else{
- my $devHash = CUL_HM_name2Hash($devName);
- my $tLast = $devHash->{"protLastRcv"};
- my @t = localtime($tod - $tSec); #time since when a trigger is expected
- my $tSince = sprintf("%04d-%02d-%02d %02d:%02d:%02d",
- $t[5]+1900, $t[4]+1, $t[3], $t[2], $t[1], $t[0]);
-
- if ((!$tLast || $tSince gt $tLast)){ #no message received in timeframe
- if ($tSince gt $actHash->{helper}{$devId}{start}){
- $noDead++;$noUnkn--;
- $state = "dead";
- };
- }else{
- $noAlive++;$noUnkn--;
- $state = "alive";
- }
- }
- if ($state && $attr{$devName}{actStatus} ne $state){
-
- DoTrigger($devName,"Activity:".$state);
- $attr{$devName}{actStatus} = $state;
- push @event, $rdName.":".$state;
- Log GetLogLevel($actName,4),"Device ".$devName." is ".$state;
- }
- }
- push @event, "state:"."alive:".$noAlive
- ." dead:".$noDead
- ." unkn:".$noUnkn
- ." off:".$noOff;
-
- CUL_HM_UpdtReadBulk($actHash,0,@event);
-
- $attr{$actName}{actCycle} = 600 if($attr{$actName}{actCycle}<30);
- $actHash->{helper}{actCycle} = $attr{$actName}{actCycle};
- InternalTimer(gettimeofday()+$attr{$actName}{actCycle},
- "CUL_HM_ActCheck", "ActionDetector", 0);
-}
-sub
-CUL_HM_UpdtReadBulk(@)
-{ # update a bunch of readings and trigger the events
- my ($hash,$doTrg,@readings) = @_;
- return if (!@readings);
- readingsBeginUpdate($hash);
- foreach my $rd (@readings){
- next if (!$rd);
- my ($rdName, $rdVal) = split(":",$rd, 2);
- readingsBulkUpdate($hash,$rdName,
- ((defined($rdVal) && $rdVal ne "")?$rdVal:"-"));
- }
- readingsEndUpdate($hash,$doTrg);
-}
-sub
-CUL_HM_setAttrIfCh($$$$)
-{
- my ($name,$att,$val,$trig) = @_;
- if($attr{$name}{$att} ne $val){
- DoTrigger($name,$trig.":".$val) if($trig);
- $attr{$name}{$att} = $val;
- }
-}
-
-1;
-
-=pod
-=begin html
-
-
-CUL_HM
-
- Support for eQ-3 HomeMatic devices via the CUL or the HMLAN.
-
-
- Define
-
- define <name> CUL_HM <6-digit-hex-code|8-digit-hex-code>
-
-
- Correct device definition is the key for HM environment simple maintenance.
-
-
- Background to define entities:
- HM devices has a 3 byte (6 digit hex value) HMid - which is key for
- addressing. Each device hosts one or more channels. HMid for a channel is
- the device's HMid plus the channel number (1 byte, 2 digit) in hex.
- Channels should be defined for all multi-channel devices. Channel entities
- cannot be defined if the hosting device does not exist Note: FHEM
- mappes channel 1 to the device if it is not defined explicitely. Therefore
- it does not need to be defined for single channel devices.
-
- Note: if a device is deleted all assotiated channels will be removed as
- well. An example for a full definition of a 2 channel switch is given
- below:
-
-
- define livingRoomSwitch CUL_HM 123456
- define LivingroomMainLight CUL_HM 12345601
- define LivingroomBackLight CUL_HM 12345602
-
-
- livingRoomSwitch is the device managing communication. This device is
- defined prior to channels to be able to setup references.
- LivingroomMainLight is channel 01 dealing with status of light, channel
- peers and channel assotiated register. If not defined channel 01 is covered
- by the device entity. LivingRoomBackLight is the second 'channel',
- channel 02. Its definition is mandatory to operate this function.
-
- Sender specials: HM threats each button of remotes, push buttons and
- similar as channels. It is possible (not necessary) to define a channel per
- button. If all channels are defined access to pairing informatin is
- possible as well as access to channel related register. Furthermore names
- make the traces better readable.
-
- define may also be invoked by the autocreate
- module, together with the necessary subType attribute.
- Usually you issue a hmPairForSec and press the
- corresponding button on the device to be paired, or issue a hmPairSerial set command if the device is a receiver
- and you know its serial number. Autocreate will then create a fhem
- device and set all necessary attributes. Without pairing the device
- will not accept messages from fhem. fhem may create the device even if
- the pairing is not successful. Upon a successful pairing you'll see a
- CommandAccepted entry in the details section of the CUL_HM device.
-
- If you cannot use autocreate, then you have to specify:
-
- - the <6-digit-hex-code>or HMid+ch <8-digit-hex-code>
- It is the unique, hardcoded device-address and cannot be changed (no,
- you cannot choose it arbitrarily like for FS20 devices). You may
- detect it by inspecting the fhem log.
- - the hmClass attribute
- which is either sender or receiver. It is not used in fhem
- - the subType attribute
- which is one of switch dimmer blindActuator remote sensor swi
- pushButton threeStateSensor motionDetector keyMatic winMatic
- smokeDetector
-
- Without these attributes fhem won't be able to decode device messages
- appropriately.
-
- Notes
-
- - If the interface is a CUL device, the rfmode
- attribute of the corresponding CUL/CUN device must be set to HomeMatic.
- Note: this mode is BidCos/Homematic only, you will not receive
- FS20/HMS/EM/S300 messages via this device. Previously defined FS20/HMS
- etc devices must be assigned to a different input device (CUL/FHZ/etc).
-
- - Currently supported device families: remote, switch, dimmer,
- blindActuator, motionDetector, smokeDetector, threeStateSensor,
- THSensor, winmatic. Special devices: KS550, HM-CC-TC and the KFM100.
-
- - Device messages can only be interpreted correctly if the device type is
- known. fhem will extract the device type from a "pairing request"
- message, even if it won't respond to it (see hmPairSerial and hmPairForSec to enable pairing).
- As an alternative, set the correct subType and model attributes, for a
- list of possible subType values see "attr hmdevice ?".
-
- - The so called "AES-Encryption" is in reality a signing request: if it is
- enabled, an actor device will only execute a received command, if a
- correct answer to a request generated by the actor is received. This
- means:
-
- - Reaction to commands is noticably slower, as 3 messages are sent
- instead of one before the action is processed by the actor.
- - Every command and its final ack from the device is sent in clear,
- so an outside observer will know the status of each device.
- - The firmware implementation is buggy: the "toggle" event is executed
- before the answer for the signing request is received, at
- least by some switches (HM-LC-Sw1-Pl and HM-LC-SW2-PB-FM).
- - The HMLAN configurator will answer signing
- requests by itself, and if it is configured with the 3-byte address
- of a foreign CCU which is still configurerd with the default
- password, it is able to answer signing requests correctly.
- - AES-Encryption is not useable with a CUL device as the interface,
- but it is supported with a HMLAN. Due to the issues above I do not
- recommend using Homematic encryption at all.
-
-
-
-
-
-
-
- Set
-
- Note: devices which are normally send-only (remote/sensor/etc) must be set
- into pairing/learning mode in order to receive the following commands.
-
-
-
- Universal commands (available to most hm devices):
-
- - actiondetect <[hhh:mm]|off>
- Supports 'alive' or better 'not alive' detection for devices. [hhh:mm] is the maxumin silent time for the device. Upon no message received in this period an event will be raised "<device> is dead". If the device sends again another notification is posted "<device> is alive".
- This actiondetect will be autocreated for each device with build in cyclic status report.
- Controlling entity is a pseudo device "ActionDetector" with HMId "000000".
- Due to performance considerations the report latency is set to 600sec (10min). It can be controlled by the attribute "actCycle" of "ActionDetector".
- Once entered to the supervision the HM device has 2 attributes:
-
- actStatus: activity status of the device
- actCycle: detection period [hhh.mm]
-
- Furthermore the overall function can be viewed checking out the "ActionDetector" entity. Here the status of all entities is present in the READING section.
- Note: This function can be enabled for devices with non-cyclic messages as well. It is up to the user to enter a reasonable cycletime.
-
- - clear <[readings|msgEvents]>
- A set of variables can be removed.
-
- readings: all readings will be deleted. Any new reading will be added usual. May be used to eliminate old data
- msgEvents: all message event counter will be removed. Also commandstack will be cleared.
-
-
- - getConfig
- Will read major configuration items stored in the HM device. Executed
- on a channel it will read pair Inforamtion, List0, List1 and List3 of
- the 1st internal peer. Furthermore the peerlist will be retrieved for
- teh given channel. If executed on a device the command will get the
- above info or all assotated channels. Not included will be the
- configuration for additional peers. The command is a shortcut
- for a selection of other commands.
-
- - getdevicepair
- will read the peers (see devicepair) that are assigned to a channel.
- This command needs to be executed per channel. Information will be
- stored in the field Peers of the channel (see devicepair for specials
- about single-channel deivces). For sender the same procedure as
- described in devicepair is necessary to get a reading. Also note that
- a proper diaplay will only be possible if define per channel (button)
- was done - see define.
- - getpair
- read pair information of the device. See also pair
- - getRegRaw [List0|List1|List2|List3|List4|List5|List6]
- <peerChannel>
-
- Read registerset in raw format. Description of the registers is beyond
- the scope of this documentation.
-
- Registers are structured in so called lists each containing a set of
- registers.
-
- List0: device-level settings e.g. CUL-pairing or dimmer thermal limit
- settings.
-
- List1: per channel settings e.g. time to drive the blind up and
- down.
-
- List3: per 'link' settings - means per peer-channel. This is a lot of
- data!. It controlls actions taken upon receive of a trigger from the
- peer.
-
- List4: settings for channel (button) of a remote
-
- <PeerChannel> paired HMid+ch, i.e. 4 byte (8 digit) value like
- '12345601'. It is mendatory for List 3 and 4 and can be left out for
- List 0 and 1.
-
- 'all' can be used to get data of each paired link of the channel.
-
- 'selfxx' can be used to address data for internal channels (associated
- with the build-in switches if any). xx is the number of the channel in
- decimal.
-
- Note1: execution depends on the entity. If List1 is requested on a
- device rather then a channel the command will retrieve List1 for all
- channels assotiated. List3 with peerChannel = all will get all link
- for all channel if executed on a device.
-
- Note2: for 'sender' see remote
-
- Note3: the information retrieval may take a while - especially for
- devices with a lot of channels and links. It may be necessary to
- refresh the web interface manually to view the results
-
- Note4: the direct buttons on a HM device are hidden by default.
- Nevertheless those are implemented as links as well. To get access to
- the 'internal links' it is necessary to issue 'set <name> regSet
- intKeyVisib 1' or 'set <name> setRegRaw List0 2 81'. Reset it
- by replacing '81' with '01' example:
-
-
- set mydimmer getRegRaw List1
- set mydimmer getRegRaw List3 all
-
-
- - pair
- Pair the device again with its known serialNumber (e.g. after a device
- reset) to the CUL. If paired, devices will report status information to
- the CUL. If not paired, the device wont respond to requests, and
- certain status information is also not reported. Paring is on device
- level and is common for all channels. See also getPair and unpair.
- - peerBulk
- peerBulk will add peer channels to the channel. All channels in the
- list will be added. This includes that the parameter and behavior
- defined for this 'link' will return to the defaults. peerBulk is only
- meant to add peers. More suffisticated funktionality as provided by
- devicepair is not supported. peerBulk
- will only add channels in 'single' button mode.
- Also note that peerBulk will not delete any existing peers, just add
- and re-add given peers.
- Main purpose of this command is the usage for re-store data to a
- device. It is recommended to restore register configuration utilising
- regBulk
- Example:
-
- set myChannel peerBulk 12345601,
- set myChannel peerBulk self01,self02,FB_Btn_04,FB_Btn_03,
-
-
- - regRaw [List0|List1|List2|List3|List4] <addr> <data>
- replaced by regBulk
- - regBulk <reg List>:<peer> <addr1:data1> <addr2:data2>...
-
- This command will replace the former regRaw. It allows to set register
- in raw format. Its main purpose is to restore a complete register list
- to values secured before.
- Values may be read by getConfig. The
- resulting readings can be used directly for this command.
- <reg List> is the list data should be written to. Format could be
- '00', 'RegL_00', '01'...
- <peer> is an optional adder in case the list requires a peer.
- The peer can be given as channel name or the 4 byte (8 chars) HM
- channel ID.
- <addr1:data1> is the list of register to be written in hex
- format.
- Example:
-
- set myChannel regBulk RegL_00: 02:01 0A:17 0B:43 0C:BF 15:FF 00:00
- RegL_03:FB_Btn_07
- 01:00 02:00 03:00 04:32 05:64 06:00 07:FF 08:00 09:FF 0A:01 0B:44 0C:54 0D:93 0E:00 0F:00 11:C8 12:00 13:00 14:00 15:00 16:00 17:00 18:00 19:00 1A:00 1B:00 1C:00 1D:FF 1E:93 1F:00 81:00 82:00 83:00 84:32 85:64 86:00 87:FF 88:00 89:FF 8A:21 8B:44 8C:54 8D:93 8E:00 8F:00 91:C8 92:00 93:00 94:00 95:00 96:00 97:00 98:00 99:00 9A:00 9B:00 9C:00 9D:05 9E:93 9F:00 00:00
- set myblind regBulk 01 0B:10
- set myblind regBulk 01 0C:00
-
- myblind will set the max drive time up for a blind actor to 25,6sec
- - regSet <regName> <value> <peerChannel>
- For some major register a readable version is implemented supporting
- register names <regName> and value conversionsing. Only a subset
- of register can be supproted.
- <value> is the data in human readable manner that will be written
- to the register.
- <peerChannel> is required if this register is defined on a per
- 'devicepair' base. It can be set to '0' other wise.See getRegRaw for full description
- Supported register for a device can be explored using
-
- Condensed register description will be printed
- using
-
-
- - reset
- Factory reset the device. You need to pair it again to use it with
- fhem.
-
- - sign [on|off]
- Activate or deactivate signing (also called AES encryption, see the note above). Warning: if the device is attached via
- a CUL, you won't be able to switch it (or deactivate signing) from
- fhem before you reset the device directly.
-
- - statusRequest
- Update device status. For multichannel devices it should be issued on
- an per channel base
-
- - unpair
- "Unpair" the device, i.e. make it available to pair with other master
- devices. See pair for description.
- - virtual <number of buttons>
- configures a defined curcuit as virtual remote controll. Then number
- of button being added is 1 to 255. If the command is issued a second
- time for the same entity additional buttons will be added.
- Example for usage:
-
- define vRemote CUL_HM 100000 # the selected HMid must not be in use
- set vRemote virtual 20 # define 20 button remote controll
- set vRemote_Btn4 devicepair 0 <actorchannel> # pairs Button 4 and 5 to the given channel
- set vRemote_Btn4 press
- set vRemote_Btn5 press long
-
- see also press
-
-
-
-
- subType (i.e family) dependent commands:
-
-
- - switch
-
- - on - set the switch on
- - off - set the switch off
- - on-for-timer <sec> -
- set the switch on for the given seconds [0-85825945].
Note:
- off-for-timer like FS20 is not supported. It needs to be programmed
- on link level.
- - on-till <time> - set the switch on for the given end time.
- set <name> on-till 20:32:10
- Currently a max of 24h is supported with endtime.
-
- - toggle - toggle the switch.
-
-
-
- - dimmer, blindActuator
-
- - 0 - 100 [on-time] [ramp-time]
- set the actuator to the given value (in percent)
- with a resolution of 0.5.
- Optional for dimmer on-time and ramp time can be choosen, both in seconds with 0.1s granularity.
- On-time is analog "on-for-timer".
- Ramp-time default is 2.5s, 0 means instantanous
-
- - on set level to 100%
- - off set level to 0%
- - toggle - toggle between off and the last on-value
- - on-for-timer <sec> - Dimmer only!
- - on-till <time> - Dimmer only!
- - stop - stop motion or dim ramp
-
-
-
- - remotes, pushButton
- This class of devices does not react on requests unless they are put
- to learn mode. FHEM obeys this behavior by stacking all requests until
- learn mode is detected. Manual interaction of the user is necessary to
- activate learn mode. Whether commands are pending is reported on
- device level with parameter 'protCmdPend'.
-
- - devicepair <btn_no> <hmDevice> [single|dual]
- [set|unset] [actor|remote]
-
- Pair/unpair will establish a connection between a sender-channel and
- an actuator-channel called link in HM nomenclatur. Trigger from
- sender-channel, e.g. button press, will be processed by the
- actuator-channel without CCU interaction. Sender-channel waits for an
- acknowledge of each actuator paired to it. Positive indication will be
- given once all actuator responded.
-
- Sender must be set into learning mode after command execution. FHEM
- postpones the commands until then.
-
- devicepair can be repeated for an existing devicepair. This will cause
- parameter reset to HM defaults for this link.
-
- Even though the command is executed on a remote or push-button it will
- as well take effect on the actuator directly. Both sides' pairing is
- virtually independant and has different impact on sender and receiver
- side.
-
- Devicepairing of one actuator-channel to multiple sender-channel as
- well as one sender-channel to multiple Actuator-channel is
- possible.
-
- <hmDevice> is the actuator-channel to be paired.
-
- <btn_no> is the sender-channel (button) to be paired. If
- 'single' is choosen buttons are counted from 1. For 'dual' btn_no is
- the number of the Button-pair to be used. I.e. '3' in dual is the
- 3rd button pair correcponding to button 5 and 6 in single mode.
-
- If the command is executed on a channel the btn_no is ignored.
-
- [single|dual]: this mode impacts the default behavior of the
- Actuator upon using this button. E.g. a dimmer can be learned to a
- single button or to a button pair.
-
- 'dual' (default) Button pairs two buttons to one actuator. With a
- dimmer this means one button for dim-up and one for dim-down.
-
- 'single' uses only one button of the sender. It is useful for e.g. for
- simple switch actuator to toggle on/off. Nevertheless also dimmer can
- be learned to only one button.
-
- 'set' will setup pairing for the channels
-
- 'unset' will remove the pairing for the channels
-
- [actor|remote|both] limits the execution to only actor or only remote.
- This gives the user the option to redo the pairing on the remote
- channel while the settings in the actor will not be removed.
-
- Example:
-
-
- set myRemote devicepair 2 mySwActChn single set # pair second button to an actuator channel
- set myRmtBtn devicepair 0 mySwActChn single set #myRmtBtn is a button of the remote. '0' is not processed here
- set myRemote devicepair 2 mySwActChn dual set #pair button 3 and 4
- set myRemote devicepair 3 mySwActChn dual unset #remove pairing for button 5 and 6
- set myRemote devicepair 3 mySwActChn dual unset aktor #remove pairing for button 5 and 6 in actor only
- set myRemote devicepair 3 mySwActChn dual set remote #pair button 5 and 6 on remote only. Link settings il mySwActChn will be maintained
-
-
-
-
-
-
- - virtual
-
- - devicepair see remote
- - press [long|short]
- simulates a button press short (default) or long. Note that the current
- implementation will not specify the duration for long. Only one trigger
- will be sent of type "long".
-
-
-
- - smokeDetector
- Note: All these commands work right now only if you have more then one
- smoekDetector, and you paired them to form a group. For issuing the
- commands you have to use the master of this group, and currently you
- have to guess which of the detectors is the master.
- smokeDetector can be setup to teams using
- devicepair. You need to pair all
- team-members to the master. Don't forget to also devicepair the master
- itself to the team - i.e. pair it to itself! doing that you have full
- controll over the team and don't need to guess.
-
- - test - execute a network test
- - alarmOn - initiate an alarm
- - alarmOff - switch off the alarm
-
-
- - 4Dis (HM-PB-4DIS-WM)
-
- - text <btn_no> [on|off] <text1> <text2>
- Set the text on the display of the device. To this purpose issue
- this set command first (or a number of them), and then choose from
- the teach-in menu of the 4Dis the "Central" to transmit the data.
- Example:
-
-
- set 4Dis text 1 on On Lamp
- set 4Dis text 1 off Kitchen Off
-
-
-
-
-
- - Climate-Control (HM-CC-TC)
-
- - day-temp <tmp>
- night-temp <tmp>
- party-temp <tmp>
- desired-temp <tmp>
- Set different temperatures. Temp must be between 6 and 30
- Celsius, and precision is half a degree.
- - tempListSat HH:MM temp ... 24:00 temp
- tempListSun HH:MM temp ... 24:00 temp
- tempListMon HH:MM temp ... 24:00 temp
- tempListTue HH:MM temp ... 24:00 temp
- tempListThu HH:MM temp ... 24:00 temp
- tempListWed HH:MM temp ... 24:00 temp
- tempListFri HH:MM temp ... 24:00 temp
- Specify a list of temperature intervals. Up to 24 intervals can be
- specified for each week day, the resolution is 10 Minutes. The
- last time spec must always be 24:00.
- Example: set th tempListSat 06:00 19 23:00 22.5 24:00 19
- Meaning: until 6:00 temperature shall be 19, from then until 23:00 temperature shall be
- 22.5, thereafter until midnight, 19 degrees celsius is desired.
- - displayMode [temp-only|temp-hum]
- displayTemp [actual|setpoint]
- displayTempUnit [celsius|fahrenheit]
- controlMode [manual|auto|central|party]
- decalcDay <day>
-
-
- - OutputUnit (HM-OU-LED16)
-
- - led [off|red|green|yellow]
- switches the LED of the channel to the color. If the command is
- executed on a device it will set all LEDs to the specified
- color.
- For Expert all LEDs can be set individual by providing a 8-digit hex number to the device.
- - ilum <brightness><duration>
- <brightness> [0-15] of backlight.
- <duration> [0-127] in sec. 0 is permanent 'on'.
-
-
-
- - OutputUnit (HM-OU-CFM-PL)
-
- - led <color>[,<color>..]
- Possible colors are [redL|greenL|yellowL|redS|greenS|yellowS]. A
- sequence of colors can be given separating the color entries by ','.
- White spaces must not be used in the list. 'S' indicates short and
- 'L' long ilumination.
- - playTone <MP3No>[,<MP3No>..]
- Play a series of tones. List is to be entered separated by ','. White
- spaces must not be used in the list.
-
-
- - HM-RC-19xxx
-
- - alarm <count>
- issue an alarm message to the remote
- - service <count>
- issue an service message to the remote
- - symbol <symbol> [set|unset]
- activate a symbol as available on the remote.
- - beep [off|1|2|3]
- activate tone
- - backlight [off|on|slow|fast]
- activate backlight
- - display <text> comma unit tone backlight <symbol(s)>
-
- control display of the remote
- <text> : up to 5 chars
- comma : 'comma' activates the comma, 'no' leaves it off
- [unit] : set the unit symbols.
- [off|Proz|Watt|x3|C|x5|x6|x7|F|x9|x10|x11|x12|x13|x14|x15]. Currently
- the x3..x15 display is not tested.
-
- tone : activate one of the 3 tones [off|1|2|3]
-
- backlight: activate backlight flash mode [off|on|slow|fast]
-
- <symbol(s)> activate symbol display. Multople symbols can be
- acticated at the same time, concatinating them comma separated. Don't
- use spaces here. Possiblesymbols are
-
- [bulb|switch|window|door|blind|scene|phone|bell|clock|arrowUp|arrowDown]
- Example:
-
- # "hello" in display, symb bulb on, backlight, beep
- set FB1 display Hello no off 1 on bulb
- # "1234,5" in display with unit 'W'. Symbols scene,phone,bell and
- # clock are active. Backlight flashing fast, Beep is second tone
- set FB1 display 12345 comma Watt 2 fast scene,phone,bell,clock
-
-
-
-
- - keyMatic
- The Keymatic uses the AES signed communication. Therefore the control
- of the Keymatic is only together with the HM-LAN adapter possible. But
- the CUL can read and react on the status information of the
- Keymatic.
-
- - lock
- The lock bolt moves to the locking position
- - unlock [sec]
- The lock bolt moves to the unlocking position.
- [sec]: Sets the delay in seconds after the lock automatically locked again.
- 0 - 65535 seconds
- - open [sec]
- Unlocked the door so that the door can be opened.
- [sec]: Sets the delay in seconds after the lock automatically locked
- again. 0 - 65535 seconds
- - inhibit [on|off]
- Block / unblock all directly paired remotes and the hardware buttons of the
- keyMatic. If inhibit set on, the door lock drive can be controlled only by
- FHEM.
- Examples:
-
- # Lock the lock
- set keymatic lock
- # open the door and relock the lock after 60 seconds
- set keymatic unlock 60
-
-
-
-
-
- - winMatic
- winMatic provides 2 channels, one for the window control and a second
- for the accumulator.
-
- - level <level> <relockDelay> <speed>
- set the level.
- <level>: range is 0 to 100%
- <relockDelay>: range 0 to 65535 sec. 'ignore' can be used to igneore the value alternaly
- <speed>: range is 0 to 100%
-
- - stop
- stop movement
-
-
- - HM-Sys-sRP-Pl
- setRepeat => "[no1..36] [bdcast-yes|no]"
-
- - setRepeat <entry> <sender> <receiver> <broadcast>
- <entry> [1..36] entry number in repeater table. The repeater can handle up to 36 entries.
- <sender> name or HMID of the sender or source which shall be repeated
- <receiver> name or HMID of the receiver or destination which shall be repeated
- <broadcast> [yes|no] determines whether broadcast from this ID shall be repeated
-
-
-
-
- Debugging:
-
-
-
-
-
-
- Get
-
- - configSave <filename>
- Saves the configuration of an entity into a file. Data is stored in a
- format to be executed from fhem command prompt.
- The file is located in the fhem home directory aside of fhem.cfg. Data
- will be stored cumulative - i.e. new data will be appended to the
- file. It is up to the user to avoid duplicate storage of the same
- entity.
- Target of the data is ONLY the HM-device information which is located
- IN the HM device. Explicitely this is the peer-list and the register.
- With the register also the pairing is included.
- The file is readable and editable by the user. Additionaly timestamps
- are stored to help user to validate.
- Restrictions:
- Even though all data of the entity will be secured to the file FHEM
- stores the data that is avalilable to FHEM at time of save!. It is up
- to the user to read the data from the HM-hardware prior to execution.
- See recommended flow below.
- This command will not store any FHEM attributes o device definitions.
- This continues to remain in fhem.cfg.
- Furthermore the secured data will not automatically be reloaded to the
- HM-hardware. It is up to the user to perform a restore.
- As with other commands also 'configSave' is best executed on a device
- rather then on a channel. If executed on a device also the assotiated
- channel data will be secured.
-
- Recommended work-order for device 'HMdev':
- set HMdev clear msgEvents # clear old events to better check flow
- set HMdev getConfig # read device & channel inforamtion
- # wait untill operation is complete
- # protState should be CMDs_done
- # there shall be no warnings amongst prot... variables
- get configSave myActorFile
-
-
- - param <paramName>
- returns the content of the relevant parameter for the entity.
- Note: if this command is executed on a channel and 'model' is
- requested the content hosting device's 'model' will be returned.
-
- - reg <addr> <list> <peerID>
- returns the value of a register. The data is taken from the storage in FHEM and not read directly outof the device. If register content is not present please use getConfig, getReg in advance.
-
- <addr> address in hex of the register. Registername can be used alternaly if decoded by FHEM. "all" will return all decoded register for this entity in one list.
- <list> list from which the register is taken. If rgistername is used list is ignored and can be set to 0.
- <peerID> identifies the registerbank in case of list3 and list4. It an be set to dummy if not used.
-
- - regList
- returns a list of register that are decoded by FHEM for this device.
- Note that there could be more register implemented for a device.
-
-
-
-
- Attributes
-
- - eventMap
- - do_not_notify
- - ignore
- - dummy
- - showtime
- - loglevel
- - readingFnAttributes
- - expert
- This attribut controls the visibility of the readings. This attibute controlls
- the presentation of device parameter in the readings.
- 3 level can be choosen:
-
- 0_off: standart level. Display commonly used parameter
- 1_on: enhanced level. Display all decoded device parameter
- 2_full: display all parameter plus raw register information as well.
-
- If expert is applied a device it is used for assotiated channels.
- It can be overruled if expert attibute is also applied to the channel device.
- Make sure to check out attribut showInternalValues in the global values as well.
- extert takes benefit of the implementation.
- Nevertheless - by definition - showInternalValues overrules expert.
-
- - hmClass,
- model,
- subType
- These attributes are set automatically after a successful pairing.
- They are not supposed to be set by hand, and are necessary in order to
- correctly interpret device messages or to be able to send them.
- - rawToReadable
- Used to convert raw KFM100 values to readable data, based on measured
- values. E.g. fill slowly your container, while monitoring the
- values reported with inform. You'll see:
-
- 10 (at 0%)
- 50 (at 20%)
- 79 (at 40%)
- 270 (at 100%)
-
- Apply these values with: "attr KFM100 rawToReadable 10:0 50:20 79:40 270:100".
- fhem will do a linear interpolation for values between the bounderies.
-
- - unit
- set the reported unit by the KFM100 if rawToReadable is active. E.g.
- attr KFM100 unit Liter
-
- - autoReadReg
- set to '1' will execute a getConfig for the device automatically after each reboot of FHEM.
- Execution will be delayed in order to prevent congestion at startup. Therefore the update
- of the readings and the display will be delayed depending on the sice of the database.
- Recommendations and constrains upon usage:
-
- use this attribute on the device or channel 01. Do not use it separate on each channel
- of a multi-channel device to avoid duplicate execution
- usage on devices which only react to 'config' mode is not recommended since executen will
- not start until config is triggered by the user
- usage on devices which support wakeup-mode is usefull. But consider that execution is delayed
- until the device "wakes up".
-
-
-
-
-
- Generated events:
-
- - KS550/HM-WDS100-C6-O:
- T: $t H: $h W: $w R: $r IR: $ir WD: $wd WDR: $wdr S: $s B: $b
- temperature $t
- humidity $h
- windSpeed $w
- windDirection $wd
- windDirRange $wdr
- rain $r
- isRaining $ir
- sunshine $s
- brightness $b
- unknown $p
-
- - HM-CC-TC:
- T: $t H: $h
- measured-temp $t
- humidity $h
- actuator $vp %
- desired-temp $dTemp
- desired-temp-manu $dTemp
- windowopen-temp-%d %.1f (sensor:%s)
- tempList$wd hh:mm $t hh:mm $t ...
- displayMode temp-[hum|only]
- displayTemp [setpoint|actual]
- displayTempUnit [fahrenheit|celsius]
- controlMode [manual|auto|central|party]
- decalcDay [Sat|Sun|Mon|Tue|Wed|Thu|Fri]
- tempValveMode [Auto|Closed|Open|unknown]
- param-change offset=$o1, value=$v1
- ValveErrorPosition_for_$dname $vep %
- ValveOffset_for_$dname : $of %
- ValveErrorPosition $vep %
- ValveOffset $of %
- time-request
-
- - HM-CC-VD:
- $vp %
- battery:[critical|low|ok]
- motorErr:[ok|blocked|loose|adjusting range too small|opening|closing|stop]
- ValvePosition:$vp %
- ValveErrorPosition:$vep %
- ValveOffset:$of %
-
- - KFM100:
- $v
- $cv,$unit
- rawValue:$v
- Sequence:$seq
- content:$cv,$unit
-
- - HM-LC-BL1-PB-FM:
- motor: [opening|closing]
-
- - HM-SEC-SFA-SM:
- powerError [on|off]
- sabotageError [on|off]
- battery: [critical|low|ok]
-
- - HM-LC-SW1-BA-PCB:
- battery: [low|ok]
-
- - HM-OU-LED16
- color $value # hex - for device only
- $value # hex - for device only
- color [off|red|green|orange] # for channel
- [off|red|green|orange] # for channel
-
- - HM-OU-CFM-PL
- [on|off|$val]
-
- - switch/dimmer/blindActuator:
- $val
- powerOn [on|off|$val]
- [unknown|motor|dim] [up|down|stop]:$val
-
- - dimmer:
- overload [on|off]
- overheat [on|off]
- reduced [on|off]
- dim: [up|down|stop]
-
- - remote/pushButton/outputUnit
- (to $dest) is added if the button is peered and does not send to broadcast
- Release is provided for peered channels only
- Btn$x onShort
- Btn$x offShort
- Btn$x onLong $counter
- Btn$x offLong $counter
- Btn$x onLongRelease $counter
- Btn$x offLongRelease $counter
- Btn$x onShort (to $dest)
- Btn$x offShort (to $dest)
- Btn$x onLong $counter (to $dest)
- Btn$x offLong $counter (to $dest)
- Btn$x onLongRelease $counter (to $dest)
- Btn$x offLongRelease $counter (to $dest)
-
- - remote/pushButton
- battery [low|ok]
-
- - swi
- Btn$x toggle
- Btn$x toggle (to $dest)
- battery: [low|ok]
-
- - motionDetector
- brightness:$b
- alive
- motion on (to $dest)
- motionCount $cnt _next:$nextTr"-"[0x0|0x1|0x2|0x3|15|30|60|120|240|0x9|0xa|0xb|0xc|0xd|0xe|0xf]
- cover [closed|open]
- battery [low|ok]
- devState_raw.$d1 $d2
-
- - smokeDetector
- [off|smoke-Alarm|alive] # for team leader
- [off|smoke-forward|smoke-alarm] # for team members
- SDteam [add|remove]_$dname
- battery [low|ok]
- smoke_detect on from $src
- test:from $src
-
- - threeStateSensor
- [open|tilted|closed]]
- [wet|damp|dry] #HM-SEC-WDS only
- cover [open|closed] #HM-SEC-WDS only
- alive yes
- battery [low|ok]
- contact [open|tilted|closed]
- contact [wet|damp|dry] #HM-SEC-WDS only
-
- - THSensor and HM-WDC7000
- T: $t H: $h AP: $ap
- temperature $t
- humidity $h
- airpress $ap #HM-WDC7000 only
-
- - winMatic
- [locked|$value]
- motorError [no|TurnError|TiltError]
- direction [no|up|down|undefined]
- charge [trickleCharge|charge|dischange|unknown]
- airing [inactiv|$air]
- course [tilt|close]
- airing [inactiv|$value]
- contact tesed
-
- - keyMatic
- unknown:40
- battery [low|ok]
- uncertain [yes|no]
- error [unknown|motor aborted|clutch failure|none']
- lock [unlocked|locked]
- [unlocked|locked|uncertain]
-
-
-
-
-=end html
-=cut
+##############################################
+##############################################
+# CUL HomeMatic handler
+# $Id$
+
+package main;
+
+# attribut conversion "#todo Updt4 remove"
+# the lines can be removed after some soak time - around version 2600
+use strict;
+use warnings;
+use HMConfig;
+
+# ========================import constants=====================================
+my %culHmModel =HMConfig::HMConfig_getHash("culHmModel");
+my %culHmRegDefShLg =HMConfig::HMConfig_getHash("culHmRegDefShLg");
+my %culHmRegDefine =HMConfig::HMConfig_getHash("culHmRegDefine");
+my %culHmRegGeneral =HMConfig::HMConfig_getHash("culHmRegGeneral");
+my %culHmRegType =HMConfig::HMConfig_getHash("culHmRegType");
+my %culHmRegModel =HMConfig::HMConfig_getHash("culHmRegModel");
+my %culHmRegChan =HMConfig::HMConfig_getHash("culHmRegChan");
+my %culHmGlobalGets =HMConfig::HMConfig_getHash("culHmGlobalGets");
+my %culHmSubTypeGets =HMConfig::HMConfig_getHash("culHmSubTypeGets");
+my %culHmModelGets =HMConfig::HMConfig_getHash("culHmModelGets");
+my %culHmGlobalSetsDevice =HMConfig::HMConfig_getHash("culHmGlobalSetsDevice");
+my %culHmGlobalSets =HMConfig::HMConfig_getHash("culHmGlobalSets");
+my %culHmSubTypeSets =HMConfig::HMConfig_getHash("culHmSubTypeSets");
+my %culHmModelSets =HMConfig::HMConfig_getHash("culHmModelSets");
+my %culHmChanSets =HMConfig::HMConfig_getHash("culHmChanSets");
+my %culHmBits =HMConfig::HMConfig_getHash("culHmBits");
+my @culHmCmdFlags =HMConfig::HMConfig_getHash("culHmCmdFlags");
+my $K_actDetID =HMConfig::HMConfig_getHash("K_actDetID");
+
+############################################################
+
+sub CUL_HM_Initialize($);
+sub CUL_HM_Define($$);
+sub CUL_HM_Undef($$);
+sub CUL_HM_Parse($$);
+sub CUL_HM_Get($@);
+sub CUL_HM_fltCvT($);
+sub CUL_HM_Set($@);
+sub CUL_HM_infoUpdtDevData($$$);
+sub CUL_HM_Pair(@);
+sub CUL_HM_getConfig($$$$$);
+sub CUL_HM_SndCmd($$);
+sub CUL_HM_responseSetup($$);
+sub CUL_HM_eventP($$);
+sub CUL_HM_respPendRm($);
+sub CUL_HM_respPendTout($);
+sub CUL_HM_PushCmdStack($$);
+sub CUL_HM_ProcessCmdStack($);
+sub CUL_HM_Resend($);
+sub CUL_HM_Id($);
+sub CUL_HM_name2Hash($);
+sub CUL_HM_name2Id(@);
+sub CUL_HM_id2Name($);
+sub CUL_HM_getDeviceHash($);
+sub CUL_HM_DumpProtocol($$@);
+sub CUL_HM_parseCommon(@);
+sub CUL_HM_encodeTime8($);
+sub CUL_HM_decodeTime8($);
+sub CUL_HM_encodeTime16($);
+sub CUL_HM_convTemp($);
+sub CUL_HM_updtRegDisp($$$);
+sub CUL_HM_decodeTime16($);
+sub CUL_HM_pushConfig($$$$$$$$);
+sub CUL_HM_maticFn($$$$$);
+sub CUL_HM_secSince2000();
+sub CUL_HM_noDup(@); #return list with no duplicates
+sub CUL_HM_noDupInString($);#return string with no duplicates, comma separated
+
+# ----------------modul globals-----------------------
+my $respRemoved; # used to control trigger of stach processing
+ # need to take care that ACK is first
+#+++++++++++++++++ startup, init, definition+++++++++++++++++++++++++++++++++++
+sub CUL_HM_Initialize($) {
+ my ($hash) = @_;
+
+ $hash->{Match} = "^A....................";
+ $hash->{DefFn} = "CUL_HM_Define";
+ $hash->{UndefFn} = "CUL_HM_Undef";
+ $hash->{ParseFn} = "CUL_HM_Parse";
+ $hash->{SetFn} = "CUL_HM_Set";
+ $hash->{GetFn} = "CUL_HM_Get";
+ $hash->{RenameFn} = "CUL_HM_Rename";
+ $hash->{AttrFn} = "CUL_HM_Attr";
+ $hash->{AttrList} = "IODev do_not_notify:1,0 ignore:1,0 dummy:1,0 ".
+ "showtime:1,0 loglevel:0,1,2,3,4,5,6 ".
+ "serialNr firmware ".
+ "rawToReadable unit ".#"KFM-Sensor" only
+ "peerIDs repPeers ".
+ "actCycle actStatus ".
+ "autoReadReg:1_restart,0_off,2_pon-restart,3_onChange ".
+ "expert:0_off,1_on,2_full ".
+
+ "hmClass:obsolete devInfo:obsolete ". #unused
+ ".stc .devInfo ".
+ $readingFnAttributes;
+ my @modellist;
+ foreach my $model (keys %culHmModel){
+ push @modellist,$culHmModel{$model}{name};
+ }
+ $hash->{AttrList} .= " model:" .join(",", sort @modellist);
+ $hash->{AttrList} .= " subType:".join(",",
+ CUL_HM_noDup(map { $culHmModel{$_}{st} } keys %culHmModel));
+ CUL_HM_initRegHash();
+}
+sub CUL_HM_autoReadConfig($){
+ # will trigger a getConfig and statusrequest for each device assigned.
+ #
+ while(@{$modules{CUL_HM}{helper}{updtCfgLst}}){
+ my $name = shift(@{$modules{CUL_HM}{helper}{updtCfgLst}});
+ my $hash = CUL_HM_name2Hash($name);
+ if (0 != substr(AttrVal($name,"autoReadReg","0"),0,1)){
+ CUL_HM_Set($hash,$name,"getSerial");
+ CUL_HM_Set($hash,$name,"getConfig");
+ CUL_HM_Set($hash,$name,"statusRequest");
+ InternalTimer(gettimeofday()+15,"CUL_HM_autoReadConfig","updateConfig",0);
+ last;
+ }
+ }
+}
+sub CUL_HM_updateConfig($){
+ # this routine is called 5 sec after the last define of a restart
+ # this gives FHEM sufficient time to fill in attributes
+ # it will also be called after each manual definition
+ # Purpose is to parse attributes and read config
+ my @getConfList;
+ my @nameList = CUL_HM_noDup(@{$modules{CUL_HM}{helper}{updtCfgLst}});
+ while(@nameList){
+ my $name = shift(@nameList);
+ my $hash = CUL_HM_name2Hash($name);
+ my $id = CUL_HM_hash2Id($hash);
+ my $isDevice = (length($id) == 6)?"true":"";
+ if ($id ne $K_actDetID){# if not action detector
+ CUL_HM_ID2PeerList($name,"",1); # update peerList out of peerIDs
+ my $actCycle = AttrVal($name,"actCycle",undef);
+ CUL_HM_ActAdd($id,$actCycle) if ($actCycle);# add to ActionDetect?
+ # --- set default attrubutes if missing ---
+ $attr{$name}{expert}= AttrVal($name,"expert","2_full") if ($isDevice);
+
+ # convert variables, delete obsolete, move to hidden level
+ $attr{$name}{".devInfo"} = $attr{$name}{devInfo} if($attr{$name}{devInfo});#todo Updt4 remove
+ delete $attr{$name}{devInfo}; #todo Updt4 remove
+ delete $attr{$name}{hmClass}; #todo Updt4 remove
+ }
+ else{
+ $attr{$name}{"event-on-change-reading"} = AttrVal($name, "event-on-change-reading", ".*");
+ ;#delete $attr{$name}{peerIDs}; # remove historical data
+ }
+
+ if ("dimmer" eq CUL_HM_Get($hash,$name,"param","subType")) {#setup virtuals
+ #configure Dimmer virtual channel assotiation
+ if (length($id) == 8 || !$hash->{"channel_01"}){
+ my $chn = substr($id,6,2);
+ $chn = "01" if (!$chn); # device acts as channel 01
+ my $devId = substr($id,0,6);
+ my $mId = CUL_HM_getMId($hash);
+ if ($culHmModel{$mId}{chn} =~ m/Sw._V/){
+ my @chnPh = (grep{$_ =~ m/Sw:/ } split ',',$culHmModel{$mId}{chn});
+ @chnPh = split ':',$chnPh[0] if (@chnPh);
+ my $chnPhyMax = $chnPh[2]?$chnPh[2]:1; # max Phys channels
+ my $chnPhy = int(($chn-$chnPhyMax+1)/2); # assotiated phy chan
+ my $idPhy = $devId.sprintf("%02X",$chnPhy);# ID assot phy chan
+ my $pHash = CUL_HM_id2Hash($idPhy); # hash assot phy chan
+ $idPhy = CUL_HM_hash2Id($pHash); # could be device!!!
+
+ if ($pHash){
+ $pHash->{helper}{vDim}{idPhy} = $idPhy;
+ my $vHash = CUL_HM_id2Hash($devId.sprintf("%02X",$chnPhyMax+2*$chnPhy-1));
+ if ($vHash){
+ $pHash->{helper}{vDim}{idV2} = CUL_HM_hash2Id($vHash);
+ $vHash->{helper}{vDim}{idPhy} = $idPhy;
+ }
+ else{
+ delete $pHash->{helper}{vDim}{idV2};
+ }
+ $vHash = CUL_HM_id2Hash($devId.sprintf("%02X",$chnPhyMax+2*$chnPhy));
+ if ($vHash){
+ $pHash->{helper}{vDim}{idV3} = CUL_HM_hash2Id($vHash);
+ $vHash->{helper}{vDim}{idPhy} = $idPhy;
+ }
+ else{
+ delete $pHash->{helper}{vDim}{idV3};
+ }
+ }
+ }
+ }
+ }
+
+ # add default web-commands
+ my $webCmd;
+ my $st = AttrVal(($hash->{device}?$hash->{device}:$name), "subType", "");
+ $webCmd = AttrVal($name,"webCmd",undef);
+ if (!defined $webCmd){
+ if((length (CUL_HM_hash2Id($hash)) == 6)&&
+ $hash->{channel_01} &&
+ $st ne "virtual" &&
+ $st ne "thermostat" ){$webCmd="getConfig";
+ }elsif($st eq "blindActuator"){$webCmd="toggle:on:off:up:down:stop:statusRequest";
+ }elsif($st eq "dimmer" ){$webCmd="toggle:on:off:up:down:statusRequest";
+ }elsif($st eq "switch" ){$webCmd="toggle:on:off:statusRequest";
+ }elsif($st eq "virtual" ){$webCmd="press short:press long";
+ }elsif($st eq "smokeDetector"){$webCmd="test:alarmOn:alarmOff";
+ }elsif($st eq "keyMatic" ){$webCmd="lock:inhibit on:inhibit off";
+ }
+ if ($webCmd){
+ my $eventMap = AttrVal($name,"eventMap",undef);
+
+ my @wc;
+ push @wc,ReplaceEventMap($name, $_, 1) foreach (split ":",$webCmd);
+ $webCmd = join ":",@wc;
+ }
+ }
+ $attr{$name}{webCmd} = $webCmd if ($webCmd);
+ push @getConfList,$name if (0 != substr(AttrVal($name,"autoReadReg","0"),0,1));
+ }
+ $modules{CUL_HM}{helper}{updtCfgLst} = \@getConfList;
+ CUL_HM_autoReadConfig("updateConfig");
+}
+sub CUL_HM_Define($$) {##############################
+ my ($hash, $def) = @_;
+ my @a = split("[ \t][ \t]*", $def);
+ my $HMid = uc($a[2]);
+ return "wrong syntax: define CUL_HM 6-digit-hex-code [Raw-Message]"
+ if(!(int(@a)==3 || int(@a)==4) || $HMid !~ m/^[A-F0-9]{6,8}$/i);
+ return "HMid DEF already used by " . CUL_HM_id2Name($HMid)
+ if ($modules{CUL_HM}{defptr}{$HMid});
+ my $name = $hash->{NAME};
+ if(length($HMid) == 8) {# define a channel
+ my $devHmId = substr($HMid, 0, 6);
+ my $chn = substr($HMid, 6, 2);
+ my $devHash = $modules{CUL_HM}{defptr}{$devHmId};
+ return "please define a device with hmId:".$devHmId." first" if(!$devHash);
+
+ my $devName = $devHash->{NAME};
+ $hash->{device} = $devName; #readable ref to device name
+ $hash->{chanNo} = $chn; #readable ref to Channel
+ $devHash->{"channel_$chn"} = $name; #reference in device as well
+ $attr{$name}{model} = AttrVal($devName, "model", undef);
+ }
+ else{# define a device
+ AssignIoPort($hash);
+ }
+ $modules{CUL_HM}{defptr}{$HMid} = $hash;
+
+ #- - - - create auto-update - - - - - -
+ CUL_HM_ActGetCreateHash() if($HMid eq '000000');#startTimer
+ $hash->{DEF} = $HMid;
+ CUL_HM_Parse($hash, $a[3]) if(int(@a) == 4);
+ RemoveInternalTimer("updateConfig");
+ InternalTimer(gettimeofday()+5,"CUL_HM_updateConfig", "updateConfig", 0);
+
+ my @arr;
+ if(!$modules{CUL_HM}{helper}{updtCfgLst}){
+ $modules{CUL_HM}{helper}{updtCfgLst} = \@arr;
+ }
+ push(@{$modules{CUL_HM}{helper}{updtCfgLst}}, $name);
+ return undef;
+}
+sub CUL_HM_Undef($$) {###############################
+ my ($hash, $name) = @_;
+ my $devName = $hash->{device};
+ my $HMid = $hash->{DEF};
+ my $chn = substr($HMid,6,2);
+ if ($chn){# delete a channel
+ my $devHash = CUL_HM_name2Hash($devName);
+ delete $devHash->{"channel_$chn"} if ($devName);
+ }
+ else{# delete a device
+ foreach my $channel (keys %{$hash}){
+ CommandDelete(undef,$hash->{$channel})
+ if ($channel =~ m/^channel_/);
+ }
+ }
+ delete($modules{CUL_HM}{defptr}{$HMid});
+ return undef;
+}
+sub CUL_HM_Rename($$$) {#############################
+ my ($name, $oldName) = @_;
+ my $HMid = CUL_HM_name2Id($name);
+ my $hash = CUL_HM_name2Hash($name);
+ if (length($HMid) == 8){# we are channel, inform the device
+ $hash->{chanNo} = substr($HMid,6,2);
+ my $devHash = CUL_HM_id2Hash(substr($HMid,0,6));
+ $hash->{device} = CUL_HM_hash2Name($devHash);
+ $devHash->{"channel_".$hash->{chanNo}} = $name;
+ }
+ else{# we are a device - inform channels if exist
+ foreach (grep {$_ =~m/^channel_/} keys%{$hash}){
+ my $chnHash = CUL_HM_name2Hash($hash->{$_});
+ $chnHash->{device} = $name;
+ }
+ }
+ return;
+}
+sub CUL_HM_Attr(@) {#################################
+ my ($cmd,$name, $attrName,$attrVal) = @_;
+ my @hashL;
+ if ($attrName eq "expert"){#[0,1,2]
+ $attr{$name}{expert} = $attrVal;
+ my $eHash = CUL_HM_name2Hash($name);
+ foreach my $chId (CUL_HM_getAssChnIds($name)){
+ my $cHash = CUL_HM_id2Hash($chId);
+ push(@hashL,$cHash) if ($eHash ne $cHash);
+ }
+ push(@hashL,$eHash);
+ foreach my $hash (@hashL){
+ my $exLvl = CUL_HM_getExpertMode($hash);
+ if ($exLvl eq "0"){# off
+ foreach my $rdEntry (keys %{$hash->{READINGS}}){
+ my $rdEntryNew;
+ $rdEntryNew = ".".$rdEntry if ($rdEntry =~m /^RegL_/);
+ if ($rdEntry =~m /^R-/){
+ my $reg = $rdEntry;
+ $reg =~ s/.*-//;
+ $rdEntryNew = ".".$rdEntry if($culHmRegDefine{$reg}{d} eq '0' );
+ }
+ next if (!defined($rdEntryNew)); # no change necessary
+ delete $hash->{READINGS}{$rdEntryNew};
+ $hash->{READINGS}{$rdEntryNew} = $hash->{READINGS}{$rdEntry};
+ delete $hash->{READINGS}{$rdEntry};
+ }
+ }
+ elsif ($exLvl eq "1"){# on: Only register values, no raw data
+ # move register to visible if available
+ foreach my $rdEntry (keys %{$hash->{READINGS}}){
+ my $rdEntryNew;
+ $rdEntryNew = substr($rdEntry,1) if ($rdEntry =~m /^\.R-/);
+ $rdEntryNew = ".".$rdEntry if ($rdEntry =~m /^RegL_/);
+ next if (!$rdEntryNew); # no change necessary
+ delete $hash->{READINGS}{$rdEntryNew};
+ $hash->{READINGS}{$rdEntryNew} = $hash->{READINGS}{$rdEntry};
+ delete $hash->{READINGS}{$rdEntry};
+ }
+ }
+ elsif ($exLvl eq "2"){# full - incl raw data
+ foreach my $rdEntry (keys %{$hash->{READINGS}}){
+ my $rdEntryNew;
+ $rdEntryNew = substr($rdEntry,1) if (($rdEntry =~m /^\.RegL_/) ||
+ ($rdEntry =~m /^\.R-/));
+ next if (!$rdEntryNew); # no change necessary
+ delete $hash->{READINGS}{$rdEntryNew};
+ $hash->{READINGS}{$rdEntryNew} = $hash->{READINGS}{$rdEntry};
+ delete $hash->{READINGS}{$rdEntry};
+ }
+ }
+ else{;
+ }
+ }
+ }
+ elsif($attrName eq "actCycle"){#"000:00" or 'off'
+ return if (CUL_HM_name2Id($name) eq $K_actDetID);
+ # Add to ActionDetector. Wait a little - config might not be finished
+ my @arr;
+ if(!$modules{CUL_HM}{helper}{updtCfgLst}){
+ $modules{CUL_HM}{helper}{updtCfgLst} = \@arr;
+ }
+ push(@{$modules{CUL_HM}{helper}{updtCfgLst}}, $name);
+
+ RemoveInternalTimer("updateConfig");
+ InternalTimer(gettimeofday()+5,"CUL_HM_updateConfig", "updateConfig", 0);
+ }
+ return;
+}
+
+#+++++++++++++++++ msg receive, parsing++++++++++++++++++++++++++++++++++++++++
+sub CUL_HM_Parse($$) {##############################
+ my ($iohash, $msg) = @_;
+ my $id = CUL_HM_Id($iohash);
+ my $ioName = $iohash->{NAME};
+ # Msg format: Allnnffttssssssddddddpp...
+ $msg =~ m/A(..)(..)(..)(..)(......)(......)(.*)/;
+ my ($len,$msgcnt,$msgFlag,$msgType,$src,$dst,$p1) = ($1,$2,$3,$4,$5,$6,$7);
+ $p1 = "" if(!defined($p1));
+ my $cmd = "$msgFlag$msgType"; #still necessary to maintain old style
+ my $lcm = "$len$cmd";
+ # $shash will be replaced for multichannel commands
+ my $shash = $modules{CUL_HM}{defptr}{$src};
+ my $dhash = $modules{CUL_HM}{defptr}{$dst};
+ my $dname = ($dst eq "000000") ? "broadcast" :
+ ($dhash ? $dhash->{NAME} :
+ ($dst eq $id ? $ioName :
+ $dst));
+ my $target = " (to $dname)";
+ my ($p,$msgStat,$myRSSI,$msgIO) = split(":",$p1,4);
+
+ return "" if($msgStat && $msgStat eq 'NACK');#discard if lowlevel error
+ return "" if($src eq $id);#discard mirrored messages
+
+ $respRemoved = 0; #set to 'no response in this message' at start
+ if(!$shash) { # Unknown source
+ # Generate an UNKNOWN event for pairing requests, ignore everything else
+ if($msgType eq "00") {
+ my $model = substr($p, 2, 4);
+ $model = $culHmModel{$model}{name} ?
+ $culHmModel{$model}{name} :
+ "ID_".$model;
+ my $sname = "CUL_HM_".$model."_$src";
+ $sname =~ s/-/_/g;
+ Log 3, "CUL_HM Unknown device $sname, please define it";
+ return "UNDEFINED $sname CUL_HM $src $msg";
+ }
+ return "";
+ }
+ CUL_HM_eventP($shash,"Rcv");
+ my $name = $shash->{NAME};
+ my @event;
+ my @entities;
+ my $st = AttrVal($name, "subType", "");
+ my $model = AttrVal($name, "model", "");
+ my $tn = TimeNow();
+ CUL_HM_storeRssi($name,
+ "at_".((hex($msgFlag)&0x40)?"rpt_":"").$ioName,# repeater?
+ $myRSSI);
+
+ my $msgX = "No:$msgcnt - t:$msgType s:$src d:$dst ".($p?$p:"");
+
+ if($shash->{lastMsg} && $shash->{lastMsg} eq $msgX) {
+ Log GetLogLevel($name,4), "CUL_HM $name dup mesg";
+ if(($id eq $dst)&& (hex($msgFlag)&0x20)){
+# CUL_HM_SndCmd($shash, $msgcnt."8002".$id.$src."00"); # Send Ack
+ Log GetLogLevel($name,4), "CUL_HM $name dup mesg - ack and ignore";
+ }
+ else{
+ Log GetLogLevel($name,4), "CUL_HM $name dup mesg - ignore";
+ }
+
+ return $name; #return something to please dispatcher
+ }
+ $shash->{lastMsg} = $msgX;
+ $iohash->{HM_CMDNR} = hex($msgcnt) if($dst eq $id);# updt message cnt to rec
+
+ CUL_HM_DumpProtocol("RCV",$iohash,$len,$msgcnt,$msgFlag,$msgType,$src,$dst,$p);
+
+ #----------start valid messages parsing ---------
+ my $parse = CUL_HM_parseCommon($msgcnt,$msgFlag,$msgType,$src,$dst,$p);
+ push @event, "powerOn" if($parse eq "powerOn");
+
+ my $sendAck = "yes";# if yes Ack will be determined automatically
+
+ if ($parse eq "ACK"){# remember - ACKinfo will be passed on
+ push @event, "";
+ }
+ elsif($parse eq "NACK"){
+ push @event, "state:NACK";
+ }
+ elsif($parse eq "done"){
+ push @event, "";
+ }
+ elsif($lcm eq "09A112") { #### Another fhem wants to talk (HAVE_DATA)
+ ;
+ }
+ elsif($msgType eq "00" ){ #### DEVICE_INFO, Pairing-Request
+ CUL_HM_ProcessCmdStack($shash) if(CUL_HM_getRxType($shash) & 0x04);#config
+ CUL_HM_infoUpdtDevData($name, $shash,$p);#update data
+
+ if($shash->{cmdStack} && (CUL_HM_getRxType($shash) & 0x04)) {
+ push @event,"";
+ }
+ else {
+ push @event, CUL_HM_Pair($name, $shash,$cmd,$src,$dst,$p);
+ }
+ }
+ elsif($model =~ m/^(KS550|HM-WDS100-C6-O)$/) { ##############################
+
+ if($msgType eq "70" && $p =~ m/^(....)(..)(....)(....)(..)(..)(..)/) {
+
+ my ( $t, $h, $r, $w, $wd, $s, $b ) =
+ (hex($1), hex($2), hex($3), hex($4), hex($5), hex($6), hex($7));
+ my $tsgn = ($t & 0x4000);
+ $t = ($t & 0x3fff)/10;
+ $t = sprintf("%0.1f", $t-1638.4) if($tsgn);
+ my $ir = $r & 0x8000;
+ $r = ($r & 0x7fff) * 0.295;
+ my $wdr = ($w>>14)*22.5;
+ $w = ($w & 0x3fff)/10;
+ $wd = $wd * 5;
+
+ push @event,
+ "state:T: $t H: $h W: $w R: $r IR: $ir WD: $wd WDR: $wdr S: $s B: $b";
+ push @event, "temperature:$t";
+ push @event, "humidity:$h";
+ push @event, "windSpeed:$w";
+ push @event, "windDirection:$wd";
+ push @event, "windDirRange:$wdr";
+ push @event, "rain:$r";
+ push @event, "isRaining:$ir";
+ push @event, "sunshine:$s";
+ push @event, "brightness:$b";
+ }
+ else {
+ push @event, "unknown:$p";
+ }
+ }
+ elsif($model eq "HM-CC-TC") { ###############################################
+ my ($sType,$chn) = ($1,$2) if($p && $p =~ m/^(..)(..)/);
+ if($msgType eq "70" && $p =~ m/^(....)(..)/) { # weather event
+ $chn = '01'; # fix definition
+ my ( $t, $h) = (hex($1), hex($2));# temp is 15 bit signed
+ $t = ($t & 0x3fff)/10*(($t & 0x4000)?-1:1);
+ my $chnHash = $modules{CUL_HM}{defptr}{$src.$chn};
+ push @entities,
+ CUL_HM_UpdtReadBulk($chnHash,1,"state:T: $t H: $h", # update weather channel
+ "measured-temp:$t",
+ "humidity:$h")
+ if ($chnHash);
+ push @event, "state:T: $t H: $h";
+ push @event, "measured-temp:$t";
+ push @event, "humidity:$h";
+ }
+ elsif($msgType eq "58" && $p =~ m/^(..)(..)/) {# climate event
+ $chn = '02'; # fix definition
+ my ( $d1, $vp) = # adjust_command[0..4] adj_data[0..250]
+ ( $1, hex($2));
+ $vp = int($vp/2.56+0.5); # valve position in %
+ my $chnHash = $modules{CUL_HM}{defptr}{$src.$chn};
+ push @entities,CUL_HM_UpdtReadSingle($chnHash,"state","$vp %",1)
+ if($chnHash);
+ push @event, "actuator:$vp %";
+
+ # Set the valve state too, without an extra trigger
+ push @entities,CUL_HM_UpdtReadBulk($dhash,1,
+ "state:set_$vp %",
+ "ValveDesired:$vp %")
+ if($dhash);
+ }
+ elsif(($msgType eq '02' &&$sType eq '01')|| # ackStatus
+ ($msgType eq '10' &&$sType eq '06')){ # infoStatus
+ $chn = substr($p,2,2);
+
+
+ my $temp = substr($p,4,2);
+ my $dTemp = ($temp eq '00')?'off':
+ (($temp eq 'C8')?'on' :
+ sprintf("%0.1f", hex($temp)/2));
+ my $chnHash = $modules{CUL_HM}{defptr}{$src.$chn};
+ if($chnHash){
+ my $chnName = $chnHash->{NAME};
+ my $mode = ReadingsVal($chnName,"R-MdTempReg","");
+ push @entities,CUL_HM_UpdtReadSingle($chnHash,"desired-temp",$dTemp,1);
+ CUL_HM_UpdtReadSingle($chnHash,"desired-temp-manu",$dTemp,1) if($mode eq 'manual ' && $msgType eq '10');
+# readingsSingleUpdate($chnHash,"desired-temp-cent",$dTemp,1) if($mode eq 'central ' && $msgType eq '02');
+# removed - shall not be changed automatically - change is only temporary
+# CUL_HM_Set($chnHash,$chnName,"desired-temp",$dTemp) if($mode eq 'central ' && $msgType eq '10');
+ }
+ push @event, "desired-temp:" .$dTemp;
+ }
+ elsif($msgType eq "10"){ # Config change report
+ $chn = substr($p,2,2);
+ if( $p =~ m/^0403(......)(..)0505(..)0000/) {# param change
+ # change of chn 3(window) list 5 register 5 - a peer window changed!
+ my ( $tdev, $tchan, $v1) = (($1), hex($2), hex($3));
+ push @event, sprintf("windowopen-temp-%d: %.1f (sensor:%s)"
+ ,$tchan, $v1/2, $tdev);
+ #todo: This will never cleanup if a peer is deleted
+ }
+ elsif($p =~ m/^0402000000000(.)(..)(..)(..)(..)(..)(..)(..)(..)/) {
+ # param list 5 or 6, 4 value pairs.
+ my ($plist, $o1, $v1, $o2, $v2, $o3, $v3, $o4, $v4) =
+ (hex($1),hex($2),hex($3),hex($4),hex($5),hex($6),hex($7),hex($8),hex($9));
+
+ my ($dayoff, $maxdays, $basevalue);
+ my @days = ("Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri");
+
+ if($plist == 5 || $plist == 6) {
+ if($plist == 5) {
+ $dayoff = 0; $maxdays = 5; $basevalue = hex("0B");
+ }
+ else {
+ $dayoff = 5; $maxdays = 2; $basevalue = hex("01");
+ }
+ my $idx = ($o1-$basevalue);
+ my $dayidx = int($idx/48);
+ if($idx % 4 == 0 && $dayidx < $maxdays) {
+ $idx -= 48*$dayidx;
+ $idx /= 2;
+ my $ptr = $shash->{TEMPLIST}{$days[$dayidx+$dayoff]};
+ $ptr->{$idx}{HOUR} = int($v1/6);
+ $ptr->{$idx}{MINUTE} = ($v1%6)*10;
+ $ptr->{$idx}{TEMP} = $v2/2;
+ $ptr->{$idx+1}{HOUR} = int($v3/6);
+ $ptr->{$idx+1}{MINUTE} = ($v3%6)*10;
+ $ptr->{$idx+1}{TEMP} = $v4/2;
+ }
+ }
+ foreach my $wd (@days) {
+ my $twentyfour = 0;
+ my $msg = 'tempList'.$wd.':';
+ foreach(my $idx=0; $idx<24; $idx++) {
+ my $ptr = $shash->{TEMPLIST}{$wd}{$idx};
+ if(defined ($ptr->{TEMP}) && $ptr->{TEMP} ne "") {
+ if($twentyfour == 0) {
+ $msg .= sprintf(" %02d:%02d %.1f",
+ $ptr->{HOUR}, $ptr->{MINUTE}, $ptr->{TEMP});
+ } else {
+ $ptr->{HOUR} = $ptr->{MINUTE} = $ptr->{TEMP} = "";
+ }
+ }
+ if($ptr->{HOUR} && 0+$ptr->{HOUR} == 24) {
+ $twentyfour = 1; # next value uninteresting, only first counts.
+ }
+ }
+ push @event, $msg; # generate one event per day entry
+ }
+ }
+ elsif($p =~ m/^04020000000005(..)(..)/) { # paramchanged L5
+ my ( $o1, $v1) = (hex($1),hex($2));# only parse list 5 for chn 2
+ my $msg;
+ my @days = ("Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri");
+ if($o1 == 1) { ### bitfield containing multiple values...
+ # MUST be IDENTICAL to the set commands assotiated
+ my %mode = (0 => "manual",1 => "auto",2 => "central",3 => "party");
+ push @event,'displayMode:temp-'.(($v1 & 1)?"hum" :"only");
+ push @event,'displayTemp:' .(($v1 & 2)?"setpoint" :"actual");
+ push @event,'displayTempUnit:' .(($v1 & 4)?"fahrenheit":"celsius");
+ push @event,'controlMode:' .($mode{(($v1 & 0x18)>>3)});
+ push @event,'decalcDay:' .$days[($v1 & 0xE0)>>5];
+ my $chnHash = $modules{CUL_HM}{defptr}{$src.$chn};
+ my $dTemp;
+ if($chnHash){
+ my $chnName = $chnHash->{NAME};
+ my $mode = ReadingsVal($chnName,"R-MdTempReg","");
+ $dTemp = ReadingsVal($chnName,"desired-temp","21.0");
+ if (!$chnHash->{helper}{oldMode} || $chnHash->{helper}{oldMode} ne $mode){
+ $dTemp = ReadingsVal($chnName,"desired-temp-manu",$dTemp)if ($mode eq 'manual ');
+ $dTemp = ReadingsVal($chnName,"desired-temp-cent",$dTemp)if ($mode eq 'central ');
+ $chnHash->{helper}{oldMode} = $mode;
+ }
+ push @entities,CUL_HM_UpdtReadSingle($chnHash,"desired-temp",$dTemp,1);
+ }
+ push @event, "desired-temp:" .$dTemp;
+ }
+ elsif($o1 == 2) {
+ my %pos = (0=>"Auto",1=>"Closed",2=>"Open",3=>"unknown");
+ push @event,"tempValveMode:".$pos{(($v1 & 0xC0)>>6)};
+ }
+ else{
+ push @event,'param-change: offset='.$o1.', value='.$v1;
+ }
+ }
+ elsif($p =~ m/^0[23]/){ # param response
+ push @event,'';#cannot be handled here as request missing
+ }
+ }
+ elsif($msgType eq "01"){ # status reports
+ if($p =~ m/^010809(..)0A(..)/) { # TC set valve for VD => post events to VD
+ my ( $of, $vep) = (hex($1), hex($2));
+ push @event, "ValveErrorPosition_for_$dname: $vep %";
+ push @event, "ValveOffset_for_$dname: $of %";
+ push @entities,
+ CUL_HM_UpdtReadBulk($dhash,1,'ValveErrorPosition:set_'.$vep.' %',
+ 'ValveOffset:set_'.$of.' %');
+ }
+ elsif($p =~ m/^010[56]/){ # 'prepare to set' or 'end set'
+ push @event,""; #
+ }
+ }
+ elsif($cmd eq "A03F" && $id eq $dst) { # Timestamp request
+ my $s2000 = sprintf("%02X", CUL_HM_secSince2000());
+ CUL_HM_SndCmd($shash, "++803F$id${src}0204$s2000");
+ push @event, "time-request";
+ $sendAck = "";
+ }
+ }
+ elsif($model eq "HM-CC-VD") { ###############################################
+ if($msgType eq "02" && $p =~ m/^(..)(..)(..)(..)/) {#subtype+chn+value+err
+ my ($chn,$vp, $err) = (hex($2),hex($3), hex($4));
+ $chn = sprintf("%02X",$chn&0x3f);
+ $vp = int($vp)/2; # valve position in %
+ push @event, "ValvePosition:$vp %";
+ push @event, "state:$vp %";
+ $shash = $modules{CUL_HM}{defptr}{"$src$chn"}
+ if($modules{CUL_HM}{defptr}{"$src$chn"});
+
+ my $stErr = ($err >>1) & 0x7; # Status-Byte Evaluation
+ push @event,"battery:".(($stErr == 4)?"critical":($err&0x80?"low":"ok"));
+ if (!$stErr){#remove both conditions
+ push @event, "motorErr:ok";
+ }
+ else{
+ push @event, "motorErr:blocked" if($stErr == 1);
+ push @event, "motorErr:loose" if($stErr == 2);
+ push @event, "motorErr:adjusting range too small" if($stErr == 3);
+# push @event, "battery:critical" if($stErr == 4);
+ }
+ push @event, "motor:opening" if(($err&0x30) == 0x10);
+ push @event, "motor:closing" if(($err&0x30) == 0x20);
+ push @event, "motor:stop" if(($err&0x30) == 0x00);
+
+ #VD hang detection
+ my $des = ReadingsVal($name, "ValveDesired", "");
+ $des =~ s/ .*//; # remove unit
+ if (defined $des && $des != $vp && ($err&0x30) == 0x00){
+ push @event, "operState:errorTargetNotMet";
+ push @event, "operStateErrCnt:".
+ (ReadingsVal($name,"ValveStateErrCnt","0")+1);
+ }
+ else{
+ push @event, "operState:".((($err&0x30) == 0x00)?"onTarget":"adjusting");
+ }
+ }
+
+ # CMD:A010 SRC:13F251 DST:5D24C9 0401 00000000 05 09:00 0A:07 00:00
+ # status change report to paired central unit
+ #read List5 reg 09 (offset) and 0A (err-pos)
+ #list 5 is channel-dependant not link dependant
+ # => Link discriminator (00000000) is fixed
+ elsif($msgType eq "10" && $p =~ m/^04..........0509(..)0A(..)/) {
+ my ( $of, $vep) = (hex($1), hex($2));
+ push @event, "ValveErrorPosition:$vep %";
+ push @event, "ValveOffset:$of %";
+ }
+ }
+ elsif($model =~ m/^(HM-Sen-Wa-Od|HM-CC-SCD)$/){ #############################
+ if (($msgType eq "02" && $p =~ m/^01/) || # handle Ack_Status
+ ($msgType eq "10" && $p =~ m/^06/) || #or Info_Status message here
+ ($msgType eq "41")) {
+ my $level = substr($p,4,2);
+ my %lvl=("00"=>"normal","64"=>"added","C8"=>"addedStrong");
+ $level = hex($level) if($model eq "HM-Sen-Wa-Od");
+ $level = $lvl{$level} if($model eq "HM-CC-SCD");
+ push @event, "state:".$level."%";
+
+ my $err = hex(substr($p,6,2));
+ push @event, "battery:".($err&0x80?"low":"ok") if (defined $err);
+ }
+ }
+ elsif($st eq "KFM100" && $model eq "KFM-Sensor") { ########################
+ if ($msgType eq "53"){
+ if($p =~ m/.14(.)0200(..)(..)(..)/) {
+ my ($seq, $k_v1, $k_v2, $k_v3) = (hex($1),$2,hex($3),hex($4));
+ my $v = 128-$k_v2; # FIXME: calibrate
+ $v += 256 if(!($k_v3 & 1));
+ push @event, "rawValue:$v";
+ my $nextSeq = (ReadingsVal($name,"Sequence","") %15)+1;
+ push @event, "Sequence:$seq".($nextSeq ne $seq?"_seqMiss":"");
+
+ my $r2r = AttrVal($name, "rawToReadable", undef);
+ if($r2r) {
+ my @r2r = split("[ :]", $r2r);
+ foreach(my $idx = 0; $idx < @r2r-2; $idx+=2) {
+ if($v >= $r2r[$idx] && $v <= $r2r[$idx+2]) {
+ my $f = (($v-$r2r[$idx])/($r2r[$idx+2]-$r2r[$idx]));
+ my $cv = ($r2r[$idx+3]-$r2r[$idx+1])*$f + $r2r[$idx+1];
+ my $unit = AttrVal($name, "unit", "");
+ push @event, sprintf("state:%.1f %s",$cv,$unit);
+ push @event, sprintf("content:%.1f %s",$cv,$unit);
+ last;
+ }
+ }
+ } else {
+ push @event, "state:$v";
+ }
+ }
+ }
+ }
+ elsif($st eq "THSensor" || $model eq "HM-WDC7000") { ########################
+ my $t = hex(substr($p,0,4));
+ $t -= 32768 if($t > 1638.4);
+ $t = sprintf("%0.1f", $t/10);
+ my $h = hex(substr($p,4,2));
+ my $ap = hex(substr($p,6,4));
+ my $statemsg = "state:T: $t";
+ $statemsg .= " H: $h" if ($h);
+ $statemsg .= " AP: $ap" if ($ap);
+ push @event, $statemsg;
+ push @event, "temperature:$t";#temp is always there
+ push @event, "humidity:$h" if ($h);
+ push @event, "airpress:$ap" if ($ap);
+ }
+ elsif($st =~ m /^(switch|dimmer|blindActuator)$/) {##########################
+ if (($msgType eq "02" && $p =~ m/^01/) || # handle Ack_Status
+ ($msgType eq "10" && $p =~ m/^06/)) { # or Info_Status message here
+
+ my ($subType,$chn,$val,$err) = ($1,hex($2),hex($3)/2,hex($4))
+ if($p =~ m/^(..)(..)(..)(..)/);
+ $chn = sprintf("%02X",$chn&0x3f);
+ $shash = $modules{CUL_HM}{defptr}{"$src$chn"}
+ if($modules{CUL_HM}{defptr}{"$src$chn"});
+ my ($x,$pl) = ($1,hex($2)/2) if($p =~ m/^........(..)(..)$/);
+ my $stUpdt = 1;# shall state be updated?
+ if (defined $pl){# device with virtual channels...
+ push @event,"virtLevel:".($val == 100?"on":($val == 0?"off":"$val %"));
+ my $vDim = $shash->{helper}{vDim};#shortcut
+ my $ph = CUL_HM_id2Hash($vDim->{idPhy}) if ($vDim->{idPhy});
+ if ($msgType eq "10"){
+ $val = $pl;# Physical level is pl - but only for InfoStatus
+ $pl = ($pl == 100 ? "on" : ($pl == 0 ? "off" : "$pl %"));
+ #set state for all virtual
+ RemoveInternalTimer("sUpdt:".$ph->{NAME});
+ if ($ph){
+ push @entities,CUL_HM_UpdtReadSingle($ph,"state",$pl,1);
+ my $vh;
+ $vh = CUL_HM_id2Hash($vDim->{idV2}) if ($vDim->{idV2});
+ push @entities,CUL_HM_UpdtReadSingle($vh,"state",$pl,1) if ($vh);
+ $vh = CUL_HM_id2Hash($vDim->{idV3}) if ($vDim->{idV3});
+ push @entities,CUL_HM_UpdtReadSingle($vh,"state",$pl,1) if ($vh);
+ }
+ }
+ else{
+ $stUpdt = 0;# no update
+ $val = ReadingsVal($name,"state",""); # keep value
+ InternalTimer(gettimeofday()+3,"CUL_HM_stateUpdat", "sUpdt:".$ph->{NAME}, 0);
+ }
+ }
+
+ $val = ($val == 100 ? "on" : ($val == 0 ? "off" : "$val %"))
+ if ($stUpdt );
+ push @event, "deviceMsg:$val$target" if($chn ne "00");
+
+ my $eventName = "unknown"; # different names for events
+ $eventName = "switch" if($st eq "switch");
+ $eventName = "motor" if($st eq "blindActuator");
+ $eventName = "dim" if($st eq "dimmer");
+ my $action; #determine action
+ if ($st ne "switch"){
+ push @event, "$eventName:up:$val" if(($err&0x30) == 0x10);
+ push @event, "$eventName:down:$val" if(($err&0x30) == 0x20);
+ push @event, "$eventName:stop:$val" if(($err&0x30) == 0x00);
+ }
+ if ($st eq "dimmer"){
+ push @event,"overload:".(($err&0x02)?"on":"off");
+ push @event,"overheat:".(($err&0x04)?"on":"off");
+ push @event,"reduced:" .(($err&0x08)?"on":"off");
+ #hack for blind - other then behaved devices blind does not send
+ # a status info for chan 0 at power on
+ # chn3 (virtual chan) and not used up to now
+ # info from it is likely a power on!
+ push @event,"powerOn" if($chn eq "03");
+ }
+ elsif ($model eq "HM-SEC-SFA-SM"){ # && $chn eq "00")
+ push @entities,
+ CUL_HM_UpdtReadBulk(CUL_HM_getDeviceHash($shash),1,
+ "powerError:" .(($err&0x02) ? "on":"off"),
+ "sabotageError:".(($err&0x04) ? "on":"off"),
+ "battery:".(($err&0x08)?"critical":($err&0x80?"low":"ok")));
+ }
+ elsif ($model eq "HM-LC-SW1-BA-PCB"){
+ push @event, "battery:" . (($err&0x80) ? "low" : "ok" );
+ }
+ push @event, "state:$val";
+ }
+ }
+ elsif($st =~ m /^(remote|pushButton|swi)$/) { ###############################
+ if($msgType =~ m/^4./ && $p =~ m/^(..)(..)$/) {
+ my ($buttonField, $bno) = (hex($1), hex($2));# button number/event count
+ my $buttonID = $buttonField&0x3f;# only 6 bit are valid
+ my $btnName;
+ my $state = "";
+ my $chnHash = $modules{CUL_HM}{defptr}{$src.sprintf("%02X",$buttonID)};
+
+ if ($chnHash){# use userdefined name - ignore this irritating on-off naming
+ $btnName = $chnHash->{NAME};
+ }
+ else{# Button not defined, use default naming
+ $chnHash = $shash;
+ if ($st eq "swi"){#maintain history for event naming
+ $btnName = "Btn$buttonField";
+ }
+ else{
+ my $btn = int((($buttonField&0x3f)+1)/2);
+ $btnName = "Btn$btn";
+ $state = ($buttonField&1 ? "off" : "on")
+ }
+ }
+ my $trigType;
+ if($buttonField & 0x40){
+ if(!$shash->{BNO} || $shash->{BNO} ne $bno){#bno = event counter
+ $shash->{BNO}=$bno;
+ $shash->{BNOCNT}=1; # message counter reest
+ }
+ $shash->{BNOCNT}+=1;
+ $state .= "Long" .($msgFlag eq "A0" ? "Release" : "").
+ " ".$shash->{BNOCNT}."-".$cmd."-";
+ $trigType = "Long";
+ }
+ else{
+ $state .= ($st eq "swi")?"toggle":"Short";#swi only support toggle
+ $trigType = "Short";
+ }
+ $shash->{helper}{addVal} = $buttonField; #store to handle changesFread
+ push @entities,CUL_HM_UpdtReadBulk($chnHash,1,
+ ,"state:".$state.$target
+ ,"trigger:".$trigType."_".$bno);
+ push @event,"battery:". (($buttonField&0x80)?"low":"ok");
+ push @event,"state:$btnName $state$target";
+ }
+ }
+ elsif($st eq "repeater"){ ###################################################
+ if (($msgType eq "02" && $p =~ m/^01/) || # handle Ack_Status
+ ($msgType eq "10" && $p =~ m/^06/)) { #or Info_Status message here
+ my ($state,$err) = ($1,hex($2)) if ($p =~ m/^....(..)(..)/);
+ # not sure what level are possible
+ push @event, "state:".($state eq '00'?"ok":"level:".$state);
+ push @event, "battery:". (($err&0x80)?"low" :"ok" );
+ my $flag = ($err>>4) &0x7;
+ push @event, "flags:". (($flag)?"none" :$flag );
+ }
+ }
+ elsif($st eq "virtual"){#####################################################
+ # possibly add code to count all acks that are paired.
+ if($msgType eq "02") {# this must be a reflection from what we sent, ignore
+ push @event, "";
+ }
+ }
+ elsif($st eq "outputUnit"){##################################################
+ if($msgType eq "40" && $p =~ m/^(..)(..)$/){
+ my ($button, $bno) = (hex($1), hex($2));
+ if(!(exists($shash->{BNO})) || $shash->{BNO} ne $bno){
+ $shash->{BNO}=$bno;
+ $shash->{BNOCNT}=1;
+ }
+ else{
+ $shash->{BNOCNT}+=1;
+ }
+ my $btn = int($button&0x3f);
+ push @event, "state:Btn$btn on$target";
+ }
+ elsif(($msgType eq "02" && $p =~ m/^01/) || # handle Ack_Status
+ ($msgType eq "10" && $p =~ m/^06/)){ # or Info_Status message
+ my ($msgChn,$msgState) = ((hex($1)&0x1f),$2) if ($p =~ m/..(..)(..)/);
+ my $chnHash = $modules{CUL_HM}{defptr}{$src.sprintf("%02X",$msgChn)};
+ if ($model eq "HM-OU-LED16") {
+ #special: all LEDs map to device state
+ my $devState = ReadingsVal($name,"color","00000000");
+ if($parse eq "powerOn"){# reset LEDs after power on
+ CUL_HM_PushCmdStack($shash,'++A011'.$id.$src."8100".$devState);
+ CUL_HM_ProcessCmdStack($shash);
+ # no event necessary, all the same as before
+ }
+ else {# just update datafields in storage
+ my $bitLoc = ($msgChn-1)*2;#calculate bit location
+ my $mask = 3<<$bitLoc;
+ my $value = sprintf("%08X",(hex($devState) &~$mask)|($msgState<<$bitLoc));
+ push @entities,
+ CUL_HM_UpdtReadBulk($shash,1,"color:".$value,
+ "state:".$value);
+ if ($chnHash){
+ $shash = $chnHash;
+ my %colorTable=("00"=>"off","01"=>"red","02"=>"green","03"=>"orange");
+ my $actColor = $colorTable{$msgState};
+ $actColor = "unknown" if(!$actColor);
+ push @event, "color:$actColor";
+ push @event, "state:$actColor";
+ }
+ }
+ }
+ elsif ($model eq "HM-OU-CFM-PL"){
+ if ($chnHash){
+ $shash = $chnHash;
+ my $val = hex($msgState)/2;
+ $val = ($val == 100 ? "on" : ($val == 0 ? "off" : "$val %"));
+ push @event, "state:$val";
+ }
+ }
+ }
+ }
+ elsif($st eq "motionDetector") { ############################################
+ # Code with help of Bassem
+ my $state;
+ if(($msgType eq "10" ||$msgType eq "02") && $p =~ m/^0601(..)(..)/) {
+ my $err;
+ ($state, $err) = ($1, hex($2));
+ my $bright = hex($state);
+ push @event, "brightness:".$bright;
+ push @event, "cover:". (($err&0x0E)?"open" :"closed");
+ push @event, "battery:". (($err&0x80)?"low" :"ok" );
+ }
+ elsif($msgType eq "41" && $p =~ m/^01(..)(..)(..)/) {#01 is channel
+ my($cnt,$bright,$nextTr);
+ ($cnt,$state,$nextTr) = (hex($1),$2,(hex($3)>>4));
+ $bright = hex($state);
+ my @nextVal = ("0x0","0x1","0x2","0x3","15" ,"30" ,"60" ,"120",
+ "240","0x9","0xa","0xb","0xc","0xd","0xe","0xf");
+ push @event, "state:motion";
+ push @event, "motion:on$target"; #added peterp
+ push @event, "motionCount:".$cnt."_next:".$nextTr."-".$nextVal[$nextTr];
+ push @event, "brightness:".$bright;
+ }
+ elsif($msgType eq "70" && $p =~ m/^7F(..)(.*)/) {
+ my($d1, $d2) = ($1, $2);
+ push @event, 'devState_raw'.$d1.':'.$d2;
+ }
+
+ if($id eq $dst && $cmd ne "8002" && $state){
+ CUL_HM_SndCmd($shash, $msgcnt."8002".$id.$src."0101${state}00");
+ $sendAck = ""; #todo why is this special?
+ }
+ }
+ elsif($st eq "smokeDetector") { #############################################
+ #Info Level: msgType=0x10 p(..)(..)(..) subtype=06, channel, state (1 byte)
+ #Event: msgType=0x41 p(..)(..)(..) channel , unknown, state (1 byte)
+
+ if ($msgType eq "10" && $p =~ m/^06..(..)/) {
+ my $state = hex($1);
+ push @event, "battery:". (($state&0x04)?"low" :"ok" );
+ push @event, "state:alive";
+ }
+ elsif ($msgType eq "40"){ #autonomous event
+ if($dhash){ # the source is in dst
+ my ($state,$trgCnt) = (hex(substr($p,0,2)),hex(substr($p,2,2)));
+ push @entities,CUL_HM_UpdtReadSingle($dhash,'test',"from $dname:$state",1)
+ if (!($state & 1));
+ push @entities,CUL_HM_UpdtReadSingle($dhash,'battery',(($state & 0x04)?"low":"ok"),1)
+ if($state&0x80);
+ }
+ push @event, "";
+ }
+ elsif ($msgType eq "41"){ #Alarm detected
+ my ($No,$state) = (substr($p,2,2),substr($p,4,2));
+ if($dhash && $dname ne $name){ # update source(ID is reported in $dst...)
+ if (!$dhash->{helper}{alarmNo} || $dhash->{helper}{alarmNo} ne $No){
+ $dhash->{helper}{alarmNo} = $No;
+ push @entities,CUL_HM_UpdtReadSingle($dhash,'state',
+ (($state eq "01")?"off":
+ (($state eq "C7")?"smoke-forward":
+ "smoke-alarm")),1);
+ }
+ }
+ # - - - - - - now handle the team - - - - - -
+ $shash->{helper}{alarmList} = "" if (!$shash->{helper}{alarmList});
+ $shash->{helper}{alarmFwd} = "" if (!$shash->{helper}{alarmFwd});
+ if ($state eq "01") { # clear Alarm for one sensor
+ $shash->{helper}{alarmList} =~ s/",".$dst//;
+ }
+ elsif($state eq "C7"){# add alarm forwarding
+ $shash->{helper}{alarmFwd} .= ",".$dst;
+ }
+ else{ # add alarm for Sensor
+ $shash->{helper}{alarmList} .= ",".$dst;
+ }
+ my $alarmList; # make alarm ID list readable
+ foreach(split(",",$shash->{helper}{alarmList})){
+ $alarmList .= CUL_HM_id2Name($_)."," if ($_);
+ }
+ if (!$alarmList){# all alarms are gone - clear forwarding
+ foreach(split(",",$shash->{helper}{alarmFwd})){
+ my $fHash = CUL_HM_id2Hash($1) if ($1);
+ push @entities,CUL_HM_UpdtReadSingle($fHash,'state',"off",1)if ($fHash);
+ }
+ $shash->{helper}{alarmList} = "";
+ $shash->{helper}{alarmFwd} = "";
+ }
+ my $alarmFwd; # make forward ID list readable
+ foreach(split(",",$shash->{helper}{alarmFwd})){
+ $alarmFwd .= CUL_HM_id2Name($_)."," if ($_);
+ }
+ push @event,"state:" .($alarmList?"smoke-Alarm":"off" );
+ push @event,"smoke_detect:" .($alarmList?$alarmList :"none");
+ push @event,"smoke_forward:".($alarmFwd ?$alarmFwd :"none");
+ }
+ elsif ($msgType eq "01"){ #Configs
+ my $sType = substr($p,0,2);
+ if($sType eq "01"){#add peer to group
+ push @event,"SDteam:add_".$dname;
+ }
+ elsif($sType eq "02"){# remove from group
+ push @event,"SDteam:remove_".$dname;
+ }
+ elsif($sType eq "05"){# set param List 3 and 4
+ push @event,"";
+ }
+ }
+ else{
+ push @event, "SDunknownMsg:$p" if(!@event);
+ }
+
+ if($id eq $dst && (hex($msgFlag)&0x20)){ # Send Ack/Nack
+ CUL_HM_SndCmd($shash, $msgcnt."8002".$id.$src.($cmd eq "A001" ? "80":"00"));
+ $sendAck = ""; #todo why is this special?
+ }
+ }
+ elsif($st eq "threeStateSensor") { ##########################################
+ #Event: msgType=0x41 p(..)(..)(..) channel , unknown, state
+ #Info Level: msgType=0x10 p(..)(..)(..)(..) subty=06, chn, state,err (3bit)
+ #AckStatus: msgType=0x02 p(..)(..)(..)(..) subty=01, chn, state,err (3bit)
+ my ($chn,$state,$err,$cnt); #define locals
+ if($msgType eq "10" || $msgType eq "02"){
+ my $mT = $msgType.substr($p,0,2);
+ if ($mT eq "1006" ||$$mT eq "0201"){
+ $p =~ m/^..(..)(..)(..)?$/;
+ ($chn,$state,$err) = (hex($1), $2, hex($3));
+ $chn = sprintf("%02X",$chn&0x3f);
+ $shash = $modules{CUL_HM}{defptr}{"$src$chn"}
+ if($modules{CUL_HM}{defptr}{"$src$chn"});
+ push @event, "alive:yes";
+ push @event, "battery:". (($err&0x80)?"low" :"ok" );
+ if ($model ne "HM-SEC-WDS"){
+ push @event, "cover:". (($err&0x0E)?"open" :"closed");
+ }
+ }
+ }
+ elsif($msgType eq "41"){
+ ($chn,$cnt,$state)=(hex($1),$2,$3) if($p =~ m/^(..)(..)(..)/);
+ $chn = sprintf("%02X",$chn&0x3f);
+ $shash = $modules{CUL_HM}{defptr}{"$src$chn"}
+ if($modules{CUL_HM}{defptr}{"$src$chn"});
+ }
+
+ if (defined($state)){# if state was detected post events
+ my %txt;
+ %txt = ("C8"=>"open", "64"=>"tilted", "00"=>"closed");
+ %txt = ("C8"=>"wet", "64"=>"damp", "00"=>"dry")
+ if($model eq "HM-SEC-WDS");
+ my $txt = $txt{$state};
+ $txt = "unknown:$state" if(!$txt);
+ push @event, "state:$txt";
+ push @event, "contact:$txt$target";
+
+ }
+ else{push @event, "3SSunknownMsg:$p" if(!@event);}
+ }
+ elsif($st eq "winMatic") { #################################################
+ my($sType,$chn,$lvl,$stat) = ($1,$2,$3,$4) if ($p =~ m/^(..)(..)(..)(..)/);
+ if(($msgType eq "10" && $sType eq "06") ||
+ ($msgType eq "02" && $sType eq "01")){
+ $shash = $modules{CUL_HM}{defptr}{"$src$chn"}
+ if($modules{CUL_HM}{defptr}{"$src$chn"});
+ # stateflag meaning unknown
+ push @event, "state:".(($lvl eq "FF")?"locked":((hex($lvl)/2)." %"));
+ if ($chn eq "01"){
+ my %err = (0=>"no",1=>"TurnError",2=>"TiltError");
+ my %dir = (0=>"no",1=>"up",2=>"down",3=>"undefined");
+ push @event, "motorError:".$err{(hex($stat)>>1)&0x02};
+ push @event, "direction:".$dir{(hex($stat)>>4)&0x02};
+# CUL_HM_SndCmd($shash, $msgcnt."8002".$id.$src."0101".$lst."00")
+# if($id eq $dst);# Send AckStatus
+# $sendAck = "";
+ }
+ else{ #should be akku
+ my %statF = (0=>"trickleCharge",1=>"charge",2=>"dischange",3=>"unknown");
+ push @event, "charge:".$statF{(hex($stat)>>4)&0x02};
+ }
+ }
+ if ($p =~ m/^0287(..)89(..)8B(..)/) {
+ my ($air, undef, $course) = ($1, $2, $3);
+ push @event, "airing:".($air eq "FF" ? "inactiv" : CUL_HM_decodeTime8($air));
+ push @event, "course:".($course eq "FF" ? "tilt" : "close");
+ }
+ elsif($p =~ m/^0201(..)03(..)04(..)05(..)07(..)09(..)0B(..)0D(..)/) {
+ my ($flg1, $flg2, $flg3, $flg4, $flg5, $flg6, $flg7, $flg8) =
+ ($1, $2, $3, $4, $5, $6, $7, $8);
+ push @event, "airing:".($flg5 eq "FF" ? "inactiv" : CUL_HM_decodeTime8($flg5));
+ push @event, "contact:tesed";
+ }
+ }
+ elsif($st eq "keyMatic") { #################################################
+ #Info Level: msgType=0x10 p(..)(..)(..)(..) subty=06, chn, state,err (3bit)
+ #AckStatus: msgType=0x02 p(..)(..)(..)(..) subty=01, chn, state,err (3bit)
+
+ if(($msgType eq "10" && $p =~ m/^06/) ||
+ ($msgType eq "02" && $p =~ m/^01/)) {
+ $p =~ m/^..(..)(..)(..)/;
+ my ($chn,$val, $err) = ($1,hex($2), hex($3));
+ $shash = $modules{CUL_HM}{defptr}{"$src$chn"}
+ if($modules{CUL_HM}{defptr}{"$src$chn"});
+
+ my $stErr = ($err >>1) & 0x7;
+ my $error = 'unknown_'.$stErr;
+ $error = 'motor aborted' if ($stErr == 2);
+ $error = 'clutch failure' if ($stErr == 1);
+ $error = 'none' if ($stErr == 0);
+ my %dir = (0=>"none",1=>"up",2=>"down",3=>"undef");
+
+ push @event, "unknown:40" if($err&0x40);
+ push @event, "battery:" .(($err&0x80) ? "low":"ok");
+ push @event, "uncertain:" .(($err&0x30) ? "yes":"no");
+ push @event, "direction:" .$dir{($err>>4)&3};
+ push @event, "error:" . ($error);
+ my $state = ($err & 0x30) ? " (uncertain)" : "";
+ push @event, "lock:" . (($val == 1) ? "unlocked" : "locked");
+ push @event, "state:" . (($val == 1) ? "unlocked" : "locked") . $state;
+ }
+ }
+ else{########################################################################
+ ; # no one wants the message
+ }
+
+ #------------ parse if FHEM or virtual actor is destination ---------------
+
+ if(AttrVal($dname, "subType", "none") eq "virtual"){# see if need for answer
+ if($msgType =~ m/^4./ && $p =~ m/^(..)(..)/) { #Push Button event
+ my ($recChn,$trigNo) = (hex($1),hex($2));# button number/event count
+ my $longPress = ($recChn & 0x40)?"long":"short";
+ my $recId = $src.sprintf("%02X",($recChn&0x3f));
+ foreach my $dChId (CUL_HM_getAssChnIds($dname)){# need to check all chan
+ next if (!$modules{CUL_HM}{defptr}{$dChId});
+ my $dChNo = substr($dChId,6,2);
+ my $dChName = CUL_HM_id2Name($dChId);
+
+ if (AttrVal($dChName,"peerIDs","") =~m/$recId/){# is in peerlist?
+ my $dChHash = CUL_HM_name2Hash($dChName);
+ $dChHash->{helper}{trgLgRpt} = 0
+ if (!defined($dChHash->{helper}{trgLgRpt}));
+ $dChHash->{helper}{trgLgRpt} +=1;
+ my $trgLgRpt = $dChHash->{helper}{trgLgRpt};
+
+ my $state = ReadingsVal($dChName,"virtActState","OFF");
+ my $tNoOld = ReadingsVal($dChName,"virtActTrigNo","0");
+ $state = ($state eq "OFF")?"ON":"OFF" if ($trigNo ne $tNoOld);
+ if (hex($msgFlag)&0x20){
+ $longPress .= "_Release";
+ $dChHash->{helper}{trgLgRpt}=0;
+ CUL_HM_SndCmd($dhash,$msgcnt."8002".$dst.$src.'01'.$dChNo.
+ (($state eq "ON")?"C8":"00")."00");
+ $sendAck = "";
+ }
+ push @entities,
+ CUL_HM_UpdtReadBulk($dChHash,1,"state:".$state,
+ "virtActState:".$state,
+ "virtActTrigger:".CUL_HM_id2Name($recId),
+ "virtActTrigType:".$longPress,
+ "virtActTrigRpt:".$trgLgRpt,
+ "virtActTrigNo:".$trigNo );
+ }
+ }
+ }
+ elsif($msgType eq "58" && $p =~ m/^(..)(..)/) {# climate event
+ my ($d1,$vp) =($1,hex($2)); # adjust_command[0..4] adj_data[0..250]
+ $vp = int($vp/2.56+0.5); # valve position in %
+ my $chnHash = $modules{CUL_HM}{defptr}{$dst."01"};
+ push @entities,
+ CUL_HM_UpdtReadBulk($chnHash,1,"ValvePosition:$vp %",
+ "ValveAdjCmd:".$d1);
+ CUL_HM_SndCmd($chnHash,$msgcnt."8002".$dst.$src.'0101'.
+ sprintf("%02X",$vp*2)."0000");#$vp, $err,$??
+ $sendAck = "";
+ }
+ elsif($msgType eq "02"){
+ if ($dhash->{helper}{respWait}{msgId} &&
+ $dhash->{helper}{respWait}{msgId} eq $msgcnt ){
+ #ack we waited for - stop Waiting
+ CUL_HM_respPendRm($dhash);
+ }
+ }
+ if (hex($msgFlag)&0x20 && ($sendAck eq "yes")){
+ CUL_HM_SndCmd($dhash, $msgcnt."8002".$dst.$src."00");#virtual must ack
+ }
+ }
+ elsif($id eq $dst){# if fhem is destination check if we need to react
+ if($msgType =~ m/^4./ && $p =~ m/^(..)/ && #Push Button event
+ (hex($msgFlag)&0x20)){ #response required Flag
+ my ($recChn) = ($1);# button number/event count
+ # fhem CUL shall ack a button press
+ CUL_HM_SndCmd($shash, $msgcnt."8002".$dst.$src."0101".
+ ((hex($recChn)&1)?"C8":"00")."00");#Actor simulation
+ $sendAck = "";
+ }
+ }
+
+ #------------ send default ACK if not applicable------------------
+ # ack if we are destination, anyone did accept the message (@event)
+ # parser did not supress
+ CUL_HM_SndCmd($shash, $msgcnt."8002".$id.$src."00") # Send Ack
+ if( ($id eq $dst) #are we adressee
+ && (hex($msgFlag)&0x20) #response required Flag
+ && @event #only ack of we identified it
+ && ($sendAck eq "yes") #sender requested ACK
+ );
+
+ CUL_HM_ProcessCmdStack($shash) if ($respRemoved); # cont stack if a response is complete
+ #------------ process events ------------------
+
+ push @event, "noReceiver:src:$src ($cmd) $p" if(!@event);
+ CUL_HM_UpdtReadBulk($shash,1,@event); #events to the channel
+ $defs{$shash->{NAME}}{EVENTS}++; # count events for channel
+ push @entities,$shash->{NAME};
+ foreach (CUL_HM_noDup(@entities)){
+ DoTrigger($_, undef) if ($_ ne $name);
+ }
+ return $name ;#general notification to the device
+}
+sub CUL_HM_parseCommon(@){#####################################################
+ # parsing commands that are device independant
+ my ($msgId,$msgFlag,$msgType,$src,$dst,$p) = @_;
+ my $shash = $modules{CUL_HM}{defptr}{$src};
+ my $dhash = $modules{CUL_HM}{defptr}{$dst};
+ return "" if(!$shash->{DEF});# this should be from ourself
+
+ my $pendType = $shash->{helper}{respWait}{Pending}?
+ $shash->{helper}{respWait}{Pending}:"";
+ #------------ parse message flag for start processing command Stack
+ # TC wakes up with 8270, not with A258
+ # VD wakes up with 8202
+ if( $shash->{cmdStack} &&
+ ((hex($msgFlag) & 0xA2) == 0x82) &&
+ (CUL_HM_getRxType($shash) & 0x08)){ #wakeup #####
+ #send wakeup and process command stack
+ CUL_HM_SndCmd($shash, '++A112'.CUL_HM_IOid($shash).$src);
+ CUL_HM_ProcessCmdStack($shash);
+ }
+
+ if ($shash->{helper}{respWait}{msgId} &&
+ $shash->{helper}{respWait}{msgId} eq $msgId ){
+ #response we waited for - stop Waiting
+ CUL_HM_respPendRm($shash);
+ }
+ if ($msgType eq "02"){# Ack/Nack #############################
+
+ #see if the channel is defined separate - otherwise go for chief
+ my $subType = substr($p,0,2);
+ my $chn = sprintf("%02X",hex(substr($p,2,2))&0x3f);
+ #mark timing on the channel, not the device
+ my $HMid = $chn?$src.$chn:$src;
+ my $chnhash = $modules{CUL_HM}{defptr}{$HMid};
+ $chnhash = $shash if(!$chnhash);
+
+ my $reply;
+ my $success;
+
+ if ($subType =~ m/^8/){ #NACK
+ $success = "no";
+ CUL_HM_eventP($shash,"Nack");
+ delete($shash->{cmdStack});
+ delete($shash->{protCmdPend});
+ CUL_HM_respPendRm($shash);
+ $reply = "NACK";
+ }
+ elsif($subType eq "01"){ #ACKinfo#################
+ $success = "yes";
+ my $rssi = substr($p,8,2);# --calculate RSSI
+ CUL_HM_storeRssi(CUL_HM_hash2Name($shash),
+ ($dhash?CUL_HM_hash2Name($dhash):$shash->{IODev}{NAME}),
+ (-1)*(hex($rssi)))
+ if ($rssi && $rssi ne '00' && $rssi ne'80');
+ $reply = "ACKStatus";
+ }
+ else{ #ACK
+ $success = "yes";
+ $reply = "ACK";
+ }
+ readingsSingleUpdate($chnhash,"CommandAccepted",$success,1);
+ CUL_HM_ProcessCmdStack($shash)
+ if($dhash->{DEF} && (CUL_HM_IOid($shash) eq $dhash->{DEF}));
+ return $reply;
+ }
+ elsif($msgType eq "00"){######################################
+ if ($pendType eq "PairSerial"){
+ if($shash->{helper}{respWait}{forChn} = substr($p,6,20)){
+ CUL_HM_respPendRm($shash);
+ }
+ }
+ }
+ elsif($msgType eq "10"){######################################
+ my $subType = substr($p,0,2);
+ if($subType eq "00"){ #storePeerList#################
+ $attr{$shash->{NAME}}{serialNr} = pack("H*",substr($p,2,20));
+ return "done";
+ }
+ elsif($subType eq "01"){ #storePeerList#################
+ if ($pendType eq "PeerList"){
+ my $chn = $shash->{helper}{respWait}{forChn};
+ my $chnhash = $modules{CUL_HM}{defptr}{$src.$chn};
+ $chnhash = $shash if (!$chnhash);
+ my $chnNname = $chnhash->{NAME};
+ my @peers = substr($p,2,) =~ /(.{8})/g;
+ $chnhash->{helper}{peerIDsRaw}.= ",".join",",@peers;
+
+ foreach my $peer(@peers){
+ CUL_HM_ID2PeerList ($chnNname,$peer,1);
+ }
+ if ($p =~ m/000000..$/) {# last entry, peerList is complete
+ CUL_HM_respPendRm($shash);
+ # check for request to get List3 data
+ my $reqPeer = $chnhash->{helper}{getCfgList};
+ if ($reqPeer){
+ my $flag = CUL_HM_getFlag($shash);
+ my $id = CUL_HM_IOid($shash);
+ my $listNo = "0".$chnhash->{helper}{getCfgListNo};
+ my @peerID = split(",", AttrVal($chnNname,"peerIDs",""));
+ foreach my $peer (@peerID){
+ next if ($peer eq '00000000');# ignore termination
+ $peer .="01" if (length($peer) == 6); # add the default
+ if ($peer &&($peer eq $reqPeer || $reqPeer eq "all")){
+ CUL_HM_PushCmdStack($shash,sprintf("++%s01%s%s%s04%s%s",
+ $flag,$id,$src,$chn,$peer,$listNo));# List3 or 4
+ }
+ }
+ }
+ delete $chnhash->{helper}{getCfgList};
+ delete $chnhash->{helper}{getCfgListNo};
+ }
+ else{
+ CUL_HM_respPendToutProlong($shash);#wasn't last - reschedule timer
+ }
+ return "done";
+ }
+ }
+ elsif($subType eq "02" ||$subType eq "03"){ #ParamResp==================
+ if ($pendType eq "RegisterRead"){
+ my $chnSrc = $src.$shash->{helper}{respWait}{forChn};
+ my $chnHash = $modules{CUL_HM}{defptr}{$chnSrc};
+ $chnHash = $shash if (!$chnHash);
+ my $chnName = $chnHash->{NAME};
+ my ($format,$data) = ($1,$2) if ($p =~ m/^(..)(.*)/);
+ my $list = $shash->{helper}{respWait}{forList};
+ $list = "00" if (!$list); #use the default
+ if ($format eq "02"){ # list 2: format aa:dd aa:dd ...
+ $data =~ s/(..)(..)/ $1:$2/g;
+ }
+ elsif ($format eq "03"){ # list 3: format aa:dddd
+ my $addr;
+ my @dataList;
+ ($addr,$data) = (hex($1),$2) if ($data =~ m/(..)(.*)/);
+ if ($addr == 0){
+ $data = "00:00";
+ }
+ else{
+ $data =~s/(..)/$1:/g;
+ foreach my $d1 (split(":",$data)){
+ push (@dataList,sprintf("%02X:%s",$addr++,$d1));
+ }
+ $data = join(" ",@dataList);
+ }
+ }
+ my $peer = $shash->{helper}{respWait}{forPeer};
+ my $regLN = ((CUL_HM_getExpertMode($chnHash) eq "2")?"":".")."RegL_".$list.":".$peer;
+ readingsSingleUpdate($chnHash,$regLN,
+ ReadingsVal($chnName,$regLN,"")." ".$data,0);
+ if ($data =~m/00:00$/){ # this was the last message in the block
+ if($list eq "00"){
+ my $name = CUL_HM_id2Name($src);
+ readingsSingleUpdate($shash,"PairedTo",
+ CUL_HM_getRegFromStore($name,"pairCentral",0,"00000000"),0);
+ }
+ CUL_HM_respPendRm($shash);
+ delete $chnHash->{helper}{shadowReg}{$regLN};#remove shadowhash
+ # peer Channel name from/for user entry.
+ CUL_HM_updtRegDisp($chnHash,$list,
+ CUL_HM_peerChId($peer,
+ substr(CUL_HM_hash2Id($chnHash),0,6),"00000000"));
+ }
+ else{
+ CUL_HM_respPendToutProlong($shash);#wasn't last - reschedule timer
+ }
+ return "done";
+ }
+ }
+ elsif($subType eq "04"){ #ParamChange===================================
+ my($chn,$peerID,$list,$data) = ($1,$2,$3,$4) if($p =~ m/^04(..)(........)(..)(.*)/);
+ my $chnHash = $modules{CUL_HM}{defptr}{$src.$chn};
+ $chnHash = $shash if(!$chnHash); # will add param to dev if no chan
+ my $regLN = ((CUL_HM_getExpertMode($chnHash) eq "2")?"":".")."RegL_".$list.":".CUL_HM_id2Name($peerID);
+ $regLN =~ s/broadcast//;
+ $regLN =~ s/ /_/g; #remove blanks
+
+ $data =~ s/(..)(..)/ $1:$2/g;
+
+ my $lN = ReadingsVal($chnHash->{NAME},$regLN,"");
+ my $shdwReg = $chnHash->{helper}{shadowReg}{$regLN};
+ foreach my $entry(split(' ',$data)){
+ my ($a,$d) = split(":",$entry);
+ last if ($a eq "00");
+ if ($lN =~m/$a:/){$lN =~ s/$a:../$a:$d/;
+ }else{ $lN .= " ".$entry;}
+ $shdwReg =~ s/ $a:..// if ($shdwReg);# confirmed: remove from shadow
+ }
+ $chnHash->{helper}{shadowReg}{$regLN} = $shdwReg;
+ $lN = join(' ',sort(split(' ',$lN)));# re-order
+ if ($lN =~ s/00:00//){$lN .= " 00:00"};
+ readingsSingleUpdate($chnHash,$regLN,$lN,0);
+ CUL_HM_updtRegDisp($chnHash,$list,$peerID);
+ }
+ elsif($subType eq "06"){ #reply to status request=======================
+ my $rssi = substr($p,8,2);# --calculate RSSI
+ CUL_HM_storeRssi(CUL_HM_hash2Name($shash),
+ ($dhash?CUL_HM_hash2Name($dhash):$shash->{IODev}{NAME}),
+ (-1)*(hex($rssi)))
+ if ($rssi && $rssi ne '00' && $rssi ne'80');
+ #todo = what is the answer to a status request
+ if ($pendType eq "StatusReq"){#it is the answer to our request
+ my $chnSrc = $src.$shash->{helper}{respWait}{forChn};
+ my $chnhash = $modules{CUL_HM}{defptr}{$chnSrc};
+ $chnhash = $shash if (!$chnhash);
+ CUL_HM_respPendRm($shash);
+ return "STATresp";
+ }
+ else{
+ my ($chn) = ($1) if($p =~ m/^..(..)/);
+ if ($chn eq "00"){
+ CUL_HM_queueAutoRead(CUL_HM_hash2Name($shash))
+ if (1 < substr(AttrVal($shash->{NAME},"autoReadReg",0),0,1));
+ return "powerOn" ;# check dst eq "000000" as well?
+ }
+ }
+ }
+ }
+ elsif($msgType eq "70"){ #Time to trigger TC##################
+ #send wakeup and process command stack
+# CUL_HM_SndCmd($shash, '++A112'.CUL_HM_IOid($shash).$src);
+# CUL_HM_ProcessCmdStack($shash);
+ }
+ return "";
+}
+sub CUL_HM_queueAutoRead($){
+ my $name = shift;
+ my @arr;
+ @arr = CUL_HM_noDup((@{$modules{CUL_HM}{helper}{updtCfgLst}}, $name));
+ $modules{CUL_HM}{helper}{updtCfgLst} =\@arr;
+ RemoveInternalTimer("updateConfig");
+ InternalTimer(gettimeofday()+5,"CUL_HM_autoReadConfig", "updateConfig", 0);
+}
+
+#+++++++++++++++++ get command+++++++++++++++++++++++++++++++++++++++++++++++++
+sub CUL_HM_Get($@) {
+ my ($hash, @a) = @_;
+ return "no get value specified" if(@a < 2);
+
+ my $name = $hash->{NAME};
+ my $devName = $hash->{device}?$hash->{device}:$name;
+ my $st = AttrVal($devName, "subType", "");
+ my $md = AttrVal($devName, "model", "");
+ my $mId = CUL_HM_getMId($hash);
+ my $rxType = CUL_HM_getRxType($hash);
+
+ my $cmd = $a[1];
+ my $dst = $hash->{DEF};
+ my $isChannel = (length($dst) == 8)?"true":"";
+ my $chn = ($isChannel)?substr($dst,6,2):"01";
+ $dst = substr($dst,0,6);
+
+ my $h = $culHmGlobalGets{$cmd};
+ $h = $culHmSubTypeGets{$st}{$cmd} if(!defined($h) && $culHmSubTypeGets{$st});
+ $h = $culHmModelGets{$md}{$cmd} if(!defined($h) && $culHmModelGets{$md});
+ my @h;
+ @h = split(" ", $h) if($h);
+
+ if(!defined($h)) {
+ my @arr = keys %culHmGlobalGets;
+ push @arr, keys %{$culHmSubTypeGets{$st}} if($culHmSubTypeGets{$st});
+ push @arr, keys %{$culHmModelGets{$md}} if($culHmModelGets{$md});
+ my $usg = "Unknown argument $cmd, choose one of ".join(" ",sort @arr);
+
+ return $usg;
+ }elsif($h eq "" && @a != 2) {
+ return "$cmd requires no parameters";
+
+ } elsif($h !~ m/\.\.\./ && @h != @a-2) {
+ return "$cmd requires parameter: $h";
+ }
+ my $devHash = CUL_HM_getDeviceHash($hash);
+ my $id = CUL_HM_IOid($hash);
+
+ #----------- now start processing --------------
+ if($cmd eq "param") { ######################################################
+ return $attr{$name}{$a[2]} if ($attr{$name}{$a[2]});
+ return $hash->{READINGS}{$a[2]}{VAL} if ($hash->{READINGS}{$a[2]});
+ return $attr{$devName}{$a[2]} if ($attr{$devName}{$a[2]});
+ return $devHash->{READINGS}{$a[2]}{VAL} if ($devHash->{READINGS}{$a[2]});
+ return $hash->{$a[2]} if ($hash->{$a[2]});
+ return $devHash->{$a[2]} if ($devHash->{$a[2]});
+ return $hash->{helper}{$a[2]} if ($hash->{helper}{$a[2]} && ref($hash->{helper}{$a[2]}) ne "HASH");
+ return $devHash->{helper}{$a[2]} if ($devHash->{helper}{$a[2]});
+ return "undefined";
+ }
+ elsif($cmd eq "reg") { #####################################################
+ my (undef,undef,$regReq,$list,$peerId) = @a;
+ if ($regReq eq 'all'){
+ my @regArr = keys %culHmRegGeneral;
+ push @regArr, keys %{$culHmRegType{$st}} if($culHmRegType{$st});
+ push @regArr, keys %{$culHmRegModel{$md}} if($culHmRegModel{$md});
+ push @regArr, keys %{$culHmRegChan{$md.$chn}} if($culHmRegChan{$md.$chn});
+
+ my @peers; # get all peers we have a reglist
+ my @listWp; # list that require peers
+ foreach my $readEntry (keys %{$hash->{READINGS}}){
+ if ($readEntry =~m /^[\.]?RegL_(.*)/){ #reg Reading "RegL_:peerN
+ my $peer = substr($1,3);
+ next if (!$peer);
+ push(@peers,$peer);
+ push(@listWp,substr($1,1,1));
+ }
+ }
+ my @regValList; #storage of results
+ my $regHeader = "list:peer\tregister :value\n";
+ foreach my $regName (@regArr){
+ my $regL = $culHmRegDefine{$regName}->{l};
+ my @peerExe = (grep (/$regL/,@listWp))?@peers:("00000000");
+ foreach my $peer(@peerExe){
+ next if($peer eq "");
+ my $regVal= CUL_HM_getRegFromStore($name,$regName,0,$peer);#determine
+ my $peerN = CUL_HM_id2Name($peer);
+ $peerN = " " if ($peer eq "00000000");
+ push @regValList,sprintf(" %d:%s\t%-16s :%s\n",
+ $regL,$peerN,$regName,$regVal)
+ if ($regVal ne 'invalid');
+ }
+ }
+ my $addInfo = "";
+ $addInfo = CUL_HM_TCtempReadings($hash)
+ if ($md eq "HM-CC-TC" && $chn eq "02");
+
+ $addInfo = CUL_HM_repReadings($hash) if ($md eq "HM-Sys-sRP-Pl");
+
+ return $name." type:".$st." - \n".
+ $regHeader.join("",sort(@regValList)).
+ $addInfo;
+ }
+ else{
+ my $regVal = CUL_HM_getRegFromStore($name,$regReq,$list,$peerId);
+ return ($regVal eq "invalid")? "Value not captured"
+ : $regVal;
+ }
+ }
+ elsif($cmd eq "regList") { #################################################
+ my @regArr = keys %culHmRegGeneral ;
+ push @regArr, keys %{$culHmRegType{$st}} if($culHmRegType{$st});
+ push @regArr, keys %{$culHmRegModel{$md}} if($culHmRegModel{$md});
+
+ if ($isChannel){
+ push @regArr, keys %{$culHmRegChan{$md.$chn}} if($culHmRegChan{$md.$chn});
+ }
+ else{# add all ugly channel register to device view
+ for my $chnId (CUL_HM_getAssChnIds($name)){
+ my $chnN = substr($chnId,6,2);
+ push @regArr, keys %{$culHmRegChan{$md.$chnN}}
+ if($culHmRegChan{$md.$chnN});
+ }
+ }
+
+ my @rI;
+ foreach my $regName (@regArr){
+ my $reg = $culHmRegDefine{$regName};
+ my $help = $reg->{t};
+ my ($min,$max) = ($reg->{min},$reg->{max});
+ if (defined($reg->{lit})){
+ $help .= " options:".join(",",keys%{$reg->{lit}});
+ $min =$max ="-";
+ }
+ push @rI,sprintf("%4d: %-16s | %3s to %-11s | %8s |%-3s| %s\n",
+ $reg->{l},$regName,$min,$max.$reg->{u},
+ ((($reg->{l} == 3)||($reg->{l} == 4))?"required":""),
+ (($reg->{d} != 1)?"exp":""),
+ $help)
+ if (!($isChannel && $reg->{l} == 0));
+ }
+
+ my $info = sprintf("list: %16s | %-18s | %-8s |%-3s| %s\n",
+ "register","range","peer","exp","description");
+ foreach(sort(@rI)){$info .= $_;}
+ return $info;
+ }
+ elsif($cmd eq "saveConfig"){ ###############################################
+ my $fName = $a[2];
+ open(aSave, ">>$fName") || return("Can't open $fName: $!");
+
+ print aSave "\n\n#======== store device data:".$devName." === from: ".TimeNow();
+ my @eNames;
+ push @eNames,$devName;
+ foreach my $e (CUL_HM_getAssChnIds($name)){
+ my $eName = CUL_HM_id2Name($e);
+ push @eNames, $eName if($eName !~ m/_chn:/);
+ }
+
+ foreach my $eName (@eNames){
+ print aSave "\n#--- entity:".$eName;
+ my $pIds = AttrVal($eName, "peerIDs", "");
+ my $timestamps = "\n# timestamp of the readings for reference";
+ if ($pIds){
+ print aSave "\n# Peer Names:".ReadingsVal($eName,"peerList","");
+ $timestamps .= "\n# ".ReadingsTimestamp($eName,"peerList","")." :peerList";
+ print aSave "\nset ".$eName." peerBulk ".$pIds;
+ }
+ my $ehash = CUL_HM_name2Hash($eName);
+ foreach my $read (sort keys %{$ehash->{READINGS}}){
+ next if ($read !~ m/^[\.]?RegL_/);
+ print aSave "\nset ".$eName." regBulk ".$read." ".ReadingsVal($eName,$read,"");
+ $timestamps .= "\n# ".ReadingsTimestamp($eName,$read,"")." :".$read;
+ }
+ print aSave $timestamps;
+ }
+ print aSave "\n======= finished ===\n";
+ close(aSave);
+ }
+
+ Log GetLogLevel($name,4), "CUL_HM get $name " . join(" ", @a[1..$#a]);
+
+ CUL_HM_ProcessCmdStack($devHash) if ($rxType & 0x03);#burst/all
+ return "";
+}
+
+#+++++++++++++++++ set command+++++++++++++++++++++++++++++++++++++++++++++++++
+sub CUL_HM_Set($@) {
+ my ($hash, @a) = @_;
+ my $ret;
+ return "no set value specified" if(@a < 2);
+
+ my $name = $hash->{NAME};
+ my $devName = $hash->{device}?$hash->{device}:$name;
+ my $st = AttrVal($devName, "subType", "");
+ my $md = AttrVal($devName, "model" , "");
+ my $rxType = CUL_HM_getRxType($hash);
+ my $flag = CUL_HM_getFlag($hash); #set burst flag
+ my $cmd = $a[1];
+ my $dst = $hash->{DEF};
+ my $isChannel = (length($dst) == 8)?"true":"";
+ my $chn = ($isChannel)?substr($dst,6,2):"01";
+ $dst = substr($dst,0,6);
+
+ my $mdCh = $md.($isChannel?$chn:"00"); # chan specific commands?
+ my $h = $culHmGlobalSets{$cmd} if($st ne "virtual");
+ $h = $culHmGlobalSetsDevice{$cmd} if(!defined($h) && $st ne "virtual" && !$isChannel);
+ $h = $culHmSubTypeSets{$st}{$cmd} if(!defined($h) && $culHmSubTypeSets{$st});
+ $h = $culHmModelSets{$md}{$cmd} if(!defined($h) && $culHmModelSets{$md} );
+ $h = $culHmChanSets{$mdCh}{$cmd} if(!defined($h) && $culHmChanSets{$mdCh} );
+
+ my @h;
+ @h = split(" ", $h) if($h);
+
+ if(!defined($h) && defined($culHmSubTypeSets{$st}{pct}) && $cmd =~ m/^\d+/) {
+ splice @a, 1, 0,"pct";#insert the actual command
+ }
+ elsif(!defined($h)) {
+ my @arr;
+ @arr = keys %culHmGlobalSets if($st ne "virtual");
+ push @arr, keys %culHmGlobalSetsDevice if($st ne "virtual" && !$isChannel);
+ push @arr, keys %{$culHmSubTypeSets{$st}} if($culHmSubTypeSets{$st});
+ push @arr, keys %{$culHmModelSets{$md}} if($culHmModelSets{$md});
+ push @arr, keys %{$culHmChanSets{$mdCh}} if($culHmChanSets{$mdCh});
+ my $usg = "Unknown argument $cmd, choose one of ".join(" ",sort @arr);
+
+ $usg =~ s/ pct/ pct:slider,0,1,100/;
+ $usg =~ s/ virtual/ virtual:slider,1,1,40/;
+
+ if($md eq "HM-CC-TC") {
+ my @list = map { ($_.".0", $_+0.5) } (6..30);
+ pop @list;
+ my $list = "on,off," . join(",",@list);
+ $usg =~ s/-temp/-temp:$list/g;
+ }
+ return $usg;
+ }
+ elsif($h eq "" && @a != 2) {
+ return "$cmd requires no parameters";
+ }
+ elsif($h !~ m/\.\.\./ && @h != @a-2) {
+ return "$cmd requires parameter: $h";
+ }
+
+ #convert 'old' commands to current methodes like regSet...
+ if($cmd =~ m/^(displayMode|displayTemp|controlMode|decalcDay|displayTempUnit)$/ ||
+ $cmd =~ m/^(day|night|party)-temp$/){ #
+ splice @a,1,0,"regSet";# make hash,regSet,reg,value
+ ($chn,$isChannel) = ("02","true");#force chn 02
+ readingsSingleUpdate(CUL_HM_getDeviceHash($hash),$a[2],$a[3],1);#for historical reason
+ }
+ elsif($cmd eq "sign"){
+ splice @a,1,0,"regSet";# make hash,regSet,reg,value
+ }
+ elsif($cmd eq "unpair"){
+ splice @a,1,3, ("regSet","pairCentral","000000");
+ }
+ $cmd = $a[1];# get converted command
+
+ #if chn cmd is executed on device but refers to a channel?
+ my $chnHash = (!$isChannel && $modules{CUL_HM}{defptr}{$dst."01"})?
+ $modules{CUL_HM}{defptr}{$dst."01"}:$hash;
+ my $devHash = CUL_HM_getDeviceHash($hash);
+ my $id = CUL_HM_IOid($hash);
+ my $state = "set_".join(" ", @a[1..(int(@a)-1)]);
+
+ if($cmd eq "raw") { ########################################################
+ return "Usage: set $a[0] $cmd data [data ...]" if(@a < 3);
+ $state = "";
+ for (my $i = 2; $i < @a; $i++) {
+ CUL_HM_PushCmdStack($hash, $a[$i]);
+ }
+ }
+ elsif($cmd eq "clear") { ####################################################
+ my (undef,undef,$sect) = @a;
+ if ($sect eq "readings"){
+ delete $hash->{READINGS};
+ }
+ elsif($sect eq "msgEvents"){
+ CUL_HM_respPendRm($hash);
+ delete ($hash->{helper}{burstEvtCnt});
+ delete ($hash->{cmdStack});
+ foreach my $var (keys %{$attr{$name}}){ # can be removed versions later
+ delete ($attr{$name}{$var}) if ($var =~ m/^prot/);
+ }
+ foreach my $var (keys %{$hash}){
+ delete ($hash->{$var}) if ($var =~ m/^prot/);
+ delete ($hash->{EVENTS});
+ delete ($hash->{helper}{rssi});
+ }
+ $hash->{protState} = "Info_Cleared" ;
+ }
+ else{
+ return "unknown section. User readings or msgEvents";
+ }
+ $state = "";
+ }
+ elsif($cmd eq "reset") { ####################################################
+ CUL_HM_PushCmdStack($hash,"++".$flag."11".$id.$dst."0400");
+ }
+ elsif($cmd eq "pair") { #####################################################
+ $state = "";
+ my $serialNr = AttrVal($name, "serialNr", undef);
+ return "serialNr is not set" if(!$serialNr);
+ CUL_HM_PushCmdStack($hash,"++A401".$id."000000010A".uc( unpack("H*",$serialNr)));
+ $hash->{hmPairSerial} = $serialNr;
+ }
+ elsif($cmd eq "statusRequest") { ############################################
+ my @chnIdList = CUL_HM_getAssChnIds($name);
+ foreach my $channel (@chnIdList){
+ my $chnNo = substr($channel,6,2);
+ CUL_HM_PushCmdStack($hash,"++".$flag.'01'.$id.$dst.$chnNo.'0E');
+ }
+ $state = "";
+ }
+ elsif($cmd eq "getSerial") { ################################################
+ CUL_HM_PushCmdStack($hash,'++'.$flag.'01'.$id.$dst.'0009');
+ $state = "";
+ }
+ elsif($cmd eq "getConfig") { ################################################
+ my $chFound = 0;
+ CUL_HM_PushCmdStack($hash,'++'.$flag.'01'.$id.$dst.'00040000000000')
+ if (!$isChannel);
+ my @chnIdList = CUL_HM_getAssChnIds($name);
+ foreach my $channel (@chnIdList){
+ my $chnHash = CUL_HM_id2Hash($channel);
+ CUL_HM_getConfig($hash,$chnHash,$id,$dst,substr($channel,6,2));
+ }
+ $state = "";
+ }
+ elsif($cmd eq "peerBulk") { #################################################
+ $state = "";
+ my $pL = $a[2];
+ foreach my $peer (split(',',$pL)){
+ next if ($peer =~ m/^self/);
+ my $pID = CUL_HM_peerChId($peer,$dst,$id);
+ return "unknown peer".$peer if (length($pID) != 8);# peer only to channel
+ my $pCh1 = substr($pID,6,2);
+ my $pCh2 = $pCh1;
+ if($culHmSubTypeSets{$st}{peerChan}||
+ $culHmModelSets{$md}{peerChan}||
+ $culHmChanSets{$mdCh}{peerChan}){
+ $pCh2 = "00"; # button behavior
+ }
+ CUL_HM_PushCmdStack($hash,'++'.$flag.'01'.$id.$dst.$chn.'01'.
+ substr($pID,0,6).$pCh1.$pCh2);
+ }
+ }
+ elsif($cmd =~ m/^(regBulk|getRegRaw)$/) { ############################### reg
+ my ($list,$addr,$data,$peerID);
+ $state = "";
+ if ($cmd eq "regBulk"){
+ ($list) = ($a[2]);
+ $list =~ s/[\.]?RegL_//;
+ ($list,$peerID) = split(":",$list);
+ return "unknown list Number:".$list if(hex($list)>6);
+ }
+ elsif ($cmd eq "getRegRaw"){
+ ($list,$peerID) = ($a[2],$a[3]);
+ return "Enter valid List0-6" if ($list !~ m/^List([0-6])$/);
+ $list ='0'.$1;
+ }
+ # as of now only hex value allowed check range and convert
+
+ $peerID = CUL_HM_peerChId(($peerID?$peerID:"00000000"),$dst,$id);
+ my $peerChn = ((length($peerID) == 8)?substr($peerID,6,2):"01");# have to split chan and id
+ $peerID = substr($peerID,0,6);
+
+ if($cmd eq "getRegRaw"){
+ if ($list eq "00"){
+ CUL_HM_PushCmdStack($hash,'++'.$flag.'01'.$id.$dst.'00040000000000');
+ }
+ else{# other lists are per channel
+ my @chnIdList = CUL_HM_getAssChnIds($name);
+ foreach my $channel (@chnIdList){
+ my $chnNo = substr($channel,6,2);
+ if ($list =~m /0[34]/){#getPeers to see if list3 is available
+ CUL_HM_PushCmdStack($hash,'++'.$flag.'01'.$id.$dst.$chnNo.'03');
+ my $chnHash = CUL_HM_id2Hash($channel);
+ $chnHash->{helper}{getCfgList} = $peerID.$peerChn;#list3 regs
+ $chnHash->{helper}{getCfgListNo} = int($list);
+ }
+ else{
+ CUL_HM_PushCmdStack($hash,'++'.$flag.'01'.$id.$dst.$chnNo.'04'
+ .$peerID.$peerChn.$list);
+ }
+ }
+ }
+ }
+ elsif($cmd eq "regBulk"){;
+ my @adIn = @a;
+ shift @adIn;shift @adIn;shift @adIn;
+ my $adList;
+ foreach my $ad (sort @adIn){
+ ($addr,$data) = split(":",$ad);
+ $adList .= sprintf("%02X%02X",hex($addr),hex($data)) if ($addr ne "00");
+ return "wrong addr or data:".$ad if (hex($addr)>255 || hex($data)>255);
+ }
+ CUL_HM_pushConfig($hash,$id,$dst,$chn,$peerID,$peerChn,$list,$adList);
+ }
+ }
+ elsif($cmd eq "regSet") { ############################################### reg
+ #set regSet
+ my ($regName,$data,$peerChnIn) = ($a[2],$a[3],$a[4]);
+ $state = "";
+ if (!$culHmRegType{$st}{$regName} &&
+ !$culHmRegGeneral{$regName} &&
+ !$culHmRegModel{$md}{$regName} &&
+ !$culHmRegChan{$md.$chn}{$regName} ){
+ my @regArr = keys %culHmRegGeneral ;
+ push @regArr, keys %{$culHmRegType{$st}} if($culHmRegType{$st});
+ push @regArr, keys %{$culHmRegModel{$md}} if($culHmRegModel{$md});
+ push @regArr, keys %{$culHmRegChan{$md.$chn}} if($culHmRegChan{$md.$chn});
+ return "$regName failed: supported register are ".join(" ",sort @regArr);
+ }
+
+ my $reg = $culHmRegDefine{$regName};
+ return $st." - ".$regName # give some help
+ ." range:". $reg->{min}." to ".$reg->{max}.$reg->{u}
+ .(($reg->{l} == 3)?" peer required":"")." : ".$reg->{t}."\n"
+ if ($data eq "?");
+ return "value:".$data." out of range for Reg \"".$regName."\""
+ if (!($reg->{c} eq 'lit'||$reg->{c} eq 'hex')&&
+ ($data < $reg->{min} ||$data > $reg->{max})); # none number
+ return"invalid value. use:". join(",",keys%{$reg->{lit}})
+ if ($reg->{c} eq 'lit' && !defined($reg->{lit}{$data}));
+
+ my $conversion = $reg->{c};
+ if (!$conversion){;# do nothing
+ }elsif($conversion eq "factor"){$data *= $reg->{f};# use factor
+ }elsif($conversion eq "fltCvT"){$data = CUL_HM_fltCvT($data);
+ }elsif($conversion eq "m10s3") {$data = $data*10-3;
+ }elsif($conversion eq "hex") {$data = hex($data);
+ }elsif($conversion eq "lit") {$data = $reg->{lit}{$data};
+ }else{return " conversion undefined - please contact admin";
+ }
+
+ my $addr = int($reg->{a}); # bit location later
+ my $list = $reg->{l};
+ my $bit = ($reg->{a}*10)%10; # get fraction
+
+ my $dLen = $reg->{s}; # datalength in bit
+ $dLen = int($dLen)*8+(($dLen*10)%10);
+ # only allow it level if length less then one byte!!
+ return "partial Word error: ".$dLen if($dLen != 8*int($dLen/8) && $dLen>7);
+ no warnings qw(overflow portable);
+ my $mask = (0xffffffff>>(32-$dLen));
+ use warnings qw(overflow portable);
+ my $dataStr = substr(sprintf("%08X",($data & $mask) << $bit),
+ 8-int($reg->{s}+0.99)*2,);
+
+ my ($lChn,$peerId,$peerChn) = ($chn,"000000","00");
+ if (($list == 3) ||($list == 4)){ # peer is necessary for list 3/4
+ return "Peer not specified" if (!$peerChnIn);
+ $peerId = CUL_HM_peerChId($peerChnIn,$dst,$id);
+ $peerChn = ((length($peerId) == 8)?substr($peerId,6,2):"01");
+ $peerId = substr($peerId,0,6);
+ return "Peer not specified" if (!$peerId);
+ }
+ elsif($list == 0){
+ $lChn = "00";
+ }
+ else{ #if($list == 1/5/6){
+ $lChn = "01" if ($chn eq "00"); #by default select chan 01 for device
+ }
+
+ my $addrData;
+ if ($dLen < 8){# fractional byte see whether we have stored the register
+ #read full 8 bit!!!
+ my $rName = CUL_HM_id2Name($dst.$lChn);
+ $rName =~ s/_chn:.*//;
+ my $curVal = CUL_HM_getRegFromStore($rName,
+ $addr,$list,$peerId.$peerChn);
+ return "cannot read current value for Bitfield - retrieve Data first"
+ if (!$curVal);
+ $curVal =~ s/set_//; # set is not relevant, we take it as given
+ $data = ($curVal & (~($mask<<$bit)))|($data<<$bit);
+ $addrData.=sprintf("%02X%02X",$addr,$data);
+ }
+ else{
+ for (my $cnt = 0;$cnt{s}+0.99);$cnt++){
+ $addrData.=sprintf("%02X",$addr+$cnt).substr($dataStr,$cnt*2,2);
+ }
+ }
+ my $cHash = CUL_HM_id2Hash($dst.($chn eq '00'?"":$chn));
+ $cHash = $hash if (!$cHash);
+ CUL_HM_pushConfig($cHash,$id,$dst,$lChn,$peerId,$peerChn,$list,$addrData);
+ }
+ elsif($cmd eq "level") { ####################################################
+ #level =>" ..."
+ my (undef,undef,$lvl,$rLocDly,$speed) = @a;
+ return "please enter level 0 to 100" if (!defined($lvl) || $lvl>100);
+ return "reloclDelay range 0..65535 or ignore"
+ if (defined($rLocDly) &&
+ ($rLocDly > 65535 ||
+ ($rLocDly < 0.1 && $rLocDly ne 'ignore' && $rLocDly ne '0' )));
+ return "select speed range 0 to 100" if (defined($speed) && $speed>100);
+ $rLocDly = 111600 if (!defined($rLocDly)||$rLocDly eq "ignore");# defaults
+ $speed = 30 if (!defined($rLocDly));
+ $rLocDly = CUL_HM_encodeTime8($rLocDly);# calculate hex value
+ CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'81'.$chn.
+ sprintf("%02X%02s%02X",$lvl*2,$rLocDly,$speed*2));
+ }
+ elsif($cmd eq "on") { #######################################################
+ CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'02'.$chn.'C80000');
+ $hash = $chnHash; # report to channel if defined
+ }
+ elsif($cmd eq "off") { ######################################################
+ CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'02'.$chn.'000000');
+ $hash = $chnHash; # report to channel if defined
+ }
+ elsif($cmd =~ m/^(on-for-timer|on-till)$/) { ################################
+ my (undef,undef,$duration,$edate) = @a; #date prepared extention to entdate
+ if ($cmd eq "on-till"){
+ # to be extended to handle end date as well
+ my ($eH,$eM,$eSec) = split(':',$duration);
+ $eSec += $eH*3600 + $eM*60;
+ my @lt = localtime;
+ my $ltSec = $lt[2]*3600+$lt[1]*60+$lt[0];# actually strip of date
+ $eSec += 3600*24 if ($ltSec > $eSec); # go for the next day
+ $duration = $eSec - $ltSec;
+ }
+ return "please enter the duration in seconds" if (!defined ($duration));
+ my $tval = CUL_HM_encodeTime16($duration);# onTime 0.0..85825945.6, 0=forever
+ CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'02'.$chn.'C80000'.$tval);
+ $hash = $chnHash; # report to channel if defined
+ }
+ elsif($cmd eq "toggle") { ###################################################
+ CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'02'.$chn.
+ (CUL_HM_getChnLvl($name) != 0 ?"000000":"C80000"));
+ $hash = $chnHash; # report to channel if defined
+ }
+ elsif($cmd eq "lock") { #####################################################
+ CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'800100FF'); # LEVEL_SET
+ }
+ elsif($cmd eq "unlock") { ###################################################
+ my $tval = (@a > 2) ? int($a[2]) : 0;
+ my $delay = ($tval > 0) ? CUL_HM_encodeTime8($tval) : "FF"; # RELOCK_DELAY (FF=never)
+ CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'800101'.$delay);# LEVEL_SET
+ }
+ elsif($cmd eq "open") { #####################################################
+ my $tval = (@a > 2) ? int($a[2]) : 0;
+ my $delay = ($tval > 0) ? CUL_HM_encodeTime8($tval) : "FF"; # RELOCK_DELAY (FF=never)
+ CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'8001C8'.$delay);# OPEN
+ }
+ elsif($cmd eq "inhibit") { ##################################################
+ return "$a[2] is not on or off" if($a[2] !~ m/^(on|off)$/);
+ my $val = ($a[2] eq "on") ? "01" : "00";
+ CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.$val.'01'); # SET_LOCK
+ }
+ elsif($cmd =~ m/^(up|down|pct)$/) { #########################################
+ my ($lvl,$tval,$rval) = ($a[2],"","");
+ if ($cmd ne "pct"){#dim [|up|down] ... [ontime] [ramptime]
+ $lvl = 10 if (!defined $a[2]); #set default step
+ $lvl = -1*$lvl if ($cmd eq "down");
+ $lvl += CUL_HM_getChnLvl($name);
+ }
+ $lvl = ($lvl > 100)?100:(($lvl < 0)?0:$lvl);
+ if ($st eq "dimmer"){# at least blind cannot stand ramp time...
+ $tval = $a[3]?CUL_HM_encodeTime16($a[3]):"FFFF";# onTime 0.05..85825945.6, 0=forever
+ $rval = CUL_HM_encodeTime16((@a > 4)?$a[4]:2.5);# rampTime 0.0..85825945.6, 0=immediate
+ }
+ CUL_HM_PushCmdStack($hash,sprintf("++%s11%s%s02%s%02X%s%s",
+ $flag,$id,$dst,$chn,$lvl*2,$rval,$tval));
+ if (defined $hash->{READINGS}{"virtLevel"}{VAL}){
+ readingsSingleUpdate($hash,"virtLevel","set_".$lvl,1);
+ }else{$state = "set_".$lvl;}
+ }
+ elsif($cmd eq "stop") { #####################################################
+ CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'03'.$chn);
+ }
+ elsif($cmd eq "text") { ################################################# reg
+ $state = "";
+ return "$a[2] is not a button number" if($a[2] !~ m/^\d$/ || $a[2] < 1);
+ return "$a[3] is not on or off" if($a[3] !~ m/^(on|off)$/);
+ my $bn = $a[2]*2-($a[3] eq "on" ? 0 : 1);
+
+ my ($l1, $l2, $s); # Create CONFIG_WRITE_INDEX string
+ $l1 = $a[4] . "\x00";
+ $l1 = substr($l1, 0, 13);
+ $s = 54;
+ $l1 =~ s/(.)/sprintf("%02X%02X",$s++,ord($1))/ge;
+
+ $l2 = $a[5] . "\x00";
+ $l2 = substr($l2, 0, 13);
+ $s = 70;
+ $l2 =~ s/(.)/sprintf("%02X%02X",$s++,ord($1))/ge;
+ $l1 .= $l2;
+
+ CUL_HM_pushConfig($hash, $id, $dst, $bn,0,0,1, $l1);
+ }
+ elsif($cmd eq "setRepeat") { ################################################
+ # setRepeat => "[no1..36] [bdcast-yes|no]"}
+ $state = "";
+ my (undef,undef,$eNo,$sId,$rId,$bCst) = @a;
+ my ($pattern,$cnt);
+ my $repPeers = AttrVal($name,"repPeers",undef);
+ my @rPeer;
+ @rPeer = split ",",$repPeers;
+ if ($eNo eq "setAll"){
+ return " too many entries in repPeer" if (int(@rPeer) > 36);
+ return "setAll: attr repPeers undefined" if (!defined $repPeers);
+ my $entry = 0;
+ foreach my $repData (@rPeer){
+ $entry++;
+ my ($s,$d,$b) =split":",$repData;
+ $s = CUL_HM_name2Id($s);
+ $d = CUL_HM_name2Id($d);
+ return "attr repPeers entry $entry irregular:$repData"
+ if (!$s || !$d || !$b
+ || $s !~ m/(^[0-9A-F]{6})$/
+ || $d !~ m/(^[0-9A-F]{6})$/
+ || $b !~ m/^[yn]$/
+ );
+ $pattern .= $s.$d.(($b eq "n")?"00":"01");
+ }
+ while ($entry < 36){
+ $entry++;
+ $pattern .= "000000"."000000"."00";
+ }
+ $cnt = 1;# set first address byte
+ }
+ else{
+ return "entry must be between 1 and 36" if ($eNo < 1 || $eNo > 36);
+ my $sndID = CUL_HM_name2Id($sId);
+ my $recID = CUL_HM_name2Id($rId);
+ return "sender ID $sId unknown:".$sndID if ($sndID !~ m/(^[0-9A-F]{6})$/);
+ return "receiver ID $rId unknown:".$recID if ($recID !~ m/(^[0-9A-F]{6})$/);
+ return "broadcast must be yes or now" if ($bCst !~ m/^(yes|no)$/);
+ $pattern = $sndID.$recID.(($bCst eq "no")?"00":"01");
+ $cnt = ($eNo-1)*7+1;
+ $rPeer[$eNo-1] = "$sId:$rId:".(($bCst eq "no")?"n":"y");
+ $attr{$name}{repPeers} = join",",@rPeer;
+ }
+ my $addrData;
+ foreach ($pattern =~ /(.{2})/g){
+ $addrData .= sprintf("%02X%s",$cnt++,$_);
+ }
+ CUL_HM_pushConfig($hash, $id, $dst, 1,0,0,2, $addrData);
+ }
+ elsif($cmd eq "display") { ##################################################
+ my (undef,undef,undef,$t,$c,$u,$snd,$blk,$symb) = @_;
+ return "cmd only possible for device or its display channel"
+ if ($isChannel && $chn ne "12");
+ my %symbol=(off => 0x0000,
+ bulb =>0x0100,switch =>0x0200,window =>0x0400,door=>0x0800,
+ blind=>0x1000,scene =>0x2000,phone =>0x4000,bell=>0x8000,
+ clock=>0x0001,arrowUp=>0x0002,arrowDown=>0x0004);
+ my %light=(off=>0,on=>1,slow=>2,fast=>3);
+ my %unit=(off =>0,Proz=>1,Watt=>2,x3=>3,C=>4,x5=>5,x6=>6,x7=>7,
+ F=>8,x9=>9,x10=>10,x11=>11,x12=>12,x13=>13,x14=>14,x15=>15);
+
+ my @symbList = split(',',$symb);
+ my $symbAdd = "";
+ foreach my $symb (@symbList){
+ if (!defined($symbol{$symb})){# wrong parameter
+ return "'$symb ' unknown. Select one of ".join(" ",sort keys(%symbol));
+ }
+ $symbAdd |= $symbol{$symb};
+ }
+
+ return "$c not specified. Select one of [comma|no]"
+ if ($c ne "comma" && $c ne "no");
+ return "'$u' unknown. Select one of ".join(" ",sort keys(%unit))
+ if (!defined($unit{$u}));
+ return "'$snd' unknown. Select one of [off|1|2|3]"
+ if ($snd ne "off" && $snd > 3);
+ return "'$blk' unknown. Select one of ".join(" ",sort keys(%light))
+ if (!defined($light{$blk}));
+ my $beepBack = $snd | $light{$blk}*4;
+
+ $symbAdd |= 0x0004 if ($c eq "comma");
+ $symbAdd |= $unit{$u};
+
+ my $text = sprintf("%5.5s",$t);#pad left with space
+ $text = uc(unpack("H*",$text));
+
+ CUL_HM_PushCmdStack($hash,sprintf("++%s11%s%s8012%s%04X%02X",
+ $flag,$id,$dst,$text,$symbAdd,$beepBack));
+ }
+ elsif($cmd =~ m/^(alarm|service)$/) { #######################################
+ return "$a[2] must be below 255" if ($a[2] >255 );
+ $chn = 18 if ($chn eq "01");
+ my $subtype = ($cmd eq "alarm")?"81":"82";
+ CUL_HM_PushCmdStack($hash,
+ sprintf("++%s11%s%s%s%s%02X",$flag,$id,$dst,$subtype,$chn, $a[2]));
+ }
+ elsif($cmd eq "led") { ######################################################
+ if ($md eq "HM-OU-LED16"){
+ my %color=(off=>0,red=>1,green=>2,orange=>3);
+ if (length($hash->{DEF}) == 6){# command called for a device, not a channel
+ my $col4all;
+ if (defined($color{$a[2]})){
+ $col4all = sprintf("%02X",$color{$a[2]}*85);#Color for 4 LEDS
+ $col4all = $col4all.$col4all.$col4all.$col4all;#and now for 16
+ }
+ elsif ($a[2] =~ m/^[A-Fa-f0-9]{1,8}$/i){
+ $col4all = sprintf("%08X",hex($a[2]));
+ }
+ else{
+ return "$a[2] unknown. use hex or: ".join(" ",sort keys(%color));
+ }
+ CUL_HM_UpdtReadBulk($hash,1,"color:".$col4all,
+ "state:set_".$col4all);
+ CUL_HM_PushCmdStack($hash,"++".$flag."11".$id.$dst."8100".$col4all);
+ }else{# operating on a channel
+ return "$a[2] unknown. use: ".join(" ",sort keys(%color))
+ if (!defined($color{$a[2]}) );
+ CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'80'.$chn.'0'.$color{$a[2]});
+ }
+ }
+ elsif($md eq "HM-OU-CFM-PL"){
+ return "use channel 1 of the device for LED" if ($chn != 1);
+ my %color = (redL =>18,greenL =>34,orangeL =>50,
+ redS =>17,greenS =>33,orangeS =>49);
+ my @ledList = split(',',$a[2]);
+ my $ledBytes;
+ foreach my $led (@ledList){
+ if (!$color{$led} ){# wrong parameter
+ return "'$led' unknown. use: ".join(" ",sort keys(%color));
+ }
+ $ledBytes .= sprintf("%02X",$color{$led});
+ }
+ CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'80'.$chn.'0101'.$ledBytes);
+ }
+ else{
+ return "device for command cannot be identified";
+ }
+ }
+ elsif($cmd eq "playTone") { #################################################
+ $chn = "02" if (length($hash->{DEF}) == 6);# be nice, select implicite
+ return "use channel 2 of the device to play MP3" if ($chn != 2);
+ my @mp3List = split(',',$a[2]);
+ my $mp3Bytes;
+ foreach my $mp3 (@mp3List){
+ $mp3Bytes .= sprintf("%02X",$mp3);
+ }
+ CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'80'.$chn.'0202'.$mp3Bytes);
+ }
+ elsif($cmd eq "ilum") { ################################################# reg
+ return "$a[2] not specified. choose 0-15 for brightness" if ($a[2]>15);
+ return "$a[3] not specified. choose 0-127 for duration" if ($a[3]>127);
+ return "unsupported for HMid:".$hash->{DEF}.", use HMId:".substr($hash->{DEF},0,6)
+ if (length($hash->{DEF}) != 6);
+ my $addrData = sprintf("04%02X08%02X",$a[2],$a[3]*2);
+ # write list0,
+ CUL_HM_pushConfig($hash,$id,$dst,0,0,0,0,$addrData);
+ }
+ elsif($cmd eq "desired-temp") { #############################################
+ CUL_HM_PushCmdStack($hash,'++'.$flag.'11'.$id.$dst.'0202'.
+ CUL_HM_convTemp($a[2]));
+ my $chnHash = CUL_HM_id2Hash($dst."02");
+ my $mode = ReadingsVal($chnHash->{NAME},"R-MdTempReg","");
+ $mode =~ s/set_//;#consider set as given
+ readingsSingleUpdate($chnHash,"desired-temp-cent",$a[2],1)
+ if($mode eq 'central ');
+ }
+ elsif($cmd =~ m/^tempList(...)/) { ###################################### reg
+ my %day2off = ( "Sat"=>"5 0B", "Sun"=>"5 3B", "Mon"=>"5 6B",
+ "Tue"=>"5 9B", "Wed"=>"5 CB", "Thu"=>"6 01",
+ "Fri"=>"6 31");
+ my $wd = $1;
+ my ($list,$addr) = split(" ", $day2off{$wd});
+ $addr = hex($addr);
+
+ return "To few arguments" if(@a < 4);
+ return "To many arguments, max is 24 pairs" if(@a > 50);
+ return "Bad format, use HH:MM TEMP ..." if(@a % 2);
+ return "Last time spec must be 24:00" if($a[@a-2] ne "24:00");
+ my $data = "";
+ my $msg = "";
+ for(my $idx = 2; $idx < @a; $idx += 2) {
+ return "$a[$idx] is not in HH:MM format"
+ if($a[$idx] !~ m/^([0-2]\d):([0-5]\d)/);
+ my ($h, $m) = ($1, $2);
+ $data .= sprintf("%02X%02X%02X%s", $addr, $h*6+($m/10), $addr+1,
+ CUL_HM_convTemp($a[$idx+1]));
+ $addr += 2;
+ $hash->{TEMPLIST}{$wd}{($idx-2)/2}{HOUR} = $h;
+ $hash->{TEMPLIST}{$wd}{($idx-2)/2}{MINUTE} = $m;
+ $hash->{TEMPLIST}{$wd}{($idx-2)/2}{TEMP} = $a[$idx+1];
+ $msg .= sprintf(" %02d:%02d %.1f", $h, $m, $a[$idx+1]);
+ }
+ CUL_HM_pushConfig($hash, $id, $dst, 2,0,0,$list, $data);
+ readingsSingleUpdate($hash,"tempList$wd",$msg,0);
+ }
+ elsif($cmd eq "valvePos") { #################################################
+ return "only number <= 100 or 'off' allowed"
+ if (!($a[2] eq "off" ||$a[2]+0 ne $a[2] ||$a[2] <100 ));
+ if ($a[2] eq "off"){
+ $state = "ValveAdjust:stopped";
+ RemoveInternalTimer("valvePos:$dst$chn");# remove responsePending timer
+ delete($hash->{helper}{virtTC});
+ }
+ else {
+ my $vp = $a[2];
+ readingsSingleUpdate($hash,"valvePosTC","$vp %",0);
+ CUL_HM_valvePosUpdt("valvePos:$dst$chn") if (!$hash->{helper}{virtTC});
+ $hash->{helper}{virtTC} = "03";
+ $state = "ValveAdjust:$vp %";
+ }
+ }
+ elsif($cmd eq "matic") { ####################################################
+ # Trigger pre-programmed action in the winmatic. These actions must be
+ # programmed via the original software.
+ CUL_HM_PushCmdStack($hash,
+ sprintf("++%s3E%s%s%s40%02X%s", $flag,$id, $dst, $id, $a[2], $chn));
+ }
+ elsif($cmd eq "create") { ###################################################
+ CUL_HM_PushCmdStack($hash,
+ sprintf("++%s01%s%s0101%s%02X%s",$flag,$id, $dst, $id, $a[2], $chn));
+ CUL_HM_PushCmdStack($hash,
+ sprintf("++A001%s%s0104%s%02X%s", $id, $dst, $id, $a[2], $chn));
+ }
+ elsif($cmd eq "keydef") { ############################################### reg
+ if ( $a[3] eq "tilt") {CUL_HM_pushConfig($hash,$id,$dst,1,$id,$a[2],3,"0B220D838B228D83");#JT_ON/OFF/RAMPON/RAMPOFF short and long
+ } elsif ($a[3] eq "close") {CUL_HM_pushConfig($hash,$id,$dst,1,$id,$a[2],3,"0B550D838B558D83");#JT_ON/OFF/RAMPON/RAMPOFF short and long
+ } elsif ($a[3] eq "closed") {CUL_HM_pushConfig($hash,$id,$dst,1,$id,$a[2],3,"0F008F00"); #offLevel (also thru register)
+ } elsif ($a[3] eq "bolt") {CUL_HM_pushConfig($hash,$id,$dst,1,$id,$a[2],3,"0FFF8FFF"); #offLevel (also thru register)
+ } elsif ($a[3] eq "speedclose"){CUL_HM_pushConfig($hash,$id,$dst,1,$id,$a[2],3,sprintf("23%02XA3%02X",$a[4]*2,$a[4]*2));#RAMPOFFspeed (also in reg)
+ } elsif ($a[3] eq "speedtilt") {CUL_HM_pushConfig($hash,$id,$dst,1,$id,$a[2],3,sprintf("22%02XA2%02X",$a[4]*2,$a[4]*2));#RAMPOFFspeed (also in reg)
+ } elsif ($a[3] eq "delete") {CUL_HM_PushCmdStack($hash,sprintf("++%s01%s%s0102%s%02X%s",$flag,$id, $dst, $id, $a[2], $chn));#unlearn key
+ } else {
+ return 'unknown argument '.$a[3];
+ }
+ }
+ elsif($cmd eq "test") { #####################################################
+ my $testnr = $hash->{TESTNR} ? ($hash->{TESTNR} +1) : 1;
+ $hash->{TESTNR} = $testnr;
+ my $msg = sprintf("++9440%s%s00%02X",$dst,$dst,$testnr);
+ CUL_HM_SndCmd($hash, $msg);# repeat non-ack messages - delivery uncertain
+ CUL_HM_SndCmd($hash, $msg);
+ CUL_HM_SndCmd($hash, $msg);
+ }
+ elsif($cmd =~ m/alarm(.*)/) { ###############################################
+ my $msg = sprintf("++9441%s%s01%s",$dst,$dst,(($1 eq "On")?"0BC8":"0C01"));
+ CUL_HM_SndCmd($hash, $msg);# repeat non-ack messages - delivery uncertain
+ CUL_HM_SndCmd($hash, $msg);
+ CUL_HM_SndCmd($hash, $msg);
+ }
+ elsif($cmd eq "virtual") { ##################################################
+ $state = "";
+ my (undef,undef,$maxBtnNo) = @a;
+ return "please give a number between 1 and 255"
+ if ($maxBtnNo < 1 ||$maxBtnNo > 255);# arbitrary - 255 should be max
+ return $name." already defines as ".$attr{$name}{subType}
+ if ($attr{$name}{subType} && $attr{$name}{subType} ne "virtual");
+ $attr{$name}{subType} = "virtual";
+ $attr{$name}{model} = "virtual_".$maxBtnNo;
+ my $devId = $hash->{DEF};
+ for (my $btn=1;$btn <= $maxBtnNo;$btn++){
+ my $chnName = $name."_Btn".$btn;
+ my $chnId = $devId.sprintf("%02X",$btn);
+ DoTrigger("global", "UNDEFINED $chnName CUL_HM $chnId")
+ if (!$modules{CUL_HM}{defptr}{$chnId});
+ }
+ foreach my $channel (keys %{$hash}){# remove higher numbers
+ my $chNo = $1 if($channel =~ m/^channel_(.*)/);
+ next if (!defined($chNo));
+ CommandDelete(undef,$hash->{$channel})
+ if (hex($chNo) > $maxBtnNo);
+ }
+ }
+ elsif($cmd eq "press") { ####################################################
+ my (undef,undef,$mode,$vChn) = @a;
+ my $pressCnt = (!$hash->{helper}{count}?1:$hash->{helper}{count}+1)%256;
+ $hash->{helper}{count}=$pressCnt;# remember for next round
+
+ my @peerList;
+ if ($st eq 'virtual'){#serve all peers of virtual button
+ foreach my $peer (sort(split(',',AttrVal($name,"peerIDs","")))) {
+ push (@peerList,substr($peer,0,6));
+ }
+ @peerList = CUL_HM_noDup(@peerList);
+ push @peerList,'00000000' if (!@peerList);#send to broadcast if no peer
+ foreach my $peer (sort @peerList){
+ my $peerFlag = $peer eq '00000000'?'A4':
+ CUL_HM_getFlag(CUL_HM_id2Hash($peer));
+ $peerFlag =~ s/0/4/;# either 'A4' or 'B4'
+ CUL_HM_PushCmdStack($hash, sprintf("++%s40%s%s%02X%02X",
+ $peerFlag,$dst,$peer,
+ $chn+(($mode && $mode eq "long")?64:0),
+ $pressCnt));
+ }
+ }
+ else{#serve internal channels for actor
+ my $pChn = $chn; # simple device, only one button per channel
+ $pChn = (($vChn && $vChn eq "off")?-1:0) + $chn*2 if($st ne 'switch');
+ CUL_HM_PushCmdStack($hash, sprintf("++%s3E%s%s%s40%02X%02X",$flag,
+ $id,$dst,$dst,
+ $pChn+(($mode && $mode eq "long")?64:0),
+ $pressCnt));
+ }
+ }
+ elsif($cmd =~ m/^(devicepair|peerChan)$/) { #################################
+ #peerChan device ... [single|dual] [set|unset] [actor|remote|both]
+ my ($bNo,$peerN,$single,$set,$target) = ($a[2],$a[3],$a[4],$a[5],$a[6]);
+ $state = "";
+ return "$bNo is not a button number" if(($bNo < 1) && !$chn);
+ my $peerDst = CUL_HM_name2Id($peerN);
+ $peerDst .= "01" if( length($peerDst)==6);
+ return "please enter peer" if(!$peerDst);
+ my $peerChn = substr($peerDst,6,2);
+ $peerDst = substr($peerDst,0,6);
+ my $peerHash;
+ $peerHash = $modules{CUL_HM}{defptr}{$peerDst.$peerChn}if ($modules{CUL_HM}{defptr}{$peerDst.$peerChn});
+ $peerHash = $modules{CUL_HM}{defptr}{$peerDst} if (!$peerHash);
+ return "$peerN not a CUL_HM device" if($target && ($target ne "remote") &&(!$peerHash ||$peerHash->{TYPE} ne "CUL_HM"));
+ return "$single must be single or dual" if(defined($single) && ($single !~ m/^(single|dual)$/));
+ return "$set must be set or unset" if(defined($set) && ($set !~ m/^(set|unset)$/));
+ return "$target must be [actor|remote|both]" if(defined($target) && ($target !~ m/^(actor|remote|both)$/));
+ return "use climate chan to pair TC" if( $md eq "HM-CC-TC" && $chn ne "02");
+ return "use - single [set|unset] actor - for smoke detector" if( $st eq "smokeDetector" && (!$single || $single ne "single" || $target ne "actor"));
+ return "use - single - for ".$st if(($st eq "threeStateSensor"||
+ $st eq "motionDetector" ) && (!$single || $single ne "single"));
+
+ $single = ($single eq "single")?1:"";#default to dual
+ $set = ($set eq "unset")?0:1;
+
+ my ($b1,$b2,$nrCh2Pair);
+ $b1 = ($isChannel) ? hex($chn):(!$bNo?"01":$bNo);
+ $b1 = $b1*2 - 1 if(!$single && !$isChannel);
+ if ($single){
+ $b2 = $b1;
+ $b1 = 0 if ($st eq "smokeDetector");
+ $nrCh2Pair = 1;
+ }
+ else{
+ $b2 = $b1 + 1;
+ $nrCh2Pair = 2;
+ }
+ my $cmdB = ($set)?"01":"02";# do we set or remove?
+
+ # First the remote (one loop for on, one for off)
+ if (!$target || $target =~ m/^(remote|both)$/){
+ for(my $i = 1; $i <= $nrCh2Pair; $i++) {
+ my $b = ($i==1 ? $b1 : $b2);
+ if ($st eq "virtual"){
+ my $btnName = CUL_HM_id2Name($dst.sprintf("%02X",$b));
+ return "button ".$b." not defined for virtual remote ".$name
+ if (!defined $attr{$btnName});
+ CUL_HM_ID2PeerList ($btnName,$peerDst.$peerChn,$set); #update peerlist
+ }
+ else{
+ my $bStr = sprintf("%02X",$b);
+ CUL_HM_PushCmdStack($hash,
+ "++".$flag."01${id}${dst}${bStr}$cmdB${peerDst}${peerChn}00");
+ CUL_HM_pushConfig($hash,$id, $dst,$b,$peerDst,hex($peerChn),4,"0100")
+ if($md ne "HM-CC-TC");
+ }
+ }
+ }
+ if (!$target || $target =~ m/^(actor|both)$/ ){
+ if (AttrVal( CUL_HM_id2Name($peerDst), "subType", "") eq "virtual"){
+ CUL_HM_ID2PeerList ($peerN,$dst.sprintf("%02X",$b2),$set); #update peerlist
+ CUL_HM_ID2PeerList ($peerN,$dst.sprintf("%02X",$b1),$set) if ($b1 & !$single); #update peerlist
+ }
+ else{
+ my $peerFlag = CUL_HM_getFlag($peerHash);
+ CUL_HM_PushCmdStack($peerHash, sprintf("++%s01%s%s%s%s%s%02X%02X",
+ $peerFlag,$id,$peerDst,$peerChn,$cmdB,$dst,$b2,$b1 ));
+ }
+ }
+ return ("",1) if ($target && $target eq "remote");#Nothing to transmit for actor
+ $devHash = $peerHash; # Exchange the hash, as the switch is always alive.
+ }
+
+ readingsSingleUpdate($hash,"state",$state,1) if($state);
+
+ $rxType = CUL_HM_getRxType($devHash);
+ Log GetLogLevel($name,2), "CUL_HM set $name " .
+ join(" ", @a[1..$#a])." rxt:".$rxType;
+ CUL_HM_ProcessCmdStack($devHash) if($rxType & 0x03);#all/burst
+ return ("",1);# no not generate trigger outof command
+}
+
+#+++++++++++++++++ set/get support subroutines+++++++++++++++++++++++++++++++++
+my $updtValveCnt = 0;
+sub CUL_HM_valvePosUpdt(@) {#update valve position periodically to please valve
+ my($in ) = @_;
+ my(undef,$vId) = split(':',$in);
+ my $hash = CUL_HM_id2Hash($vId);
+ my $vDevId = substr($vId,0,6);
+ my $nextTimer = 150;
+
+# if ($updtValveCnt++ %2){
+# $nextTimer = 20;
+# CUL_HM_PushCmdStack($hash,"++8670".$vDevId."00000000D036");# some weather event -
+# }
+# else{
+ my $name = $hash->{NAME};
+ my $vp = ReadingsVal($name,"valvePosTC","15 %");
+ $vp =~ s/ %//;
+ $vp *=2.56;
+ foreach my $peer (sort(split(',',AttrVal($name,"peerIDs","")))) {
+ next if (length($peer) != 8);
+ $peer = substr($peer,0,6);
+ CUL_HM_PushCmdStack($hash,sprintf("++A258%s%s%s%02X",$vDevId,$peer,$hash->{helper}{virtTC},$vp));
+ }
+# }
+ $hash->{helper}{virtTC} = "00";
+ CUL_HM_ProcessCmdStack($hash);
+ InternalTimer(gettimeofday()+$nextTimer,"CUL_HM_valvePosUpdt","valvePos:$vId",0);
+}
+sub CUL_HM_infoUpdtDevData($$$) {#autoread config
+ my($name,$hash,$p) = @_;
+ my($fw,$mId,$serNo,$stc,$devInfo) = ($1,$2,$3,$4,$5)
+ if($p =~ m/(..)(.{4})(.{20})(.{2})(.*)/);
+ my $model = $culHmModel{$mId}{name} ? $culHmModel{$mId}{name}:"unknown";
+ $attr{$name}{model} = $model;
+ $attr{$name}{subType} = $culHmModel{$mId}{st};
+ $attr{$name}{serialNr} = pack('H*',$serNo);
+ #expert level attributes
+ $attr{$name}{firmware} =
+ sprintf("%d.%d", hex(substr($p,0,1)),hex(substr($p,1,1)));
+ $attr{$name}{".devInfo"} = $devInfo;
+ $attr{$name}{".stc"} = $stc;
+
+ delete $hash->{helper}{rxType};
+ CUL_HM_getRxType($hash); #will update rxType
+ $mId = CUL_HM_getMId($hash);# set helper valiable and use result
+
+ # autocreate undefined channels
+ my @chanTypesList = split(',',$culHmModel{$mId}{chn});
+ my $startime = gettimeofday()+1;
+ foreach my $chantype (@chanTypesList){
+ my ($chnTpName,$chnStart,$chnEnd) = split(':',$chantype);
+ my $chnNoTyp = 1;
+ for (my $chnNoAbs = $chnStart; $chnNoAbs <= $chnEnd;$chnNoAbs++){
+ my $chnId = $hash->{DEF}.sprintf("%02X",$chnNoAbs);
+ if (!$modules{CUL_HM}{defptr}{$chnId}){
+ my $chnName = $name."_".$chnTpName.(($chnStart == $chnEnd)?
+ '':'_'.sprintf("%02d",$chnNoTyp));
+ InternalTimer($startime++,"CUL_HM_infoUpdtChanData",
+ "$chnName,$chnId,$model",0);
+ }
+ $attr{CUL_HM_id2Name($chnId)}{model} = $model;
+ $chnNoTyp++;
+ }
+ }
+ if ($culHmModel{$mId}{cyc}){
+ CUL_HM_ActAdd($hash->{DEF},AttrVal($name,"actCycle",
+ $culHmModel{$mId}{cyc}));
+ }
+
+}
+sub CUL_HM_infoUpdtChanData(@) {# verify attributes after reboot
+ my($in ) = @_;
+ my($chnName,$chnId,$model ) = split(',',$in);
+ DoTrigger("global", 'UNDEFINED '.$chnName.' CUL_HM '.$chnId);
+ $attr{CUL_HM_id2Name($chnId)}{model} = $model;
+}
+sub CUL_HM_Pair(@) {
+ my ($name, $hash,$cmd,$src,$dst,$p) = @_;
+ my $iohash = $hash->{IODev};
+ my $id = CUL_HM_Id($iohash);
+ my $serNo = $attr{$name}{serialNr};
+
+ Log GetLogLevel($name,3),
+ "CUL_HM pair: $name $attr{$name}{subType}, model $attr{$name}{model} serialNr $serNo";
+
+ # Abort if we are not authorized
+ if($dst eq "000000") {
+ if(!$iohash->{hmPair} &&
+ (!$iohash->{hmPairSerial} || $iohash->{hmPairSerial} ne $serNo)) {
+ Log GetLogLevel($name,3),
+ $iohash->{NAME}. " pairing (hmPairForSec) not enabled";
+ return "";
+ }
+ }
+ elsif($dst ne $id) {
+ return "" ;
+ }
+ elsif($cmd eq "0400") { # WDC7000
+ return "" ;
+ }
+ elsif($iohash->{hmPairSerial}) {
+ delete($iohash->{hmPairSerial});
+ }
+
+ my ($idstr, $s) = ($id, 0xA);
+ $idstr =~ s/(..)/sprintf("%02X%s",$s++,$1)/ge;
+ CUL_HM_pushConfig($hash, $id, $src,0,0,0,0, "0201$idstr");
+ CUL_HM_ProcessCmdStack($hash); # start processing immediately
+ return "";
+}
+sub CUL_HM_getConfig($$$$$){
+ my ($hash,$chnhash,$id,$dst,$chn) = @_;
+ my $flag = CUL_HM_getFlag($hash);
+
+ foreach my $readEntry (keys %{$chnhash->{READINGS}}){
+ delete $chnhash->{READINGS}{$readEntry} if ($readEntry =~ m/^[\.]?RegL_/);
+ }
+ #get Peer-list in any case - it is part of config
+ CUL_HM_PushCmdStack($hash,sprintf("++%s01%s%s%s03",$flag,$id,$dst,$chn));
+ my $lstAr = $culHmModel{CUL_HM_getMId($hash)}{lst};
+ if($lstAr){
+ my @list = split(",",$lstAr); #get valid lists e.g."1, 5:2:3.p ,6:2"
+ foreach my$listEntry (@list){# each list that is define for this channel
+ my ($peerReq,$chnValid)= (0,0);
+ my ($listNo,$chnLst1) = split(":",$listEntry);
+ if (!$chnLst1){
+ $chnValid = 1; #if no entry channel is valid
+ $peerReq = 1 if($listNo==3 ||$listNo==4); #default
+ }
+ else{
+ my @chnLst = split('\.',$chnLst1);
+ foreach my $lchn (@chnLst){
+ $peerReq = 1 if ($lchn =~ m/p/);
+ no warnings;#know that lchan may be followed by a 'p' causing a warning
+ $chnValid = 1 if (int($lchn) == hex($chn));
+ use warnings;
+ last if ($chnValid);
+ }
+ }
+ if ($chnValid){# yes, we will go for a list
+ if ($peerReq){# need to get the peers first
+ $chnhash->{helper}{getCfgList} = "all"; # peers first
+ $chnhash->{helper}{getCfgListNo} = $listNo;
+ }
+ else{
+ CUL_HM_PushCmdStack($hash,sprintf("++%s01%s%s%s0400000000%02X",$flag,$id,$dst,$chn,$listNo));
+ }
+ }
+ }
+ }
+ }
+
+#+++++++++++++++++ Protocol stack, sending, repeat++++++++++++++++++++++++++++
+sub CUL_HM_SndCmd($$) {
+ my ($hash, $cmd) = @_;
+ $hash = CUL_HM_getDeviceHash($hash);
+ my $io = $hash->{IODev};
+ return if(!$io);
+
+ $cmd =~ m/^(..)(.*)$/;
+ my ($mn, $cmd2) = ($1, $2);
+
+ if($mn eq "++") {
+ $mn = $io->{HM_CMDNR} ? (($io->{HM_CMDNR} +1)&0xff) : 1;
+ }
+ elsif($cmd =~ m/^[+-]/){; #continue pure
+ IOWrite($hash, "", $cmd);
+ return;
+ }
+ else {
+ $mn = hex($mn);
+ }
+ $io->{HM_CMDNR} = $mn;
+ $cmd = sprintf("As%02X%02X%s", length($cmd2)/2+1, $mn, $cmd2);
+ IOWrite($hash, "", $cmd);
+ CUL_HM_responseSetup($hash,$cmd);
+ $cmd =~ m/As(..)(..)(..)(..)(......)(......)(.*)/;
+ CUL_HM_DumpProtocol("SND", $io, ($1,$2,$3,$4,$5,$6,$7));
+}
+sub CUL_HM_responseSetup($$) {#store all we need to handle the response
+ #setup repeatTimer and cmdStackControll
+ my ($hash,$cmd) = @_;
+ my ($msgId, $msgFlag,$msgType,$dst,$p) = ($2,hex($3),$4,$6,$7)
+ if ($cmd =~ m/As(..)(..)(..)(..)(......)(......)(.*)/);
+ my ($chn,$subType) = ($1,$2) if($p =~ m/^(..)(..)/);
+ my $rTo = rand(20)/10+4; #default response timeout
+ if ($msgType eq "01" && $subType){
+ if ($subType eq "03"){ #PeerList-------------
+ #--- remember request params in device level
+ $hash->{helper}{respWait}{Pending} = "PeerList";
+ $hash->{helper}{respWait}{PendCmd} = $cmd;
+ $hash->{helper}{respWait}{forChn} = substr($p,0,2);#channel info we await
+
+ # define timeout - holdup cmdStack until response complete or timeout
+ InternalTimer(gettimeofday()+$rTo, "CUL_HM_respPendTout", "respPend:$dst", 0);
+
+ #--- remove readings in channel
+ my $chnhash = $modules{CUL_HM}{defptr}{"$dst$chn"};
+ $chnhash = $hash if (!$chnhash);
+ delete $chnhash->{READINGS}{peerList};#empty old list
+ delete $chnhash->{helper}{peerIDsRaw};
+ $attr{$chnhash->{NAME}}{peerIDs} = '';
+ return;
+ }
+ elsif($subType eq "04"){ #RegisterRead-------
+ my ($peer, $list) = ($1,$2) if ($p =~ m/..04(........)(..)/);
+ $peer = ($peer ne "00000000")?CUL_HM_peerChName($peer,$dst,""):"";
+ #--- set messaging items
+ $hash->{helper}{respWait}{Pending}= "RegisterRead";
+ $hash->{helper}{respWait}{PendCmd}= $cmd;
+ $hash->{helper}{respWait}{forChn} = $chn;
+ $hash->{helper}{respWait}{forList}= $list;
+ $hash->{helper}{respWait}{forPeer}= $peer;
+
+ # define timeout - holdup cmdStack until response complete or timeout
+ InternalTimer(gettimeofday()+$rTo,"CUL_HM_respPendTout","respPend:$dst", 0);
+ #--- remove channel entries that will be replaced
+ my $chnhash = $modules{CUL_HM}{defptr}{"$dst$chn"};
+ $chnhash = $hash if(!$chnhash);
+
+ $peer ="" if($list !~ m/^0[34]$/);
+ #empty val since reading will be cumulative
+ my $rlName = ((CUL_HM_getExpertMode($hash) eq "2")?"":".")."RegL_".$list.":".$peer;
+ $chnhash->{READINGS}{$rlName}{VAL}="";
+ delete ($chnhash->{READINGS}{$rlName}{TIME});
+ return;
+ }
+# elsif($subType eq "0A"){ #Pair Serial----------
+# #--- set messaging items
+# $hash->{helper}{respWait}{Pending} = "PairSerial";
+# $hash->{helper}{respWait}{PendCmd} = $cmd;
+# $hash->{helper}{respWait}{forChn} = substr($p,4,20);
+#
+# # define timeout - holdup cmdStack until response complete or timeout
+# InternalTimer(gettimeofday()+$rTo, "CUL_HM_respPendTout", "respPend:$dst", 0);
+# return;
+# }
+ }
+
+ if (($msgFlag & 0x20) && ($dst ne '000000')){
+ $hash->{helper}{respWait}{cmd} = $cmd;
+ $hash->{helper}{respWait}{msgId} = $msgId; #msgId we wait to ack
+ $hash->{helper}{respWait}{reSent} = 1;
+
+ my $off = 2;
+ InternalTimer(gettimeofday()+$off, "CUL_HM_Resend", $hash, 0);
+ }
+}
+sub CUL_HM_eventP($$) {#handle protocol events
+ #todo: add severity, counter, history and acknowledge
+ my ($hash, $evntType) = @_;
+ my $name = $hash->{NAME};
+ my $nAttr = $hash;
+ return if (!$name);
+ if ($evntType eq "Rcv"){
+ $nAttr->{"protLastRcv"} = TimeNow();
+ return;
+ }
+
+ my $evnt = $nAttr->{"prot".$evntType}?$nAttr->{"prot".$evntType}:"0 > x";
+ my ($evntCnt,undef) = split(' last_at:',$evnt);
+ $nAttr->{"prot".$evntType} = ++$evntCnt." last_at:".TimeNow();
+
+ if ($evntType ne "Snd"){#count unusual events
+ if ($hash->{helper}{burstEvtCnt}){
+ $hash->{helper}{burstEvtCnt}++;
+ }else {$hash->{helper}{burstEvtCnt}=1;};
+ }
+ if ($evntType eq "Nack" ||$evntType eq "ResndFail"){
+ my $delMsgSum;
+ $nAttr->{protCmdDel} = 0 if(!$nAttr->{protCmdDel});
+ $nAttr->{protCmdDel} += scalar @{$hash->{cmdStack}} if ($hash->{cmdStack});
+ $hash->{protState} = "CMDs_done".($hash->{helper}{burstEvtCnt}?
+ ("_events:".$hash->{helper}{burstEvtCnt}):"");
+ }
+}
+sub CUL_HM_respPendRm($) {#del response related entries in messageing entity
+ my ($hash) = @_;
+ delete ($hash->{helper}{respWait});
+ RemoveInternalTimer($hash); # remove resend-timer
+ RemoveInternalTimer("respPend:$hash->{DEF}");# remove responsePending timer
+ $respRemoved = 1;
+}
+sub CUL_HM_respPendTout($) {
+ my ($HMid) = @_;
+ $HMid =~ s/.*://; #remove timer identifier
+ my $hash = $modules{CUL_HM}{defptr}{$HMid};
+ if ($hash && $hash->{DEF} ne '000000'){
+ my $pendCmd = $hash->{helper}{respWait}{Pending};# secure before remove
+
+ my $pendRsndCnt = $hash->{helper}{respWait}{PendingRsend};
+ $pendRsndCnt = 1 if (!$pendRsndCnt);
+ if ($pendRsndCnt < 5 && # some retries
+ (CUL_HM_getRxType($hash) & 0x03) != 0){# to slow for wakeup and config
+ my $name = $hash->{NAME};
+ Log GetLogLevel($name,4),"CUL_HM_Resend: ".$name. " nr ".$pendRsndCnt;
+ $hash->{helper}{respWait}{PendingRsend} = $pendRsndCnt + 1;
+ CUL_HM_SndCmd($hash,substr($hash->{helper}{respWait}{PendCmd},4));
+ CUL_HM_eventP($hash,"Resnd") if ($pendCmd);
+ }
+ else{
+ CUL_HM_eventP($hash,"ResndFail") if ($pendCmd);
+ if ((CUL_HM_getRxType($hash) & 0x03) == 0){#to slow for wakeup and config
+ delete($hash->{cmdStack});
+ delete($hash->{protCmdPend});
+ $hash->{protState} = "CMDs_done".($hash->{helper}{burstEvtCnt}?
+ ("_events:".$hash->{helper}{burstEvtCnt}):"");
+ }
+ CUL_HM_respPendRm($hash);
+ CUL_HM_ProcessCmdStack($hash); # continue processing commands
+ readingsSingleUpdate($hash,"state","RESPONSE TIMEOUT:".$pendCmd,1);
+ }
+ }
+}
+sub CUL_HM_respPendToutProlong($) {#used when device sends part responses
+ my ($hash) = @_;
+ RemoveInternalTimer("respPend:$hash->{DEF}");
+ InternalTimer(gettimeofday()+1, "CUL_HM_respPendTout", "respPend:$hash->{DEF}", 0);
+}
+sub CUL_HM_PushCmdStack($$) {
+ my ($chnhash, $cmd) = @_;
+ my @arr = ();
+ my $hash = CUL_HM_getDeviceHash($chnhash);
+ my $name = $hash->{NAME};
+ if(!$hash->{cmdStack}){
+ $hash->{cmdStack} = \@arr;
+ delete ($hash->{helper}{burstEvtCnt}) if (!$hash->{helper}{respWait});
+ }
+ push(@{$hash->{cmdStack}}, $cmd);
+ my $entries = scalar @{$hash->{cmdStack}};
+ $hash->{protCmdPend} = $entries." CMDs_pending";
+ $hash->{protState} = "CMDs_pending"
+ if (!$hash->{helper}{respWait}{cmd} &&
+ !$hash->{helper}{respWait}{Pending});
+}
+sub CUL_HM_ProcessCmdStack($) {
+ my ($chnhash) = @_;
+ my $hash = CUL_HM_getDeviceHash($chnhash);
+ my $name = $hash->{NAME};
+ my $sent;
+ if($hash->{cmdStack} && !$hash->{helper}{respWait}{cmd} &&
+ !$hash->{helper}{respWait}{Pending}){
+ if(@{$hash->{cmdStack}}) {
+ CUL_HM_SndCmd($hash, shift @{$hash->{cmdStack}});
+ $sent = 1;
+ $hash->{protCmdPend} = scalar @{$hash->{cmdStack}}." CMDs pending";
+ $hash->{protState} = "CMDs_processing...";
+ CUL_HM_eventP($hash,"Snd");
+ }
+ if(!@{$hash->{cmdStack}}) {
+ delete($hash->{cmdStack});
+ delete($hash->{protCmdPend});
+ #-- update info ---
+ $hash->{protState} = "CMDs_done".($hash->{helper}{burstEvtCnt}?
+ ("_events:".$hash->{helper}{burstEvtCnt}):"");
+ }
+ }
+ return $sent;
+}
+sub CUL_HM_pushConfig($$$$$$$$) {#generate messages to cnfig data to register
+ my ($hash,$src,$dst,$chn,$peerAddr,$peerChn,$list,$content) = @_;
+ my $flag = CUL_HM_getFlag($hash);
+ my $tl = length($content);
+ $chn = sprintf("%02X",$chn);
+ $peerChn = sprintf("%02X",$peerChn);
+ $list = sprintf("%02X",$list);
+
+ # --store pending changes in shadow to handle bit manipulations cululativ--
+ $peerAddr = "000000" if(!$peerAddr);
+ my $peerN = ($peerAddr ne "000000")?CUL_HM_id2Name($peerAddr.$peerChn):"";
+ $peerN =~ s/broadcast//;
+ $peerN =~ s/ /_/g;#remote blanks
+ my $regLN = ((CUL_HM_getExpertMode($hash) eq "2")?"":".").
+ "RegL_".$list.":".$peerN;
+ #--- copy data from readings to shadow
+ my $chnhash = $modules{CUL_HM}{defptr}{$dst.$chn};
+ $chnhash = $hash if (!$chnhash);
+ if (!$chnhash->{helper}{shadowReg} ||
+ !$chnhash->{helper}{shadowReg}{$regLN}){
+ $chnhash->{helper}{shadowReg}{$regLN} =
+ ReadingsVal($chnhash->{NAME},$regLN,"");
+ }
+ #--- update with ne value
+ my $regs = $chnhash->{helper}{shadowReg}{$regLN};
+ for(my $l = 0; $l < $tl; $l+=4) { #substitute changed bytes in shadow
+ my $addr = substr($content,$l,2);
+ my $data = substr($content,$l+2,2);
+ if(!$regs || !($regs =~ s/$addr:../$addr:$data/)){
+ $regs .= " ".$addr.":".$data;
+ }
+ }
+ $chnhash->{helper}{shadowReg}{$regLN} = $regs;
+ CUL_HM_updtRegDisp($hash,$list,$peerAddr.$peerChn);
+ CUL_HM_PushCmdStack($hash, "++".$flag.'01'.$src.$dst.$chn.'05'.
+ $peerAddr.$peerChn.$list);
+ for(my $l = 0; $l < $tl; $l+=28) {
+ my $ml = $tl-$l < 28 ? $tl-$l : 28;
+ CUL_HM_PushCmdStack($hash, "++A001".$src.$dst.$chn."08".
+ substr($content,$l,$ml));
+ }
+ CUL_HM_PushCmdStack($hash,"++A001".$src.$dst.$chn."06");
+ CUL_HM_queueAutoRead(CUL_HM_hash2Name($hash))
+ if (2 < substr(CUL_HM_Get($hash,$hash->{NAME},"param","autoReadReg"),0,1));
+}
+sub CUL_HM_Resend($) {#resend a message if there is no answer
+ my $hash = shift;
+ my $name = $hash->{NAME};
+ return if(!$hash->{helper}{respWait}{reSent}); # Double timer?
+ if($hash->{helper}{respWait}{reSent} >= 3) {
+ CUL_HM_eventP($hash,"ResndFail");
+ delete($hash->{cmdStack});
+ delete($hash->{protCmdPend});
+ CUL_HM_respPendRm($hash);
+ $hash->{protState} = "CMDs_done".($hash->{helper}{burstEvtCnt}?
+ ("_events:".$hash->{helper}{burstEvtCnt}):"");
+ readingsSingleUpdate($hash,"state","MISSING ACK",1);
+ }
+ else {
+ CUL_HM_eventP($hash,"Resnd");
+ IOWrite($hash, "", $hash->{helper}{respWait}{cmd});
+ $hash->{helper}{respWait}{reSent}++;
+ Log GetLogLevel($name,4),"CUL_HM_Resend: ".$name. " nr ".$hash->{helper}{respWait}{reSent};
+ InternalTimer(gettimeofday()+rand(40)/10+1, "CUL_HM_Resend", $hash, 0);
+ }
+}
+
+###################-----------helper and shortcuts--------#####################
+################### Peer Handling ################
+sub CUL_HM_ID2PeerList ($$$) {
+ my($name,$peerID,$set) = @_;
+ my $peerIDs = AttrVal($name,"peerIDs","");
+ my $hash = CUL_HM_name2Hash($name);
+ $peerIDs =~ s/$peerID//g; #avoid duplicate, support unset
+ $peerID =~ s/^000000../00000000/; #correct end detector
+ $peerIDs.= $peerID."," if($set);
+
+ my %tmpHash = map { $_ => 1 } split(",",$peerIDs);#remove duplicates
+ $peerIDs = ""; #clear list
+ my $peerNames = ""; #prepare names
+ my $dId = substr(CUL_HM_name2Id($name),0,6); #get own device ID
+ foreach my $pId (sort(keys %tmpHash)){
+ next if ($pId !~ m/^[0-9A-F]{8}$/); #ignore non-channel IDs
+ $peerIDs .= $pId.","; #append ID
+ next if ($pId eq "00000000"); # and end detection
+ $peerNames .= (($dId eq substr($pId,0,6))? #is own channel?
+ ("self".substr($pId,6,2)): #yes, name it 'self'
+ (CUL_HM_id2Name($pId))) #find name otherwise
+ .","; # dont forget separator
+ }
+ $attr{$name}{peerIDs} = $peerIDs; # make it public
+ if ($peerNames){
+ readingsSingleUpdate($hash,"peerList",$peerNames,0) ;
+ }
+ else{
+ delete $hash->{READINGS}{peerList};
+ }
+}
+sub CUL_HM_peerChId($$$) {# in: , out:channelID
+ my($pId,$dId,$iId)=@_;
+ my $pSc = substr($pId,0,4); #helper for shortcut spread
+ return $dId.sprintf("%02X",'0'.substr($pId,4)) if ($pSc eq 'self');
+ return $iId.sprintf("%02X",'0'.substr($pId,4)) if ($pSc eq 'fhem');
+ return "all" if ($pId eq 'all');#used by getRegList
+ my $repID = CUL_HM_name2Id($pId);
+ $repID .= '01' if (length( $repID) == 6);# add default 01 if this is a device
+ return $repID;
+}
+sub CUL_HM_peerChName($$$) {#in: , out:name
+ my($pId,$dId,$iId)=@_;
+ my($pDev,$pChn) = ($1,$2) if ($pId =~ m/(......)(..)/);
+ return 'self'.$pChn if ($pDev eq $dId);
+ return 'fhem'.$pChn if ($pDev eq $iId);
+ return CUL_HM_id2Name($pId);
+}
+sub CUL_HM_getMId($) {#in: hash(chn or dev) out:model key (key for %culHmModel)
+ # Will store result in device helper
+ my ($hash) = @_;
+ $hash = CUL_HM_getDeviceHash($hash);
+ my $mId = $hash->{helper}{mId};
+ if (!$mId){
+ my $model = AttrVal($hash->{NAME}, "model", "");
+ foreach my $mIdKey(keys%culHmModel){
+ next if (!$culHmModel{$mIdKey}{name} || $culHmModel{$mIdKey}{name} ne $model);
+ $mId = $hash->{helper}{mId} = $mIdKey ;
+ return $mIdKey;
+ }
+ return "";
+ }
+ return $mId;
+}
+sub CUL_HM_getRxType($) { #in:hash(chn or dev) out:binary coded Rx type
+ # Will store result in device helper
+ my ($hash) = @_;
+ $hash = CUL_HM_getDeviceHash($hash);
+ no warnings; #convert regardless of content
+ my $rxtEntity = int($hash->{helper}{rxType});
+ use warnings;
+ if (!$rxtEntity){ #at least one bit must be set
+ my $MId = CUL_HM_getMId($hash);
+ my $rxtOfModel = $culHmModel{$MId}{rxt} if ($MId && $culHmModel{$MId}{rxt});
+ if ($rxtOfModel){
+ $rxtEntity |= ($rxtOfModel =~ m/b/)?0x02:0;#burst
+ $rxtEntity |= ($rxtOfModel =~ m/c/)?0x04:0;#config
+ $rxtEntity |= ($rxtOfModel =~ m/w/)?0x08:0;#wakeup
+ }
+ $rxtEntity = 1 if (!$rxtEntity);#always
+ $hash->{helper}{rxType} = $rxtEntity;
+ }
+ return $rxtEntity;
+}
+sub CUL_HM_getFlag($) {#msgFlag 'A0' or 'B0' for burst/normal devices
+ # currently not supported is the wakeupflag since it is hardly used
+ my ($hash) = @_;
+ return (CUL_HM_getRxType($hash) & 0x02)?"B0":"A0"; #set burst flag
+}
+sub CUL_HM_getAssChnIds($) { #in: name out:ID list of assotiated channels
+ # if it is a channel only return itself
+ # if device and no channel
+ my ($name) = @_;
+ my @chnIdList;
+ my $hash = CUL_HM_name2Hash($name);
+ foreach my $channel (grep {$_ =~m/^channel_/} keys %{$hash}){
+ my $chnHash = CUL_HM_name2Hash($hash->{$channel});
+ push @chnIdList,$chnHash->{DEF} if ($chnHash);
+ }
+ my $dId = CUL_HM_name2Id($name);
+
+ push @chnIdList,$dId."01" if (length($dId) == 6 && !$hash->{channel_01});
+ push @chnIdList,$dId if (length($dId) == 8);
+ return sort(@chnIdList);
+}
+sub CUL_HM_getExpertMode($) { # get expert level for the entity.
+ # if expert level is not set try to get it for device
+ my ($hash) = @_;
+ my $expLvl = AttrVal($hash->{NAME},"expert","");
+ my $dHash = CUL_HM_getDeviceHash($hash);
+ $expLvl = AttrVal($dHash->{NAME},"expert","0")
+ if ($expLvl eq "");
+ return substr($expLvl,0,1);
+}
+
+#+++++++++++++++++ Conversions names, hashes, ids++++++++++++++++++++++++++++++
+sub CUL_HM_Id($) {#in: ioHash out: ioHMid
+ my ($io) = @_;
+ my $fhtid = defined($io->{FHTID}) ? $io->{FHTID} : "0000";
+ return AttrVal($io->{NAME}, "hmId", "F1$fhtid");
+}
+sub CUL_HM_IOid($) {#in: hash out: id of IO device
+ my ($hash) = @_;
+ my $dHash = CUL_HM_getDeviceHash($hash);
+ my $ioHash = $dHash->{IODev};
+ my $fhtid = defined($ioHash->{FHTID}) ? $ioHash->{FHTID} : "0000";
+ return AttrVal($ioHash->{NAME}, "hmId", "F1$fhtid");
+}
+sub CUL_HM_hash2Id($) {#in: id, out:hash
+ my ($hash) = @_;
+ return $hash->{DEF};
+}
+sub CUL_HM_hash2Name($) {#in: id, out:name
+ my ($hash) = @_;
+ return $hash->{NAME};
+}
+sub CUL_HM_name2Hash($) {#in: name, out:hash
+ my ($name) = @_;
+ return $defs{$name};
+}
+sub CUL_HM_name2Id(@) { #in: name or HMid ==>out: HMid, "" if no match
+ my ($name,$idHash) = @_;
+ my $hash = $defs{$name};
+ return $hash->{DEF} if ($hash); #name is entity
+ return "000000" if($name eq "broadcast"); #broadcast
+ return $defs{$1}->{DEF}.$2 if($name =~ m/(.*)_chn:(..)/); # chn:xx
+ return $name if($name =~ m/^[A-F0-9]{6,8}$/i);#was already HMid
+ return $idHash->{DEF}.sprintf("%02X",$1)
+ if($idHash && ($name =~ m/self(.*)/));
+ return "";
+}
+sub CUL_HM_id2Name($) { #in: name or HMid out: name
+ my ($p) = @_;
+ return $p if($attr{$p}); # is already name
+ return $p if ($p =~ m/_chn:/);
+ my $devId= substr($p, 0, 6);
+ return "broadcast" if($devId eq "000000");
+ my ($chn,$chnId);
+ if (length($p) == 8){
+ $chn = substr($p, 6, 2);;
+ $chnId = $p;
+ }
+ my $defPtr = $modules{CUL_HM}{defptr};
+ return $defPtr->{$chnId}{NAME} if( $chnId && $defPtr->{$chnId});#channel
+ return $defPtr->{$devId}{NAME} if(!$chnId && $defPtr->{$devId});#device only
+
+ return $defPtr->{$devId}{NAME}."_chn:".$chn
+ if( $chnId && $defPtr->{$devId});#device, add chn
+ return $devId. ($chn ? ("_chn:".$chn):""); #not defined, return ID only
+}
+sub CUL_HM_id2Hash($) {#in: id, out:hash
+ my ($id) = @_;
+ return $modules{CUL_HM}{defptr}{$id} if ($modules{CUL_HM}{defptr}{$id});
+ return $modules{CUL_HM}{defptr}{substr($id,0,6)}; # could be chn 01 of dev
+}
+sub CUL_HM_getDeviceHash($) {#in: hash out: devicehash
+ my ($hash) = @_;
+ return $hash if(!$hash->{DEF});
+ my $devHash = $modules{CUL_HM}{defptr}{substr($hash->{DEF},0,6)};
+ return ($devHash)?$devHash:$hash;
+}
+
+#+++++++++++++++++ debug ++++++++++++++++++++++++++++++++++++++++++++++++++++++
+sub CUL_HM_DumpProtocol($$@) {
+ my ($prefix, $iohash, $len,$cnt,$msgFlags,$msgType,$src,$dst,$p) = @_;
+ my $iname = $iohash->{NAME};
+ no warnings;# conv 2 number would cause a warning - which is ok
+ my $hmProtocolEvents = int(AttrVal($iname, "hmProtocolEvents", 0));
+ use warnings;
+ return if(!$hmProtocolEvents);
+
+ my $p01 = substr($p,0,2);
+ my $p02 = substr($p,0,4);
+ my $p11 = (length($p) > 2 ? substr($p,2,2) : "");
+
+ # decode message flags for printing
+ my $msgFlLong="";
+ my $msgFlagsHex = hex($msgFlags);
+ for(my $i = 0; $i < @culHmCmdFlags; $i++) {
+ $msgFlLong .= ",$culHmCmdFlags[$i]" if($msgFlagsHex & (1<<$i));
+ }
+
+ my $ps;
+ $ps = $culHmBits{"$msgType;p11=$p11"} if(!$ps);
+ $ps = $culHmBits{"$msgType;p01=$p01"} if(!$ps);
+ $ps = $culHmBits{"$msgType;p02=$p02"} if(!$ps);
+ $ps = $culHmBits{"$msgType"} if(!$ps);
+ my $txt = "";
+ if($ps) {
+ $txt = $ps->{txt};
+ if($ps->{params}) {
+ $ps = $ps->{params};
+ foreach my $k (sort {$ps->{$a} cmp $ps->{$b} } keys %{$ps}) {
+ my ($o,$l,$expr) = split(",", $ps->{$k}, 3);
+ last if(length($p) <= $o);
+ my $val = $l ? substr($p,$o,$l) : substr($p,$o);
+ eval $expr if($hmProtocolEvents > 1 && $expr);
+ $txt .= " $k:".(($hmProtocolEvents > 1 && $expr)?"":"0x")."$val";
+ }
+ }
+ $txt = " ($txt)" if($txt);
+ }
+ $src=CUL_HM_id2Name($src);
+ $dst=CUL_HM_id2Name($dst);
+ my $msg ="$prefix L:$len N:$cnt F:$msgFlags CMD:$msgType SRC:$src DST:$dst $p$txt ($msgFlLong)";
+ Log GetLogLevel($iname, 4), $msg;
+ DoTrigger($iname, $msg) if($hmProtocolEvents > 2);
+}
+
+#+++++++++++++++++ handling register updates ++++++++++++++++++++++++++++++++++
+sub CUL_HM_getRegFromStore($$$$) {#read a register from backup data
+ my($name,$regName,$list,$peerId)=@_;
+ my $hash = CUL_HM_name2Hash($name);
+ my ($size,$pos,$conversion,$factor,$unit) = (8,0,"",1,""); # default
+ my $addr = $regName;
+ my $dId = substr(CUL_HM_name2Id($name),0,6);#id of device
+ my $iId = CUL_HM_IOid($hash); #id of IO device
+ my $reg = $culHmRegDefine{$regName};
+ if ($reg) { # get the register's information
+ $addr = $reg->{a};
+ $pos = ($addr*10)%10;
+ $addr = int($addr);
+ $list = $reg->{l};
+ $size = $reg->{s};
+ $size = int($size)*8 + ($size*10)%10;
+ $conversion = $reg->{c}; #unconvert formula
+ $factor = $reg->{f};
+ $unit = $reg->{u};
+ }
+ else{
+ ;# use address instead of
+ }
+ $peerId = CUL_HM_peerChId(($peerId?$peerId:"00000000"),$dId,$iId);
+
+ my $regLN = ((CUL_HM_getExpertMode($hash) eq "2")?"":".").
+ "RegL_".sprintf("%02X",$list).":".CUL_HM_peerChName($peerId,$dId,$iId);
+ $regLN =~ s/broadcast//;
+
+ my $data=0;
+ my $convFlg = "";# confirmation flag - indicates data not confirmed by device
+ for (my $size2go = $size;$size2go>0;$size2go -=8){
+ my $addrS = sprintf("%02X",$addr);
+
+ my $dReadS;
+ if ($hash->{helper}{shadowReg}&&$hash->{helper}{shadowReg}{$regLN}){
+ $dReadS = $1 if($hash->{helper}{shadowReg}{$regLN} =~ m/$addrS:(..)/);
+ }
+ my $dReadR = " ";
+ if ($hash->{READINGS}{$regLN}) {
+ $dReadR = $1 if($hash->{READINGS}{$regLN}{VAL} =~ m/$addrS:(..)/);
+ }
+ $convFlg = "set_" if ($dReadS && $dReadR ne $dReadS);
+ my $dRead = $dReadS?$dReadS:$dReadR;
+ return "invalid" if (!defined($dRead) || $dRead eq ""|| $dRead eq " ");
+
+ $data = ($data<< 8)+hex($dRead);
+ $addr++;
+ }
+
+ $data = ($data>>$pos) & (0xffffffff>>(32-$size));
+ if (!$conversion){ ;# do nothing
+ } elsif($conversion eq "factor"){ $data /= $factor;
+ } elsif($conversion eq "fltCvT"){ $data = CUL_HM_CvTflt($data);
+ } elsif($conversion eq "m10s3") { $data = ($data+3)/10;
+ } elsif($conversion eq "hex" ) { $data = sprintf("0x%X",$data);
+ } elsif(defined($reg->{lit})) {
+ foreach (keys%{$reg->{lit}}){
+ if ($data == $reg->{lit}{$_}){ $data = $_; last; }
+ }
+ } else { return " conversion undefined - please contact admin";
+ }
+ return $convFlg.$data.' '.$unit;
+
+}
+sub CUL_HM_updtRegDisp($$$) {
+ my $starttime = gettimeofday();
+ my($hash,$list,$peerId)=@_;
+ my $listNo = $list+0;
+ my $name = $hash->{NAME};
+ my $peer = ($peerId && $peerId ne '00000000' )?
+ CUL_HM_peerChName($peerId,substr($hash->{DEF},0,6),"")."-":"";
+ $peer=~s/:/-/;
+ my $devName =CUL_HM_getDeviceHash($hash)->{NAME};# devName as protocol entity
+ my $st = AttrVal($devName, "subType", "");
+ my $md = AttrVal($devName, "model", "");
+ my $chn = $hash->{DEF};
+ $chn = (length($chn) == 8)?substr($chn,6,2):"";
+ my @regArr = keys %culHmRegGeneral;
+ push @regArr, keys %{$culHmRegType{$st}} if($culHmRegType{$st});
+ push @regArr, keys %{$culHmRegModel{$md}} if($culHmRegModel{$md});
+ push @regArr, keys %{$culHmRegChan{$md.$chn}} if($culHmRegChan{$md.$chn});
+ my @changedRead;
+ my $expLvl = (CUL_HM_getExpertMode($hash) ne "0")?1:0;
+ foreach my $regName (@regArr){
+ next if ($culHmRegDefine{$regName}->{l} ne $listNo);
+ my $rgVal = CUL_HM_getRegFromStore($name,$regName,$list,$peerId);
+ next if (!$rgVal || $rgVal eq "invalid");
+ my $readName = "R-".$peer.$regName;
+ $readName = ($culHmRegDefine{$regName}->{d}?"":".").$readName if (!$expLvl); #expert?
+ push (@changedRead,$readName.":".$rgVal)
+ if (ReadingsVal($name,$readName,"") ne $rgVal);
+ }
+
+ # --- handle specifics - no general approach so far.
+ CUL_HM_TCtempReadings($hash) if (($list == 5 ||$list == 6) &&
+ substr($hash->{DEF},6,2) eq "02" &&
+ CUL_HM_Get($hash,$name,"param","model") eq "HM-CC-TC");
+ CUL_HM_repReadings($hash) if (($list == 2) &&
+ CUL_HM_Get($hash,$name,"param","subType") eq "repeater");
+# CUL_HM_dimLog($hash) if(CUL_HM_Get($hash,$name,"param","subType") eq "dimmer");
+
+ CUL_HM_UpdtReadBulk($hash,1,@changedRead) if (@changedRead);
+}
+#############################
+#+++++++++++++++++ parameter cacculations +++++++++++++++++++++++++++++++++++++
+my @culHmTimes8 = ( 0.1, 1, 5, 10, 60, 300, 600, 3600 );
+my %fltCvT = (0.1=>3.1,1=>31,5=>155,10=>310,60=>1860,300=>9300,
+ 600=>18600,3600=>111601);
+sub CUL_HM_encodeTime8($) {#####################
+ my $v = shift;
+ return "00" if($v < 0.1);
+ for(my $i = 0; $i < @culHmTimes8; $i++) {
+ if($culHmTimes8[$i] * 32 > $v) {
+ for(my $j = 0; $j < 32; $j++) {
+ if($j*$culHmTimes8[$i] >= $v) {
+ return sprintf("%X", $i*32+$j);
+ }
+ }
+ }
+ }
+ return "FF";
+}
+sub CUL_HM_decodeTime8($) {#####################
+ my $v = hex(shift);
+ return "undef" if($v > 255);
+ my $v1 = int($v/32);
+ my $v2 = $v%32;
+ return $v2 * $culHmTimes8[$v1];
+}
+sub CUL_HM_encodeTime16($) {####################
+ my $v = shift;
+ return "0000" if($v < 0.05);
+
+ my $ret = "FFFF";
+ my $mul = 10;
+ for(my $i = 0; $i < 32; $i++) {
+ if($v*$mul < 0x7ff) {
+ $ret=sprintf("%04X", ((($v*$mul)<<5)+$i));
+ last;
+ }
+ $mul /= 2;
+ }
+ return ($ret);
+}
+sub CUL_HM_convTemp($) {########################
+ my ($val) = @_;
+
+ if(!($val eq "on" || $val eq "off" ||
+ ($val =~ m/^\d*\.?\d+$/ && $val >= 6 && $val <= 30))) {
+ my @list = map { ($_.".0", $_+0.5) } (6..30);
+ pop @list;
+ return "Invalid temperature $val, choose one of on off " . join(" ",@list);
+ }
+ $val = 100 if($val eq "on");
+ $val = 0 if($val eq "off");
+ return sprintf("%02X", $val*2);
+}
+sub CUL_HM_decodeTime16($) {####################
+ my $v = hex(shift);
+ my $m = int($v>>5);
+ my $e = $v & 0x1f;
+ my $mul = 0.1;
+ return 2^$e*$m*0.1;
+}
+sub CUL_HM_secSince2000() {#####################
+ # Calculate the local time in seconds from 2000.
+ my $t = time();
+ my @l = localtime($t);
+ my @g = gmtime($t);
+ $t += 60*(($l[2]-$g[2] + ((($l[5]<<9)|$l[7]) <=> (($g[5]<<9)|$g[7])) * 24 + $l[8]) * 60 + $l[1]-$g[1])
+ # timezone and daylight saving...
+ - 946684800 # seconds between 01.01.2000, 00:00 and THE EPOCH (1970)
+ - 7200; # HM Special
+ return $t;
+}
+sub CUL_HM_getChnLvl($){# in: name out: vit or phys level
+ my $name = shift;
+ my $curVal = ReadingsVal($name,"virtLevel",undef);
+ $curVal = ReadingsVal($name,"state",0) if (!defined $curVal);
+ $curVal =~ s/set_//;
+ $curVal =~ s/ .*//;#strip unit
+ return ($curVal eq "on")?100:(($curVal eq "off")?0:$curVal);
+}
+
+#--------------- Conversion routines for register settings---------------------
+sub CUL_HM_initRegHash() { #duplicate short and long press register
+ foreach my $reg (keys %culHmRegDefShLg){ #update register list
+ %{$culHmRegDefine{"sh".$reg}} = %{$culHmRegDefShLg{$reg}};
+ %{$culHmRegDefine{"lg".$reg}} = %{$culHmRegDefShLg{$reg}};
+ $culHmRegDefine{"lg".$reg}{a} +=0x80;
+ }
+ foreach my $type(sort(keys %culHmRegType)){ #update references to register
+ foreach my $reg (sort(keys %{$culHmRegType{$type}})){
+ if ($culHmRegDefShLg{$reg}){
+ delete $culHmRegType{$type}{$reg};
+ $culHmRegType{$type}{"sh".$reg} = 1;
+ $culHmRegType{$type}{"lg".$reg} = 1;
+ }
+ }
+ }
+ foreach my $type(sort(keys %culHmRegModel)){ #update references to register
+ foreach my $reg (sort(keys %{$culHmRegModel{$type}})){
+ if ($culHmRegDefShLg{$reg}){
+ delete $culHmRegModel{$type}{$reg};
+ $culHmRegModel{$type}{"sh".$reg} = 1;
+ $culHmRegModel{$type}{"lg".$reg} = 1;
+ }
+ }
+ }
+ foreach my $type(sort(keys %culHmRegChan)){ #update references to register
+ foreach my $reg (sort(keys %{$culHmRegChan{$type}})){
+ if ($culHmRegDefShLg{$reg}){
+ delete $culHmRegChan{$type}{$reg};
+ $culHmRegChan{$type}{"sh".$reg} = 1;
+ $culHmRegChan{$type}{"lg".$reg} = 1;
+ }
+ }
+ }
+}
+sub CUL_HM_fltCvT($) { # float -> config time
+ my ($inValue) = @_;
+ my $exp = 0;
+ my $div2;
+ foreach my $div(sort{$a <=> $b} keys %fltCvT){
+ $div2 = $div;
+ last if ($inValue < $fltCvT{$div});
+ $exp++;
+ }
+ return ($exp << 5)+int($inValue/$div2);
+}
+sub CUL_HM_CvTflt($) { # config time -> float
+ my ($inValue) = @_;
+ return ($inValue & 0x1f)*((sort {$a <=> $b} keys(%fltCvT))[$inValue >> 5]);
+}
+
+sub CUL_HM_TCtempReadings($) {# parse TC readings
+ my ($hash)=@_;
+ my $name = $hash->{NAME};
+ my $regLN = ((CUL_HM_getExpertMode($hash) eq "2")?"":".")."RegL_";
+ my $reg5 = ReadingsVal($name,$regLN."05:" ,"");
+ my $reg6 = ReadingsVal($name,$regLN."06:" ,"");
+ my @days = ("Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri");
+ $reg5 =~ s/.* 0B://; #remove register up to addr 11 from list 5
+ my $tempRegs = $reg5.$reg6; #one row
+ $tempRegs =~ s/ 00:00/ /g; #remove regline termination
+ $tempRegs =~ s/ ..:/,/g; #remove addr Info
+ $tempRegs =~ s/ //g; #blank
+ my @Tregs = split(",",$tempRegs);
+ my @time = @Tregs[grep !($_ % 2), 0..$#Tregs]; # even-index =time
+ my @temp = @Tregs[grep $_ % 2, 0..$#Tregs]; # odd-index =data
+ return "reglist incomplete\n" if (scalar( @time )<168);
+ foreach (@time){$_=hex($_)*10};
+ foreach (@temp){$_=hex($_)/2};
+ my $setting;
+ my @changedRead;
+ push (@changedRead,"tempList_State:".
+ (($hash->{helper}{shadowReg}{$regLN."05:"} ||
+ $hash->{helper}{shadowReg}{$regLN."06:"} )?"set":"verified"));
+ for (my $day = 0;$day<7;$day++){
+ my $tSpan = 0;
+ my $dayRead = "";
+ for (my $entry = 0;$entry<24;$entry++){
+ my $reg = $day *24 + $entry;
+ last if ($tSpan > 1430);
+ $tSpan = $time[$reg];
+ my $entry = sprintf("%02d:%02d %3.01f",($tSpan/60),($tSpan%60),$temp[$reg]);
+ $setting .= "Temp set: ".$days[$day]." ".$entry." C\n";
+ $dayRead .= " ".$entry;
+ $tSpan = $time[$reg];
+ }
+ push (@changedRead,"tempList".$days[$day].":".$dayRead);
+ }
+ CUL_HM_UpdtReadBulk($hash,1,@changedRead) if (@changedRead);
+ return $setting;
+}
+sub CUL_HM_repReadings($) {# for repeater in:hash, out: string with peers
+ my ($hash)=@_;
+ my %pCnt;
+ my $cnt = 0;
+ return "" if (!$hash->{helper}{peerIDsRaw});
+ foreach my$pId(split',',$hash->{helper}{peerIDsRaw}){
+ next if (!$pId || $pId eq "00000000");
+ $pCnt{$pId.$cnt}{cnt}=$cnt++;
+ }
+
+ my @pS;
+ my @pD;
+ my @pB;
+ foreach (split",",(AttrVal($hash->{NAME},"repPeers",undef))){
+ my ($s,$d,$b) = split":",$_;
+ push @pS,$s;
+ push @pD,$d;
+ push @pB,$b;
+ }
+ my @readList;
+ for (my $n=0;$n<36;$n++){
+ push @readList,"repPeer_$n:undefined";
+ }
+ my @retL;
+ foreach my$pId(sort keys %pCnt){
+ my ($pdID,$bdcst,$no) = ($1,$2,$3) if ($pId =~ m/(......)(..)(.*)/);
+ my $fNo = $no-1;#shorthand field number, used often
+ my $sName = CUL_HM_id2Name($pdID);
+ my $eS = sprintf("%02d %-15s %-15s %-3s %-4s",
+ $no,$sName
+ ,((!$pS[$fNo] || $pS[$fNo] ne $sName)?"unknown":" dst>$pD[$fNo]")
+ ,($bdcst eq "01"?"yes":"no ")
+ ,( (($bdcst eq "01" && $pB[$fNo] && $pB[$fNo] eq "y")||
+ ($bdcst eq "00" && $pB[$fNo] && $pB[$fNo] eq "n")) ?"ok":"fail")
+ );
+ push @retL, $eS;
+ $readList[$fNo]=sprintf("repPeer_%02d:%s",$no,$eS);
+ }
+ CUL_HM_UpdtReadBulk($hash,0,@readList);
+ return "No Source Dest Bcast\n". join"\n", sort @retL;
+}
+sub CUL_HM_dimLog($) {# dimmer readings - support virtual chan - unused so far
+ my ($hash)=@_;
+ my $lComb = CUL_HM_Get($hash,$hash->{NAME},"reg","logicCombination");
+ return if (!$lComb);
+ my %logicComb=(
+ inactive=>{calc=>'$val=$val' ,txt=>'unused'},
+ or =>{calc=>'$val=$in>$val?$in:$val' ,txt=>'max(state,chan)'},
+ and =>{calc=>'$val=$in<$val?$in:$val' ,txt=>'min(state,chan)'},
+ xor =>{calc=>'$val=!($in!=0&&$val!=0)?($in>$val?$in:$val): 0' ,txt=>'0 if both are != 0, else max'},
+ nor =>{calc=>'$val=100-($in>$val?$in : $val)' ,txt=>'100-max(state,chan)'},
+ nand =>{calc=>'$val=100-($in<$val?$in : $val)' ,txt=>'100-min(state,chan)'},
+ orinv =>{calc=>'$val=(100-$in)>$val?(100-$in) : $val' ,txt=>'max((100-chn),state)'},
+ andinv =>{calc=>'$val=(100-$in)<$val?(100-$in) : $val' ,txt=>'min((100-chn),state)'},
+ plus =>{calc=>'$val=($in + $val)<100?($in + $val) : 100' ,txt=>'state + chan'},
+ minus =>{calc=>'$val=($in - $val)>0?($in + $val) : 0' ,txt=>'state - chan'},
+ mul =>{calc=>'$val=($in * $val)<100?($in + $val) : 100' ,txt=>'state * chan'},
+ plusinv =>{calc=>'$val=($val+100-$in)<100?($val+100-$in) : 100' ,txt=>'state + 100 - chan'},
+ minusinv=>{calc=>'$val=($val-100+$in)>0?($val-100+$in) : 0' ,txt=>'state - 100 + chan'},
+ mulinv =>{calc=>'$val=((100-$in)*$val)<100?(100-$in)*$val) : 100',txt=>'state * (100 - chan)'},
+ invPlus =>{calc=>'$val=(100-$val-$in)>0?(100-$val-$in) : 0' ,txt=>'100 - state - chan'},
+ invMinus=>{calc=>'$val=(100-$val+$in)<100?(100-$val-$in) : 100' ,txt=>'100 - state + chan'},
+ invMul =>{calc=>'$val=(100-$val*$in)>0?(100-$val*$in) : 0' ,txt=>'100 - state * chan'},
+ );
+ readingsSingleUpdate($hash,"R-logicCombTxt" ,$logicComb{$lComb}{txt},0);
+ readingsSingleUpdate($hash,"R-logicCombCalc",$logicComb{$lComb}{calc},0);
+ return "";
+}
+
+#+++++++++++++++++ Action Detector ++++++++++++++++++++++++++++++++++++++++++++
+# verify that devices are seen in a certain period of time
+# It will generate events if no message is seen sourced by the device during
+# that period.
+# ActionDetector will use the fixed HMid 000000
+sub CUL_HM_ActGetCreateHash() {# get ActionDetector - create if necessary
+ if (!$modules{CUL_HM}{defptr}{"000000"}){
+ DoTrigger("global", "UNDEFINED ActionDetector CUL_HM 000000");
+ $attr{ActionDetector}{actCycle} = 600;
+ $attr{ActionDetector}{"event-on-change-reading"} = ".*";
+ }
+ my $actHash = $modules{CUL_HM}{defptr}{"000000"};
+ my $actName = $actHash->{NAME} if($actHash);
+
+ if (!$actHash->{helper}{actCycle} ||
+ $actHash->{helper}{actCycle} != $attr{$actName}{actCycle}){
+ $attr{$actName}{actCycle} = 30 if(!$attr{$actName}{actCycle} ||
+ $attr{$actName}{actCycle}<30);
+ $actHash->{helper}{actCycle} = $attr{$actName}{actCycle};
+ RemoveInternalTimer("ActionDetector");
+ $actHash->{STATE} = "active";
+ InternalTimer(gettimeofday()+$attr{$actName}{actCycle},
+ "CUL_HM_ActCheck", "ActionDetector", 0);
+ }
+ return $actHash;
+}
+sub CUL_HM_time2sec($) {
+ my ($timeout) = @_;
+ my ($h,$m) = split(":",$timeout);
+ no warnings;
+ $h = int($h);
+ $m = int($m);
+ use warnings;
+ return ((sprintf("%03s:%02d",$h,$m)),((int($h)*60+int($m))*60));
+}
+sub CUL_HM_ActAdd($$) {# add an HMid to list for activity supervision
+ my ($devId,$timeout) = @_; #timeout format [hh]h:mm
+ $timeout = 0 if (!$timeout);
+ return $devId." is not an HM device - action detection cannot be added"
+ if (length($devId) != 6);
+ my ($cycleString,undef)=CUL_HM_time2sec($timeout);
+ my $devName = CUL_HM_id2Name($devId);
+ my $devHash = CUL_HM_name2Hash($devName);
+
+ $attr{$devName}{actCycle} = $cycleString;
+ $attr{$devName}{actStatus}=""; # force trigger
+ # get last reading timestamp-------
+ my $recent = "";
+ my @entities = CUL_HM_getAssChnIds($devName);
+ for (@entities){$_ = CUL_HM_id2Hash($_)}
+ push @entities,$devHash if ($devHash->{channel_01});
+ foreach my $ehash (@entities){
+ no strict; #convert regardless of content
+ next if (!defined $ehash->{NAME});
+ use strict;
+ my $eName = CUL_HM_hash2Name($ehash);
+ next if (!$eName);
+ foreach my $rName (keys %{$ehash->{READINGS}}){
+ next if (!$rName ||
+ $rName eq "PairedTo" || # derived
+ $rName eq "peerList" || # derived
+ $rName eq "Activity:"|| # derived
+ $rName =~ m/^[.]?R-/ || # no Regs - those are derived from Reg
+ ReadingsVal($eName,$rName,"") =~ m/^set_/); # ignore setting
+ my $ts = ReadingsTimestamp($eName,$rName,"");
+ $recent = $ts if ($ts gt $recent);
+ }
+ }
+ my $actHash = CUL_HM_ActGetCreateHash();
+ $actHash->{helper}{$devId}{start} = TimeNow();
+ $actHash->{helper}{$devId}{recent} = $recent;
+ $actHash->{helper}{peers} = CUL_HM_noDupInString(
+ ($actHash->{helper}{peers}?$actHash->{helper}{peers}:"")
+ .",$devId");
+ Log 3,"Device ".$devName." added to ActionDetector with "
+ .$cycleString." time";
+ #run ActionDetector
+ RemoveInternalTimer("ActionDetector");
+ CUL_HM_ActCheck();
+ return;
+}
+sub CUL_HM_ActDel($) {# delete HMid for activity supervision
+ my ($devId) = @_;
+ my $devName = CUL_HM_id2Name($devId);
+ CUL_HM_setAttrIfCh($devName,"actStatus","deleted","Activity");#post trigger
+ delete $attr{$devName}{actCycle};
+ delete $attr{$devName}{actStatus};
+
+ my $actHash = CUL_HM_ActGetCreateHash();
+ delete ($actHash->{helper}{$devId});
+
+ my $peerIDs = $actHash->{helper}{peers};
+ $peerIDs =~ s/$devId//g if($peerIDs);
+ $actHash->{helper}{peers} = CUL_HM_noDupInString($peerIDs);
+ Log 3,"Device ".$devName
+ ." removed from ActionDetector";
+ RemoveInternalTimer("ActionDetector");
+ CUL_HM_ActCheck();
+ return;
+}
+sub CUL_HM_ActCheck() {# perform supervision
+ my $actHash = CUL_HM_ActGetCreateHash();
+ my $tod = int(gettimeofday());
+ my $actName = $actHash->{NAME};
+ my $peerIDs = $actHash->{helper}{peers}?$actHash->{helper}{peers}:"";
+ my @event;
+ my ($cntUnkn,$cntAlive,$cntDead,$cntOff) =(0,0,0,0);
+
+ foreach my $devId (split(",",$peerIDs)){
+ next if (!$devId);
+ my $devName = CUL_HM_id2Name($devId);
+ if(!$devName || !defined($attr{$devName}{actCycle})){
+ CUL_HM_ActDel($devId);
+ next;
+ }
+ my $devHash = CUL_HM_name2Hash($devName);
+ my $state;
+ my $oldState = AttrVal($devName,"actStatus","unset");
+ my (undef,$tSec)=CUL_HM_time2sec($attr{$devName}{actCycle});
+ if ($tSec == 0){# detection switched off
+ $cntOff++;
+ $state = "switchedOff";
+ }
+ else{
+ $actHash->{helper}{$devId}{recent} = $devHash->{"protLastRcv"} #update recent
+ if ($devHash->{"protLastRcv"});
+ my $tLast = $actHash->{helper}{$devId}{recent};
+ my @t = localtime($tod - $tSec); #time since when a trigger is expected
+ my $tSince = sprintf("%04d-%02d-%02d %02d:%02d:%02d",
+ $t[5]+1900, $t[4]+1, $t[3], $t[2], $t[1], $t[0]);
+
+ if (!$tLast){ #cannot determine time
+ if ($actHash->{helper}{$devId}{start} lt $tSince){
+ $state = "dead";
+ $cntDead++;
+ }
+ else{
+ $state = "unknown";
+ $cntUnkn++;
+ }
+ }
+ elsif ($tSince gt $tLast){ #no message received in window
+ $cntDead++;
+ $state = "dead";
+ }
+ else{ #message in time
+ $cntAlive++;
+ $state = "alive";
+ }
+ }
+ if ($oldState ne $state){
+ readingsSingleUpdate($devHash,"Activity:",$state,1);
+ $attr{$devName}{actStatus} = $state;
+ Log 4,"Device ".$devName." is ".$state;
+ }
+ push @event, "status_".$devName.":".$state;
+ }
+ push @event, "state:"."alive:".$cntAlive
+ ." dead:".$cntDead
+ ." unkn:".$cntUnkn
+ ." off:" .$cntOff;
+
+ my $allState = join " ",@event;# search and remove outdated readings
+ foreach (keys %{$actHash->{READINGS}}){
+ delete $actHash->{READINGS}{$_} if ($allState !~ m/$_:/);
+ }
+
+ CUL_HM_UpdtReadBulk($actHash,1,@event);
+
+ $attr{$actName}{actCycle} = 600 if($attr{$actName}{actCycle}<30);
+ $actHash->{helper}{actCycle} = $attr{$actName}{actCycle};
+ InternalTimer(gettimeofday()+$attr{$actName}{actCycle},
+ "CUL_HM_ActCheck", "ActionDetector", 0);
+}
+
+#+++++++++++++++++ helper +++++++++++++++++++++++++++++++++++++++++++++++++++++
+sub CUL_HM_UpdtReadBulk(@) { #update a bunch of readings and trigger the events
+ my ($hash,$doTrg,@readings) = @_;
+ return if (!@readings);
+ readingsBeginUpdate($hash);
+ foreach my $rd (@readings){
+ next if (!$rd);
+ my ($rdName, $rdVal) = split(":",$rd, 2);
+ readingsBulkUpdate($hash,$rdName,
+ ((defined($rdVal) && $rdVal ne "")?$rdVal:"-"));
+ }
+ readingsEndUpdate($hash,$doTrg);
+ return $hash->{NAME};
+}
+sub CUL_HM_UpdtReadSingle(@) { #update single reading and trigger the event
+ my ($hash,$rName,$val,$updt) = @_;
+ readingsSingleUpdate($hash,$rName,$val,$updt);
+ return $hash->{NAME};
+}
+sub CUL_HM_setAttrIfCh($$$$) {
+ my ($name,$att,$val,$trig) = @_;
+ if($attr{$name}{$att} ne $val){
+ DoTrigger($name,$trig.":".$val) if($trig);
+ $attr{$name}{$att} = $val;
+ }
+}
+sub CUL_HM_noDup(@) {#return list with no duplicates
+ my %all;
+ $all{$_}=0 for @_;
+ delete $all{""}; #remove empties if present
+ return (sort keys %all);
+}
+sub CUL_HM_noDupInString($) {#return string with no duplicates, comma separated
+ my ($str) = @_;
+ return join ",",CUL_HM_noDup(split ",",$str);
+}
+sub CUL_HM_storeRssi(@){
+ my ($name,$peerName,$val) = @_;
+ $defs{$name}{helper}{rssi}{$peerName}{lst} = $val;
+ my $rssiP = $defs{$name}{helper}{rssi}{$peerName};
+ $rssiP->{min} = $val if (!$rssiP->{min} || $rssiP->{min} > $val);
+ $rssiP->{max} = $val if (!$rssiP->{max} || $rssiP->{max} < $val);
+ $rssiP->{cnt} ++;
+ if ($rssiP->{cnt} == 1){
+ $rssiP->{avg} = $val;
+ }
+ else{
+ $rssiP->{avg} += ($val - $rssiP->{avg}) /$rssiP->{cnt};
+ }
+ my $hash = CUL_HM_name2Hash($name);
+ my $rssi;
+ foreach (keys %{$rssiP}){
+ $rssi .= $_.":".(int($rssiP->{$_}*100)/100)." ";
+ }
+ $hash->{"rssi_".$peerName} = $rssi;
+ return ;
+}
+sub CUL_HM_stateUpdat($){#in:name, send status-request
+ my $name = shift;
+ (undef,$name)=split":",$name,2;
+ CUL_HM_Set(CUL_HM_name2Hash($name),$name,"statusRequest") if ($name);
+}
+
+#+++++++++++++++++ external use +++++++++++++++++++++++++++++++++++++++++++++++
+sub CUL_HM_putHash($) {# provide data for HMinfo
+ my ($info) = @_;
+ return %culHmModel if ($info eq "culHmModel");
+}
+
+1;
+
+=pod
+=begin html
+
+
+CUL_HM
+
+ Support for eQ-3 HomeMatic devices via the CUL or the HMLAN.
+
+
+ Define
+
+ define <name> CUL_HM <6-digit-hex-code|8-digit-hex-code>
+
+
+ Correct device definition is the key for HM environment simple maintenance.
+
+
+ Background to define entities:
+ HM devices has a 3 byte (6 digit hex value) HMid - which is key for
+ addressing. Each device hosts one or more channels. HMid for a channel is
+ the device's HMid plus the channel number (1 byte, 2 digit) in hex.
+ Channels should be defined for all multi-channel devices. Channel entities
+ cannot be defined if the hosting device does not exist Note: FHEM
+ mappes channel 1 to the device if it is not defined explicitely. Therefore
+ it does not need to be defined for single channel devices.
+
+ Note: if a device is deleted all assotiated channels will be removed as
+ well. An example for a full definition of a 2 channel switch is given
+ below:
+
+
+ define livingRoomSwitch CUL_HM 123456
+ define LivingroomMainLight CUL_HM 12345601
+ define LivingroomBackLight CUL_HM 12345602
+
+
+ livingRoomSwitch is the device managing communication. This device is
+ defined prior to channels to be able to setup references.
+ LivingroomMainLight is channel 01 dealing with status of light, channel
+ peers and channel assotiated register. If not defined channel 01 is covered
+ by the device entity. LivingRoomBackLight is the second 'channel',
+ channel 02. Its definition is mandatory to operate this function.
+
+ Sender specials: HM threats each button of remotes, push buttons and
+ similar as channels. It is possible (not necessary) to define a channel per
+ button. If all channels are defined access to pairing informatin is
+ possible as well as access to channel related register. Furthermore names
+ make the traces better readable.
+
+ define may also be invoked by the autocreate
+ module, together with the necessary subType attribute.
+ Usually you issue a hmPairForSec and press the
+ corresponding button on the device to be paired, or issue a hmPairSerial set command if the device is a receiver
+ and you know its serial number. Autocreate will then create a fhem
+ device and set all necessary attributes. Without pairing the device
+ will not accept messages from fhem. fhem may create the device even if
+ the pairing is not successful. Upon a successful pairing you'll see a
+ CommandAccepted entry in the details section of the CUL_HM device.
+
+ If you cannot use autocreate, then you have to specify:
+
+ - the <6-digit-hex-code>or HMid+ch <8-digit-hex-code>
+ It is the unique, hardcoded device-address and cannot be changed (no,
+ you cannot choose it arbitrarily like for FS20 devices). You may
+ detect it by inspecting the fhem log.
+ - the subType attribute
+ which is one of switch dimmer blindActuator remote sensor swi
+ pushButton threeStateSensor motionDetector keyMatic winMatic
+ smokeDetector
+
+ Without these attributes fhem won't be able to decode device messages
+ appropriately.
+
+ Notes
+
+ - If the interface is a CUL device, the rfmode
+ attribute of the corresponding CUL/CUN device must be set to HomeMatic.
+ Note: this mode is BidCos/Homematic only, you will not receive
+ FS20/HMS/EM/S300 messages via this device. Previously defined FS20/HMS
+ etc devices must be assigned to a different input device (CUL/FHZ/etc).
+
+ - Currently supported device families: remote, switch, dimmer,
+ blindActuator, motionDetector, smokeDetector, threeStateSensor,
+ THSensor, winmatic. Special devices: KS550, HM-CC-TC and the KFM100.
+
+ - Device messages can only be interpreted correctly if the device type is
+ known. fhem will extract the device type from a "pairing request"
+ message, even if it won't respond to it (see hmPairSerial and hmPairForSec to enable pairing).
+ As an alternative, set the correct subType and model attributes, for a
+ list of possible subType values see "attr hmdevice ?".
+
+ - The so called "AES-Encryption" is in reality a signing request: if it is
+ enabled, an actor device will only execute a received command, if a
+ correct answer to a request generated by the actor is received. This
+ means:
+
+ - Reaction to commands is noticably slower, as 3 messages are sent
+ instead of one before the action is processed by the actor.
+ - Every command and its final ack from the device is sent in clear,
+ so an outside observer will know the status of each device.
+ - The firmware implementation is buggy: the "toggle" event is executed
+ before the answer for the signing request is received, at
+ least by some switches (HM-LC-Sw1-Pl and HM-LC-SW2-PB-FM).
+ - The HMLAN configurator will answer signing
+ requests by itself, and if it is configured with the 3-byte address
+ of a foreign CCU which is still configurerd with the default
+ password, it is able to answer signing requests correctly.
+ - AES-Encryption is not useable with a CUL device as the interface,
+ but it is supported with a HMLAN. Due to the issues above I do not
+ recommend using Homematic encryption at all.
+
+
+
+
+
+
+ Set
+
+ Note: devices which are normally send-only (remote/sensor/etc) must be set
+ into pairing/learning mode in order to receive the following commands.
+
+
+
+ Universal commands (available to most hm devices):
+
+ - actiondetect <[hhh:mm]|off>
+ outdated command. This functionality is started by entering or modify of the attribute actCycle. see attribure section for details
+
+ - clear <[readings|msgEvents]>
+ A set of variables can be removed.
+
+ readings: all readings will be deleted. Any new reading will be added usual. May be used to eliminate old data
+ msgEvents: all message event counter will be removed. Also commandstack will be cleared.
+
+
+ - getConfig
+ Will read major configuration items stored in the HM device. Executed
+ on a channel it will read pair Inforamtion, List0, List1 and List3 of
+ the 1st internal peer. Furthermore the peerlist will be retrieved for
+ teh given channel. If executed on a device the command will get the
+ above info or all assotated channels. Not included will be the
+ configuration for additional peers. The command is a shortcut
+ for a selection of other commands.
+
+ - getRegRaw [List0|List1|List2|List3|List4|List5|List6]
+ <peerChannel>
+
+ Read registerset in raw format. Description of the registers is beyond
+ the scope of this documentation.
+
+ Registers are structured in so called lists each containing a set of
+ registers.
+
+ List0: device-level settings e.g. CUL-pairing or dimmer thermal limit
+ settings.
+
+ List1: per channel settings e.g. time to drive the blind up and
+ down.
+
+ List3: per 'link' settings - means per peer-channel. This is a lot of
+ data!. It controlls actions taken upon receive of a trigger from the
+ peer.
+
+ List4: settings for channel (button) of a remote
+
+ <PeerChannel> paired HMid+ch, i.e. 4 byte (8 digit) value like
+ '12345601'. It is mendatory for List 3 and 4 and can be left out for
+ List 0 and 1.
+
+ 'all' can be used to get data of each paired link of the channel.
+
+ 'selfxx' can be used to address data for internal channels (associated
+ with the build-in switches if any). xx is the number of the channel in
+ decimal.
+
+ Note1: execution depends on the entity. If List1 is requested on a
+ device rather then a channel the command will retrieve List1 for all
+ channels assotiated. List3 with peerChannel = all will get all link
+ for all channel if executed on a device.
+
+ Note2: for 'sender' see remote
+
+ Note3: the information retrieval may take a while - especially for
+ devices with a lot of channels and links. It may be necessary to
+ refresh the web interface manually to view the results
+
+ Note4: the direct buttons on a HM device are hidden by default.
+ Nevertheless those are implemented as links as well. To get access to
+ the 'internal links' it is necessary to issue 'set <name> regSet
+ intKeyVisib 1' or 'set <name> setRegRaw List0 2 81'. Reset it
+ by replacing '81' with '01' example:
+
+
+ set mydimmer getRegRaw List1
+ set mydimmer getRegRaw List3 all
+
+
+ - getSerial
+ Read serial number from device and write it to attribute serialNr.
+
+ - pair
+ Pair the device with a known serialNumber (e.g. after a device reset)
+ to FHEM Central unit. FHEM Central is usualy represented by CUL/CUNO,
+ HMLAN,...
+ If paired, devices will report status information to
+ FHEM. If not paired, the device won't respond to some requests, and
+ certain status information is also not reported. Paring is on device
+ level. Channels cannot be paired to central separate from the device.
+ See also getPair and
+ unpair.
+ Don't confuse pair (to a central) with peer (channel to channel) with
+ peerChan.
+ - peerBulk
+ peerBulk will add peer channels to the channel. All channels in the
+ list will be added. This includes that the parameter and behavior
+ defined for this 'link' will return to the defaults. peerBulk is only
+ meant to add peers. More suffisticated funktionality as provided by
+ peerChan is not supported. peerBulk
+ will only add channels in 'single' button mode.
+ Also note that peerBulk will not delete any existing peers, just add
+ and re-add given peers.
+ Main purpose of this command is the usage for re-store data to a
+ device. It is recommended to restore register configuration utilising
+ regBulk
+ Example:
+
+ set myChannel peerBulk 12345601,
+ set myChannel peerBulk self01,self02,FB_Btn_04,FB_Btn_03,
+
+
+ - regRaw [List0|List1|List2|List3|List4] <addr> <data>
+ replaced by regBulk
+ - regBulk <reg List>:<peer> <addr1:data1> <addr2:data2>...
+
+ This command will replace the former regRaw. It allows to set register
+ in raw format. Its main purpose is to restore a complete register list
+ to values secured before.
+ Values may be read by getConfig. The
+ resulting readings can be used directly for this command.
+ <reg List> is the list data should be written to. Format could be
+ '00', 'RegL_00', '01'...
+ <peer> is an optional adder in case the list requires a peer.
+ The peer can be given as channel name or the 4 byte (8 chars) HM
+ channel ID.
+ <addr1:data1> is the list of register to be written in hex
+ format.
+ Example:
+
+ set myChannel regBulk RegL_00: 02:01 0A:17 0B:43 0C:BF 15:FF 00:00
+ RegL_03:FB_Btn_07
+ 01:00 02:00 03:00 04:32 05:64 06:00 07:FF 08:00 09:FF 0A:01 0B:44 0C:54 0D:93 0E:00 0F:00 11:C8 12:00 13:00 14:00 15:00 16:00 17:00 18:00 19:00 1A:00 1B:00 1C:00 1D:FF 1E:93 1F:00 81:00 82:00 83:00 84:32 85:64 86:00 87:FF 88:00 89:FF 8A:21 8B:44 8C:54 8D:93 8E:00 8F:00 91:C8 92:00 93:00 94:00 95:00 96:00 97:00 98:00 99:00 9A:00 9B:00 9C:00 9D:05 9E:93 9F:00 00:00
+ set myblind regBulk 01 0B:10
+ set myblind regBulk 01 0C:00
+
+ myblind will set the max drive time up for a blind actor to 25,6sec
+ - regSet <regName> <value> <peerChannel>
+ For some major register a readable version is implemented supporting
+ register names <regName> and value conversionsing. Only a subset
+ of register can be supproted.
+ <value> is the data in human readable manner that will be written
+ to the register.
+ <peerChannel> is required if this register is defined on a per
+ 'peerChan' base. It can be set to '0' other wise.See getRegRaw for full description
+ Supported register for a device can be explored using
+
+ Condensed register description will be printed
+ using
+
+
+ - reset
+ Factory reset the device. You need to pair it again to use it with
+ fhem.
+
+ - sign [on|off]
+ Activate or deactivate signing (also called AES encryption, see the note above). Warning: if the device is attached via
+ a CUL, you won't be able to switch it (or deactivate signing) from
+ fhem before you reset the device directly.
+
+ - statusRequest
+ Update device status. For multichannel devices it should be issued on
+ an per channel base
+
+ - unpair
+ "Unpair" the device, i.e. make it available to pair with other master
+ devices. See pair for description.
+ - virtual <number of buttons>
+ configures a defined curcuit as virtual remote controll. Then number
+ of button being added is 1 to 255. If the command is issued a second
+ time for the same entity additional buttons will be added.
+ Example for usage:
+
+ define vRemote CUL_HM 100000 # the selected HMid must not be in use
+ set vRemote virtual 20 # define 20 button remote controll
+ set vRemote_Btn4 peerChan 0 <actorchannel> # peers Button 4 and 5 to the given channel
+ set vRemote_Btn4 press
+ set vRemote_Btn5 press long
+
+ see also press
+
+
+
+
+ subType (i.e family) dependent commands:
+
+
+ - switch
+
+ - on - set the switch on
+ - off - set the switch off
+ - on-for-timer <sec> -
+ set the switch on for the given seconds [0-85825945].
Note:
+ off-for-timer like FS20 is not supported. It needs to be programmed
+ on link level.
+ - on-till <time> - set the switch on for the given end time.
+ set <name> on-till 20:32:10
+ Currently a max of 24h is supported with endtime.
+
+ - press <[short|long]><[on|off]>
+ simulate a press of the local button or direct connected switch of the actor.
+ [short|long] choose whether to simulate a short or long press of the button.
+ [on|off] is relevant only for devices with direct buttons per channel.
+ Those are available for dimmer and blind-actor, usually not for switches
+
+ - toggle - toggle the switch.
+
+
+
+ - dimmer, blindActuator
+ Dimmer may support virtual channels. Those are autocrated if applicable. Usually there are 2 virtual channels
+ in addition to the primary channel. Virtual dimmer channels are inactive by default but can be used in
+ in parallel to the primay channel to control light.
+ Virtual channels have default naming SW_V. e.g. Dimmer_SW1_V1 and Dimmer_SW1_V2.
+ Dimmer virtual channels are completely different from FHEM virtual buttons and actors but
+ are part of the HM device. Documentation and capabilities for virtual channels is out of scope.
+
+ - 0 - 100 [on-time] [ramp-time]
+ set the actuator to the given value (in percent)
+ with a resolution of 0.5.
+ Optional for dimmer on-time and ramp time can be choosen, both in seconds with 0.1s granularity.
+ On-time is analog "on-for-timer".
+ Ramp-time default is 2.5s, 0 means instantanous
+
+ - on set level to 100%
+ - off set level to 0%
+ - press <[short|long]><[on|off]>
+ - toggle - toggle between off and the last on-value
+ - on-for-timer <sec> - Dimmer only!
+ - on-till <time> - Dimmer only!
+ - stop - stop motion or dim ramp
+ - up [changeValue] [ontime] [ramptime] dim up one step
+ - down [changeValue] [ontime] [ramptime] dim up one step
+
+
+
+ - remotes, pushButton
+ This class of devices does not react on requests unless they are put
+ to learn mode. FHEM obeys this behavior by stacking all requests until
+ learn mode is detected. Manual interaction of the user is necessary to
+ activate learn mode. Whether commands are pending is reported on
+ device level with parameter 'protCmdPend'.
+
+ - peerChan <btn_no> <hmDevice> [single|dual]
+ [set|unset] [actor|remote]
+
+ peerChan will establish a connection between a sender-channel and
+ an actuator-channel called link in HM nomenclatur. Peering must not be
+ confused with pairing.
+ Pairing refers to assign a device to the central.
+ Peering refers to virtally connect two channels.
+ Peering allowes direkt interaction between sender and aktor without
+ the necessity of a CCU
+ Peering a sender-channel causes the sender to expect an ack from -each-
+ of its peers after sending a trigger. It will give positive feedback (e.g. LED green)
+ only if all peers acknowledged.
+ Peering an aktor-channel will setup a parameter set which defines the action to be
+ taken once a trigger from -this- peer arrived. In other words an aktor will
+ - process trigger from peers only
+ - define the action to be taken dedicated for each peer's trigger
+ An actor channel will setup a default action upon peering - which is actor dependant.
+ It may also depend whether one or 2 buttons are peered in one command.
+ A swich may setup oen button for 'on' and the other for 'off' if 2 button are
+ peered. If only one button is peered the funktion will likely be 'toggle'.
+ The funtion can be modified by programming the register (aktor dependant).
+
+ Even though the command is executed on a remote or push-button it will
+ as well take effect on the actuator directly. Both sides' peering is
+ virtually independant and has different impact on sender and receiver
+ side.
+
+ Peering of one actuator-channel to multiple sender-channel as
+ well as one sender-channel to multiple Actuator-channel is
+ possible.
+
+ <hmDevice> is the actuator-channel to be peered.
+
+ <btn_no> is the sender-channel (button) to be peered. If
+ 'single' is choosen buttons are counted from 1. For 'dual' btn_no is
+ the number of the Button-pair to be used. I.e. '3' in dual is the
+ 3rd button pair correcponding to button 5 and 6 in single mode.
+
+ If the command is executed on a channel the btn_no is ignored.
+
+ [single|dual]: this mode impacts the default behavior of the
+ Actuator upon using this button. E.g. a dimmer can be learned to a
+ single button or to a button pair.
+
+ 'dual' (default) Button pairs two buttons to one actuator. With a
+ dimmer this means one button for dim-up and one for dim-down.
+
+ 'single' uses only one button of the sender. It is useful for e.g. for
+ simple switch actuator to toggle on/off. Nevertheless also dimmer can
+ be learned to only one button.
+
+ 'set' will setup peering for the channels
+
+ 'unset' will remove the peering for the channels
+
+ [actor|remote|both] limits the execution to only actor or only remote.
+ This gives the user the option to redo the peering on the remote
+ channel while the settings in the actor will not be removed.
+
+ Example:
+
+
+ set myRemote peerChan 2 mySwActChn single set #peer second button to an actuator channel
+ set myRmtBtn peerChan 0 mySwActChn single set #myRmtBtn is a button of the remote. '0' is not processed here
+ set myRemote peerChan 2 mySwActChn dual set #peer button 3 and 4
+ set myRemote peerChan 3 mySwActChn dual unset #remove peering for button 5 and 6
+ set myRemote peerChan 3 mySwActChn dual unset aktor #remove peering for button 5 and 6 in actor only
+ set myRemote peerChan 3 mySwActChn dual set remote #peer button 5 and 6 on remote only. Link settings il mySwActChn will be maintained
+
+
+
+
+
+
+ - virtual
+
+ - peerChan see remote
+ - press [long|short]
+ simulates a button press short (default) or long. Note that the current
+ implementation will not specify the duration for long. Only one trigger
+ will be sent of type "long".
+
+
+
+ - smokeDetector
+ Note: All these commands work right now only if you have more then one
+ smoekDetector, and you peered them to form a group. For issuing the
+ commands you have to use the master of this group, and currently you
+ have to guess which of the detectors is the master.
+ smokeDetector can be setup to teams using
+ peerChan. You need to peer all
+ team-members to the master. Don't forget to also peerChan the master
+ itself to the team - i.e. peer it to itself! doing that you have full
+ controll over the team and don't need to guess.
+
+ - test - execute a network test
+ - alarmOn - initiate an alarm
+ - alarmOff - switch off the alarm
+
+
+ - 4Dis (HM-PB-4DIS-WM)
+
+ - text <btn_no> [on|off] <text1> <text2>
+ Set the text on the display of the device. To this purpose issue
+ this set command first (or a number of them), and then choose from
+ the teach-in menu of the 4Dis the "Central" to transmit the data.
+ Example:
+
+
+ set 4Dis text 1 on On Lamp
+ set 4Dis text 1 off Kitchen Off
+
+
+
+
+
+ - Climate-Control (HM-CC-TC)
+
+ - day-temp <tmp>
+ night-temp <tmp>
+ party-temp <tmp>
+ desired-temp <tmp>
+ Set different temperatures. Temp must be between 6 and 30
+ Celsius, and precision is half a degree.
+ - tempListSat HH:MM temp ... 24:00 temp
+ tempListSun HH:MM temp ... 24:00 temp
+ tempListMon HH:MM temp ... 24:00 temp
+ tempListTue HH:MM temp ... 24:00 temp
+ tempListThu HH:MM temp ... 24:00 temp
+ tempListWed HH:MM temp ... 24:00 temp
+ tempListFri HH:MM temp ... 24:00 temp
+ Specify a list of temperature intervals. Up to 24 intervals can be
+ specified for each week day, the resolution is 10 Minutes. The
+ last time spec must always be 24:00.
+ Example: set th tempListSat 06:00 19 23:00 22.5 24:00 19
+ Meaning: until 6:00 temperature shall be 19, from then until 23:00 temperature shall be
+ 22.5, thereafter until midnight, 19 degrees celsius is desired.
+ - displayMode [temp-only|temp-hum]
+ displayTemp [actual|setpoint]
+ displayTempUnit [celsius|fahrenheit]
+ controlMode [manual|auto|central|party]
+ decalcDay <day>
+
+
+ - OutputUnit (HM-OU-LED16)
+
+ - led [off|red|green|yellow]
+ switches the LED of the channel to the color. If the command is
+ executed on a device it will set all LEDs to the specified
+ color.
+ For Expert all LEDs can be set individual by providing a 8-digit hex number to the device.
+ - ilum <brightness><duration>
+ <brightness> [0-15] of backlight.
+ <duration> [0-127] in sec. 0 is permanent 'on'.
+
+
+
+ - OutputUnit (HM-OU-CFM-PL)
+
+ - led <color>[,<color>..]
+ Possible colors are [redL|greenL|yellowL|redS|greenS|yellowS]. A
+ sequence of colors can be given separating the color entries by ','.
+ White spaces must not be used in the list. 'S' indicates short and
+ 'L' long ilumination.
+ - playTone <MP3No>[,<MP3No>..]
+ Play a series of tones. List is to be entered separated by ','. White
+ spaces must not be used in the list.
+
+
+ - HM-RC-19xxx
+
+ - alarm <count>
+ issue an alarm message to the remote
+ - service <count>
+ issue an service message to the remote
+ - symbol <symbol> [set|unset]
+ activate a symbol as available on the remote.
+ - beep [off|1|2|3]
+ activate tone
+ - backlight [off|on|slow|fast]
+ activate backlight
+ - display <text> comma unit tone backlight <symbol(s)>
+
+ control display of the remote
+ <text> : up to 5 chars
+ comma : 'comma' activates the comma, 'no' leaves it off
+ [unit] : set the unit symbols.
+ [off|Proz|Watt|x3|C|x5|x6|x7|F|x9|x10|x11|x12|x13|x14|x15]. Currently
+ the x3..x15 display is not tested.
+
+ tone : activate one of the 3 tones [off|1|2|3]
+
+ backlight: activate backlight flash mode [off|on|slow|fast]
+
+ <symbol(s)> activate symbol display. Multople symbols can be
+ acticated at the same time, concatinating them comma separated. Don't
+ use spaces here. Possiblesymbols are
+
+ [bulb|switch|window|door|blind|scene|phone|bell|clock|arrowUp|arrowDown]
+ Example:
+
+ # "hello" in display, symb bulb on, backlight, beep
+ set FB1 display Hello no off 1 on bulb
+ # "1234,5" in display with unit 'W'. Symbols scene,phone,bell and
+ # clock are active. Backlight flashing fast, Beep is second tone
+ set FB1 display 12345 comma Watt 2 fast scene,phone,bell,clock
+
+
+
+
+ - keyMatic
+ The Keymatic uses the AES signed communication. Therefore the control
+ of the Keymatic is only together with the HM-LAN adapter possible. But
+ the CUL can read and react on the status information of the
+ Keymatic.
+
+ - lock
+ The lock bolt moves to the locking position
+ - unlock [sec]
+ The lock bolt moves to the unlocking position.
+ [sec]: Sets the delay in seconds after the lock automatically locked again.
+ 0 - 65535 seconds
+ - open [sec]
+ Unlocked the door so that the door can be opened.
+ [sec]: Sets the delay in seconds after the lock automatically locked
+ again. 0 - 65535 seconds
+ - inhibit [on|off]
+ Block / unblock all directly peered remotes and the hardware buttons of the
+ keyMatic. If inhibit set on, the door lock drive can be controlled only by
+ FHEM.
+ Examples:
+
+ # Lock the lock
+ set keymatic lock
+ # open the door and relock the lock after 60 seconds
+ set keymatic unlock 60
+
+
+
+
+
+ - winMatic
+ winMatic provides 2 channels, one for the window control and a second
+ for the accumulator.
+
+ - level <level> <relockDelay> <speed>
+ set the level.
+ <level>: range is 0 to 100%
+ <relockDelay>: range 0 to 65535 sec. 'ignore' can be used to igneore the value alternaly
+ <speed>: range is 0 to 100%
+
+ - stop
+ stop movement
+
+
+ - HM-Sys-sRP-Pl
+ setRepeat => "[no1..36] [bdcast-yes|no]"
+
+ - setRepeat <entry> <sender> <receiver> <broadcast>
+ <entry> [1..36] entry number in repeater table. The repeater can handle up to 36 entries.
+ <sender> name or HMID of the sender or source which shall be repeated
+ <receiver> name or HMID of the receiver or destination which shall be repeated
+ <broadcast> [yes|no] determines whether broadcast from this ID shall be repeated
+
+ short application:
+ setRepeat setAll 0 0 0
+ will rewrite the complete list to the deivce. Data will be taken from attribut repPeer.
+ attribut repPeer is formated:
+ src1:dst1:[y/n],src2:dst2:[y/n],src2:dst2:[y/n],...
+ up to 36entries can be applied.
+
+
+
+
+ Debugging:
+
+
+
+
+
+ Get
+
+ - configSave <filename>
+ Saves the configuration of an entity into a file. Data is stored in a
+ format to be executed from fhem command prompt.
+ The file is located in the fhem home directory aside of fhem.cfg. Data
+ will be stored cumulative - i.e. new data will be appended to the
+ file. It is up to the user to avoid duplicate storage of the same
+ entity.
+ Target of the data is ONLY the HM-device information which is located
+ IN the HM device. Explicitely this is the peer-list and the register.
+ With the register also the peering is included.
+ The file is readable and editable by the user. Additionaly timestamps
+ are stored to help user to validate.
+ Restrictions:
+ Even though all data of the entity will be secured to the file FHEM
+ stores the data that is avalilable to FHEM at time of save!. It is up
+ to the user to read the data from the HM-hardware prior to execution.
+ See recommended flow below.
+ This command will not store any FHEM attributes o device definitions.
+ This continues to remain in fhem.cfg.
+ Furthermore the secured data will not automatically be reloaded to the
+ HM-hardware. It is up to the user to perform a restore.
+ As with other commands also 'configSave' is best executed on a device
+ rather then on a channel. If executed on a device also the assotiated
+ channel data will be secured.
+
+ Recommended work-order for device 'HMdev':
+ set HMdev clear msgEvents # clear old events to better check flow
+ set HMdev getConfig # read device & channel inforamtion
+ # wait untill operation is complete
+ # protState should be CMDs_done
+ # there shall be no warnings amongst prot... variables
+ get configSave myActorFile
+
+
+ - param <paramName>
+ returns the content of the relevant parameter for the entity.
+ Note: if this command is executed on a channel and 'model' is
+ requested the content hosting device's 'model' will be returned.
+
+ - reg <addr> <list> <peerID>
+ returns the value of a register. The data is taken from the storage in FHEM and not read directly outof the device. If register content is not present please use getConfig, getReg in advance.
+
+ <addr> address in hex of the register. Registername can be used alternaly if decoded by FHEM. "all" will return all decoded register for this entity in one list.
+ <list> list from which the register is taken. If rgistername is used list is ignored and can be set to 0.
+ <peerID> identifies the registerbank in case of list3 and list4. It an be set to dummy if not used.
+
+ - regList
+ returns a list of register that are decoded by FHEM for this device.
+ Note that there could be more register implemented for a device.
+
+
+
+
+ Attributes
+
+ - eventMap
+ - do_not_notify
+ - ignore
+ - dummy
+ - showtime
+ - loglevel
+ - readingFnAttributes
+ - actCycle
+ actCycle <[hhh:mm]|off>
+ Supports 'alive' or better 'not alive' detection for devices. [hhh:mm] is the maxumin silent time for the device. Upon no message received in this period an event will be raised "<device> is dead". If the device sends again another notification is posted "<device> is alive".
+ This actiondetect will be autocreated for each device with build in cyclic status report.
+ Controlling entity is a pseudo device "ActionDetector" with HMId "000000".
+ Due to performance considerations the report latency is set to 600sec (10min). It can be controlled by the attribute "actCycle" of "ActionDetector".
+ Once entered to the supervision the HM device has 2 attributes:
+
+ actStatus: activity status of the device
+ actCycle: detection period [hhh:mm]
+
+ The overall function can be viewed checking out the "ActionDetector" entity. The status of all entities is present in the READING section.
+ Note: This function can be enabled for devices with non-cyclic messages as well. It is up to the user to enter a reasonable cycletime.
+
+ - expert
+ This attribut controls the visibility of the readings. This attibute controlls
+ the presentation of device parameter in the readings.
+ 3 level can be choosen:
+
+ 0_off: standart level. Display commonly used parameter
+ 1_on: enhanced level. Display all decoded device parameter
+ 2_full: display all parameter plus raw register information as well.
+
+ If expert is applied a device it is used for assotiated channels.
+ It can be overruled if expert attibute is also applied to the channel device.
+ Make sure to check out attribut showInternalValues in the global values as well.
+ extert takes benefit of the implementation.
+ Nevertheless - by definition - showInternalValues overrules expert.
+
+ - model,
+ subType
+ These attributes are set automatically after a successful pairing.
+ They are not supposed to be set by hand, and are necessary in order to
+ correctly interpret device messages or to be able to send them.
+ - rawToReadable
+ Used to convert raw KFM100 values to readable data, based on measured
+ values. E.g. fill slowly your container, while monitoring the
+ values reported with inform. You'll see:
+
+ 10 (at 0%)
+ 50 (at 20%)
+ 79 (at 40%)
+ 270 (at 100%)
+
+ Apply these values with: "attr KFM100 rawToReadable 10:0 50:20 79:40 270:100".
+ fhem will do a linear interpolation for values between the bounderies.
+
+ - unit
+ set the reported unit by the KFM100 if rawToReadable is active. E.g.
+ attr KFM100 unit Liter
+
+ - autoReadReg
+ '1' will execute a getConfig for the device automatically after each reboot of FHEM.
+ '2' like '1' plus execute after power_on.
+ '3' includes '2' plus updates on writes to the device
+ Execution will be delayed in order to prevent congestion at startup. Therefore the update
+ of the readings and the display will be delayed depending on the sice of the database.
+ Recommendations and constrains upon usage:
+
+ use this attribute on the device or channel 01. Do not use it separate on each channel
+ of a multi-channel device to avoid duplicate execution
+ usage on devices which only react to 'config' mode is not recommended since executen will
+ not start until config is triggered by the user
+ usage on devices which support wakeup-mode is usefull. But consider that execution is delayed
+ until the device "wakes up".
+
+
+
+
+
+ Generated events:
+
+ - KS550/HM-WDS100-C6-O:
+ T: $t H: $h W: $w R: $r IR: $ir WD: $wd WDR: $wdr S: $s B: $b
+ temperature $t
+ humidity $h
+ windSpeed $w
+ windDirection $wd
+ windDirRange $wdr
+ rain $r
+ isRaining $ir
+ sunshine $s
+ brightness $b
+ unknown $p
+
+ - HM-CC-TC:
+ T: $t H: $h
+ measured-temp $t
+ humidity $h
+ actuator $vp %
+ desired-temp $dTemp
+ desired-temp-manu $dTemp
+ windowopen-temp-%d %.1f (sensor:%s)
+ tempList$wd hh:mm $t hh:mm $t ...
+ displayMode temp-[hum|only]
+ displayTemp [setpoint|actual]
+ displayTempUnit [fahrenheit|celsius]
+ controlMode [manual|auto|central|party]
+ decalcDay [Sat|Sun|Mon|Tue|Wed|Thu|Fri]
+ tempValveMode [Auto|Closed|Open|unknown]
+ param-change offset=$o1, value=$v1
+ ValveErrorPosition_for_$dname $vep %
+ ValveOffset_for_$dname : $of %
+ ValveErrorPosition $vep %
+ ValveOffset $of %
+ time-request
+
+ - HM-CC-VD:
+ $vp %
+ battery:[critical|low|ok]
+ motorErr:[ok|blocked|loose|adjusting range too small|opening|closing|stop]
+ ValvePosition:$vp %
+ ValveErrorPosition:$vep %
+ ValveOffset:$of %
+ ValveDesired:$vp % # set by TC
+ operState:[errorTargetNotMet|onTarget|adjusting] # operational condition
+ operStateErrCnt:$cnt # number of failed settings
+
+ - KFM100:
+ $v
+ $cv,$unit
+ rawValue:$v
+ Sequence:$seq
+ content:$cv,$unit
+
+ - HM-LC-BL1-PB-FM:
+ motor: [opening|closing]
+
+ - HM-SEC-SFA-SM:
+ powerError [on|off]
+ sabotageError [on|off]
+ battery: [critical|low|ok]
+
+ - HM-LC-SW1-BA-PCB:
+ battery: [low|ok]
+
+ - HM-OU-LED16
+ color $value # hex - for device only
+ $value # hex - for device only
+ color [off|red|green|orange] # for channel
+ [off|red|green|orange] # for channel
+
+ - HM-OU-CFM-PL
+ [on|off|$val]
+
+ - switch/dimmer/blindActuator:
+ $val
+ powerOn [on|off|$val]
+ [unknown|motor|dim] [up|down|stop]:$val
+
+ - dimmer:
+ overload [on|off]
+ overheat [on|off]
+ reduced [on|off]
+ dim: [up|down|stop]
+
+ - remote/pushButton/outputUnit
+ (to $dest) is added if the button is peered and does not send to broadcast
+ Release is provided for peered channels only
+ Btn$x onShort
+ Btn$x offShort
+ Btn$x onLong $counter
+ Btn$x offLong $counter
+ Btn$x onLongRelease $counter
+ Btn$x offLongRelease $counter
+ Btn$x onShort (to $dest)
+ Btn$x offShort (to $dest)
+ Btn$x onLong $counter (to $dest)
+ Btn$x offLong $counter (to $dest)
+ Btn$x onLongRelease $counter (to $dest)
+ Btn$x offLongRelease $counter (to $dest)
+
+ - remote/pushButton
+ battery [low|ok]
+ trigger [Long|Short]_$no trigger event from channel
+
+ - swi
+ Btn$x toggle
+ Btn$x toggle (to $dest)
+ battery: [low|ok]
+
+ - motionDetector
+ brightness:$b
+ alive
+ motion on (to $dest)
+ motionCount $cnt _next:$nextTr"-"[0x0|0x1|0x2|0x3|15|30|60|120|240|0x9|0xa|0xb|0xc|0xd|0xe|0xf]
+ cover [closed|open]
+ battery [low|ok]
+ devState_raw.$d1 $d2
+
+ - smokeDetector
+ [off|smoke-Alarm|alive] # for team leader
+ [off|smoke-forward|smoke-alarm] # for team members
+ SDteam [add|remove]_$dname
+ battery [low|ok]
+ smoke_detect on from $src
+ test:from $src
+
+ - threeStateSensor
+ [open|tilted|closed]]
+ [wet|damp|dry] #HM-SEC-WDS only
+ cover [open|closed] #HM-SEC-WDS only
+ alive yes
+ battery [low|ok]
+ contact [open|tilted|closed]
+ contact [wet|damp|dry] #HM-SEC-WDS only
+
+ - THSensor and HM-WDC7000
+ T: $t H: $h AP: $ap
+ temperature $t
+ humidity $h
+ airpress $ap #HM-WDC7000 only
+
+ - winMatic
+ [locked|$value]
+ motorError [no|TurnError|TiltError]
+ direction [no|up|down|undefined]
+ charge [trickleCharge|charge|dischange|unknown]
+ airing [inactiv|$air]
+ course [tilt|close]
+ airing [inactiv|$value]
+ contact tesed
+
+ - keyMatic
+ unknown:40
+ battery [low|ok]
+ uncertain [yes|no]
+ error [unknown|motor aborted|clutch failure|none']
+ lock [unlocked|locked]
+ [unlocked|locked|uncertain]
+
+
+
+
+=end html
+=cut
diff --git a/FHEM/10_EnOcean.pm b/FHEM/10_EnOcean.pm
index fbbd21648..6c9047fc1 100755
--- a/FHEM/10_EnOcean.pm
+++ b/FHEM/10_EnOcean.pm
@@ -1,10 +1,6 @@
##############################################
# $Id$
-# 2013-01-31 klaus.schauer
-# eltakoDimmer: dimspeed corrections ($dimTime)
-# Log $ll2: patch Rocker Switch logging
-# attr subDef: $subDef = $hash->{DEF} if attr subDef is not defined
-# Rocker Switch (PTM200): enable attr subDef
+
package main;
use strict;
@@ -18,15 +14,19 @@ sub EnOcean_Set($@);
sub EnOcean_MD15Cmd($$$);
sub EnOcean_GetMyDeviceId($);
-my %EnO_rorgname = ("F6"=>"switch", # RPS
- "D5"=>"contact", # 1BS
- "A5"=>"sensor", # 4BS
+my %EnO_rorgname = ("F6"=>"switch", # org 05, RPS
+ "D5"=>"contact", # org 06, 1BS
+ "A5"=>"sensor", # org 07, 4BS
);
my @EnO_ptm200btn = ("AI", "A0", "BI", "B0", "CI", "C0", "DI", "D0");
my %EnO_ptm200btn;
+# Peha House Control System (PHC)
+# PHC Gateway Commands
+my @EnO_phcCmd = ("switching", "dimming", "setpointShift", "setpointBasic", "controlVar", "fanStage");
+
# Some Manufacturers (e.g. Jaeger Direkt) also sell EnOcean products without an
-# intry in the table below. This table is only needed for A5 category devices
+# entry in the table below. This table is only needed for A5 category devices.
my %EnO_manuf = (
"001" => "Peha",
"002" => "Thermokon",
@@ -56,10 +56,81 @@ my %EnO_manuf = (
"01A" => "Res.",
"01B" => "Lutuo Technology",
"01C" => "CAN2GO",
+ "7FF" => "Multi user Manufacturer ID",
);
my %EnO_subType = (
+ "A5.02.01" => "tempSensor.01",
+ "A5.02.02" => "tempSensor.02",
+ "A5.02.03" => "tempSensor.03",
+ "A5.02.04" => "tempSensor.04",
+ "A5.02.05" => "tempSensor.05",
+ "A5.02.06" => "tempSensor.06",
+ "A5.02.07" => "tempSensor.07",
+ "A5.02.08" => "tempSensor.08",
+ "A5.02.09" => "tempSensor.09",
+ "A5.02.0A" => "tempSensor.0A",
+ "A5.02.0B" => "tempSensor.0B",
+ "A5.02.10" => "tempSensor.10",
+ "A5.02.11" => "tempSensor.11",
+ "A5.02.12" => "tempSensor.12",
+ "A5.02.13" => "tempSensor.13",
+ "A5.02.14" => "tempSensor.14",
+ "A5.02.15" => "tempSensor.15",
+ "A5.02.16" => "tempSensor.16",
+ "A5.02.17" => "tempSensor.17",
+ "A5.02.18" => "tempSensor.18",
+ "A5.02.19" => "tempSensor.19",
+ "A5.02.1A" => "tempSensor.1A",
+ "A5.02.1B" => "tempSensor.1B",
+ "A5.02.20" => "tempSensor.20",
+ "A5.02.30" => "tempSensor.30",
+ "A5.04.01" => "roomSensorControl.01",
+ "A5.04.02" => "tempHumiSensor.02",
+ "A5.06.01" => "lightSensor.01",
+ "A5.06.02" => "lightSensor.02",
+ "A5.07.01" => "occupSensor.01",
+ "A5.08.01" => "lightTempOccupSensor.01",
+ "A5.08.02" => "lightTempOccupSensor.02",
+ "A5.08.03" => "lightTempOccupSensor.03",
+ "A5.09.01" => "COSensor.01",
+ "A5.09.04" => "tempHumiCO2Sensor.01",
+ "A5.10.01" => "roomSensorControl.05",
+ "A5.10.02" => "roomSensorControl.05",
+ "A5.10.03" => "roomSensorControl.05",
+ "A5.10.04" => "roomSensorControl.05",
+ "A5.10.05" => "roomSensorControl.05",
+ "A5.10.06" => "roomSensorControl.05",
+ "A5.10.07" => "roomSensorControl.05",
+ "A5.10.08" => "roomSensorControl.05",
+ "A5.10.09" => "roomSensorControl.05",
+ "A5.10.0A" => "roomSensorControl.05",
+ "A5.10.0B" => "roomSensorControl.05",
+ "A5.10.0C" => "roomSensorControl.05",
+ "A5.10.0D" => "roomSensorControl.05",
+ "A5.10.10" => "roomSensorControl.01",
+ "A5.10.11" => "roomSensorControl.01",
+ "A5.10.12" => "roomSensorControl.01",
+ "A5.10.13" => "roomSensorControl.01",
+ "A5.10.14" => "roomSensorControl.01",
+ "A5.10.15" => "roomSensorControl.02",
+ "A5.10.16" => "roomSensorControl.02",
+ "A5.10.17" => "roomSensorControl.02",
+ "A5.12.00" => "autoMeterReading.00",
+ "A5.12.01" => "autoMeterReading.01",
+ "A5.12.02" => "autoMeterReading.02",
+ "A5.12.03" => "autoMeterReading.03",
+ "A5.13.01" => "weatherStation",
+ "A5.13.02" => "weatherStation",
+ "A5.13.03" => "weatherStation",
+ "A5.13.04" => "weatherStation",
+ "A5.13.05" => "weatherStation",
+ "A5.13.06" => "weatherStation",
"A5.20.01" => "MD15",
+ "A5.30.01" => "digitalInput.01",
+ "A5.30.02" => "digitalInput.02",
+ "A5.38.08" => "phcGateway",
+ "A5.3F.7F" => "manufProfile",
1 => "switch",
2 => "contact",
3 => "sensor",
@@ -70,20 +141,31 @@ my %EnO_subType = (
8 => "FBH",
9 => "FTF",
10 => "SR04",
+ 11 => "FRW",
+ 12 => "keycard",
);
my @EnO_models = qw (
other
MD15-FtL-HE
- SR04 SR04P SR04PT SR04PST SR04PMS
+ SR04 SR04P SR04T SR04PT SR04PMS SR04PS SR04PST
+ FT55
FAH60 FAH63 FIH63
FABH63 FBH63 FIBH63
- PM101
+ FAFT60 FIFT63AP
+ FMS14 FMS61
+ FSB12 FSB14 FSB61 FSB70
+ FSG70
+ FSM12 FSM61
+ FSR14 FSR61
FTF55
- FSB61
- FSM61
+ FTN14
+ FTS12
+ FUD12 FUD14 FUD61 FUD70
+ PM101
);
+# Initialize
sub
EnOcean_Initialize($)
{
@@ -91,6 +173,7 @@ EnOcean_Initialize($)
$hash->{Match} = "^EnOcean:";
$hash->{DefFn} = "EnOcean_Define";
+ $hash->{UndefFn} = "EnOcean_Undef";
$hash->{ParseFn} = "EnOcean_Parse";
$hash->{SetFn} = "EnOcean_Set";
$hash->{AttrFn} = "EnOcean_Attr";
@@ -98,7 +181,9 @@ EnOcean_Initialize($)
"showtime:1,0 loglevel:0,1,2,3,4,5,6 ".
"model:".join(",",@EnO_models)." ".
"subType:".join(",",values %EnO_subType)." ".
- "subDef actualTemp dimTime shutTime ".
+ "actualTemp dimTime dimValueOn manufID phcCmd ".
+ "rampTime shutTime subDef subDef0 subDefI ".
+ "switchMode switchType ".
$readingFnAttributes;
for(my $i=0; $i<@EnO_ptm200btn;$i++) {
@@ -146,8 +231,7 @@ EnOcean_Define($$)
return undef;
}
-
-#############################
+# Set
sub
EnOcean_Set($@)
{
@@ -157,7 +241,10 @@ EnOcean_Set($@)
my $updateState = 1;
my $name = $hash->{NAME};
my $st = AttrVal($name, "subType", "");
+ my $manufID = AttrVal($name, "manufID", "");
+ my $model = AttrVal($name, "model", "");
my $ll2 = GetLogLevel($name, 2);
+ my $sendCmd = "yes";
shift @a;
my $tn = TimeNow();
@@ -165,21 +252,27 @@ EnOcean_Set($@)
for(my $i = 0; $i < @a; $i++) {
my $cmd = $a[$i];
- #####################
- # See also http://www.oscat.de/community/index.php/topic,985.30.html
if($st eq "MD15") {
+ # Battery Powered Actuator (EEP A5-20-01)
+ # [Kieback&Peter MD15-FTL-xx]
+ # See also http://www.oscat.de/community/index.php/topic,985.30.html
+ # Maintenance commands (runInit, liftSet, valveOpen, valveClosed)
my %sets = (
"desired-temp" => "\\d+(\\.\\d)?",
"actuator" => "\\d+",
"unattended" => "",
"initialize" => "",
+ "runInit" => "",
+ "liftSet" => "",
+ "valveOpen" => "",
+ "valveClosed" => "",
);
my $re = $sets{$a[0]};
return "Unknown argument $cmd, choose one of ".join(" ", sort keys %sets)
if(!defined($re));
- return "Need a parameter" if($re && @a < 2);
+ return "Need a parameter" if ($re && @a < 2);
return "Argument $a[1] is incorrect (expect $re)"
- if($re && $a[1] !~ m/^$re$/);
+ if ($re && $a[1] !~ m/^$re$/);
$hash->{CMD} = $cmd;
$hash->{READINGS}{CMD}{TIME} = $tn;
@@ -194,11 +287,10 @@ EnOcean_Set($@)
$hash->{READINGS}{$cmd}{TIME} = $tn;
$hash->{READINGS}{$cmd}{VAL} = $arg;
- ###########################
-
- } elsif($st eq "eltakoDimmer") {
+ } elsif($st eq "eltakoDimmer" && $model ne "FSG70") {
+ # Dimmer
my $sendDimCmd=0;
- my $dimTime=AttrVal($name, "dimTime", 0);
+ my $dimTime=AttrVal($name, "dimTime", 1);
my $onoff=1;
my $subDef = AttrVal($name, "subDef", "$hash->{DEF}");
my $dimVal=$hash->{READINGS}{dimValue}{VAL};
@@ -215,6 +307,7 @@ EnOcean_Set($@)
# for eltako relative (0-100) (but not compliant to EEP because DB0.2
# is 0)
$dimVal=$a[1];
+ readingsSingleUpdate($hash,"dimValueStored",$dimVal,1);
shift(@a);
if(defined($a[1])) {
$dimTime=sprintf("%X",(($a[1]*2.55)-255)*-1);
@@ -225,6 +318,7 @@ EnOcean_Set($@)
} elsif($cmd eq "dimup") {
return "Usage: $cmd percent [dimspeed 1-100]" if(@a<2 or $a[1]>100);
$dimVal+=$a[1];
+ readingsSingleUpdate($hash,"dimValueStored",$dimVal,1);
shift(@a);
if(defined($a[1])) {
$dimTime=sprintf("%X",(($a[1]*2.55)-255)*-1);
@@ -235,6 +329,7 @@ EnOcean_Set($@)
} elsif($cmd eq "dimdown") {
return "Usage: $cmd percent [dimspeed 1-100]" if(@a<2 or $a[1]>100);
$dimVal-=$a[1];
+ readingsSingleUpdate($hash,"dimValueStored",$dimVal,1);
shift(@a);
if(defined($a[1])) {
$dimTime=sprintf("%X",(($a[1]*2.55)-255)*-1);
@@ -245,7 +340,23 @@ EnOcean_Set($@)
} elsif($cmd eq "on" || $cmd eq "B0") {
$dimTime=1;
$sendDimCmd=1;
- $dimVal=100;
+ my $dimValueOn = AttrVal($name, "dimValueOn", 100);
+ if ($dimValueOn eq "stored") {
+ $dimVal = ReadingsVal($name, "dimValueStored", 100);
+ if ($dimVal < 1) {
+ $dimVal = 100;
+ readingsSingleUpdate($hash, "dimValueStored", $dimVal, 1);
+ }
+ } elsif ($dimValueOn eq "last") {
+ $dimVal = ReadingsVal($name, "dimValueLast", 100);
+ if ($dimVal < 1) {
+ $dimVal = 100;
+ }
+ } else {
+ $dimVal = $dimValueOn;
+ if($dimValueOn > 100) { $dimVal = 100; }
+ if($dimValueOn < 1) { $dimVal = 1; }
+ }
} elsif($cmd eq "off" || $cmd eq "BI") {
$dimTime=1;
@@ -257,7 +368,6 @@ EnOcean_Set($@)
my $list = "dim:slider,0,1,100 dimup:slider,0,1,100 ".
"dimdown:slider,0,1,100 on off teach";
return SetExtensions($hash, $list, $name, @a);
-
}
if($sendDimCmd) {
@@ -295,6 +405,7 @@ EnOcean_Set($@)
# }
#
} elsif($st eq "eltakoShutter") {
+ # Shutter
my $shutTime=AttrVal($name, "shutTime", 0);
my $subDef = AttrVal($name, "subDef", "$hash->{DEF}");
my $shutCmd = 0x00;
@@ -334,45 +445,282 @@ EnOcean_Set($@)
shift(@a);
if($shutCmd || ($cmd eq "stop")) {
$updateState = 0;
- # EEP: A5/3F/7F Universal ???
my $data = sprintf("A5%02X%02X%02X%02X%s00",
0x00, $shutTime, $shutCmd, 0x08, $subDef);
IOWrite($hash, "000A0001", $data);
Log $ll2, "EnOcean: set $name $cmd";
}
- ###########################
- # Rocker Switch, simulate a PTM200 switch module
+ } elsif ($st eq "phcGateway") {
+ # PHC Gateway (EEP A5-38-08)
+ my $data;
+ my $phcCmd = AttrVal($name, "phcCmd", "");
+ my $phcCmdID;
+ my $setCmd = 0;
+ my $subDef = AttrVal($name, "subDef", "$hash->{DEF}");
+ my $time = 0;
+ if ($phcCmd eq "switching") {
+ # Switching
+ $phcCmdID = 1;
+ if($cmd eq "teach") {
+ $data = sprintf "A5%02X000000%s00", $phcCmdID, $subDef;
+ } elsif ($cmd eq "on" || $cmd eq "B0") {
+ $setCmd = 9;
+ if ($a[1]) {
+ return "Usage: $cmd [lock|unlock]" if (($a[1] ne "lock") && ($a[1] ne "unlock"));
+ $setCmd = $setCmd | 4 if ($a[1] eq "lock");
+ shift(@a);
+ }
+ $updateState = 0;
+ $data = sprintf "A5%02X%04X%02X%s00", $phcCmdID, $time, $setCmd, $subDef;
+ } elsif ($cmd eq "off" || $cmd eq "BI") {
+ $setCmd = 8;
+ if ($a[1]) {
+ return "Usage: $cmd [lock|unlock]" if (($a[1] ne "lock") && ($a[1] ne "unlock"));
+ $setCmd = $setCmd | 4 if ($a[1] eq "lock");
+ shift(@a);
+ }
+ $updateState = 0;
+ $data = sprintf "A5%02X%04X%02X%s00", $phcCmdID, $time, $setCmd, $subDef;
+ } else {
+ my $cmdList = "B0 BI off on teach";
+ return SetExtensions ($hash, $cmdList, $name, @a);
+ $updateState = 0;
+ $data = sprintf "A5%02X%04X%02X%s00", $phcCmdID, $time, $setCmd, $subDef;
+ }
+
+ } elsif ($phcCmd eq "dimming") {
+ # Dimming
+ $phcCmdID = 2;
+ my $dimVal = $hash->{READINGS}{dimValue}{VAL};
+ my $rampTime = AttrVal($name, "rampTime", 1);
+ my $sendDimCmd = 0;
+ $setCmd = 9;
+ my $subDef = AttrVal($name, "subDef", "$hash->{DEF}");
+ if ($cmd eq "teach") {
+ $data = sprintf "A5%02X000000%s00", $phcCmdID, $subDef;
+
+ } elsif ($cmd eq "dim") {
+ return "Usage: $cmd dim/% [rampTime/s lock|unlock]"
+ if((@a < 2) or ($a[1] < 0) or ($a[1] > 100) or ($a[1] !~ m/^[+-]?\d+$/));
+ # for eltako relative (0-100) (but not compliant to EEP because DB0.2 is 0)
+ # >> if manufID needed: set DB2.0
+ $dimVal = $a[1];
+ readingsSingleUpdate ($hash, "dimValueStored", $dimVal, 1);
+ shift(@a);
+ if (defined($a[1])) {
+ return "Usage: $cmd dim/% [rampTime/s lock|unlock]" if ($a[1] !~ m/^[+-]?\d+$/);
+ $rampTime = $a[1];
+ shift(@a);
+ }
+ $sendDimCmd = 1;
+
+ } elsif ($cmd eq "dimup") {
+ return "Usage: $cmd dim/% [rampTime/s lock|unlock]"
+ if((@a < 2) or ($a[1] < 0) or ($a[1] > 100) or ($a[1] !~ m/^[+-]?\d+$/));
+ $dimVal += $a[1];
+ readingsSingleUpdate ($hash, "dimValueStored", $dimVal, 1);
+ shift(@a);
+ if (defined($a[1])) {
+ return "Usage: $cmd dim/% [rampTime/s lock|unlock]" if ($a[1] !~ m/^[+-]?\d+$/);
+ $rampTime = $a[1];
+ shift(@a);
+ }
+ $sendDimCmd = 1;
+
+ } elsif ($cmd eq "dimdown") {
+ return "Usage: $cmd dim/% [rampTime/s lock|unlock]"
+ if((@a < 2) or ($a[1] < 0) or ($a[1] > 100) or ($a[1] !~ m/^[+-]?\d+$/));
+ $dimVal -= $a[1];
+ readingsSingleUpdate ($hash, "dimValueStored", $dimVal,1);
+ shift(@a);
+ if (defined($a[1])) {
+ return "Usage: $cmd dim/% [rampTime/s lock|unlock]" if ($a[1] !~ m/^[+-]?\d+$/);
+ $rampTime = $a[1];
+ shift(@a);
+ }
+ $sendDimCmd = 1;
+
+ } elsif ($cmd eq "on" || $cmd eq "B0") {
+ $rampTime = 1;
+ my $dimValueOn = AttrVal($name, "dimValueOn", 100);
+ if ($dimValueOn eq "stored") {
+ $dimVal = ReadingsVal($name, "dimValueStored", 100);
+ if ($dimVal < 1) {
+ $dimVal = 100;
+ readingsSingleUpdate ($hash, "dimValueStored", $dimVal, 1);
+ }
+ } elsif ($dimValueOn eq "last") {
+ $dimVal = ReadingsVal ($name, "dimValueLast", 100);
+ if ($dimVal < 1) { $dimVal = 100; }
+ } else {
+ if ($dimValueOn !~ m/^[+-]?\d+$/) {
+ $dimVal = 100;
+ } elsif ($dimValueOn > 100) {
+ $dimVal = 100;
+ } elsif ($dimValueOn < 1) {
+ $dimVal = 1;
+ } else {
+ $dimVal = $dimValueOn;
+ }
+ }
+ $sendDimCmd = 1;
+
+ } elsif ($cmd eq "off" || $cmd eq "BI") {
+ $dimVal = 0;
+ $rampTime = 1;
+ $setCmd = 8;
+ $sendDimCmd = 1;
+
+ } else {
+ my $cmdList = "dim:slider,0,1,100 B0 BI on off teach";
+ return SetExtensions ($hash, $cmdList, $name, @a);
+ }
+ if($sendDimCmd) {
+ if (defined($a[1])) {
+ return "Usage: $cmd dim/% [rampTime/s lock|unlock]" if (($a[1] ne "lock") && ($a[1] ne "unlock"));
+ if ($manufID eq "OOD") {
+ # Eltako devices: block dimming value
+ if ($a[1] eq "lock") { $setCmd = $setCmd | 4; }
+ } else {
+ # Dimming value relative
+ $setCmd = $setCmd | 4;
+ }
+ shift(@a);
+ } else {
+ if ($manufID ne "OOD") { $setCmd = $setCmd | 4; }
+ }
+ $a[0] = "on";
+ if ($dimVal > 100) { $dimVal=100; }
+ if ($dimVal <= 0) { $dimVal=0; $setCmd = 8; $a[0]="off"; }
+ if ($rampTime > 255) { $rampTime = 255; }
+ if ($rampTime < 0) { $rampTime = 0; }
+ $updateState = 0;
+ $data = sprintf "A502%02X%02X%02X%s00", $dimVal, $rampTime, $setCmd, $subDef;
+ }
+
+ } elsif ($phcCmd eq "setpointShift") {
+ $phcCmdID = 3;
+ if($cmd eq "teach") { $data = sprintf "A5%02X000000%s00", $phcCmdID, $subDef;
+ } else {
+
+ }
+
+ } elsif ($phcCmd eq "setpointBasic") {
+ $phcCmdID = 4;
+ if($cmd eq "teach") { $data = sprintf "A5%02X000000%s00", $phcCmdID, $subDef;
+ } else {
+
+ }
+
+ } elsif ($phcCmd eq "controlVar") {
+ $phcCmdID = 5;
+ if($cmd eq "teach") { $data = printf "A5%02X000000%s00", $phcCmdID, $subDef;
+ } else {
+
+ }
+
+ } elsif ($phcCmd eq "fanStage") {
+ $phcCmdID = 6;
+ if($cmd eq "teach") { $data = sprintf "A5%02X000000%s00", $phcCmdID, $subDef;
+ } else {
+
+ }
+
+ }
+ # write phcGateway command
+ # len: 0x000A optlen: 0x00 pakettype: 0x01(radio)
+ IOWrite($hash, "000A0001", $data);
+ Log $ll2, "EnOcean: set $name $cmd";
+
} else {
+ # Rocker Switch, simulate a PTM200 switch module
+ # separate first and second action
my ($c1,$c2) = split(",", $cmd, 2);
+ # check values
if(!defined($EnO_ptm200btn{$c1}) ||
($c2 && !defined($EnO_ptm200btn{$c2}))) {
my $list = join(" ", sort keys %EnO_ptm200btn);
return SetExtensions($hash, $list, $name, @a);
}
+ my $channelA = ReadingsVal($name, "channelA", undef);
+ my $channelB = ReadingsVal($name, "channelB", undef);
+ my $channelC = ReadingsVal($name, "channelC", undef);
+ my $channelD = ReadingsVal($name, "channelD", undef);
+ my $subDef = AttrVal($name, "subDef", "$hash->{DEF}");
+ my $subDef0 = AttrVal($name, "subDef0", "$hash->{DEF}");
+ my $subDefI = AttrVal($name, "subDefI", "$hash->{DEF}");
+ my $switchMode = AttrVal($name, "switchMode", "switch");
+ my $switchType = AttrVal($name, "switchType", "direction");
+ # first action
+ if ($switchType eq "central") {
+ if ($c1 =~ m/.0/ || $c1 eq "released") {
+ $subDef = $subDef0;
+ } else {
+ $subDef = $subDefI;
+ }
+ }
+ if ($switchType eq "universal") {
+ if ($c1 =~ m/A0|AI/ && (!$channelA || ($c1 ne $channelA))) {
+ $c1 = "A0";
+ } elsif ($c1 =~ m/B0|BI/ && (!$channelB || $c1 ne $channelB)) {
+ $c1 = "B0";
+ } elsif ($c1 =~ m/C0|CI/ && (!$channelC || ($c1 ne $channelC))) {
+ $c1 = "C0";
+ } elsif ($c1 =~ m/D0|DI/ && (!$channelD || ($c1 ne $channelD))) {
+ $c1 = "D0";
+ } elsif ($c1 eq "released") {
+
+ } else {
+ $sendCmd = "no";
+ }
+ }
+ # second action
+ if ($c2 && $switchType eq "universal") {
+ if ($c2 =~ m/A0|AI/ && (!$channelA || ($c2 ne $channelA))) {
+ $c2 = "A0";
+ } elsif ($c2 =~ m/B0|BI/ && (!$channelB || $c2 ne $channelB)) {
+ $c2 = "B0";
+ } elsif ($c2 =~ m/C0|CI/ && (!$channelC || ($c2 ne $channelC))) {
+ $c2 = "C0";
+ } elsif ($c2 =~ m/D0|DI/ && (!$channelD || ($c2 ne $channelD))) {
+ $c2 = "D0";
+ } else {
+ $c2 = undef;
+ }
+ if ($c2 && $sendCmd eq "no") {
+ # only second action has changed, send as first action
+ $c1 = $c2;
+ $c2 = undef;
+ $sendCmd = "yes";
+ }
+ }
+ # convert and send first and second command
my ($db_3, $status) = split(":", $EnO_ptm200btn{$c1}, 2);
$db_3 <<= 5;
$db_3 |= 0x10 if($c1 ne "released"); # set the pressed flag
- if($c2) {
+ if($c2 && $switchType ne "central") {
my ($d2, undef) = split(":", $EnO_ptm200btn{$c2}, 2);
$db_3 |= ($d2<<1) | 0x01;
}
- my $subDef = AttrVal($name, "subDef", "$hash->{DEF}");
- IOWrite($hash, "", sprintf("6B05%02X000000%s%s", $db_3, $subDef, $status));
- Log $ll2, "EnOcean: set $name $cmd";
+ if ($sendCmd ne "no") {
+ IOWrite($hash, "", sprintf("6B05%02X000000%s%s", $db_3, $subDef, $status));
+ Log $ll2, "EnOcean: set $name $cmd";
+ if ($switchMode eq "pushbutton") {
+ IOWrite($hash, "", sprintf("6B0500000000%s20", $subDef));
+ Log $ll2, "EnOcean: set $name released";
+ }
+ }
}
-
select(undef, undef, undef, 0.2); # Tested by joerg. He prefers 0.3 :)
}
-
if($updateState == 1) {
readingsSingleUpdate($hash, "state", join(" ", @a), 1);
return undef;
}
}
-#############################
-# "EnOcean:F6:50000000:0011C8D4:FF" -> EnO_switch on (BI)
+# Parse
sub
EnOcean_Parse($$)
{
@@ -396,7 +744,6 @@ EnOcean_Parse($$)
Log $ll4, "$name: ORG:$rorg DATA:$data ID:$id STATUS:$status";
my @event;
- #push @event, "1:rp_counter:".(hex($status)&0xf);
my $dl = length($data);
my $db_3 = hex substr($data,0,2);
@@ -404,19 +751,21 @@ EnOcean_Parse($$)
my $db_1 = hex substr($data,4,2) if($dl > 4);
my $db_0 = hex substr($data,6,2) if($dl > 6);
my $st = AttrVal($name, "subType", "");
+ my $manufID = AttrVal($name, "manufID", "");
my $model = AttrVal($name, "model", "");
- #################################
- # RPS: PTM200 based switch/remote or a windowHandle
if($rorg eq "F6") {
+ # RPS Telegram (PTM200)
+ # Rocker Switch (EEP F6-02-01 ... F6-03-02)
+ # Position Switch, Home and Office Application (EEP F6-04-01)
+ # Mechanical Handle (EEP F6-10-00)
+ my $event = "state";
my $nu = ((hex($status)&0x10)>>4);
-
# unused flags (AFAIK)
#push @event, "1:T21:".((hex($status)&0x20)>>5);
#push @event, "1:NU:$nu";
if($nu) {
-
# Theoretically there can be a released event with some of the A0,BI
# pins set, but with the plastic cover on this wont happen.
$msg = $EnO_ptm200btn[($db_3&0xe0)>>5];
@@ -424,12 +773,12 @@ EnOcean_Parse($$)
$msg .= " released" if(!($db_3 & 0x10));
} else {
-
- if($db_3 == 112) { # KeyCard, not tested
+ if($db_3 == 112) {
+ # Key Card, not tested
$msg = "keycard inserted";
- # Only the windowHandle is setting these bits when nu=0
} elsif($db_3 & 0xC0) {
+ # Only a Mechanical Handle is setting these bits when nu=0
$msg = "closed" if($db_3 == 0xF0);
$msg = "open" if($db_3 == 0xE0);
$msg = "tilted" if($db_3 == 0xD0);
@@ -438,42 +787,75 @@ EnOcean_Parse($$)
} else {
if($st eq "keycard") {
$msg = "keycard removed";
-
- } else {
- $msg = (($db_3&0x10) ? "pressed" : "released");
-
}
-
+ else {
+ $msg = (($db_3&0x10) ? "pressed" : "released");
+ }
}
-
}
-
-
+ if ($st eq "FRW") {
+ # smoke detector Eltako FRW, untested
+ if ($msg =~ m/A0$/) {
+ push @event, "3:battery:low";
+ } elsif ($msg =~ m/AI$/) {
+ push @event, "3:alarm:smoke-alarm";
+ $msg = "smoke-alarm";
+ } elsif ($msg =~ m/released$/) {
+ push @event, "3:alarm:off";
+ push @event, "3:battery:ok";
+ $msg = "off";
+ }
+ } else {
+ if ($msg =~ m/A0$/) {
+ push @event, "3:channelA:A0";
+ } elsif ($msg =~ m/AI$/) {
+ push @event, "3:channelA:AI";
+ } elsif ($msg =~ m/B0$/) {
+ push @event, "3:channelB:B0";
+ } elsif ($msg =~ m/BI$/) {
+ push @event, "3:channelB:BI";
+ } elsif ($msg =~ m/C0$/) {
+ push @event, "3:channelC:C0";
+ } elsif ($msg =~ m/CI$/) {
+ push @event, "3:channelC:CI";
+ } elsif ($msg =~ m/D0$/) {
+ push @event, "3:channelD:D0";
+ } elsif ($msg =~ m/DI$/) {
+ push @event, "3:channelD:DI";
+ }
# released events are disturbing when using a remote, since it overwrites
- # the "real" state immediately.
- # In the case of an ElTako FSB61 the state should remain released (by Thomas)
- my $event = "state";
- $event = "buttons" if($msg =~ m/released$/ &&
- $model ne "FSB61" &&
- $model ne "FSM61");
-
+ # the "real" state immediately. In the case of an Eltako FSB14, FSB61
+ # the state should remain released. (by Thomas)
+ $event = "buttons" if ($msg =~ m/released$/ &&
+ $model ne "FT55" && $model ne "FSB14" &&
+ $model ne "FSB61" && $model ne "FSB70" &&
+ $model ne "FSM12" && $model ne "FSM61" &&
+ $model ne "FTS12");
+ }
push @event, "3:$event:$msg";
- #################################
- # 1BS. Only contact is defined in the EEP2.1 for 1BS
} elsif($rorg eq "D5") {
- push @event, "3:state:" . ($db_3&1 ? "closed" : "open");
- push @event, "3:learnBtn:on" if(!($db_3&0x8));
+ # 1BS Telegram
+ # Single Input Contact (EEP D5-00-01)
+ # [Eltako FTK, STM-250]
+ push @event, "3:state:" . ($db_3 & 1 ? "closed" : "open");
+ push @event, "3:learnBtn:on" if (!($db_3 & 0x8));
- #################################
} elsif($rorg eq "A5") {
+ # 4BS Telegram
if(($db_0 & 0x08) == 0) {
+ # teach-in telegram
if($db_0 & 0x80) {
- my $fn = sprintf "%02x", ($db_3>>2);
- my $tp = sprintf "%02X", ((($db_3&3) << 5) | ($db_2 >> 3));
- my $mf = sprintf "%03X", ((($db_2&7) << 8) | $db_1);
- $mf = $EnO_manuf{$mf} if($EnO_manuf{$mf});
- my $m = "teach-in:class A5.$fn.$tp (manufacturer: $mf)";
+ # teach-in telegram with EEP and Manufacturer ID
+ my $fn = sprintf "%02x", ($db_3 >> 2);
+ my $tp = sprintf "%02X", ((($db_3 & 3) << 5) | ($db_2 >> 3));
+ my $mf = sprintf "%03X", ((($db_2 & 7) << 8) | $db_1);
+
+ # manufID to account for vendor-specific features
+ $attr{$name}{manufID} = $mf;
+
+ $mf = $EnO_manuf{$mf} if($EnO_manuf{$mf});
+ my $m = "teach-in:EEP A5-$fn-$tp Manufacturer: $mf";
Log 1, $m;
push @event, "3:$m";
my $st = "A5.$fn.$tp";
@@ -486,63 +868,47 @@ EnOcean_Parse($$)
select(undef, undef, undef, 0.5);
EnOcean_MD15Cmd($hash, $name, 128); # 128 == 20 degree C
}
-
+ # subType, manufID storing corrected
+ CommandSave(undef, undef);
} else {
- push @event, "3:teach-in:no type/manuf. data transmitted";
-
+ push @event, "3:teach-in:No EEP profile identifier and no Manufacturer ID";
}
- } elsif($model =~ m/^SR04/ || $st eq "SR04") {
- my ($fspeed, $temp, $present, $solltemp);
- $fspeed = 3;
- $fspeed = 2 if($db_3 >= 145);
- $fspeed = 1 if($db_3 >= 165);
- $fspeed = 0 if($db_3 >= 190);
- $fspeed = "Auto" if($db_3 >= 210);
- $temp = sprintf("%0.1f", 40-$db_1/6.375); # 40..0
- $present= $db_0&0x1 ? "no" : "yes";
- $solltemp= sprintf("%0.1f", $db_2/6.375);
-
- push @event, "3:state:temperature $temp";
- push @event, "3:set_point: $solltemp";
- push @event, "3:fan:$fspeed";
- push @event, "3:present:$present" if($present eq "yes");
- push @event, "3:learnBtn:on" if(!($db_0&0x8));
- push @event, "3:T:$temp SP: $db_3 F: $fspeed P: $present";
-
} elsif($st eq "MD15") {
- push @event, "3:state:$db_3 %";
+ # Battery Powered Actuator (EEP A5-20-01)
+ # [Kieback&Peter MD15-FTL-xx]
+ push @event, "3:state:$db_3";
push @event, "3:currentValue:$db_3";
push @event, "3:serviceOn:" . (($db_2 & 0x80) ? "yes" : "no");
push @event, "3:energyInput:" . (($db_2 & 0x40) ? "enabled":"disabled");
push @event, "3:energyStorage:". (($db_2 & 0x20) ? "charged":"empty");
- push @event, "3:battery:" . (($db_2 & 0x10) ? "ok" : "empty");
+ push @event, "3:battery:" . (($db_2 & 0x10) ? "ok" : "low");
push @event, "3:cover:" . (($db_2 & 0x08) ? "open" : "closed");
push @event, "3:tempSensor:" . (($db_2 & 0x04) ? "failed" : "ok");
push @event, "3:window:" . (($db_2 & 0x02) ? "open" : "closed");
push @event, "3:actuatorStatus:".(($db_2 & 0x01) ? "obstructed" : "ok");
push @event, "3:measured-temp:". sprintf "%0.1f", ($db_1*40/255);
+ push @event, "3:selfCtl:" . (($db_0 & 0x04) ? "on" : "off");
EnOcean_MD15Cmd($hash, $name, $db_1);
} elsif($model eq "PM101") {
- ####################################
- # Ratio Presence Sensor Eagle PM101, code by aicgazi
- ####################################
+ # Light and Presence Sensor [Omnio Ratio eagle-PM101]
+ # The sensor also sends switching commands (RORG F6) with the senderID-1
+ # code by aicgazi
+ # $db_2 is the illuminance where max value 0xFF stands for 1000 lx
my $lux = sprintf "%3d", $db_2;
- # content of $db_2 is the illuminance where max value 0xFF stands for 1000 lx
$lux = sprintf "%04.2f", ( $lux * 1000 / 255 ) ;
push @event, "3:brightness:$lux";
push @event, "3:channel1:" . ($db_0 & 0x01 ? "off" : "on");
push @event, "3:channel2:" . ($db_0 & 0x02 ? "off" : "on");
+ push @event, "3:motion:" . ($db_0 & 0x02 ? "off" : "on");
+ push @event, "3:state:" . ($db_0 & 0x02 ? "off" : "on");
} elsif($st eq "FAH" || $model =~ /^(FAH60|FAH63|FIH63)$/) {
- ####################################
- # Eltako FAH60+FAH63+FIH63
- # (EEP: 07-06-01 plus Data_byte3)
- ####################################
- # $db_3 is the illuminance where min 0x00 = 0 lx, max 0xFF = 100 lx;
+ # Light Sensor
+ # [Eltako FAH60, FAH63, FIH63] (EEP A5-06-01 plus Data_byte3)
+ # $db_3 is the illuminance where min 0x00 = 0 lx, max 0xFF = 100 lx
# $db_2 must be 0x00
-
if($db_2 eq 0x00) {
my $luxlow = sprintf "%3d", $db_3;
$luxlow = sprintf "%d", ( $luxlow * 100 / 255 ) ;
@@ -557,13 +923,10 @@ EnOcean_Parse($$)
}
} elsif($st eq "FBH" || $model =~ /^(FABH63|FBH55|FBH63|FIBH63)$/) {
- ####################################
- # Eltako FABH63+FBH55+FBH63+FIBH63
- # (EEP: similar 07-08-01)
- ####################################
+ # Light and Occupancy Sensor (no Temperature)
+ # [Eltako FABH63, FBH55, FBH63, FIBH63] (EEP similar A5-08-01)
# $db_0 motion detection where 0x0D = motion and 0x0F = no motion
# (DB0_Bit1 = 1 or 0)
-
if($db_0 eq 0x0D) {
push @event, "3:motion:yes";
push @event, "3:state:yes";
@@ -576,39 +939,583 @@ EnOcean_Parse($$)
my $lux = sprintf "%3d", $db_2;
$lux = sprintf "%d", ( $lux * 2048 / 255 ) ;
push @event, "3:brightness:$lux";
- # $db_3 is voltage in EEP 07-08-01 but not used by Eltako !?
+ # $db_3 is voltage in EEP A5-08-01 but not used by Eltako !?
# push @event, "3:voltage:$db_3";
} elsif($st eq "FTF" || $model eq "FTF55") {
- ####################################
- # Eltako FTF55
- # (EEP: 07-02-05)
- ####################################
- # $db_1 is the temperature where 0x00 = 40?C and 0xFF 0?C
+ # Temperature Sensor (EEP A5-02-05)
+ # [Eltako FTF55, Thermokon SR04]
+ # $db_1 is the temperature where 0x00 = 40°C and 0xFF = 0°C
my $temp = sprintf "%3d", $db_1;
$temp = sprintf "%0.1f", ( 40 - $temp * 40 / 255 ) ;
+ push @event, "3:state:$temp";
+ push @event, "3:temperature:$temp";
+
+ } elsif($model =~ m/^SR04/ || $st eq "SR04") {
+ # Room Sensor and Control Unit
+ # [Thermokon SR04 *]
+ my ($fspeed, $temp, $present, $solltemp);
+ $fspeed = 3;
+ $fspeed = 2 if($db_3 >= 145);
+ $fspeed = 1 if($db_3 >= 165);
+ $fspeed = 0 if($db_3 >= 190);
+ $fspeed = "Auto" if($db_3 >= 210);
+ $temp = sprintf("%0.1f", 40-$db_1/6.375); # 40..0
+ $present= $db_0&0x1 ? "no" : "yes";
+ $solltemp= sprintf("%0.1f", $db_2/6.375);
+ push @event, "3:state:temperature $temp";
+ push @event, "3:set_point: $solltemp";
+ push @event, "3:setpoint:$db_2";
+ push @event, "3:fan:$fspeed";
+ push @event, "3:present:$present" if($present eq "yes");
+ push @event, "3:learnBtn:on" if(!($db_0&0x8));
+ push @event, "3:T:$temp SP: $db_3 F: $fspeed P: $present";
+
+ } elsif ($st =~ m/^tempSensor/) {
+ # Temperature Sensor with with different ranges (EEP A5-02-01 ... A5-02-1B)
+ # $db_1 is the temperature where 0x00 = max °C ... 0xFF = min °C
+ my $temp;
+ $temp = sprintf "%0.1f", -40 - $db_1 / 6.375 if ($st eq "tempSensor.01");
+ $temp = sprintf "%0.1f", -30 - $db_1 / 6.375 if ($st eq "tempSensor.02");
+ $temp = sprintf "%0.1f", -20 - $db_1 / 6.375 if ($st eq "tempSensor.03");
+ $temp = sprintf "%0.1f", -10 - $db_1 / 6.375 if ($st eq "tempSensor.04");
+ $temp = sprintf "%0.1f", 0 - $db_1 / 6.375 if ($st eq "tempSensor.05");
+ $temp = sprintf "%0.1f", 10 - $db_1 / 6.375 if ($st eq "tempSensor.06");
+ $temp = sprintf "%0.1f", 20 - $db_1 / 6.375 if ($st eq "tempSensor.07");
+ $temp = sprintf "%0.1f", 30 - $db_1 / 6.375 if ($st eq "tempSensor.08");
+ $temp = sprintf "%0.1f", 40 - $db_1 / 6.375 if ($st eq "tempSensor.09");
+ $temp = sprintf "%0.1f", 50 - $db_1 / 6.375 if ($st eq "tempSensor.0A");
+ $temp = sprintf "%0.1f", 60 - $db_1 / 6.375 if ($st eq "tempSensor.0B");
+ $temp = sprintf "%0.1f", -60 - $db_1 / 3.1875 if ($st eq "tempSensor.10");
+ $temp = sprintf "%0.1f", -50 - $db_1 / 3.1875 if ($st eq "tempSensor.11");
+ $temp = sprintf "%0.1f", -40 - $db_1 / 3.1875 if ($st eq "tempSensor.12");
+ $temp = sprintf "%0.1f", -30 - $db_1 / 3.1875 if ($st eq "tempSensor.13");
+ $temp = sprintf "%0.1f", -20 - $db_1 / 3.1875 if ($st eq "tempSensor.14");
+ $temp = sprintf "%0.1f", -10 - $db_1 / 3.1875 if ($st eq "tempSensor.15");
+ $temp = sprintf "%0.1f", 0 - $db_1 / 3.1875 if ($st eq "tempSensor.16");
+ $temp = sprintf "%0.1f", 10 - $db_1 / 3.1875 if ($st eq "tempSensor.17");
+ $temp = sprintf "%0.1f", 20 - $db_1 / 3.1875 if ($st eq "tempSensor.18");
+ $temp = sprintf "%0.1f", 30 - $db_1 / 3.1875 if ($st eq "tempSensor.19");
+ $temp = sprintf "%0.1f", 40 - $db_1 / 3.1875 if ($st eq "tempSensor.1A");
+ $temp = sprintf "%0.1f", 50 - $db_1 / 3.1875 if ($st eq "tempSensor.1B");
+ $temp = sprintf "%0.2f", -10 - (($db_2 << 8) | $db_1) / 19.98 if ($st eq "tempSensor.20");
+ $temp = sprintf "%0.1f", -40 - (($db_2 << 8) | $db_1) / 6.3 if ($st eq "tempSensor.30");
push @event, "3:temperature:$temp";
push @event, "3:state:$temp";
- } elsif($st eq "eltakoDimmer") {
- # todo: create a more general solution for the central-command responses
-
- # response command from (Eltako-)Actor ( Central-Command:A5/38/08 )
- if($db_3 eq 0x01) { # switch
- push @event, "3:state:" . (($db_0 & 0x01) ? "on": "off");
- push @event, "3:time:" . ($db_2<<8 + $db_1);
- push @event, "3:timeType:" . (($db_0 & 0x02) ? "delay": "duration");
-
- } elsif($db_3 eq 0x02) { # dimm
- push @event, "3:state:" . (($db_0 & 0x01) ? "on": "off");
- push @event, "3:value:$db_2";
-
- } elsif($db_3 eq 0x03) { # setpoint-switch, todo
- } elsif($db_3 eq 0x04) { # basic setpoint, todo
- } elsif($db_3 eq 0x05) { # control-variable, todo
- } elsif($db_3 eq 0x06) { # fan-stage, todo
+ } elsif($st eq "COSensor.01") {
+ # Gas Sensor, CO Sensor (EEP A5-09-01)
+ # [untested]
+ # $db_3 is the CO concentration where 0x00 = 0 ppm ... 0xFF = 255 ppm
+ # $db_2 is the CO concentration where 0x00 = 0 ppm ... 0xFF = 255 ppm
+ # $db_1 is the temperature where 0x00 = 0 °C ... 0xFF = 255 °C
+ # $db_0 bit D1 temperature sensor available 0 = no, 1 = yes
+ my $coChannel1 = $db_3;
+ my $coChannel2 = $db_2;
+ push @event, "3:Channel1:$coChannel1";
+ push @event, "3:Channel2:$coChannel2";
+ if ($coChannel1 == $coChannel2) {
+ push @event, "3:state:$coChannel1";
+ } else {
+ push @event, "3:state:measuring error";
+ }
+ if ($db_0 & 2) {
+ my $temp = $db_1;
+ push @event, "3:temperature:$temp";
+ }
+
+ } elsif($st eq "tempHumiCO2Sensor.01") {
+ # Gas Sensor, CO2 Sensor (EEP A5-09-04)
+ # [Thermokon SR04 CO2 *, untested]
+ # $db_3 is the humidity where 0x00 = 0 %rH ... 0xC8 = 100 %rH
+ # $db_2 is the CO2 concentration where 0x00 = 0 ppm ... 0xFF = 2500 ppm
+ # $db_1 is the temperature where 0x00 = 0°C ... 0xFF = +51 °C
+ # $db_0 bit D2 humidity sensor available 0 = no, 1 = yes
+ # $db_0 bit D1 temperature sensor available 0 = no, 1 = yes
+ my $humi = "unknown";
+ my $temp = "unknown";
+ my $airQuality;
+ if ($db_0 & 4) {
+ $humi = $db_3 >> 1;
+ push @event, "3:humidity:$humi";
}
+ my $co2 = sprintf "%d", $db_2 * 10;
+ push @event, "3:CO2:$co2";
+ if ($db_0 & 2) {
+ $temp = sprintf "%0.1f", $db_1 * 51 / 255 ;
+ push @event, "3:temperature:$temp";
+ }
+ if ($co2 <= 400) {
+ $airQuality = "high";
+ } elsif ($co2 <= 600) {
+ $airQuality = "mean";
+ } elsif ($co2 <= 1000) {
+ $airQuality = "moderate";
+ } else {
+ $airQuality = "low";
+ }
+ push @event, "3:airQuality:$airQuality";
+ push @event, "3:state:CO2 $co2 AQ: $airQuality T: $temp H: $humi";
+
+ } elsif ($st eq "roomSensorControl.05") {
+ # Room Sensor and Control Unit (EEP A5-10-01 ... A5-10-0D)
+ # [Eltako FTF55D, FTF55H, Thermokon SR04 *, Thanos SR *, untested]
+ # $db_3 is the fan speed or night reduction for Eltako
+ # $db_2 is the setpoint where 0x00 = min ... 0xFF = max or
+ # reference temperature for Eltako whre 0x00 = 0°C ... 0xFF = 40°C
+ # $db_1 is the temperature where 0x00 = +40°C ... 0xFF = 0°C
+ # $db_0 bit D0 is the occupy button, pushbutton or slide switch
+ my $temp = sprintf "%0.1f", 40 - $db_1 / 6.375;
+ if ($manufID eq "00D") {
+ my $nightReduction = 0;
+ $nightReduction = 1 if ($db_3 == 0x06);
+ $nightReduction = 2 if ($db_3 == 0x0c);
+ $nightReduction = 3 if ($db_3 == 0x13);
+ $nightReduction = 4 if ($db_3 == 0x19);
+ $nightReduction = 5 if ($db_3 == 0x1f);
+ my $setpointTemp = sprintf "%0.1f", $db_2 / 6.375;
+ push @event, "3:state:T: $temp SPT: $setpointTemp NR: $nightReduction";
+ push @event, "3:nightReduction:$nightReduction";
+ push @event, "3:setpointTemp:$setpointTemp";
+ } else {
+ my $fspeed = 3;
+ $fspeed = 2 if ($db_3 >= 145);
+ $fspeed = 1 if ($db_3 >= 165);
+ $fspeed = 0 if ($db_3 >= 190);
+ $fspeed = "Auto" if ($db_3 >= 210);
+ my $switch = $db_0 & 1;
+ push @event, "3:state:T: $temp SP: $db_2 F: $fspeed SW: $switch";
+ push @event, "3:fan:$fspeed";
+ push @event, "3:switch:$switch";
+ push @event, "3:setpoint:$db_2";
+ }
+ push @event, "3:temperature:$temp";
+
+ } elsif($st eq "roomSensorControl.01") {
+ # Room Sensor and Control Unit (EEP A5-04-01, A5-10-10 ... A5-10-14)
+ # [Thermokon SR04 * rH, Thanus SR *, untested]
+ # $db_3 is the setpoint where 0x00 = min ... 0xFF = max
+ # $db_2 is the humidity where 0x00 = 0%rH ... 0xFA = 100%rH
+ # $db_1 is the temperature where 0x00 = 0°C ... 0xFA = +40°C
+ # $db_0 bit D0 is the occupy button, pushbutton or slide switch
+ my $temp = sprintf "%0.1f", $db_1 * 40 / 250;
+ my $humi = sprintf "%d", $db_2 / 2.5;
+ my $switch = $db_0 & 1;
+ push @event, "3:state:T: $temp H: $humi SP: $db_2 SW: $switch";
+ push @event, "3:humidity:$humi";
+ push @event, "3:switch:$switch";
+ push @event, "3:setpoint:$db_2";
+ push @event, "3:temperature:$temp";
+
+ } elsif($st eq "roomSensorControl.02") {
+ # Room Sensor and Control Unit (A5-10-15 ... A5-10-17)
+ # [untested]
+ # $db_2 bit D7 ... D2 is the setpoint where 0 = min ... 63 = max
+ # $db_2 bit D1 ... $db_1 bit D0 is the temperature where 0 = -10°C ... 1023 = +41.2°C
+ # $db_0_bit_0 is Occupany Button where 0 = pressed, 1 = released
+ my $temp = sprintf "%0.2f", -10 + ((($db_2 & 3) << 8) | $db_1) / 19.98;
+ my $setpoint = ($db_2 & 0xFC) >> 2;
+ my $presence = $db_0 & 1 ? "present" : "absent";
+ push @event, "3:state:T: $temp P: $presence SP: $setpoint ";
+ push @event, "3:presence:$presence";
+ push @event, "3:setpoint:$setpoint";
+ push @event, "3:temperature:$temp";
+
+ } elsif($st eq "tempHumiSensor.02") {
+ # Temperatur and Humidity Sensor(EEP A5-04-02)
+ # [Eltako FAFT60, FIFT63AP]
+ # $db_3 is the voltage where 0x59 = 2.5V ... 0x9B = 4V, only at Eltako
+ # $db_2 is the humidity where 0x00 = 0%rH ... 0xFA = 100%rH
+ # $db_1 is the temperature where 0x00 = -20°C ... 0xFA = +60°C
+ #my $temp = sprintf "%3d", $db_1;
+ #my $voltage = sprintf "%3d", $db_3;
+ my $humi = sprintf "%d", $db_2 / 2.5;
+ my $temp = sprintf "%0.1f", -20 + $db_1 * 80 / 250;
+ my $battery = "unknown";
+ if ($manufID eq "00D") {
+ # Eltako sensor
+ my $voltage = sprintf "%0.1f", $db_3 * 6.58 / 255;
+ my $energyStorage = "unknown";
+ if ($db_3 <= 0x58) {
+ $energyStorage = "empty";
+ $battery = "low";
+ }
+ elsif ($db_3 <= 0xDC) {
+ $energyStorage = "charged";
+ $battery = "ok";
+ }
+ else {
+ $energyStorage = "full";
+ $battery = "ok";
+ }
+ push @event, "3:battery:$battery";
+ push @event, "3:energyStorage:$energyStorage";
+ push @event, "3:voltage:$voltage";
+ }
+ push @event, "3:state:T: $temp H: $humi B: $battery";
+ push @event, "3:humidity:$humi";
+ push @event, "3:temperature:$temp";
+
+ } elsif ($st eq "lightSensor.01") {
+ # Light Sensor (EEP A5-06-01)
+ # [Eltako FAH60, FAH63, FIH63, Thermokon SR65 LI, untested]
+ # $db_3 is the voltage where 0x00 = 0 V ... 0xFF = 5.1 V
+ # $db_3 is the low illuminance for Eltako devices where
+ # min 0x00 = 0 lx, max 0xFF = 100 lx, if $db_2 = 0
+ # $db_2 is the illuminance (ILL2) where min 0x00 = 300 lx, max 0xFF = 30000 lx
+ # $db_1 is the illuminance (ILL1) where min 0x00 = 600 lx, max 0xFF = 60000 lx
+ # $db_0_bit_0 is Range select where 0 = ILL1, 1 = ILL2
+ my $lux;
+ my $voltage = "unknown";
+ # operation prüfen
+ if ($manufID eq "00D") {
+ if($db_2 == 0) {
+ $lux = sprintf "%d", $db_3 * 100 / 255;
+ } else {
+ $lux = sprintf "%d", $db_2 * 116.48 + 300;
+ }
+ } else {
+ $voltage = sprintf "%d", $db_3 * 0.02;
+ if($db_0 & 1) {
+ $lux = sprintf "%d", $db_2 * 116.48 + 300;
+ } else {
+ $lux = sprintf "%d", $db_1 * 232.94 + 600;
+ }
+ push @event, "3:voltage:$voltage";
+ }
+ push @event, "3:brightness:$lux";
+ push @event, "3:state:$lux";
+
+ } elsif ($st eq "lightSensor.02") {
+ # Light Sensor (EEP A5-06-02)
+ # $db_3 is the voltage where 0x00 = 0 V ... 0xFF = 5.1 V
+ # $db_2 is the illuminance (ILL2) where min 0x00 = 0 lx, max 0xFF = 510 lx
+ # $db_1 is the illuminance (ILL1) where min 0x00 = 0 lx, max 0xFF = 1020 lx
+ # $db_0_bit_0 is Range select where 0 = ILL1, 1 = ILL2
+ my $lux;
+ my $voltage = sprintf "%d", $db_3 * 0.02;
+ if($db_0 & 1) {
+ $lux = $db_2 << 1;
+ } else {
+ $lux = $db_1 << 2;
+ }
+ push @event, "3:voltage:$voltage";
+ push @event, "3:brightness:$lux";
+ push @event, "3:state:$lux";
+
+ } elsif ($st eq "occupSensor.01") {
+ # Occupancy Sensor (EEP A5-07-01)
+ # $db_1 is PIR Status (motion) where 0 ... 127 = off, 128 ... 255 = on
+ my $motion = "off";
+ if ($db_1 >= 128) {$motion = "on";}
+ push @event, "3:motion:$motion";
+ push @event, "3:state:$motion";
+
+ } elsif ($st =~ m/^lightTempOccupSensor/) {
+ # Light, Temperatur and Occupancy Sensor (EEP A5-08-01 ... A5-08-03)
+ # $db_3 is the voltage where 0x00 = 0 V ... 0xFF = 5.1 V
+ # $db_2 is the illuminance where min 0x00 = 0 lx, max 0xFF = 510 lx, 1020 lx, (2048 lx)
+ # $db_1 is the temperature whrere 0x00 = 0 °C ... 0xFF = 51 °C or -30 °C ... 50°C
+ # $db_0_bit_1 is PIR Status (motion) where 0 = on, 1 = off
+ # $db_0_bit_0 is Occupany Button where 0 = pressed, 1 = released
+ my $lux;
+ my $temp;
+ my $voltage = sprintf "%0.1f", $db_3 * 0.02;
+ my $motion = $db_0 & 2 ? "off" : "on";
+ my $presence = $db_0 & 1 ? "present" : "absent";
+
+ if ($st eq "lightTempOccupSensor.01") {
+ # Light, Temperatur and Occupancy Sensor (EEP A5-08-01)
+ # [Eltako FABH63, FBH55, FBH63, FIBH63]
+ if ($manufID eq "00D") {
+ $lux = sprintf "%d", $db_2 * 2048 / 255;
+ push @event, "3:state:M: $motion E: $lux";
+ } else {
+ $lux = $db_2 << 1;
+ $temp = sprintf "%0.1f", $db_1 * 0.2;
+ push @event, "3:state:M: $motion E: $lux P: $presence T: $temp U: $voltage";
+ push @event, "3:presence:$presence";
+ push @event, "3:temperature:$temp";
+ push @event, "3:voltage:$voltage";
+ }
+ } elsif ($st eq "lightTempOccupSensor.02") {
+ # Light, Temperatur and Occupancy Sensor (EEP A5-08-02)
+ $lux = $db_2 << 2;
+ $temp = sprintf "%0.1f", $db_1 * 0.2;
+ push @event, "3:state:M: $motion E: $lux P: $presence T: $temp U: $voltage";
+ push @event, "3:presence:$presence";
+ push @event, "3:temperature:$temp";
+ push @event, "3:voltage:$voltage";
+ } elsif ($st eq "lightTempOccupSensor.03") {
+ # Light, Temperatur and Occupancy Sensor (EEP A5-08-03)
+ $lux = $db_2 * 6;
+ $temp = sprintf "%0.1f", -30 + $db_1 * 80 / 255;
+ push @event, "3:state:M: $motion E: $lux P: $presence T: $temp U: $voltage";
+ push @event, "3:presence:$presence";
+ push @event, "3:temperature:$temp";
+ push @event, "3:voltage:$voltage";
+ }
+ push @event, "3:brightness:$lux";
+ push @event, "3:motion:$motion";
+
+ } elsif ($st =~ m/^autoMeterReading/) {
+ # Automated meter reading (AMR) (EEP A5-12-00 ... A5-12-03)
+ # $db_3 (MSB) + $db_2 + $db_1 (LSB) is the Meter reading
+ # $db_0_bit_7 ... $db_0_bit_4 is the Measurement channel
+ # $db_0_bit_2 is the Data type where 0 = cumulative value, 1 = current value
+ # $db_0_bit_1 ... $db_0_bit_0 is the Divisor where 0 = x/1, 1 = x/10,
+ # 2 = x/100, 3 = x/1000
+ # my $meterReading = hex sprintf "%02x%02x%02x", $db_3, $db_2, $db_1;
+ my $dataType = ($db_0 & 4) >> 3;
+ my $divisor = $db_0 & 3;
+ if ($divisor == 3) {
+ $divisor = 1000;
+ } elsif ($divisor == 2) {
+ $divisor = 100;
+ } elsif ($divisor == 1) {
+ $divisor = 10;
+ } else {
+ $divisor = 1;
+ }
+ my $meterReading = sprintf "%0.1f", (($db_3 << 16) | ($db_2 << 8) | $db_1) / $divisor;
+ my $channel = $db_0 >> 4;
+
+ if ($st eq "autoMeterReading.00") {
+ # Automated meter reading (AMR), Counter (EEP A5-12-01)
+ # [Thermokon SR-MI-HS, untested]
+ if ($dataType == 1) {
+ # current value
+ push @event, "3:currentValue:$meterReading";
+ push @event, "3:state:$meterReading";
+ } else {
+ # cumulative counter
+ push @event, "3:counter$channel:$meterReading";
+ }
+ } elsif ($st eq "autoMeterReading.01") {
+ # Automated meter reading (AMR), Electricity (EEP A5-12-01)
+ # [Eltako FSS12, FWZ12, DSZ14DRS, DSZ14WDRS]
+ # $db_0_bit_7 ... $db_0_bit_4 is the Tariff info
+ # $db_0_bit_2 is the Data type where 0 = cumulative value kWh,
+ # 1 = current value W
+ if ($dataType == 1) {
+ # momentary power
+ push @event, "3:power:$meterReading";
+ push @event, "3:state:$meterReading";
+ } elsif ($db_0 == 0x8F && $manufID eq "00D") {
+ # Eltako, read meter serial number
+ my $serialNumber;
+ if ($db_0 == 0) {
+ # first 2 digits of the serial number
+ $serialNumber = printf "S-%01x%01x", $db_3 >> 4, $db_3 & 0x0F;
+ } else {
+ # last 4 digits of the serial number
+ $serialNumber = substr (ReadingsVal($name, "serialNumber", "S---"), 0, 4);
+ $serialNumber = printf "%4c%01x%01x%01x%01x", $serialNumber,
+ $db_2 >> 4, $db_2 & 0x0F, $db_3 >> 4, $db_3 & 0x0F;
+ }
+ push @event, "3:serialNumber:$serialNumber";
+ } else {
+ # power consumption
+ push @event, "3:energy$channel:$meterReading";
+ push @event, "3:currentTariff:$channel";
+ }
+ } elsif ($st eq "autoMeterReading.02" | $st eq "autoMeterReading.03") {
+ # Automated meter reading (AMR), Gas, Water (EEP A5-12-02, A5-12-03)
+ if ($dataType == 1) {
+ # current value
+ push @event, "3:flowrate:$meterReading";
+ push @event, "3:state:$meterReading";
+ } else {
+ # cumulative counter
+ push @event, "3:consumption$channel:$meterReading";
+ push @event, "3:currentTariff:$channel";
+ }
+ }
+
+ } elsif ($st eq "weatherStation") {
+ # Weather Station (EEP A5-13-01 ... EEP A5-13-06)
+ # [Eltako FWS61, untested]
+ # $db_0_bit_7 ... $db_0_bit_4 is the Identifier
+ my $identifier = $db_0 >> 4;
+ if ($identifier == 1) {
+ # EEP A5-13-01
+ # $db_3 is the dawn sensor where 0x00 = 0 lx ... 0xFF = 999 lx
+ # $db_2 is the temperature where 0x00 = -40 °C ... 0xFF = 80 °C
+ # $db_1 is the wind speed where 0x00 = 0 m/s ... 0xFF = 70 m/s
+ # $db_0_bit_2 is day / night where 0 = day, 1 = night
+ # $db_0_bit_1 is rain indication where 0 = no (no rain), 1 = yes (rain)
+ my $dawn = sprintf "%d", $db_3 * 999 / 255;
+ my $temp = sprintf "%0.1f", -40 + $db_2 * 120 / 255;
+ my $windSpeed = sprintf "%0.1f", $db_1 * 70 / 255;
+ my $dayNight = $db_0 & 2 ? "night" : "day";
+ my $isRaining = $db_0 & 1 ? "yes" : "no";
+ push @event, "3:brightness:$dawn";
+ push @event, "3:dayNight:$dayNight";
+ push @event, "3:isRaining:$isRaining";
+ push @event, "3:temperature:$temp";
+ push @event, "3:windSpeed:$windSpeed";
+ push @event, "3:state:T: $temp B: $dawn W: $windSpeed IR: $isRaining";
+ } elsif ($identifier == 2) {
+ # EEP A5-13-02
+ # $db_3 is the sun exposure west where 0x00 = 1 lx ... 0xFF = 150 klx
+ # $db_2 is the sun exposure south where 0x00 = 1 lx ... 0xFF = 150 klx
+ # $db_1 is the sun exposure east where 0x00 = 1 lx ... 0xFF = 150 klx
+ my $sunWest = sprintf "%d", 1 + $db_3 * 149999 / 255;
+ my $sunSouth = sprintf "%d", 1 + $db_2 * 149999 / 255;
+ my $sunEast = sprintf "%d", 1 + $db_1 * 149999 / 255;
+ push @event, "3:sunWest:$sunWest";
+ push @event, "3:sunSouth:$sunSouth";
+ push @event, "3:sunEast:$sunEast";
+ } else {
+ # EEP A5-13-03 ... EEP A5-13-06 not implemented
+ }
+
+ } elsif ($st =~ m/^digitalInput/) {
+ # Digital Input (EEP A5-30-01, A5-30-02)
+ my $contact;
+ if ($st eq "digtalInput.01") {
+ # Single Input Contact, Batterie Monitor (EEP A5-30-01)
+ # [Thermokon SR65 DI, untested]
+ # $db_2 is the supply voltage, if >= 121 = battery ok
+ # $db_1 is the input state, if <= 195 = contact closed
+ my $battery = $db_2 >= 121 ? "ok" : "low";
+ $contact = $db_1 <= 195 ? "closed" : "open";
+ push @event, "3:battery:$battery";
+ } else {
+ # Single Input Contact (EEP A5-30-01)
+ # $db_0_bit_0 is the input state where 0 = closed, 1 = open
+ $contact = $db_0 & 1 ? "open" : "closed";
+ }
+ push @event, "3:contact:$contact";
+ push @event, "3:state:$contact";
+
+ } elsif ($st eq "phcGateway") {
+ # PHC Gateway (EEP A5-38-08)
+ # $db_3 is the command ID ($phcCmdID)
+ # Eltako devices not send teach-in telegrams
+ if(($db_0 & 8) == 0) {
+ # teach-in, identify and store command type in attr phcCmd
+ my $phcCmd = AttrVal($name, "phcCmd", undef);
+ if (!$phcCmd) {
+ $phcCmd = $EnO_phcCmd[$db_3];
+ $attr{$name}{phcCmd} = $phcCmd;
+ }
+ }
+ if ($db_3 == 1) {
+ # Switching
+ # Eltako devices not send A5 telegrams
+ push @event, "3:executeTime:" . sprintf "%0.1f", (($db_2 << 8) | $db_1) / 10;
+ push @event, "3:lock:" . $db_0 & 4 ? "lock" : "unlock";
+ push @event, "3:executeType" . $db_0 & 2 ? "delay" : "duration";
+ push @event, "3:state:" . $db_0 & 1 ? "on" : "off";
+ } elsif ($db_3 == 2) {
+ # Dimming
+ # $db_0_bit_2 is store final value, not used, because
+ # dimming value is always stored
+ push @event, "3:rampTime:$db_1";
+ push @event, "3:state:" . ($db_0 & 0x01 ? "on" : "off");
+ if ($db_0 & 4) {
+ # Relative Dimming Range
+ push @event, "3:dimValue:" . sprintf "%d", $db_2 * 100 / 255;
+ } else {
+ push @event, "3:dimValue:$db_2";
+ }
+ push @event, "3:dimValueLast:$db_2" if ($db_2 > 0);
+ } elsif ($db_3 == 3) {
+ # Setpoint shift
+ # $db1 is setpoint shift where 0 = -12.7 K ... 255 = 12.8 K
+ my $setpointShift = sprintf "%0.1f", -12.7 + $db_1 / 10;
+ push @event, "3:setpointShift:$setpointShift";
+ push @event, "3:state:$setpointShift";
+ } elsif ($db_3 == 4) {
+ # Basic Setpoint
+ # $db1 is setpoint where 0 = 0 °C ... 255 = 51.2 °C
+ my $setpoint = sprintf "%0.1f", $db_1 / 5;
+ push @event, "3:setpoint:$setpoint";
+ push @event, "3:state:$setpoint";
+ } elsif ($db_3 == 5) {
+ # Control variable
+ # $db1 is control variable override where 0 = 0 % ... 255 = 100 %
+ push @event, "3:controlVar:" . sprintf "%d", $db_1 * 100 / 255;
+ my $controllerMode = ($db_0 & 0x60) >> 5;
+ if ($controllerMode == 0) {
+ push @event, "3:controllerMode:auto";
+ push @event, "3:state:auto";
+ } elsif ($controllerMode == 1){
+ push @event, "3:controllerMode:heating";
+ push @event, "3:state:heating";
+ } elsif ($controllerMode == 2){
+ push @event, "3:controllerMode:colling";
+ push @event, "3:state:colling";
+ } elsif ($controllerMode == 3){
+ push @event, "3:controllerMode:off";
+ push @event, "3:state:off";
+ }
+ push @event, "3:controllerState:" . $db_0 & 0x10 ? "override" : "auto";
+ push @event, "3:energyHoldOff:" . $db_0 & 4 ? "holdoff" : "normal";
+ my $occupancy = $db_0 & 3;
+ if ($occupancy == 0) {
+ push @event, "3:presence:present";
+ } elsif ($occupancy == 1){
+ push @event, "3:presence:absent";
+ } elsif ($occupancy == 2){
+ push @event, "3:presence:standby";
+ }
+ } elsif ($db_3 == 6) {
+ # Fan stage
+ #
+ if ($db_1 == 0) {
+ push @event, "3:fanStage:0";
+ push @event, "3:state:0";
+ } elsif ($db_1 == 1){
+ push @event, "3:fanStage:1";
+ push @event, "3:state:1";
+ } elsif ($db_1 == 2){
+ push @event, "3:fanStage:2";
+ push @event, "3:state:2";
+ } elsif ($db_1 == 3){
+ push @event, "3:fanStage:3";
+ push @event, "3:state:3";
+ } elsif ($db_1 == 255){
+ push @event, "3:fanStage:auto";
+ push @event, "3:state:auto";
+ }
+ } else {
+ push @event, "3:state:PHC Gateway Command ID $db_3 unknown.";
+ }
+
+ } elsif ($st eq "manufProfile") {
+ # Manufacturer Specific Applications (EEP A5-3F-7F)
+ if ($manufID eq "002") {
+ # [Thermokon SR65 3AI, untested]
+ # $db_3 is the input 3 where 0x00 = 0 V ... 0xFF = 10 V
+ # $db_2 is the input 2 where 0x00 = 0 V ... 0xFF = 10 V
+ # $db_1 is the input 1 where 0x00 = 0 V ... 0xFF = 10 V
+ my $input3 = sprintf "%0.2f", $db_3 * 10 / 255;
+ my $input2 = sprintf "%0.2f", $db_2 * 10 / 255;
+ my $input1 = sprintf "%0.2f", $db_1 * 10 / 255;
+ push @event, "3:input1:$input1";
+ push @event, "3:input2:$input2";
+ push @event, "3:input3:$input3";
+ push @event, "3:state:I1: $input1 I2: $input2 I3: $input3";
+ } else {
+ # Unknown Application
+ push @event, "3:state:Manufacturer Specific Application unknown";
+ }
+
+ } elsif ($st eq "eltakoDimmer") {
+ # Dimmer
+ # todo: create a more general solution for the central-command responses
+ if($db_3 eq 0x02) { # dim
+ push @event, "3:state:" . ($db_0 & 0x01 ? "on" : "off");
+ push @event, "3:dimValue:" . $db_2;
+ if ($db_2 > 0) {
+ push @event, "3:dimValueLast:" . $db_2;
+ }
+ }
+
} else {
+ # unknown devices
push @event, "3:state:$db_3";
push @event, "3:sensor1:$db_3";
push @event, "3:sensor2:$db_2";
@@ -617,9 +1524,7 @@ EnOcean_Parse($$)
push @event, "3:D2:".(($db_0&0x4)?1:0);
push @event, "3:D1:".(($db_0&0x2)?1:0);
push @event, "3:D0:".(($db_0&0x1)?1:0);
-
}
-
}
readingsBeginUpdate($hash);
@@ -633,6 +1538,7 @@ EnOcean_Parse($$)
return $name;
}
+# MD15Cmd
sub
EnOcean_MD15Cmd($$$)
{
@@ -641,19 +1547,27 @@ EnOcean_MD15Cmd($$$)
if($cmd) {
my $msg; # Unattended
my $arg1 = ReadingsVal($name, $cmd, 0); # Command-Argument
-
if($cmd eq "actuator") {
- $msg = sprintf("%02X000000", $arg1);
-
+# $msg = sprintf("%02X000000", $arg1);
+ $msg = sprintf("%02X7F0008", $arg1);
} elsif($cmd eq "desired-temp") {
- $msg = sprintf("%02X%02X0400", $arg1*255/40,
- AttrVal($name, "actualTemp", ($db_1*40/255)) * 255/40);
-
+# $msg = sprintf "%02X%02X0400", $arg1*255/40, AttrVal($name, "actualTemp", ($db_1*40/255)) * 255/40;
+# $msg = sprintf "%02X%02X0408", $arg1*255/40, AttrVal($name, "actualTemp", (255 - $db_1)*40/255) *255/40;
+# $msg = sprintf "%02X7F0408", $arg1*255/40;
+ $msg = sprintf "%02X%02X0408", $arg1*255/40, AttrVal($name, "actualTemp", 127);
} elsif($cmd eq "initialize") {
- $msg = sprintf("00006400");
-
- }
-
+# $msg = sprintf("00006400");
+ $msg = "00006408";
+ # Maintenance commands
+ } elsif($cmd eq "runInit") {
+ $msg = "00008108";
+ } elsif($cmd eq "liftSet") {
+ $msg = "00004108";
+ } elsif($cmd eq "valveOpen") {
+ $msg = "00002108";
+ } elsif($cmd eq "valveClosed") {
+ $msg = "00001108";
+ }
if($msg) {
select(undef, undef, undef, 0.2);
EnOcean_A5Cmd($hash, $msg, EnOcean_GetMyDeviceId($hash));
@@ -665,6 +1579,7 @@ EnOcean_MD15Cmd($$$)
}
}
+# A5Cmd
sub
EnOcean_A5Cmd($$$)
{
@@ -674,6 +1589,15 @@ EnOcean_A5Cmd($$$)
# type=A5 msg:4 senderId:4 status=00 subTelNum=01 destId:4 dBm=FF Security=00
}
+# Undef
+sub
+EnOcean_Undef($$)
+{
+ my ($hash, $arg) = @_;
+ delete $modules{EnOcean}{defptr}{uc($hash->{DEF})};
+ return undef;
+}
+
# ---------------------------------------------
#
@@ -702,107 +1626,289 @@ sub EnOcean_GetMyDeviceId($)
EnOcean
- Devices sold by numerous hardware verndors (e.g. Eltako, Peha, etc), using
- the RF Protocol provided by the EnOcean Alliance.
+ EnOcean devices are sold by numerous hardware vendors (e.g. Eltako, Peha, etc),
+ using the RF Protocol provided by the EnOcean Alliance. Depending on the
+ function of the device an specific device profile is used, called EnOcean
+ Equipment Profile (EEP). Basically three profiles will be differed, e. g.
+ switches, contacts, sensors. Some manufacturers use additional proprietary
+ extensions. Further technical information can be found at the
+ EnOcean Alliance,
+ see in particular the
+ EnOcean Equipment Profiles (EEP).
+ Fhem recognizes a number of devices automatically. In order to teach-in, for
+ some devices the sending of confirmation telegrams has to be turned on.
+ Some equipment types and/or device models must be manually specified.
+ Do so using the attributes
+ subType and model, see chapter
+ Set and
+ Generated events. With the help of additional
+ attributes, the behavior of the devices can be
+ changed separately.
+
+ The teach-in procedure depends on the type of the devices. Switches (EEP RORG
+ F6) and contacts (EEP RORG D5) are recognized when receiving the first message.
+ Contacts can also send a teach-in telegram. Fhem not need this telegram.
+ Sensors (EEP RORG A5) has to send a teach-in telegram. The profile-less
+ A5 teach-in procedure transfers no EEP profile identifier and no manufacturer
+ ID. In this case Fhem does not recognize the device automatically. The proper
+ device type must be set manually, use the attributes
+ subType, manufID and/or
+ model. If the EEP profile identifier and the manufacturer
+ ID are sent the device is clearly identifiable. FHEM automatically assigns
+ these devices to the correct profile. Some A5 devices must be paired
+ bidirectional, see Bidirectional A5 Teach-In.
+ Fhem supports many of most common EnOcean profiles and manufacturer-specific
+ devices. Additional profiles and devices can be added if required.
+
+ In order to enable communication with EnOcean remote stations a
+ TCM module is necessary.
+
+
Define
- define <name> EnOcean <ID>
+ define <name> EnOcean <def>
- Define an EnOcean device, connected via a TCM. The
- <ID> parameter is an 8 digit hex number. For remotes and sensors the
- autocreate module may help you.
+ Define an EnOcean device, connected via a TCM modul. The
+ <def> is the SenderID/DestinationID of the device (8 digit hex number).
+ The autocreate module may help you.
Example:
define switch1 EnOcean ffc54500
-
+
+ In order to control devices, you cannot reuse the SenderIDs/
+ DestinationID of other devices (like remotes), instead you have to create
+ your own, which must be in the allowed SenderID range of the underlying Fhem
+ IO device. For this first query the TCM with the
+ get <tcm> idbase command for the BaseID. You can use
+ up to 127 IDs starting with the BaseID + 1 shown there. The BaseID is
+ used for A5 devices with a bidectional teach-in only. If you are using an Fhem
+ SenderID outside of the allowed range, you will see an ERR_ID_RANGE
+ message in the Fhem log.
+ In order to control bidirectional F6 devices (switches, actors) with
+ additional SenderIDs you can use the attributes subDef,
+ subDef0 and subDefI.
+ Fhem communicates unicast with the BaseID, if the A5 devices are teached-in with the
+ Bidirectional A5 Teach-In procedure. In this case
+ Fhem send telegrams with its SenderID (BaseID) and the DestinationID of the
+ device.
-
Set
-
- - MD15 commands. Note: The command is not sent until the MD15
- wakes up and sends a mesage, usually every 10 minutes.
+
- Bidirectional A5 Teach-In
- - actuator <value>
- Set the actuator to the specifed percent value (0-100)
- - desired-temp <value>
- Use the builtin PI regulator, and set the desired temperature to the
- specified degree. The actual value will be taken from the temperature
- reported by the MD15 or from the attribute actualTemp if it is set
- - unattended
- Do not regulate the MD15.
-
-
- - subType eltakoDimmer, tested with Eltako devices only
-
- - teach
- initiate teach-in mode
- - dimm percent [time 1-100]
- issue dim command.
- - dimmup percent [time 1-100]
- issue dim command.
- - dimmdown percent [time 1-100]
- issue dim command.
- - dimm on
- issue switch on command.
- - dimm off
- issue switch off command.
-
-
- Note: set extensions are supported
-
- - subType eltakoShutter, tested with Eltako devices only (additionally set attr model to FSB61)
-
- - teach
- initiate teach-in mode
- - up [percent]
- issue roll up command.
- - down [percent]
- issue roll down command.
- - stop
- issue stop command.
-
-
-
- - all other:
-
- set switch1 <value>
+ set <name> pairForSec <t/s>
- where value is one of A0,AI,B0,BI,C0,CI,D0,DI, combinations of
- these and released, in fact we are trying to emulate a PTM100 type remote.
+ Set the EnOcean Transceiver module (TCM Modul) in the bidirectional pairing
+ mode. A device, which is then also put in this state is to paired with
+ Fhem bidirectional. Bidirectional pearing is only used for some EEP A5-xx-xx,
+ e. g. EEP A5-20-01 (Battery Powered Actuator).
+ name is the name of the TCM Module . t/s is the
+ time for the teach-in period.
+
+ Example:
+ set TCM_0 pairForSec 600
+
+
+
+
+ - Switch, Pushbutton Switch, Bidirectional Actor (EEP F6-02-01 ... F6-03-02)
+
+ set <name> <value>
+
+ where value is one of A0, AI, B0, BI, C0, CI, D0, DI,
+ combinations of these and released. First and second action can be sent
+ simultaneously. Separate first and second action with a comma.
+ In fact we are trying to emulate a PT200 type remote.
If you define an eventMap attribute with on/off,
- then you'll be able to easily set the device from the WEB frontend.
- In order to control devices, you cannot reuse the ID's of other devices
- (like remotes), instead you have to create your own, which must be in the
- allowed ID-Range of the underlying IO device. For this first query the
- TCM with the "get <tcm> idbase" command. You can use
- up to 128 ID's starting with the base shown there. If you are using an
- ID outside of the allowed range, you'll see an ERR_ID_RANGE message in the
- fhem log.
-
+ then you will be able to easily set the device from the WEB frontend.
+ set extensions are supported, if the corresponding
+ eventMap specifies the on and off
+ mappings.
+ With the help of additional attributes, the
+ behavior of the devices can be adapt.
+
Example:
set switch1 BI
set switch1 B0,CI
attr eventMap BI:on B0:off
set switch1 on
-
- Note: set extensions are supported,
- if the corresponding eventMap specifies
- the on and off mappings.
-
+
+
-
+ Staircase off-delay timer (EEP F6-02-01 ... F6-02-02)
+ [Eltako FTN14, tested with Eltako FTN14 only]
+
+ set <name> <value>
+
+ where value is
+ - on
+ issue switch on command
+ - released
+ start timer
+
+ Set attr eventMap to B0:on BI:off, attr subType to switch, attr
+ webCmd to on:released and if needed attr switchMode to pushbutton manually.
+ Use the sensor type "Schalter" for Eltako devices. The Staircase
+ off-delay timer is switched on when pressing "on" and the time will be started
+ when pressing "released". "released" immediately after "on" is sent if
+ the attr switchMode is set to "pushbutton".
+
+
+
+ Dimmer
+ [Eltako FUD12, FUD14, FUD61, FUD70, tested with Eltako devices only]
+
+ set <name> <value>
+
+ where value is
+ - teach
+ initiate teach-in mode
+ - on
+ issue switch on command
+ - off
+ issue switch off command
+ - dim dim/% [dim time 1-100/%]
+ issue dim command
+ - dimup dim/% [dim time 1-100/%]
+ issue dim command
+ - dimdown dim/% [dim time 1-100/%]
+ issue dim command
+ - set extensions are supported.
+
+ Set attr subType to eltakoDimmer manually.
+ Use the sensor type "PC/FVS" for Eltako devices.
+
+
+
+ Dimmer for fluorescent lamps
+ [Eltako FSG70, tested with Eltako FSG70 only]
+
+ set <name> <value>
+
+ where value is
+ - on
+ issue switch on command
+ - off
+ issue switch off command
+ - set extensions are supported.
+
+ Set attr eventMap to B0:on BI:off, attr model to FSG70, attr
+ subType to eltakoDimmer and attr switchMode to pushbutton manually.
+ Use the sensor type "Richtungstaster" for Eltako devices.
+
+
+
+ Battery Powered Actuator (EEP A5-20-01)
+ [Kieback&Peter MD15-FTL-xx]
+
+ set <name> <value>
+
+ where value is
+ - actuator setpoint/%
+ Set the actuator to the specifed setpoint (0-100)
+ - desired-temp <value>
+ Use the builtin PI regulator, and set the desired temperature to the
+ specified degree. The actual value will be taken from the temperature
+ reported by the MD15 or from the attribute actualTemp if it is set.
+ - runInit
+ Maintenance Mode (service on): Run init sequence.
+ - liftSet
+ Maintenance Mode (service on): Lift set
+ - valveOpen
+ Maintenance Mode (service on): Valve open
+ - valveClosed
+ DMaintenance Mode (service on): Valve closed
+ - unattended
+ Do not regulate the actuator.
+
+ The attr subType must be MD15. This is done if the device was
+ created by autocreate. To control the device, it must be bidirectional paired,
+ see Bidirectional A5 Teach-In.
+ The command is not sent until the device wakes up and sends a mesage, usually
+ every 10 minutes.
+
+
+
+ PHC Gateway (EEP A5-38-08)
+ Switching
+ [Eltako FLC61, FSR14]
+
+ set <name> <value>
+
+ where value is
+ - teach
+ initiate teach-in mode
+ - on [lock|unlock]
+ issue switch on command
+ - off [lock|unlock]
+ issue switch off command
+ - set extensions are supported.
+
+ The attr subType must be phcGateway and phcCmd must be switching. This is done if the device was
+ created by autocreate.
+ For Eltako devices attributes must be set manually.
+
+
+
+ PHC Gateway (EEP A5-38-08)
+ Dimming
+ [Eltako FUD14, FUD61, FUD70, FSG14, ...]
+
+ set <name> <value>
+
+ where value is
+ - teach
+ initiate teach-in mode
+ - on [lock|unlock]
+ issue switch on command
+ - off [lock|unlock]
+ issue switch off command
+ - dim dim/% [rampTime/s lock|unlock]
+ issue dim command
+ - dimup dim/% [rampTime/s lock|unlock]
+ issue dim command
+ - dimdown dim/% [rampTime/s lock|unlock]
+ issue dim command
+ - set extensions are supported.
+
+ rampTime Range: t = 1 s ... 255 s or 0 if no time specified,
+ for Eltako: t = 1 = fast dimming ... 255 = slow dimming or 0 = dimming speed on the dimmer used)
+ The attr subType must be phcGateway and phcCmd must be dimming. This is done if the device was
+ created by autocreate.
+ For Eltako devices attributes must be set manually. Use the sensor type "PC/FVS" for Eltako devices.
+
+
+
+ Shutter (EEP F6-02-01 ... F6-02-02 and A5-3F-7F)
+ [Eltako FSB12, FSB14, FSB61, FSB70, tested with Eltako devices only]
+
+ set <name> <value>
+
+ where value is
+ - teach
+ initiate teach-in mode
+ - up [position/%]
+ issue roll up command
+ - down [position/%]
+ issue roll down command
+ - stop
+ issue stop command
+
+ Set attr subType to eltakoShutter and attr model to
+ FSB12|FSB14|FSB61|FSB70 manually.
+ Use the sensor type "Szenentaster/PC" for Eltako devices.
+
@@ -812,37 +1918,113 @@ sub EnOcean_GetMyDeviceId($)
Attributes
- - eventMap
- - IODev
- - loglevel
- - do_not_notify
- - ignore
- - showtime
- - model
- - subType
- - readingFnAttributes
- - actualTemp
+ - actualTemp t/°C
The value of the actual temperature, used when controlling MD15 devices.
Should by filled via a notify from a distinct temperature sensor. If
absent, the reported temperature from the MD15 is used.
- - subDef
- SenderID (TCM BaseID + offset) to control a bidirectional switch or actor
-
+ - devStateIcon
+ - dimTime relative, [dimTime] = 1 is default.
+ No ramping or for Eltako dimming speed set on the dimmer if [dimTime] = 0.
+ Ramping time which fast to low dimming if [dimTime] = 1 ... 100.
+ dimTime is supported for dimmer.
+
+ - dimValueOn dim/%|last|stored,
+ [dimValueOn] = 100 is default.
+ Dim value for the command "on".
+ The dimmer switched on with the value 1 % ... 100 % if [dimValueOn] =
+ 1 ... 100.
+ The dimmer switched to the last dim value received from the
+ bidirectional dimmer if [dimValueOn] = last.
+ The dimmer switched to the last Fhem dim value if [dimValueOn] =
+ stored.
+ dimValueOn is supported for dimmer.
+
+ - do_not_notify
+ - eventMap
+ - ignore
+ - IODev
+ - loglevel
+ - model
+ - rampTime t/s or relative, [rampTime] = 1 is default.
+ No ramping or for Eltako dimming speed set on the dimmer if [rampTime] = 0.
+ Ramping time 1 s to 255 s or relative fast to low dimming speed if [rampTime] = 1 ... 255.
+ rampTime is supported for phcGateway, command dimming.
+
+ - readingFnAttributes
+ - showtime
+ - shutTime t/s
+ Use the attr shutTime to set the time delay to the position "Halt" in
+ seconds. Select a delay time that is at least as long as the shading element
+ or roller shutter needs to move from its end position to the other position.
+ shutTime is supported for shutter.
+
+ - subDef <EnOcean SenderID>,
+ [subDef] = [def] is default.
+ SenderID (TCM BaseID + offset) to control a bidirectional switch or actor.
In order to control bidirectional devices, you cannot reuse the ID of this
devices, instead you have to create your own, which must be in the
allowed ID-Range of the underlying IO device. For this first query the
- TCM with the "get <tcm> idbase" command. You can use
- up to 128 ID's starting with the base shown there.
- subDef is supported for switch devices, eltakoDimmer and eltakoShutter.
+ TCM with the "get <tcm> idbase" command. You can use
+ up to 128 IDs starting with the base shown there.
+ subDef is supported for switches, staircase off-delay timer, dimmer and
+ shutter.
+
+ - subDef0 <EnOcean SenderID>,
+ [subDef0] = [def] is default.
+ SenderID (TCM BaseID + offset) for [value] = A0|B0|C0|D0|released
+ Used with switch type "central". Set attr switchType to central.
+ Use the sensor type "zentral aus/ein" for Eltako devices.
+ subDef0 is supported for switches.
+ Second action is not sent.
+
+ - subDefI <EnOcean SenderID>,
+ [subDefI] = [def] is default.
+ SenderID (TCM BaseID + offset) for [value] = AI|BI|CI|DI
+ Used with switch type "central". Set attr switchType to central.
+ Use the sensor type "zentral aus/ein" for Eltako devices.
+ subDefI is supported for switches.
+ Second action is not sent.
+
+ - subType
+ - switchMode switch|pushbutton,
+ [SwitchMode] = switch is default.
+ The set command "released" immediately after <value> is sent if the
+ attribute is set to "pushbutton".
+
+ - switchType direction|universal|central,
+ [SwitchType] = direction is default.
+ EnOcean Devices support different types of sensors, e. g. direction
+ switch, universal switch or pushbutton, central on/off.
+ For Eltako devices these are the sensor types "Richtungstaster",
+ "Universalschalter" or "Universaltaster", "Zentral aus/ein".
+ With the sensor type direction switch on/off commands are
+ accepted, e. g. B0, BI, released. Fhem can control an device with this
+ sensor type unique. This is the default function and should be
+ preferred.
+ Some devices only support the sensor type universal switch
+ or pushbutton. With a Fhem command, for example,
+ B0 or BI is switched between two states. In this case Fhem cannot
+ control this device unique. But if the Attribute switchType
+ is set to universal Fhem synchronized with
+ a bidirectional device and normal on/off commands can be used.
+ If the bidirectional device response with the channel B
+ confirmation telegrams also B0 and BI commands are to be sent,
+ e g. channel A with A0 and AI. Also note that confirmation telegrams
+ needs to be sent.
+ Partly for the sensor type central two different SenderID
+ are required. In this case set the Attribute switchType to
+ central and define the Attributes
+ subDef0 and subDefI.
+
+ - webCmd
- Generated events:
+ Generated events
- - switch. Switches (remotes) with more than one (pair) of buttons
- are separate devices with separate address.
+
- Switch / Bidirectional Actor (EEP F6-02-01 ... F6-03-02)
- A0
- AI
@@ -852,121 +2034,514 @@ sub EnOcean_GetMyDeviceId($)
- CI
- D0
- DI
- - A0,BI
- - <BtnX,BtnY> where BtnX and BtnY is one of the above, e.g.
- A0,BI or D0,CI
- - buttons:released
- - buttons:<BtnX> released
-
-
-
- - FSB61/FSM61 (set model to FSB61 or FSM61 manually)
+ - <BtnX,BtnY> First and second action where BtnX and BtnY is
+ one of the above, e.g. A0,BI or D0,CI
+ - buttons: released
+ - buttons: <BtnX> released
+
+ Switches (remote controls) or actors with more than one
+ (pair) keys may have multiple channels e. g. B0/BI, A0/AI with one
+ SenderID or with separate addresses.
+
+
+
+ Pushbutton Switch, Pushbutton Input Module (EEP F6-02-01 ... F6-02-02)
+ [Eltako FT55, FSM12, FSM61, FTS12]
- - B0
- - BI
- - released
- The status of the device may become "released", this is not the case
- for a normal switch.
-
+ A0
+ AI
+ B0
+ BI
+ released
+ state: A0|AI|B0|BI|released
+
+ The status of the device may become "released", this
+ is not the case for a normal switch.
+ Set attr model to FT55|FSM12|FSM61|FTS12 manually.
+
+
- windowHandle (HOPPE SecuSignal). Set the subType attr to windowHandle.
+ Key Card Activated Switch (EEP F6-04-01)
+ [Eltako FKC, FKF, FZS, untested]
+
+ - keycard inserted
+ - keycard removed
+ - state: keycard inserted|keycard removed
+
+ Set attr subType to keycard manually.
+
+
+
+ Single Input Contact, Door/Window Contact
+ (EEP D5-00-01, 1BS Telegram)
+ [Eltako FTK, Peha D 450 FU, STM-250, BSC ?]
+
+ - closed
+ - open
+ - learnBtn: on
+ - state: open|closed
+
+
+
+ Dimmer
+ [Eltako FUD14, FUD61, FUD70, FSG14, FSG70, ...]
+
+ - on
+ - off
+ - dimValue: Dim/% (Sensor Range: Dim = 0 % ... 100 %)
+ - dimValueLast: Dim/%
+ Last value received from the bidirectional dimmer.
+ - dimValueStored: Dim/%
+ Last value saved by set <name> dim <value>.
+ - state: on|off
+
+ Set attr subType to eltakoDimmer manually.
+
+
+
+ Shutter (EEP F6-02-01 ... F6-02-02)
+ [Eltako FSB14, FSB61, FSB70]
+
+ - B0
+ The status of the device will become "B0" after the TOP endpoint is
+ reached, or it has finished an "up %" command.
+ - BI
+ The status of the device will become "BI" if the BOTTOM endpoint is
+ reached
+ - released
+ The status of the device become "released" between one of the endpoints.
+ - state: BO|BI|released
+
+ Set attr subType to eltakoShutter and attr model to
+ FSB14|FSB61|FSB70 manually.
+
+
+
+ Window Handle (EEP F6-10-00)
+ [HOPPE SecuSignal]
- closed
- open
- tilted
- open from tilted
-
+ state: closed|open|tilted|open from tilted
+
+ The device should be created by autocreate.
+
+
- keycard. Set the subType attr to keycard. (untested)
+ Smoke Detector (EEP F6-02-01 ... F6-02-02)
+ [Eltako FRW, untested]
- - keycard inserted
- - keycard removed
-
+ smoke-alarm
+ off
+ alarm: smoke-alarm|off
+ battery: low|ok
+ state: smoke-alarm|off
+
+ Set attr subType to FRW.
+
+
- STM-250 Door and window contact.
+ Battery Powered Actuator (EEP A5-20-01)
+ [Kieback&Peter MD15-FTL-xx]
- - closed
- - open
- - learnBtn: on
-
+ Actuator/%
+ actuator: ok|obstructed
+ battery: ok|low
+ currentValue: Actuator/%
+ cover: open|closed
+ energyInput: enabled|disabled
+ energyStorage: charged|empty
+ selfCtl: on|off
+ serviceOn: yes|no
+ temperature: t/°C
+ tempSensor: failed|ok
+ window: open|closed
+ state: Actuator/%
+
+ The attr subType must be MD15. This is done if the device was created by
+ autocreate.
+
+
- SR04* (Temp sensor + Presence button and desired temp dial).
- Set the
- model attribute to one of SR04 SR04P SR04PT SR04PST SR04PMS or the
- subType attribute to SR04.
+ Temperature Sensors with with different ranges (EEP A5-02-01 ... A5-02-30)
+ [Thermokon SR65, untested]
- - temperature: XY.Z
- - set_point: [0..255]
- - fan: [0|1|2|3|Auto]
- - present: yes
- - learnBtn: on
- - T: XY.Z SP: [0..255] F: [0|1|2|3|Auto] P: [yes|no]
-
-
- MD15-FtL-HE (Heating/Valve-regulator)
- The subType attibute must be MD15. This is done if the device was created by
- autocreate.
-
- - $actuator %
- - currentValue: $actuator
- - serviceOn: [yes|no]
- - energyInput: [enabled|disabled]
- - energyStorage: [charged|empty]
- - battery: [ok|empty]
- - cover: [open|closed]
- - tempSensor: [failed|ok]
- - window: [open|closed]
- - actuator: [ok|obstructed]
- - temperature: $tmp
-
-
- Ratio Presence Sensor Eagle PM101.
- Set the model attribute to PM101
-
- - brightness: $lux
- - channel1: [on|off]
- - channel2: [on|off]
-
-
- FAH60,FAH63,FIH63 brigthness senor.
- Set subType to FAH or model to FAH60/FAH63/FIH63 manually.
-
- - brightness: $lux
- - state: $lux
-
-
- FABH63,FBH55,FBH63,FIBH63 Motion/brightness sensor.
- Set subType to FBH or model to FABH63/FBH55/FBH63/FIBH63 manually.
-
- - brightness: $lux
- - motion:[yes|no]
- - state: [motion: yes|no]
-
-
- FTF55 Temperature sensor.
- Set subType to FTF or model to FTF55 manually.
-
- - temperature: $temp
- - state: $temp
-
-
- eltakoDimmer
-
+ t/°C
+ temperature: t/°C (Sensor Range: t = <t min> °C ... <t max> °C)
+ state: t/°C
+
+ The attr subType must be tempSensor.01 ... tempSensor.30. This is done if the device was
+ created by autocreate.
+
+
- eltakoShutter
+ Gas Sensor, CO Sensor (EEP A5-09-01)
+ [untested]
- - B0
- The status of the device will become B0 after the TOP endpoint is
- reached, or it has finished an "up %%" command.
- - BI
- The status of the device will become BI if the BOTTOM endpoint is
- reached
- - released
- The status of the device may become "released", this is not the case
- for a normal switch.
-
+ Channel1, Channel2: c/ppm (Sensor Range: c = 0 ppm ... 255 ppm)
+ temperature: t/°C (Sensor Range: t = 0 °C ... 255 °C)
+ state: c/ppm | measuring error
+
+ The attr subType must be COSensor.01. This is done if the device was
+ created by autocreate.
+
+
+
+ Gas Sensor, CO2 Sensor (EEP A5-09-04)
+ [Thermokon SR04 CO2 *, untested]
+
+ - airQuality: high|mean|moderate|low (Air Quality Classes DIN EN 13779)
+ - CO2: c/ppm (Sensor Range: c = 0 ppm ... 2550 ppm)
+ - humidity: rH/% (Sensor Range: rH = 0 % ... 100 %)
+ - temperature: t/°C (Sensor Range: t = 0 °C ... 51 °C)
+ - state: CO2: c/ppm AQ: high|mean|moderate|low T: t/°C H: rH/%
+
+ The attr subType must be tempHumiCO2Sensor.01. This is done if the device was
+ created by autocreate.
+
+
+
+ Room Sensor and Control Unit (EEP A5-10-01 ... A5-10-0D)
+ [Eltako FTF55, FTR55*, Thermokon SR04 *, Thanos SR *, untested]
+
+ - T: t/°C SP: 0 ... 255 F: 0|1|2|3|Auto SW: 0|1
+ - fan: 0|1|2|3|Auto
+ - switch: 0|1
+ - setpoint: 0 ... 255
+ - temperature: t/°C (Sensor Range: t = 0 °C ... 40 °C)
+ - state: T: t/°C SP: 0 ... 255 F: 0|1|2|3|Auto SW: 0|1
+ Alternatively for Eltako devices
+ - T: t/°C SPT: t/°C NR: t/°C
+ - nightReduction: t/°C
+ - setpointTemp: t/°C
+ - temperature: t/°C (Sensor Range: t = 0 °C ... 40 °C)
+ - state: T: t/°C SPT: t/°C NR: t/°C
+
+ The attr subType must be roomSensorControl.05 and attr
+ manufID must be 00D for Eltako Devices. This is done if the device was
+ created by autocreate.
+
+
+ Room Sensor and Control Unit (EEP A5-04-01, A5-10-10 ... A5-10-14)
+ [Thermokon SR04 * rH, Thanos SR *, untested]
+
+ - T: t/°C H: rH/% SP: 0 ... 255 SW: 0|1
+ - humidity: rH/% (Sensor Range: rH = 0 % ... 100 %)
+ - switch: 0|1
+ - temperature: t/°C (Sensor Range: t = 0 °C ... 40 °C)
+ - setpoint: 0 ... 255
+ - state: T: t/°C H: rH/% SP: 0 ... 255 SW: 0|1
+
+ The attr subType must be roomSensorControl.01. This is
+ done if the device was created by autocreate.
+
+
+
+ Room Sensor and Control Unit (EEP A5-10-15 - A5-10-17)
+ [untested]
+
+ - T: t/°C P: absent|present SP: 0 ... 63
+ - presence: absent|present
+ - temperature: t/°C (Sensor Range: t = -10 °C ... 41.2 °C)
+ - setpoint: 0 ... 63
+ - state: T: t/°C P: absent|present SP: 0 ... 63
+
+ The attr subType must be roomSensorControl.02. This is done if the device was
+ created by autocreate.
+
+
+
+ Temperatur and Humidity Sensor (EEP A5-04-02)
+ [Eltako FAFT60, FIFT63AP]
+
+ - T: t/°C H: rH/% B: unknown|low|ok
+ - battery: unknown|low|ok
+ - energyStorage: unknown|empty|charged|full
+ - humidity: rH/% (Sensor Range: rH = 0 % ... 100 %)
+ - temperature: t/°C (Sensor Range: t = -20 °C ... 60 °C)
+ - voltage: U/V
(Sensor Range: U = 0 V ... 6.6 V)
+ - state: T: t/°C H: rH/% B: unknown|low|ok
+
+ The attr subType must be tempHumiSensor.02 and attr
+ manufID must be 00D for Eltako Devices. This is done if the device was
+ created by autocreate.
+
+
+
+ Light Sensor (EEP A5-06-01)
+ [Eltako FAH60, FAH63, FIH63, Thermokon SR65 LI]
+
+ - E/lx
+ - brightness: E/lx (Sensor Range: 300 lx ... 30 klx, 600 lx ... 60 klx
+ , Sensor Range for Eltako: E = 0 lx ... 100 lx, 300 lx ... 30 klx)
+ - voltage: U/V
(Sensor Range: U = 0 V ... 5.1 V)
+ - state: E/lx
+
+ Eltako devices only support Brightness.
+ The attr subType must be lightSensor.01 and attr manufID must be 00D
+ for Eltako Devices. This is done if the device was created by
+ autocreate.
+
+
+
+ Light Sensor (EEP A5-06-02)
+ [untested]
+
+ - E/lx
+ - brightness: E/lx (Sensor Range: 0 lx ... 1020 lx
+ - voltage: U/V
(Sensor Range: U = 0 V ... 5.1 V)
+ - state: E/lx
+
+ The attr subType must be lightSensor.02. This is done if the device was
+ created by autocreate.
+
+
+
+ Occupancy Sensor (EEP A5-07-01)
+ [untested]
+
+ - on|off
+ - motion: on|off
+ - state: on|off
+
+ The attr subType must be occupSensor.01. This is done if the device was
+ created by autocreate.
+
+
+
+ Light, Temperatur and Occupancy Sensor (EEP A5-08-01 ... A5-08-03)
+ [Eltako FABH63, FBH55, FBH63, FIBH63, Thermokon SR-MDS]
+
+ - M: on|off E: E/lx P: absent|present T: t/°C U: U/V
+ - brightness: E/lx (Sensor Range: E = 0 lx ... 510, 1020, 1530 or 2048 lx)
+ - motion: on|off
+ - presence: absent|present
+ - temperature: t/°C (Sensor Range: t = 0 °C ... 51 °C or -30 °C ... 50 °C)
+ - voltage: U/V
(Sensor Range: U = 0 V ... 5.1 V)
+ - state: M: on|off E: E/lx P: absent|present T: t/°C U: U/V
+
+ Eltako devices only support Brightness and Motion.
+ The attr subType must be lightTempOccupSensor.<01|02|03> and attr
+ manufID must be 00D for Eltako Devices. This is done if the device was
+ created by autocreate.
+
+
+
+ Automated meter reading (AMR), Counter (EEP A5-12-00)
+ [Thermokon SR-MI-HS, untested]
+
+ - 1/s
+ - currentValue: 1/s
+ - counter<0 ... 15>: 0 ... 16777215
+ - channel: 0 ... 15
+ - state: 1/s
+
+ The attr subType must be autoMeterReading.00. This is done if the device was
+ created by autocreate.
+
+
+
+ Automated meter reading (AMR), Electricity (EEP A5-12-01)
+ [Eltako FSS12, DSZ14DRS, DSZ14WDRS, Thermokon SR-MI-HS, untested]
+ [Eltako FWZ12-16A tested]
+
+ - P/W
+ - power: P/W
+ - energy<0 ... 15>: E/kWh
+ - currentTariff: 0 ... 15
+ - serialNumber: S-<nnnnnn>
+ - state: P/W
+
+ The attr subType must be autoMeterReading.01 and attr
+ manufID must be 00D for Eltako Devices. This is done if the device was
+ created by autocreate.
+
+
+
+ Automated meter reading (AMR), Gas, Water (EEP A5-12-02, A5-12-03)
+ [untested]
+
+ - Vs/l
+ - flowrate: Vs/l
+ - consumption<0 ... 15>: V/m3
+ - currentTariff: 0 ... 15
+ - state: Vs/l
+
+ The attr subType must be autoMeterReading.02|autoMeterReading.02.
+ This is done if the device was created by autocreate.
+
+
+
+ Weather Station (EEP A5-13-01 ... EEP A5-13-06)
+ [Eltako FWS61, untested]
+
+ - T: t/°C B: E/lx W: Vs/m IR: yes|no
+ - brightness: E/lx (Sensor Range: E = 0 lx ... 999 lx)
+ - temperature: t/°C (Sensor Range: t = -40 °C ... 80 °C)
+ - dayNight: day|night
+ - isRaining: yes|no
+ - sunEast: E/lx (Sensor Range: E = 1 lx ... 150 klx)
+ - sunSouth: E/lx (Sensor Range: E = 1 lx ... 150 klx)
+ - sunWest: E/lx (Sensor Range: E = 1 lx ... 150 klx)
+ - windSpeed: Vs/m (Sensor Range: V = 0 m/s ... 70 m/s)
+ - state:T: t/°C B: E/lx W: Vs/m IR: yes|no
+
+ Brightness is the strength of the dawn light. SunEast,
+ sunSouth and sunWest are the solar radiation from the respective
+ compass direction. IsRaining is the rain indicator.
+ The attr subType must be weatherStation and attr manufID must be 00D
+ for Eltako Devices. This is done if the device was created by
+ autocreate.
+ The Eltako Weather Station FWS61 supports not the day/night indicator
+ (dayNight).
+ EEP A5-13-03 ... EEP A5-13-06 are not implemented.
+
+
+
+ Digital Input (EEP A5-30-01, A5-30-02)
+ [Thermokon SR65 DI, untested]
+
+ - open|closed
+ - battery: ok|low (only EEP A5-30-01)
+ - contact: open|closed
+ - state: open|closed
+
+ The attr subType must be digitalInput.01 or digitalInput.02. This is done if the device was
+ created by autocreate.
+
+
+
+ PHC Gateway (EEP A5-38-08)
+ Switching
+ [Eltako FLC61, FSR14]
+
+ - on
+ - off
+ - executeTime: t/s (Sensor Range: t = 0.1 s ... 6553.5 s or 0 if no time specified)
+ - executeType: duration|delay
+ - lock: lock|unlock
+ - state: on|off
+
+ The attr subType must be phcGateway and phcCmd must be switching. This is done if the device was
+ created by autocreate.
+ For Eltako devices attributes must be set manually. Eltako devices only send on/off.
+
+
+
+ PHC Gateway (EEP A5-38-08)
+ Dimming
+ [Eltako FUD14, FUD61, FUD70, FSG14, ...]
+
+ - on
+ - off
+ - dimValue: dim/% (Sensor Range: dim = 0 % ... 100 %)
+ - dimValueLast: dim/%
+ Last value received from the bidirectional dimmer.
+ - dimValueStored: dim/%
+ Last value saved by set <name> dim <value>.
+ - rampTime: t/s (Sensor Range: t = 1 s ... 255 s or 0 if no time specified,
+ for Eltako: t = 1 = fast dimming ... 255 = slow dimming or 0 = dimming speed on the dimmer used)
+ - state: on|off
+
+ The attr subType must be phcGateway, phcCmd must be dimming and attr manufID must be 00D
+ for Eltako Devices. This is done if the device was created by autocreate.
+ For Eltako devices attributes must be set manually. Eltako devices only send on/off and dimValue.
+
+
+
+ Manufacturer Specific Applications (EEP A5-3F-7F)
+ Wireless Analog Input Module
+ [Thermokon SR65 3AI, untested]
+
+ - I1: U/V I2: U/V I3: U/V
+ - input1: U/V (Sensor Range: U = 0 V ... 10 V)
+ - input2: U/V (Sensor Range: U = 0 V ... 10 V)
+ - input3: U/V (Sensor Range: U = 0 V ... 10 V)
+ - state: I1: U/V I2: U/V I3: U/V
+
+ The attr subType must be manufProfile and attr manufID must be 002
+ for Thermokon Devices. This is done if the device was
+ created by autocreate.
+
+
+
+ Light Sensor (EEP similar 07-06-01)
+ [Eltako FAH60, FAH63, FIH63]
+
+ - E/lx
+ - brightness: E/lx (Sensor Range: E = 0 lx ... 100 lx, 300 lx ... 30 klx)
+ - state: E/lx
+
+ Old profile, use subType lightSensor.01 alternative.
+ Set attr subType to FAH or attr model to FAH60|FAH63|FIH63
+ manually.
+
+
+
+ Light and Occupancy Sensor (EEP similar A5-08-01)
+ [Eltako FABH63, FBH55, FBH63, FIBH63]
+
+ - yes
+ - no
+ - brightness: E/lx (Sensor Range: E = 0 lx ... 2048 lx)
+ - motion: yes|no
+ - state: yes|no
+
+ Old profile, use subType lightTempOccupSensor.01 alternative.
+ Set attr subType to FBH or attr model to FABH63|FBH55|FBH63|FIBH63
+ manually.
+
+
+
+ Temperature Sensor (EEP A5-02-05)
+ [Eltako FTF55]
+
+ - t/°C
+ - temperature: t/°C (Sensor Range: t = 0 °C ... 40 °C)
+ - state: t/°C
+
+ Old profile, use subType roomSensorControl.05 alternative.
+ Set attr subType to FTF or attr model to FTF55 manually.
+
+
+
+ Room Sensor and Control Unit (EEP A5-02-05, A5-10-03, A5-10-0C, A5-10-05,
+ A5-10-06, A5-10-04, A5-10-01)
+ [Thermokon SR04 *]
+
+ - T: t/°C SP: 0 ... 255 F: 0|1|2|3|Auto P: yes|no
+ - fan: 0|1|2|3|Auto
+ - learnBtn: on
+ - present: yes
+ - temperature: t/°C
+ - set_point: 0 ... 255
+ - state: T: t/°C SP: 0 ... 255 F: 0|1|2|3|Auto P: yes|no
+
+ Old profile, use subType roomSensorControl.05 alternative.
+ Set attr model to SR04|SR04P|SR04T|SR04PT|SR04PMS|SR04PS|SR04PST or
+ attr subType to SR04.
+
+
+
+ Light and Presence Sensor
+ [Omnio Ratio eagle-PM101]
+
+ - on
+ - off
+ - brightness: E/lx (Sensor Range: E = 0 lx ... 1000 lx)
+ - channel1: on|off
(Motion message in depending on the brightness threshold)
+ - channel2: on|off
(Motion message)
+ - motion: on|off
(Channel 2)
+ - state: on|off
(Channel 2)
+
+ The sensor also sends switching commands (RORG F6) with
+ the senderID-1.
+ Set attr model to PM101 manually. Automatic teach-in is not possible,
+ since no EEP and manufacturer ID are sent.
+
diff --git a/FHEM/10_FBDECT.pm b/FHEM/10_FBDECT.pm
new file mode 100755
index 000000000..adff59d24
--- /dev/null
+++ b/FHEM/10_FBDECT.pm
@@ -0,0 +1,363 @@
+##############################################
+# $Id: 10_FBDECT.pm 2779 2013-02-21 08:52:27Z rudolfkoenig $
+package main;
+
+# TODO: test multi-dev, test on the FB
+
+use strict;
+use warnings;
+use SetExtensions;
+
+sub FBDECT_Parse($$@);
+sub FBDECT_Set($@);
+sub FBDECT_Get($@);
+sub FBDECT_Cmd($$@);
+
+my @fbdect_models = qw(
+ "AVM FRITZ!Dect Powerline 546E"
+ "AVM FRITZ!Dect 200"
+);
+
+my %fbdect_payload = (
+ 7 => { n=>"connected" },
+ 8 => { n=>"disconnected" },
+ 10 => { n=>"configChanged" },
+ 15 => { n=>"state", fmt=>'hex($pyld)?"on":"off"' },
+ 18 => { n=>"current", fmt=>'sprintf("%0.4f A", hex($pyld)/10000)' },
+ 19 => { n=>"voltage", fmt=>'sprintf("%0.3f V", hex($pyld)/1000)' },
+ 20 => { n=>"power", fmt=>'sprintf("%0.2f W", hex($pyld)/100)' },
+ 21 => { n=>"energy", fmt=>'sprintf("%0.0f Wh",hex($pyld))' },
+ 22 => { n=>"powerFactor", fmt=>'sprintf("%0.3f", hex($pyld))' },
+ 23 => { n=>"temperature", fmt=>'sprintf("%0.1f C", hex($pyld)/10)' },
+);
+
+
+sub
+FBDECT_Initialize($)
+{
+ my ($hash) = @_;
+ $hash->{Match} = ".*";
+ $hash->{SetFn} = "FBDECT_Set";
+ $hash->{GetFn} = "FBDECT_Get";
+ $hash->{DefFn} = "FBDECT_Define";
+ $hash->{UndefFn} = "FBDECT_Undef";
+ $hash->{ParseFn} = "FBDECT_Parse";
+ $hash->{AttrList} =
+ "IODev do_not_notify:1,0 ignore:1,0 dummy:1,0 showtime:1,0 ".
+ "loglevel:0,1,2,3,4,5,6 $readingFnAttributes " .
+ "model:".join(",", sort @fbdect_models);
+}
+
+
+#############################
+sub
+FBDECT_Define($$)
+{
+ my ($hash, $def) = @_;
+ my @a = split("[ \t][ \t]*", $def);
+ my $name = shift @a;
+ my $type = shift(@a); # always FBDECT
+
+ my $u = "wrong syntax for $name: define FBDECT id props";
+ return $u if(int(@a) != 2);
+
+ my $id = shift @a;
+ return "define $name: wrong id ($id): need a number"
+ if( $id !~ m/^\d+$/i );
+ $hash->{id} = $id;
+ $hash->{props} = shift @a;
+
+ $modules{FBDECT}{defptr}{$id} = $hash;
+ AssignIoPort($hash);
+ return undef;
+}
+
+###################################
+my %sets = ("on"=>1, "off"=>1);
+sub
+FBDECT_Set($@)
+{
+ my ($hash, @a) = @_;
+ my $ret = undef;
+ my $cmd = $a[1];
+
+ if(!$sets{$cmd}) {
+ return SetExtensions($hash, join(" ", sort keys %sets), @a);
+ }
+ my $relay = sprintf("%08x%04x0000%08x", 15, 4, $cmd eq "on" ? 1 : 0);
+ my $msg = sprintf("%04x0000%08x$relay", $hash->{id}, length($relay)/2);
+ IOWrite($hash, "07", $msg);
+ readingsSingleUpdate($hash, "state", "set_$cmd", 1);
+ return undef;
+}
+
+my %gets = ("devInfo"=>1);
+sub
+FBDECT_Get($@)
+{
+ my ($hash, @a) = @_;
+ my $ret = undef;
+ my $cmd = ($a[1] ? $a[1] : "");
+
+ if(!$gets{$cmd}) {
+ return "Unknown argument $cmd, choose one of ".join(" ", sort keys %gets);
+ }
+
+ if($cmd eq "devInfo") {
+ my @answ = FBAHA_getDevList($hash->{IODev}, $hash->{id});
+ return $answ[0] if(@answ == 1);
+ my $d = pop @answ;
+ my $state = "inactive" if($answ[0] =~ m/ inactive,/);
+ while($d) {
+ my ($ptyp, $plen, $pyld) = FBDECT_decodePayload($d);
+ if($ptyp eq "state" &&
+ ReadingsVal($hash->{NAME}, $ptyp, "") ne $pyld) {
+ readingsSingleUpdate($hash, $ptyp, ($state ? $state : $pyld), 1);
+ }
+ push @answ, " $ptyp: $pyld";
+ $d = substr($d, 16+$plen*2);
+ }
+ return join("\n", @answ);
+ }
+ return undef;
+}
+
+###################################
+sub
+FBDECT_Parse($$@)
+{
+ my ($iodev, $msg, $local) = @_;
+ my $ioName = $iodev->{NAME};
+
+ my $mt = substr($msg, 0, 2);
+ if($mt ne "07" && $mt ne "04") {
+ Log 1, "FBDECT: unknown message type $mt";
+ return;
+ }
+
+ my $id = hex(substr($msg, 16, 4));
+ my $hash = $modules{FBDECT}{defptr}{$id};
+ if(!$hash) {
+ my $ret = "UNDEFINED FBDECT_$id FBDECT $id switch";
+ Log 1, $ret;
+ DoTrigger("global", $ret);
+ return "";
+ }
+
+ readingsBeginUpdate($hash);
+
+ if($mt eq "07") {
+ my $d = substr($msg, 32);
+ while($d) {
+ my ($ptyp, $plen, $pyld) = FBDECT_decodePayload($d);
+ readingsBulkUpdate($hash, $ptyp, $pyld);
+ $d = substr($d, 16+$plen*2);
+ }
+ }
+ if($mt eq "04") {
+ my @answ = FBAHA_configInd(substr($msg,16), $id);
+ my $state = "";
+ if($answ[0] =~ m/ inactive,/) {
+ $state = "inactive";
+
+ } else {
+ my $d = pop @answ;
+ while($d) {
+ my ($ptyp, $plen, $pyld) = FBDECT_decodePayload($d);
+ last if(!$plen);
+ push @answ, " $ptyp: $pyld";
+ $d = substr($d, 16+$plen*2);
+ }
+ # Ignore the rest, is too confusing.
+ @answ = grep /state:/, @answ;
+ (undef, $state) = split(": ", $answ[0], 2);
+ }
+ readingsBulkUpdate($hash, "state", $state);
+ }
+
+ readingsEndUpdate($hash, 1);
+
+ return $hash->{NAME};
+}
+
+sub
+FBDECT_decodePayload($)
+{
+ my ($d) = @_;
+ my $ptyp = hex(substr($d, 0, 8));
+ my $plen = hex(substr($d, 8, 4));
+ my $pyld = substr($d, 16, $plen*2);
+ if($fbdect_payload{$ptyp}) {
+ $pyld = eval $fbdect_payload{$ptyp}{fmt} if($fbdect_payload{$ptyp}{fmt});
+ $ptyp = $fbdect_payload{$ptyp}{n};
+ }
+ return ($ptyp, $plen, $pyld);
+}
+
+#####################################
+sub
+FBDECT_Undef($$)
+{
+ my ($hash, $arg) = @_;
+ my $homeId = $hash->{homeId};
+ my $id = $hash->{id};
+ delete $modules{FBDECT}{defptr}{$id};
+ return undef;
+}
+
+1;
+
+=pod
+=begin html
+
+
+FBDECT
+
+ This module is used to control AVM FRITZ!DECT devices via FHEM, see also the
+ FBAHA module for the base.
+
+
+ Define
+
+ define <name> FBDECT <homeId> <id> [classes]
+
+
+ <id> is the id of the device, the classes argument ist ignored for now.
+
+ Example:
+
+ define lamp FBDECT 16 switch,powerMeter
+
+ Note:Usually the device is created via
+ autocreate
+
+
+
+ Set
+
+ - on/off
+ set the device on or off.
+ -
+ set extensions are supported.
+
+
+
+
+ Get
+
+ - devInfo
+ report device information
+
+
+
+
+ Attributes
+
+
+
+
+ Generated events:
+
+ - on
+ - off
+ - set_on
+ - set_off
+ - current: $v A
+ - voltage: $v V
+ - power: $v W
+ - energy: $v Wh
+ - powerFactor: $v"
+ - temperature: $v C
+
+
+
+=end html
+
+=begin html_DE
+
+
+FBDECT
+
+ Dieses Modul wird verwendet, um AVM FRITZ!DECT Geräte via FHEM zu
+ steuern, siehe auch das FBAHA Modul für die
+ Anbindung an das FRITZ!Box.
+
+
+ Define
+
+ define <name> FBDECT <homeId> <id> [classes]
+
+
+ <id> ist das Geräte-ID, das Argument wird z.Zt ignoriert.
+
+ Beispiel:
+
+ define lampe FBDECT 16 switch,powerMeter
+
+ Achtung:FBDECT Einträge werden noralerweise per
+ autocreate angelegt.
+
+
+
+ Set
+
+ - on/off
+ Gerät einschalten bzw. ausschalten.
+ -
+ Die set extensions werden unterstützt.
+
+
+
+
+ Get
+
+ - devInfo
+ meldet Geräte-Informationen.
+
+
+
+
+ Attribute
+
+
+
+
+ Generierte events:
+
+ - on
+ - off
+ - set_on
+ - set_off
+ - current: $v A
+ - voltage: $v V
+ - power: $v W
+ - energy: $v Wh
+ - powerFactor: $v"
+ - temperature: $v C
+
+
+=end html_DE
+
+=cut
diff --git a/FHEM/10_FRM.pm b/FHEM/10_FRM.pm
index 7b3d2991d..67859627e 100755
--- a/FHEM/10_FRM.pm
+++ b/FHEM/10_FRM.pm
@@ -3,6 +3,16 @@ package main;
use strict;
use warnings;
+
+#add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though...
+BEGIN {
+ if (!grep(/FHEM\/lib$/,@INC)) {
+ foreach my $inc (grep(/FHEM$/,@INC)) {
+ push @INC,$inc."/lib";
+ };
+ };
+};
+
use Device::Firmata::Constants qw/ :all /;
use Device::Firmata::IO;
use Device::Firmata::Protocol;
@@ -10,17 +20,15 @@ use Device::Firmata::Platform;
sub FRM_Set($@);
sub FRM_Attr(@);
-sub Log($$);
#####################################
sub FRM_Initialize($) {
- my ($hash) = @_;
+ my $hash = shift @_;
require "$main::attr{global}{modpath}/FHEM/DevIo.pm";
# Provider
- $hash->{Clients} =
- ":FRM_IN:FRM_OUT:FRM_AD:FRM_PWM:FRM_I2C:FRM_SERVO:OWX:";
+ $hash->{Clients} = ":FRM_IN:FRM_OUT:FRM_AD:FRM_PWM:FRM_I2C:FRM_SERVO:OWX:FRM_LCD:";
$hash->{ReadyFn} = "FRM_Ready";
$hash->{ReadFn} = "FRM_Read";
@@ -31,28 +39,44 @@ sub FRM_Initialize($) {
$hash->{SetFn} = "FRM_Set";
$hash->{AttrFn} = "FRM_Attr";
- $hash->{AttrList} = "model:nano dummy:1,0 loglevel:0,1,2,3,4,5 sampling-interval i2c-config $main::readingFnAttributes";
+ $hash->{AttrList} = "model:nano dummy:1,0 loglevel:0,1,2,3,4,5,6 sampling-interval i2c-config $main::readingFnAttributes";
}
#####################################
sub FRM_Define($$) {
my ( $hash, $def ) = @_;
- my @a = split( "[ \t][ \t]*", $def );
- my $po;
-
+
+ my ($name, $type, $dev, $global) = split("[ \t]+", $def);
+
+ my $isServer = 1 if($dev && $dev =~ m/^(IPV6:)?\d+$/);
+# my $isClient = 1 if($dev && $dev =~ m/^(IPV6:)?.*:\d+$/);
+
+# return "Usage: define FRM {[@] | [IPV6:] [global]}"
+# if(!($isServer || $isClient) ||
+# ($isClient && $global) ||
+# ($global && $global ne "global"));
+
+ # Make sure that fhem only runs once
+ if($isServer) {
+ my $ret = TcpServer_Open($hash, $dev, $global);
+ if (!$ret) {
+ $hash->{STATE}="listening";
+ }
+ return $ret;
+ }
+
DevIo_CloseDev($hash);
- my $name = $a[0];
- my $dev = $a[2];
-
if ( $dev eq "none" ) {
- Log 1, "FRM device is none, commands will be echoed only";
+ Log (GetLogLevel($hash->{NAME}), "FRM device is none, commands will be echoed only");
$main::attr{$name}{dummy} = 1;
return undef;
}
$hash->{DeviceName} = $dev;
+
my $ret = DevIo_OpenDev($hash, 0, "FRM_DoInit");
- main::readingsSingleUpdate($hash,"state","Initialized", 1);
+ return $ret if (defined $ret and $ret eq "");
+ $hash->{STATE}="Initialized";
return $ret;
}
@@ -60,7 +84,7 @@ sub FRM_Define($$) {
sub FRM_Undef($) {
my $hash = @_;
FRM_forall_clients($hash,\&FRM_Client_Unassign,undef);
- DevIo_CloseDev($hash);
+ DevIo_Disconnected($hash);
my $device = $hash->{FirmataDevice};
if (defined $device) {
if (defined $device->{io}) {
@@ -88,7 +112,7 @@ sub FRM_Set($@) {
} elsif ( $a[1] eq 'reinit' ) {
FRM_forall_clients($hash,\&FRM_Init_Client,undef);
} else {
- return "Unknown argument $a[1], supported arguments are 'reset', 'reinit'";
+ return "Unknown argument $a[1], supported arguments are reset, reinit";
}
return undef;
}
@@ -116,8 +140,18 @@ sub FRM_Get($@) {
#####################################
# called from the global loop, when the select for hash->{FD} reports data
+
sub FRM_Read($) {
+
my ( $hash ) = @_;
+ if($hash->{SERVERSOCKET}) { # Accept and create a child
+ my $chash = TcpServer_Accept($hash, "FRM");
+ return if(!$chash);
+ $chash->{DeviceName}=$hash->{PORT}; # required for DevIo_CloseDev and FRM_Ready
+ $chash->{TCPDev}=$chash->{CD};
+ FRM_DoInit($chash);
+ return;
+ }
my $device = $hash->{FirmataDevice} or return;
$device->poll();
}
@@ -125,7 +159,17 @@ sub FRM_Read($) {
sub FRM_Ready($) {
my ($hash) = @_;
- return DevIo_OpenDev($hash, 1, "FRM_DoInit") if($hash->{READINGS}{state} eq "disconnected");
+ my $name = $hash->{NAME};
+ if ($name=~/^^FRM:.+:\d+$/) { # this is a closed tcp-connection, remove it
+ TcpServer_Close($hash);
+ delete $main::defs{$hash->{SNAME}}{FirmataDevice} if (defined $hash->{SNAME} && defined $main::defs{$hash->{SNAME}}{FirmataDevice});
+ my $dev = $hash->{DeviceName};
+ delete $main::readyfnlist{"$name.$dev"};
+ delete $main::attr{$name};
+ delete $main::defs{$name};
+ return undef;
+ }
+ return DevIo_OpenDev($hash, 1, "FRM_DoInit") if($hash->{STATE} eq "disconnected");
}
sub FRM_Attr(@) {
@@ -133,7 +177,8 @@ sub FRM_Attr(@) {
if ($command eq "set") {
$main::attr{$name}{$attribute}=$value;
if ($attribute eq "sampling-interval"
- or $attribute eq "i2c-config" ) {
+ or $attribute eq "i2c-config"
+ or $attribute eq "loglevel" ) {
FRM_apply_attribute($main::defs{$name},$attribute);
}
}
@@ -145,21 +190,30 @@ sub FRM_apply_attribute {
my $name = $hash->{NAME};
if (defined $firmata) {
if ($attribute eq "sampling-interval") {
- $firmata->sampling_interval(main::AttrVal($name,$attribute,"1000"));
+ $firmata->sampling_interval(AttrVal($name,$attribute,"1000"));
} elsif ($attribute eq "i2c-config") {
- my $i2cattr = main::AttrVal($name,$attribute,undef);
+ my $i2cattr = AttrVal($name,$attribute,undef);
if (defined $i2cattr) {
my @a = split(" ", $i2cattr);
- my $i2cpins = $firmata->{metadata}{i2c_pins};
+ my $i2cpins = $firmata->{metadata}{i2c_pins};
+ my $err;
if (defined $i2cpins and scalar @$i2cpins) {
- foreach my $i2cpin (@$i2cpins) {
- $firmata->pin_mode($i2cpin,PIN_I2C);
- }
- $firmata->i2c_config(@a);
- $firmata->observe_i2c(\&FRM_i2c_observer,$hash);
+ eval {
+ foreach my $i2cpin (@$i2cpins) {
+ $firmata->pin_mode($i2cpin,PIN_I2C);
+ }
+ $firmata->i2c_config(@a);
+ $firmata->observe_i2c(\&FRM_i2c_observer,$hash);
+ };
+ $err = $@ if ($@);
} else {
- Log 1,"Error, arduino doesn't support I2C";
+ $err = "Error, arduino doesn't support I2C";
}
+ Log (GetLogLevel($hash->{NAME},2),$err) if ($err);
+ }
+ } elsif ($attribute eq "loglevel") {
+ if (defined $firmata->{io}) {
+ $firmata->{io}->{loglevel} = AttrVal($name,$attribute,5);
}
}
}
@@ -169,8 +223,9 @@ sub FRM_DoInit($) {
my ($hash) = @_;
- my $name = $hash->{NAME};
- $hash->{loglevel} = main::GetLogLevel($name);
+ my $name = $hash->{SNAME}; #is this a serversocket-connection?
+ my $shash = defined $name ? $main::defs{$name} : $hash;
+ $name = $hash->{NAME} if (!defined $name);
my $firmata_io = Firmata_IO->new($hash);
my $device = Device::Firmata::Platform->attach($firmata_io) or return 1;
@@ -179,70 +234,82 @@ sub FRM_DoInit($) {
$device->observe_string(\&FRM_string_observer,$hash);
my $found; # we cannot call $device->probe() here, as it doesn't select bevore read, so it would likely cause IODev to close the connection on the first attempt to read from empty stream
+ my $endTicks = time+5;
+ my $queryTicks = time+2;
+ $device->system_reset();
do {
- $device->system_reset();
- $device->firmware_version_query();
- for (my $i=0;$i<50;$i++) {
- if (FRM_poll($hash)) {
- if ($device->{metadata}{firmware} && $device->{metadata}{firmware_version}){
- $device->{protocol}->{protocol_version} = $device->{metadata}{firmware_version};
- $main::defs{$name}{firmware} = $device->{metadata}{firmware};
- $main::defs{$name}{firmware_version} = $device->{metadata}{firmware_version};
- $device->analog_mapping_query();
- $device->capability_query();
- for (my $j=0;$j<100;$j++) {
- if (FRM_poll($hash)) {
- if (($device->{metadata}{analog_mappings}) and ($device->{metadata}{capabilities})) {
- my $inputpins = $device->{metadata}{input_pins};
- $main::defs{$name}{input_pins} = join(",", sort{$a<=>$b}(@$inputpins));
- my $outputpins = $device->{metadata}{output_pins};
- $main::defs{$name}{output_pins} = join(",", sort{$a<=>$b}(@$outputpins));
- my $analogpins = $device->{metadata}{analog_pins};
- $main::defs{$name}{analog_pins} = join(",", sort{$a<=>$b}(@$analogpins));
- my $pwmpins = $device->{metadata}{pwm_pins};
- $main::defs{$name}{pwm_pins} = join(",", sort{$a<=>$b}(@$pwmpins));
- my $servopins = $device->{metadata}{servo_pins};
- $main::defs{$name}{servo_pins} = join(",", sort{$a<=>$b}(@$servopins));
- my $i2cpins = $device->{metadata}{i2c_pins};
- $main::defs{$name}{i2c_pins} = join(",", sort{$a<=>$b}(@$i2cpins));
- my $onewirepins = $device->{metadata}{onewire_pins};
- $main::defs{$name}{onewire_pins} = join(",", sort{$a<=>$b}(@$onewirepins));
- my @analog_resolutions;
- foreach my $pin (sort{$a<=>$b}(keys $device->{metadata}{analog_resolutions})) {
- push @analog_resolutions,$pin.":".$device->{metadata}{analog_resolutions}{$pin};
- }
- $main::defs{$name}{analog_resolutions} = join(",",@analog_resolutions);
- my @pwm_resolutions;
- foreach my $pin (sort{$a<=>$b}(keys $device->{metadata}{pwm_resolutions})) {
- push @pwm_resolutions,$pin.":".$device->{metadata}{pwm_resolutions}{$pin};
- }
- $main::defs{$name}{pwm_resolutions} = join(",",@pwm_resolutions);
- my @servo_resolutions;
- foreach my $pin (sort{$a<=>$b}(keys $device->{metadata}{servo_resolutions})) {
- push @servo_resolutions,$pin.":".$device->{metadata}{servo_resolutions}{$pin};
- }
- $main::defs{$name}{servo_resolutions} = join(",",@servo_resolutions);
- $found = 1;
- last;
- }
- } else {
- select (undef,undef,undef,0.1);
- }
+ FRM_poll($hash);
+ if ($device->{metadata}{firmware} && $device->{metadata}{firmware_version}) {
+ $device->{protocol}->{protocol_version} = $device->{metadata}{firmware_version};
+ $main::defs{$name}{firmware} = $device->{metadata}{firmware};
+ $main::defs{$name}{firmware_version} = $device->{metadata}{firmware_version};
+ Log (3, "Firmata Firmware Version: ".$device->{metadata}{firmware}." ".$device->{metadata}{firmware_version});
+ $device->analog_mapping_query();
+ $device->capability_query();
+ do {
+ FRM_poll($hash);
+ if ($device->{metadata}{analog_mappings} and $device->{metadata}{capabilities}) {
+ my $inputpins = $device->{metadata}{input_pins};
+ $main::defs{$name}{input_pins} = join(",", sort{$a<=>$b}(@$inputpins)) if (defined $inputpins and scalar @$inputpins);
+ my $outputpins = $device->{metadata}{output_pins};
+ $main::defs{$name}{output_pins} = join(",", sort{$a<=>$b}(@$outputpins)) if (defined $outputpins and scalar @$outputpins);
+ my $analogpins = $device->{metadata}{analog_pins};
+ $main::defs{$name}{analog_pins} = join(",", sort{$a<=>$b}(@$analogpins)) if (defined $analogpins and scalar @$analogpins);
+ my $pwmpins = $device->{metadata}{pwm_pins};
+ $main::defs{$name}{pwm_pins} = join(",", sort{$a<=>$b}(@$pwmpins)) if (defined $pwmpins and scalar @$pwmpins);
+ my $servopins = $device->{metadata}{servo_pins};
+ $main::defs{$name}{servo_pins} = join(",", sort{$a<=>$b}(@$servopins)) if (defined $servopins and scalar @$servopins);
+ my $i2cpins = $device->{metadata}{i2c_pins};
+ $main::defs{$name}{i2c_pins} = join(",", sort{$a<=>$b}(@$i2cpins)) if (defined $i2cpins and scalar @$i2cpins);
+ my $onewirepins = $device->{metadata}{onewire_pins};
+ $main::defs{$name}{onewire_pins} = join(",", sort{$a<=>$b}(@$onewirepins)) if (defined $onewirepins and scalar @$onewirepins);
+ if (defined $device->{metadata}{analog_resolutions}) {
+ my @analog_resolutions;
+ foreach my $pin (sort{$a<=>$b}(keys %{$device->{metadata}{analog_resolutions}})) {
+ push @analog_resolutions,$pin.":".$device->{metadata}{analog_resolutions}{$pin};
+ }
+ $main::defs{$name}{analog_resolutions} = join(",",@analog_resolutions) if (scalar @analog_resolutions);
+ }
+ if (defined $device->{metadata}{pwm_resolutions}) {
+ my @pwm_resolutions;
+ foreach my $pin (sort{$a<=>$b}(keys %{$device->{metadata}{pwm_resolutions}})) {
+ push @pwm_resolutions,$pin.":".$device->{metadata}{pwm_resolutions}{$pin};
+ }
+ $main::defs{$name}{pwm_resolutions} = join(",",@pwm_resolutions) if (scalar @pwm_resolutions);
+ }
+ if (defined $device->{metadata}{servo_resolutions}) {
+ my @servo_resolutions;
+ foreach my $pin (sort{$a<=>$b}(keys %{$device->{metadata}{servo_resolutions}})) {
+ push @servo_resolutions,$pin.":".$device->{metadata}{servo_resolutions}{$pin};
+ }
+ $main::defs{$name}{servo_resolutions} = join(",",@servo_resolutions) if (scalar @servo_resolutions);
}
$found = 1;
- last;
- }
- } else {
- select (undef,undef,undef,0.1);
+ } else {
+ select (undef,undef,undef,0.01);
+ }
+ } while (time < $endTicks and !$found);
+ $found = 1;
+ } else {
+ select (undef,undef,undef,0.01);
+ if (time > $queryTicks) {
+ Log (3, "querying Firmata Firmware Version");
+ $device->firmware_version_query();
+ $queryTicks++;
}
}
- } while (!$found);
-
- FRM_apply_attribute($hash,"sampling-interval");
- FRM_apply_attribute($hash,"i2c-config");
- FRM_forall_clients($hash,\&FRM_Init_Client,undef);
-
- return undef;
+ } while (time < $endTicks and !$found);
+ if ($found) {
+ $shash->{FirmataDevice} = $device;
+ FRM_apply_attribute($shash,"sampling-interval");
+ FRM_apply_attribute($shash,"i2c-config");
+ FRM_forall_clients($shash,\&FRM_Init_Client,undef);
+ return undef;
+ }
+ Log (3, "no response from Firmata, closing DevIO");
+ DevIo_Disconnected($hash);
+ delete $hash->{FirmataDevice};
+ return "FirmataDevice not responding";
}
sub
@@ -262,22 +329,32 @@ FRM_forall_clients($$$)
sub
FRM_Init_Client($$) {
my ($hash,$args) = @_;
- $hash->{loglevel} = main::GetLogLevel($hash->{NAME});
- main::CallFn($hash->{NAME},"InitFn",$hash,$args);
+ if (!defined $args and defined $hash->{DEF}) {
+ my @a = split("[ \t][ \t]*", $hash->{DEF});
+ $args = \@a;
+ }
+ my $ret = CallFn($hash->{NAME},"InitFn",$hash,$args);
+ if ($ret) {
+ Log (GetLogLevel($hash->{NAME},2),"error initializing ".$hash->{NAME}.": ".$ret);
+ }
}
sub
FRM_Init_Pin_Client($$$) {
my ($hash,$args,$mode) = @_;
- my $u = "wrong syntax: define FRM_XXX pin";
- return $u if(int(@$args) < 3);
- my $pin = @$args[2];
+ my $u = "wrong syntax: define FRM_XXX pin";
+ return $u unless defined $args and int(@$args) > 0;
+ my $pin = @$args[0];
$hash->{PIN} = $pin;
if (defined $hash->{IODev} and defined $hash->{IODev}->{FirmataDevice}) {
- $hash->{IODev}->{FirmataDevice}->pin_mode($pin,$mode);
- return 1;
+ eval {
+ $hash->{IODev}->{FirmataDevice}->pin_mode($pin,$mode);
+ };
+ return "error setting Firmata pin_mode for ".$hash->{NAME}.": ".$@ if ($@);
+ return undef;
}
- return undef;
+ return "no IODev set" unless defined $hash->{IODev};
+ return "no FirmataDevice assigned to ".$hash->{IODev}->{NAME};
}
sub
@@ -286,10 +363,10 @@ FRM_Client_Define($$)
my ($hash, $def) = @_;
my @a = split("[ \t][ \t]*", $def);
- main::readingsSingleUpdate($hash,"state","defined",0);
+ $hash->{STATE}="defined";
- main::AssignIoPort($hash);
- FRM_Init_Client($hash,\@a);
+ AssignIoPort($hash);
+ FRM_Init_Client($hash,[@a[2..scalar(@a)-1]]);
return undef;
}
@@ -305,39 +382,41 @@ FRM_Client_Unassign($)
{
my ($dev) = @_;
delete $dev->{IODev} if defined $dev->{IODev};
- main::readingsSingleUpdate($dev,"state","defined",0);
+ $dev->{STATE}="defined";
}
-package Firmata_IO {
-
- sub new {
- my ($class,$hash) = @_;
- return bless {
- hash => $hash,
- }, $class;
- }
+package Firmata_IO;
- sub data_write {
- my ( $self, $buf ) = @_;
- main::Log 5, ">".join(",",map{sprintf"%02x",ord$_}split//,$buf);
- main::DevIo_SimpleWrite($self->{hash},$buf,undef);
- }
-
- sub data_read {
- my ( $self, $bytes ) = @_;
- my $string = main::DevIo_SimpleRead($self->{hash});
- if (defined $string ) {
- main::Log 5,"<".join(",",map{sprintf"%02x",ord$_}split//,$string);
- }
- return $string;
- }
+sub new {
+ my ($class,$hash) = @_;
+ return bless {
+ hash => $hash,
+ loglevel => main::GetLogLevel($hash->{NAME},5),
+ }, $class;
}
+sub data_write {
+ my ( $self, $buf ) = @_;
+ main::Log ($self->{loglevel}, ">".join(",",map{sprintf"%02x",ord$_}split//,$buf));
+ main::DevIo_SimpleWrite($self->{hash},$buf,undef);
+}
+
+sub data_read {
+ my ( $self, $bytes ) = @_;
+ my $string = main::DevIo_SimpleRead($self->{hash});
+ if (defined $string ) {
+ main::Log ($self->{loglevel},"<".join(",",map{sprintf"%02x",ord$_}split//,$string));
+ }
+ return $string;
+}
+
+package main;
+
sub
FRM_i2c_observer
{
my ($data,$hash) = @_;
- main::Log 5,"onI2CMessage address: '".$data->{address}."', register: '".$data->{register}."' data: '".$data->{data}."'";
+ Log GetLogLevel($hash->{NAME},5),"onI2CMessage address: '".$data->{address}."', register: '".$data->{register}."' data: '".$data->{data}."'";
FRM_forall_clients($hash,\&FRM_i2c_update_device,$data);
}
@@ -346,20 +425,20 @@ sub FRM_i2c_update_device
my ($hash,$data) = @_;
if (defined $hash->{"i2c-address"} && $hash->{"i2c-address"}==$data->{address}) {
my $replydata = $data->{data};
- my @values = split(" ",main::ReadingsVal($hash->{NAME},"values",""));
+ my @values = split(" ",ReadingsVal($hash->{NAME},"values",""));
splice(@values,$data->{register},@$replydata, @$replydata);
- main::readingsBeginUpdate($hash);
- main::readingsBulkUpdate($hash,"state","active",0);
- main::readingsBulkUpdate($hash,"values",join (" ",@values),1);
- main::readingsEndUpdate($hash,undef);
+ readingsBeginUpdate($hash);
+ $hash->{STATE}="active";
+ readingsBulkUpdate($hash,"values",join (" ",@values),1);
+ readingsEndUpdate($hash,undef);
}
}
sub FRM_string_observer
{
my ($string,$hash) = @_;
- main::Log 4, "received String_data: ".$string;
- main::readingsSingleUpdate($hash,"error",$string,1);
+ Log (GetLogLevel($hash->{NAME},3), "received String_data: ".$string);
+ readingsSingleUpdate($hash,"error",$string,1);
}
sub FRM_poll
@@ -381,21 +460,19 @@ sub
FRM_OWX_Init($$)
{
my ($hash,$args) = @_;
- if (FRM_Init_Pin_Client($hash,$args,PIN_ONEWIRE)) {
- $hash->{INTERFACE} = "firmata";
- my $firmata = $hash->{IODev}->{FirmataDevice};
- my $pin = $hash->{PIN};
- $firmata->observe_onewire($pin,\&FRM_OWX_observer,$hash);
- $hash->{FRM_OWX_REPLIES} = {};
- $hash->{DEVS} = [];
- if ( main::AttrVal($hash->{NAME},"buspower","") eq "parasitic" ) {
- $firmata->onewire_config($pin,1);
- }
- main::readingsSingleUpdate($hash,"state","Initialized",1);
- $firmata->onewire_search($pin);
- return undef;
+ my $ret = FRM_Init_Pin_Client($hash,$args,PIN_ONEWIRE);
+ return $ret if (defined $ret);
+ my $firmata = $hash->{IODev}->{FirmataDevice};
+ my $pin = $hash->{PIN};
+ $firmata->observe_onewire($pin,\&FRM_OWX_observer,$hash);
+ $hash->{FRM_OWX_REPLIES} = {};
+ $hash->{DEVS} = [];
+ if ( AttrVal($hash->{NAME},"buspower","") eq "parasitic" ) {
+ $firmata->onewire_config($pin,1);
}
- return 1;
+ $hash->{STATE}="Initialized";
+ $firmata->onewire_search($pin);
+ return undef;
}
sub FRM_OWX_observer
@@ -416,7 +493,7 @@ sub FRM_OWX_observer
}
if ($command eq "SEARCH_REPLY") {
$hash->{DEVS} = \@owx_devices;
- $main::attr{$hash->{NAME}}{"ow-devices"} = join " ",@owx_devices;
+ #$main::attr{$hash->{NAME}}{"ow-devices"} = join " ",@owx_devices;
} else {
$hash->{ALARMDEVS} = \@owx_devices;
}
@@ -502,10 +579,10 @@ sub FRM_OWX_Complex ($$$$) {
#-- get the interface
my $frm = $hash->{IODev};
- return undef unless defined $frm;
+ return 0 unless defined $frm;
my $firmata = $frm->{FirmataDevice};
my $pin = $hash->{PIN};
- return undef unless ( defined $firmata and defined $pin );
+ return 0 unless ( defined $firmata and defined $pin );
my $ow_command = {};
@@ -536,7 +613,7 @@ sub FRM_OWX_Complex ($$$$) {
$firmata->onewire_command_series( $pin, $ow_command );
if ($numread) {
- my $times = main::AttrVal($hash,"ow-read-timeout",1000) / 50; #timeout in ms, defaults to 1 sec
+ my $times = AttrVal($hash,"ow-read-timeout",1000) / 50; #timeout in ms, defaults to 1 sec
for (my $i=0;$i<$times;$i++) {
if (FRM_poll($hash->{IODev})) {
if (defined $hash->{FRM_OWX_REPLIES}->{$owx_dev}) {
@@ -624,7 +701,7 @@ sub FRM_OWX_Discover ($) {
Define
- define <name> FRM <device>
+ define <name> FRM {<device> | <port> [global]}
Specifies the FRM device.
@@ -653,9 +730,18 @@ sub FRM_OWX_Discover ($) {
Network-connected devices:
- <device> specifies the host:port of the device. E.g.
- 192.168.0.244:2323
- As of now EthernetFirmata is still eperimental.
+ <port> specifies the port the FRM device listens on. If 'global' is
+ specified the socket is bound to all local ip-addresses, otherwise to localhost
+ only.
+ The Arduino must run a Version of firmata that connects in client-mode (the
+ connection is initiated by the arduino). The ip-address and port of the fhem-server
+ has to be configured an the arduino, so it knows where to connect to.
+ As of now only a single Arduino per FRM-device configured is supported. Multiple
+ Arduinos may connect to different FRM-devices configured for different ports.
+ The support for Firmata over ethernet is still experimental. Firmata-ethenet-client
+ can be found here:
+
+ ConfigurableEthernetclient.ino
If the device is called none, then no device will be opened, so you
diff --git a/FHEM/10_FS20.pm b/FHEM/10_FS20.pm
index 60cd265f1..d13fb264f 100755
--- a/FHEM/10_FS20.pm
+++ b/FHEM/10_FS20.pm
@@ -199,7 +199,7 @@ FS20_Set($@)
return "Bad time spec" if($na == 3 && $a[2] !~ m/^\d*\.?\d+$/);
my $v = join(" ", @a);
- Log GetLogLevel($name,2), "FS20 set $v";
+ Log GetLogLevel($name,3), "FS20 set $v";
(undef, $v) = split(" ", $v, 2); # Not interested in the name...
my $val;
@@ -389,7 +389,7 @@ FS20_Parse($$)
return "" if(IsIgnored($n)); # Little strange.
readingsSingleUpdate($lh, "state", $v, 1);
- Log GetLogLevel($n,2), "FS20 $n $v";
+ Log GetLogLevel($n,4), "FS20 $n $v";
if($modules{FS20}{ldata}{$n}) {
CommandDelete(undef, $n . "_timer");
@@ -519,25 +519,25 @@ four2hex($$)
set <name> <value> [<time>]
where value is one of:
-
- dim06% dim12% dim18% dim25% dim31% dim37% dim43% dim50%
- dim56% dim62% dim68% dim75% dim81% dim87% dim93% dim100%
- dimdown
- dimup
- dimupdown
- off
- off-for-timer
- on # dimmer: set to value before switching it off
- on-for-timer # see the note
- on-old-for-timer # set to previous (before switching it on)
- ramp-on-time # time to reach the desired dim value on dimmers
- ramp-off-time # time to reach the off state on dimmers
- reset
- sendstate
- timer
- toggle # between off and previous dim val
- on-till # Special, see the note
-
+
+ dim06% dim12% dim18% dim25% dim31% dim37% dim43% dim50%
+ dim56% dim62% dim68% dim75% dim81% dim87% dim93% dim100%
+ dimdown
+ dimup
+ dimupdown
+ off
+ off-for-timer
+ on # dimmer: set to value before switching it off
+ on-for-timer # see the note
+ on-old-for-timer # set to previous (before switching it on)
+ ramp-on-time # time to reach the desired dim value on dimmers
+ ramp-off-time # time to reach the off state on dimmers
+ reset
+ sendstate
+ timer
+ toggle # between off and previous dim val
+ on-till # Special, see the note
+
The set extensions are also supported.
Examples:
diff --git a/FHEM/10_MAX.pm b/FHEM/10_MAX.pm
index 074317ea7..38f0bfd83 100755
--- a/FHEM/10_MAX.pm
+++ b/FHEM/10_MAX.pm
@@ -2,15 +2,12 @@
# $Id$
# Written by Matthias Gehre, M.Gehre@gmx.de, 2012-2013
#
-# TODO:
-# - Bounds checking in "fake", allow temperature above 25.5
-# - Defer updating of state until Ack
-# - Send RemoveGroupId packet if groupid is set to 0
package main;
use strict;
use warnings;
use MIME::Base64;
+use MaxCommon;
sub MAX_Define($$);
sub MAX_Undef($$);
@@ -22,19 +19,6 @@ sub MAX_DateTime2Internal($);
my @ctrl_modes = ( "auto", "manual", "temporary", "boost" );
-use vars qw(%device_types);
-use vars qw(%msgId2Cmd);
-use vars qw(%msgCmd2Id);
-
-%device_types = (
- 0 => "Cube",
- 1 => "HeatingThermostat",
- 2 => "HeatingThermostatPlus",
- 3 => "WallMountedThermostat",
- 4 => "ShutterContact",
- 5 => "PushButton"
-);
-
my %boost_durations = (0 => 0, 1 => 5, 2 => 10, 3 => 15, 4 => 20, 5 => 25, 6 => 30, 7 => 60);
my %boost_durationsInv = reverse %boost_durations;
@@ -48,10 +32,12 @@ sub validBoostDuration { return $_[0] ~~ /^\d+$/ && exists($boost_durationsInv{$
sub validValveposition { return $_[0] ~~ /^\d+$/ && $_[0] >= 0 && $_[0] <= 100; }
sub validDecalcification { my ($decalcDay, $decalcHour) = ($_[0] =~ /^(...) (\d{1,2}):00$/);
return defined($decalcDay) && defined($decalcHour) && exists($decalcDaysInv{$decalcDay}) && 0 <= $decalcHour && $decalcHour < 24; }
+sub validWeekProfile { return length($_[0]) == 4*13*7; }
+sub validGroupid { return $_[0] ~~ /^\d+$/ && $_[0] >= 0 && $_[0] <= 255; }
my %readingDef = ( #min/max/default
- "maximumTemperature" => [ \&validTemperature, 30.5],
- "minimumTemperature" => [ \&validTemperature, 4.5],
+ "maximumTemperature" => [ \&validTemperature, "on"],
+ "minimumTemperature" => [ \&validTemperature, "off"],
"comfortTemperature" => [ \&validTemperature, 21],
"ecoTemperature" => [ \&validTemperature, 17],
"windowOpenTemperature" => [ \&validTemperature, 12],
@@ -62,43 +48,10 @@ my %readingDef = ( #min/max/default
"decalcification" => [ \&validDecalcification, "Sat 12:00" ],
"maxValveSetting" => [ \&validValveposition, 100 ],
"valveOffset" => [ \&validValveposition, 00 ],
+ "groupid" => [ \&validGroupid, 0 ],
+ ".weekProfile" => [ \&validWeekProfile, $defaultWeekProfile ],
);
-%msgId2Cmd = (
- "00" => "PairPing",
- "01" => "PairPong",
- "02" => "Ack",
- "03" => "TimeInformation",
-
- "10" => "ConfigWeekProfile",
- "11" => "ConfigTemperatures", #like eco/comfort etc
- "12" => "ConfigValve",
-
- "20" => "AddLinkPartner",
- "21" => "RemoveLinkPartner",
- "22" => "SetGroupId",
- "23" => "RemoveGroupId",
-
- "30" => "ShutterContactState",
-
- "40" => "SetTemperature", #to thermostat
- "42" => "WallThermostatState", #by WallMountedThermostat
- #Sending this without payload to thermostat sets desiredTempeerature to the comfort/eco temperature
- #We don't use it, we just do SetTemperature
- "43" => "SetComfortTemperature",
- "44" => "SetEcoTemperature",
-
- "50" => "PushButtonState",
-
- "60" => "ThermostatState", #by HeatingThermostat
-
- "82" => "SetDisplayActualTemperature",
-
- "F1" => "WakeUp",
- "F0" => "Reset",
- );
-%msgCmd2Id = reverse %msgId2Cmd;
-
my %interfaces = (
"Cube" => undef,
"HeatingThermostat" => "thermostat;battery;temperature",
@@ -113,14 +66,14 @@ MAX_Initialize($)
{
my ($hash) = @_;
- Log 5, "Calling MAX_Initialize";
+ Log GetLogLevel($hash->{NAME}, 5), "Calling MAX_Initialize";
$hash->{Match} = "^MAX";
$hash->{DefFn} = "MAX_Define";
$hash->{UndefFn} = "MAX_Undef";
$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 ".
+ "showtime:1,0 loglevel:0,1,2,3,4,5,6 keepAuto:0,1 scanTemp:0,1 ".
$readingFnAttributes;
return undef;
}
@@ -132,7 +85,8 @@ MAX_Define($$)
my ($hash, $def) = @_;
my @a = split("[ \t][ \t]*", $def);
my $name = $hash->{NAME};
- return "wrong syntax: define MAX addr"
+ return "name \"$name\" is reserved for internal use" if($name eq "fakeWallThermostat" or $name eq "fakeShutterContact");
+ return "wrong syntax: define MAX type addr"
if(int(@a)!=4 || $a[3] !~ m/^[A-F0-9]{6}$/i);
my $type = $a[2];
@@ -142,7 +96,7 @@ MAX_Define($$)
Log 1, $msg;
return $msg;
}
- Log 5, "Max_define $type with addr $addr ";
+ Log GetLogLevel($hash->{NAME}, 5), "Max_define $type with addr $addr ";
$hash->{type} = $type;
$hash->{addr} = $addr;
$modules{MAX}{defptr}{$addr} = $hash;
@@ -158,6 +112,7 @@ MAX_Undef($$)
{
my ($hash,$name) = @_;
delete($modules{MAX}{defptr}{$hash->{addr}});
+ return undef;
}
sub
@@ -184,6 +139,7 @@ MAX_CheckIODev($)
return !defined($hash->{IODev}) || ($hash->{IODev}{TYPE} ne "MAXLAN" && $hash->{IODev}{TYPE} ne "CUL_MAX");
}
+#Idenitify for numeric values and maps "on" and "off" to their temperatures
sub
MAX_ParseTemperature($)
{
@@ -198,12 +154,14 @@ MAX_Validate(@)
return $readingDef{$name}[0]->($val);
}
+#Get a reading, validating it's current value (maybe forcing to the default if invalid)
+#"on" and "off" are converted to their numeric values
sub
MAX_ReadingsVal(@)
{
my ($hash,$name) = @_;
- my $val = MAX_ParseTemperature(ReadingsVal($hash->{NAME},$name,""));
+ my $val = ReadingsVal($hash->{NAME},$name,"");
#$readingDef{$name} array is [validatingFunc, defaultValue]
if(exists($readingDef{$name}) and !$readingDef{$name}[0]->($val)) {
#Error: invalid value
@@ -217,10 +175,61 @@ MAX_ReadingsVal(@)
readingsSingleUpdate($hash,$name,$val,0);
}
}
- return $val;
+ return MAX_ParseTemperature($val);
}
+sub
+MAX_ParseWeekProfile(@) {
+ my ($hash ) = @_;
+ # Format of weekprofile: 16 bit integer (high byte first) for every control point, 13 control points for every day
+ # each 16 bit integer value is parsed as
+ # int time = (value & 0x1FF) * 5;
+ # int hour = (time / 60) % 24;
+ # int minute = time % 60;
+ # int temperature = ((value >> 9) & 0x3F) / 2;
+
+ my $curWeekProfile = MAX_ReadingsVal($hash, ".weekProfile");
+ #parse weekprofiles for each day
+ for (my $i=0;$i<7;$i++) {
+ my (@time_prof, @temp_prof);
+ for(my $j=0;$j<13;$j++) {
+ $time_prof[$j] = (hex(substr($curWeekProfile,($i*52)+ 4*$j,4))& 0x1FF) * 5;
+ $temp_prof[$j] = (hex(substr($curWeekProfile,($i*52)+ 4*$j,4))>> 9 & 0x3F ) / 2;
+ }
+
+ my @hours;
+ my @minutes;
+ my $j;
+ for($j=0;$j<13;$j++) {
+ $hours[$j] = ($time_prof[$j] / 60 % 24);
+ $minutes[$j] = ($time_prof[$j]%60);
+ #if 00:00 reached, last point in profile was found
+ last if(int($hours[$j])==0 && int($minutes[$j])==0 );
+ }
+ my $time_prof_str = "00:00";
+ my $temp_prof_str;
+ for (my $k=0;$k<=$j;$k++) {
+ $time_prof_str .= sprintf("-%02d:%02d", $hours[$k], $minutes[$k]);
+ $temp_prof_str .= sprintf("%2.1f °C",$temp_prof[$k]);
+ if ($k < $j) {
+ $time_prof_str .= " / " . sprintf("%02d:%02d", $hours[$k], $minutes[$k]);
+ $temp_prof_str .= " / ";
+ }
+ }
+ readingsBulkUpdate($hash, "weekprofile-$i-$decalcDays{$i}-time", $time_prof_str );
+ readingsBulkUpdate($hash, "weekprofile-$i-$decalcDays{$i}-temp", $temp_prof_str );
+ }
+}
#############################
+
+sub
+MAX_WakeUp($)
+{
+ my $hash = $_[0];
+ #3F corresponds to 31 seconds wakeup (so its probably the lower 5 bits)
+ return ($hash->{IODev}{Send})->($hash->{IODev},"WakeUp",$hash->{addr}, "3F", callbackParam => "31" );
+}
+
sub
MAX_Set($@)
{
@@ -229,13 +238,19 @@ MAX_Set($@)
return "Invalid IODev" if(MAX_CheckIODev($hash));
- if($setting eq "desiredTemperature" and $hash->{type} ~~ ["HeatingThermostat","WallMountedThermostat"]) {
+ if($setting eq "desiredTemperature" and $hash->{type} =~ /.*Thermostat.*/) {
return "missing a value" if(@args == 0);
my $temperature;
my $until = undef;
my $ctrlmode = 1; #0=auto, 1=manual; 2=temporary
+ if(AttrVal($hash->{NAME},"keepAuto","0") ne "0"
+ && MAX_ReadingsVal($hash,"mode") eq "auto") {
+ Log 5, "MAX_Set: staying in auto mode";
+ $ctrlmode = 0; #auto
+ }
+
if($args[0] eq "auto") {
#This enables the automatic/schedule mode where the thermostat follows the weekly program
$temperature = @args > 1 ? MAX_ParseTemperature($args[1]) : 0;
@@ -246,10 +261,8 @@ MAX_Set($@)
#TODO: auto mode with temperature is also possible
} elsif($args[0] eq "eco") {
$temperature = MAX_ReadingsVal($hash,"ecoTemperature");
- return "No ecoTemperature defined" if(!$temperature);
} elsif($args[0] eq "comfort") {
$temperature = MAX_ReadingsVal($hash,"comfortTemperature");
- return "No comfortTemperature defined" if(!$temperature);
}else{
$temperature = MAX_ParseTemperature($args[0]);
}
@@ -261,10 +274,11 @@ MAX_Set($@)
my $payload = sprintf("%02x",int($temperature*2.0) | ($ctrlmode << 6));
$payload .= $until if(defined($until));
- return ($hash->{IODev}{Send})->($hash->{IODev},"SetTemperature",$hash->{addr},$payload);
+ my $groupid = MAX_ReadingsVal($hash,"groupid");
+ return ($hash->{IODev}{Send})->($hash->{IODev},"SetTemperature",$hash->{addr},$payload, groupId => sprintf("%02x",$groupid), flags => ( $groupid ? "04" : "00" ));
}elsif($setting ~~ ["boostDuration", "boostValveposition", "decalcification","maxValveSetting","valveOffset"]
- and $hash->{type} eq "HeatingThermostat"){
+ and $hash->{type} =~ /HeatingThermostat.*/){
my $val = join(" ",@args); #decalcification contains a space
@@ -274,27 +288,32 @@ MAX_Set($@)
return $msg;
}
- readingsSingleUpdate($hash, $setting, $val, 0);
+ my %h;
+ $h{boostDuration} = MAX_ReadingsVal($hash,"boostDuration");
+ $h{boostValveposition} = MAX_ReadingsVal($hash,"boostValveposition");
+ $h{decalcification} = MAX_ReadingsVal($hash,"decalcification");
+ $h{maxValveSetting} = MAX_ReadingsVal($hash,"maxValveSetting");
+ $h{valveOffset} = MAX_ReadingsVal($hash,"valveOffset");
- my $boostDuration = MAX_ReadingsVal($hash,"boostDuration");
- my $boostValveposition = MAX_ReadingsVal($hash,"boostValveposition");
- my $decalcification = MAX_ReadingsVal($hash,"decalcification");
- my $maxValveSetting = MAX_ReadingsVal($hash,"maxValveSetting");
- my $valveOffset = MAX_ReadingsVal($hash,"valveOffset");
+ $h{$setting} = MAX_ParseTemperature($val);
- my ($decalcDay, $decalcHour) = ($decalcification =~ /^(...) (\d{1,2}):00$/);
+ my ($decalcDay, $decalcHour) = ($h{decalcification} =~ /^(...) (\d{1,2}):00$/);
my $decalc = ($decalcDaysInv{$decalcDay} << 5) | $decalcHour;
- my $boost = ($boost_durationsInv{$boostDuration} << 5) | int($boostValveposition/5);
+ my $boost = ($boost_durationsInv{$h{boostDuration}} << 5) | int($h{boostValveposition}/5);
- my $payload = sprintf("%02x%02x%02x%02x", $boost, $decalc, int($maxValveSetting*255/100), int($valveOffset*255/100));
- return ($hash->{IODev}{Send})->($hash->{IODev},"ConfigValve",$hash->{addr},$payload);
+ my $payload = sprintf("%02x%02x%02x%02x", $boost, $decalc, int($h{maxValveSetting}*255/100), int($h{valveOffset}*255/100));
+ return ($hash->{IODev}{Send})->($hash->{IODev},"ConfigValve",$hash->{addr},$payload,callbackParam => "$setting,$val");
}elsif($setting eq "groupid"){
return "argument needed" if(@args == 0);
- return ($hash->{IODev}{Send})->($hash->{IODev},"SetGroupId",$hash->{addr}, sprintf("%02x",$args[0]) );
+ if($args[0]) {
+ return ($hash->{IODev}{Send})->($hash->{IODev},"SetGroupId",$hash->{addr}, sprintf("%02x",$args[0]), callbackParam => "$args[0]" );
+ } else {
+ return ($hash->{IODev}{Send})->($hash->{IODev},"RemoveGroupId",$hash->{addr}, "00", callbackParam => "0");
+ }
- }elsif( $setting ~~ ["ecoTemperature", "comfortTemperature", "measurementOffset", "maximumTemperature", "minimumTemperature", "windowOpenTemperature", "windowOpenDuration" ] and ($hash->{type} eq "HeatingThermostat" or $hash->{type} eq "WallMountedThermostat")) {
+ }elsif( $setting ~~ ["ecoTemperature", "comfortTemperature", "measurementOffset", "maximumTemperature", "minimumTemperature", "windowOpenTemperature", "windowOpenDuration" ] and $hash->{type} =~ /.*Thermostat.*/) {
return "Cannot set without IODev" if(!exists($hash->{IODev}));
if(!MAX_Validate($setting, $args[0])) {
@@ -303,34 +322,35 @@ MAX_Set($@)
return $msg;
}
- readingsSingleUpdate($hash, $setting, $args[0], 0);
+ my %h;
+ $h{comfortTemperature} = MAX_ReadingsVal($hash,"comfortTemperature");
+ $h{ecoTemperature} = MAX_ReadingsVal($hash,"ecoTemperature");
+ $h{maximumTemperature} = MAX_ReadingsVal($hash,"maximumTemperature");
+ $h{minimumTemperature} = MAX_ReadingsVal($hash,"minimumTemperature");
+ $h{windowOpenTemperature} = MAX_ReadingsVal($hash,"windowOpenTemperature");
+ $h{windowOpenDuration} = MAX_ReadingsVal($hash,"windowOpenDuration");
+ $h{measurementOffset} = MAX_ReadingsVal($hash,"measurementOffset");
- my $comfortTemperature = MAX_ReadingsVal($hash,"comfortTemperature");
- my $ecoTemperature = MAX_ReadingsVal($hash,"ecoTemperature");
- my $maximumTemperature = MAX_ReadingsVal($hash,"maximumTemperature");
- my $minimumTemperature = MAX_ReadingsVal($hash,"minimumTemperature");
- my $windowOpenTemperature = MAX_ReadingsVal($hash,"windowOpenTemperature");
- my $windowOpenDuration = MAX_ReadingsVal($hash,"windowOpenDuration");
- my $measurementOffset = MAX_ReadingsVal($hash,"measurementOffset");
+ $h{$setting} = MAX_ParseTemperature($args[0]);
- my $comfort = int(MAX_ParseTemperature($comfortTemperature)*2);
- my $eco = int(MAX_ParseTemperature($ecoTemperature)*2);
- my $max = int(MAX_ParseTemperature($maximumTemperature)*2);
- my $min = int(MAX_ParseTemperature($minimumTemperature)*2);
- my $offset = int(($measurementOffset + 3.5)*2);
- my $windowOpenTemp = int(MAX_ParseTemperature($windowOpenTemperature)*2);
- my $windowOpenTime = int($windowOpenDuration/5);
+ my $comfort = int($h{comfortTemperature}*2);
+ my $eco = int($h{ecoTemperature}*2);
+ my $max = int($h{maximumTemperature}*2);
+ my $min = int($h{minimumTemperature}*2);
+ my $offset = int(($h{measurementOffset} + 3.5)*2);
+ my $windowOpenTemp = int($h{windowOpenTemperature}*2);
+ my $windowOpenTime = int($h{windowOpenDuration}/5);
my $payload = sprintf("%02x%02x%02x%02x%02x%02x%02x",$comfort,$eco,$max,$min,$offset,$windowOpenTemp,$windowOpenTime);
- return ($hash->{IODev}{Send})->($hash->{IODev},"ConfigTemperatures",$hash->{addr},$payload)
+ return ($hash->{IODev}{Send})->($hash->{IODev},"ConfigTemperatures",$hash->{addr},$payload, callbackParam => "$setting,$args[0]")
} elsif($setting eq "displayActualTemperature" and $hash->{type} eq "WallMountedThermostat") {
return "Invalid arg" if($args[0] ne "0" and $args[0] ne "1");
- readingsSingleUpdate($hash, $setting, $args[0], 0);
- return ($hash->{IODev}{Send})->($hash->{IODev},"SetDisplayActualTemperature",$hash->{addr},sprintf("%02x",$args[0] ? 4 : 0));
+ return ($hash->{IODev}{Send})->($hash->{IODev},"SetDisplayActualTemperature",$hash->{addr},
+ sprintf("%02x",$args[0] ? 4 : 0), callbackParam => "$setting,$args[0]");
- } elsif($setting eq "fake") {
+ } elsif($setting eq "fake") { #Deprecated, use fakeWT and fakeSC of CUL_MAX
#Resolve first argument to address
return "Invalid number of arguments" if(@args == 0);
my $dest = $args[0];
@@ -343,13 +363,14 @@ MAX_Set($@)
if($hash->{type} eq "ShutterContact") {
return "Invalid number of arguments" if(@args != 2);
+ Log 2, "fake is deprectaed and will be removed. Please use CUL_MAX's fakeSC";
my $state = $args[1] ? "12" : "10";
- return ($hash->{IODev}{Send})->($hash->{IODev},"ShutterContactState",$dest,$state,"06",undef,undef,$hash->{addr});
+ return ($hash->{IODev}{Send})->($hash->{IODev},"ShutterContactState",$dest,$state, flags => "06", src => $hash->{addr});
} elsif($hash->{type} eq "WallMountedThermostat") {
return "Invalid number of arguments" if(@args != 3);
return "desiredTemperature is invalid" if($args[1] < 4.5 || $args[2] > 30.5);
-
+ Log 2, "fake is deprectaed and will be removed. Please use CUL_MAX's fakeWT";
$args[2] = 0 if($args[2] < 0); #Clamp temperature to minimum of 0 degree
#Encode into binary form
@@ -358,24 +379,39 @@ MAX_Set($@)
my $arg1 = (($arg2&0x100)>>1) | (int(2*$args[1])&0x7F);
$arg2 &= 0xFF; #only take the lower 8 bits
- return ($hash->{IODev}{Send})->($hash->{IODev},"WallThermostatState",$dest,
- sprintf("%02x%02x",$arg1,$arg2),"04",undef,undef,$hash->{addr});
+ return ($hash->{IODev}{Send})->($hash->{IODev},"WallThermostatControl",$dest,
+ sprintf("%02x%02x",$arg1,$arg2),flags => "04", src => $hash->{addr});
} else {
return "fake does not work for device type $hash->{type}";
}
} elsif($setting ~~ ["associate", "deassociate"]) {
my $dest = $args[0];
- if(exists($defs{$dest})) {
- return "Destination is not a MAX device" if($defs{$dest}{TYPE} ne "MAX");
- #return "Destination is not a thermostat" if($defs{$dest}{type} ne "HeatingThermostat" and $defs{$dest}{type} ne "WallMountedThermostat");
- $dest = $defs{$dest}{addr};
+ my $destType;
+ if($dest eq "fakeWallThermostat") {
+ return "IODev is not CUL_MAX" if($hash->{IODev}->{TYPE} ne "CUL_MAX");
+ $dest = AttrVal($hash->{IODev}->{NAME}, "fakeWTaddr", "111111");
+ return "Invalid fakeWTaddr attribute set (must not be 000000)" if($dest eq "000000");
+ $destType = MAX_TypeToTypeId("WallMountedThermostat");
+
+ } elsif($dest eq "fakeShutterContact") {
+ return "IODev is not CUL_MAX" if($hash->{IODev}->{TYPE} ne "CUL_MAX");
+ $dest = AttrVal($hash->{IODev}->{NAME}, "fakeSCaddr", "222222");
+ return "Invalid fakeSCaddr attribute set (must not be 000000)" if($dest eq "000000");
+ $destType = MAX_TypeToTypeId("ShutterContact");
+
} else {
- return "No MAX device with address $dest" if(!exists($modules{MAX}{defptr}{$dest}));
+ if(exists($defs{$dest})) {
+ return "Destination is not a MAX device" if($defs{$dest}{TYPE} ne "MAX");
+ $dest = $defs{$dest}{addr};
+ } else {
+ return "No MAX device with address $dest" if(!exists($modules{MAX}{defptr}{$dest}));
+ }
+ $destType = MAX_TypeToTypeId($modules{MAX}{defptr}{$dest}{type});
+ Log 2, "Warning: Device do not have same groupid" if($hash->{groupid} != $modules{MAX}{defptr}{$dest}{groupid});
}
- my $destType = MAX_TypeToTypeId($modules{MAX}{defptr}{$dest}{type});
- Log 2, "Warning: Device do not have same groupid" if($hash->{groupid} != $modules{MAX}{defptr}{$dest}{groupid});
- Log 5, "Using dest $dest, destType $destType";
+
+ Log GetLogLevel($hash->{NAME}, 5), "Using dest $dest, destType $destType";
if($setting eq "associate") {
return ($hash->{IODev}{Send})->($hash->{IODev},"AddLinkPartner",$hash->{addr},sprintf("%s%02x", $dest, $destType));
} else {
@@ -394,7 +430,52 @@ MAX_Set($@)
}
} elsif($setting eq "wakeUp") {
- return ($hash->{IODev}{Send})->($hash->{IODev},"WakeUp",$hash->{addr}, 0x3F);
+ return MAX_WakeUp($hash);
+
+ } elsif($setting eq "weekProfile" and $hash->{type} =~ /.*Thermostat.*/) {
+ return "Number of arguments must be even" if(@args%2 == 1);
+
+ #Send wakeUp, so we can send the weekprofile pakets without preamble
+ MAX_WakeUp($hash) if( @args > 2 );
+
+ for(my $i = 0; $i < @args; $i += 2) {
+ return "Expected day, got $args[$i]" if(!exists($decalcDaysInv{$args[$i]}));
+ my $day = $decalcDaysInv{$args[$i]};
+ my @controlpoints = split(',',$args[$i+1]);
+ return "Not more than 13 control points are allowed!" if(@controlpoints > 13*2);
+ my $newWeekprofilePart = "";
+ for(my $j = 0; $j < 13*2; $j += 2) {
+ if( $j >= @controlpoints ) {
+ $newWeekprofilePart .= "4520";
+ next;
+ }
+ my ($hour, $min);
+ if($j + 1 == @controlpoints) {
+ $hour = 0; $min = 0;
+ } else {
+ ($hour, $min) = ($controlpoints[$j+1] =~ /^(\d{1,2}):(\d{1,2})$/);
+ }
+ my $temperature = $controlpoints[$j];
+ return "Invalid time: $controlpoints[$j+1]" if(!defined($hour) || !defined($min) || $hour > 23 || $min > 59);
+ return "Invalid temperature" if(!validTemperature($temperature));
+ $temperature = MAX_ParseTemperature($temperature); #replace "on" and "off" by their values
+ $newWeekprofilePart .= sprintf("%04x", (int($temperature*2) << 9) | int(($hour * 60 + $min)/5));
+ }
+ Log GetLogLevel($hash->{NAME}, 5), "New Temperature part for $day: $newWeekprofilePart";
+ #Each day has 2 bytes * 13 controlpoints = 26 bytes = 52 hex characters
+ #we don't have to update the rest, because the active part is terminated by the time 0:00
+
+ #First 7 controlpoints (2*7=14 bytes => 2*2*7=28 hex characters )
+ ($hash->{IODev}{Send})->($hash->{IODev},"ConfigWeekProfile",$hash->{addr},
+ sprintf("0%1d%s", $day, substr($newWeekprofilePart,0,2*2*7)),
+ callbackParam => "$day,0,".substr($newWeekprofilePart,0,2*2*7));
+ #And then the remaining 6
+ ($hash->{IODev}{Send})->($hash->{IODev},"ConfigWeekProfile",$hash->{addr},
+ sprintf("1%1d%s", $day, substr($newWeekprofilePart,2*2*7,2*2*6)),
+ callbackParam => "$day,1,".substr($newWeekprofilePart,2*2*7,2*2*6))
+ if(@controlpoints > 2*7);
+ }
+ Log GetLogLevel($hash->{NAME}, 5), "New weekProfile: " . MAX_ReadingsVal($hash, ".weekProfile");
}else{
my $templist = "off,".join(",",map { sprintf("%2.1f",$_/2) } (10..60)) . ",on";
@@ -402,13 +483,17 @@ MAX_Set($@)
my $assoclist;
#Build list of devices which this device can be associated to
- if($hash->{type} eq "HeatingThermostat") {
- $assoclist = join(",", map { defined($_->{type}) && $_->{type} ~~ ["HeatingThermostat", "WallMountedThermostat", "ShutterContact"] ? $_->{NAME} : () } values %{$modules{MAX}{defptr}});
+ if($hash->{type} =~ /HeatingThermostat.*/) {
+ $assoclist = join(",", map { defined($_->{type}) && $_->{type} ~~ ["HeatingThermostat", "HeatingThermostatPlus", "WallMountedThermostat", "ShutterContact"] && $_ != $hash ? $_->{NAME} : () } values %{$modules{MAX}{defptr}});
+ if($hash->{IODev}->{TYPE} eq "CUL_MAX") {
+ $assoclist .= "," if(length($assoclist));
+ $assoclist .= "fakeWallThermostat,fakeShutterContact";
+ }
} elsif($hash->{type} ~~ ["ShutterContact", "WallMountedThermostat"]) {
- $assoclist = join(",", map { defined($_->{type}) && $_->{type} eq "HeatingThermostat" ? $_->{NAME} : () } values %{$modules{MAX}{defptr}});
+ $assoclist = join(",", map { defined($_->{type}) && $_->{type} =~ /HeatingThermostat.*/ ? $_->{NAME} : () } values %{$modules{MAX}{defptr}});
}
- if($hash->{type} eq "HeatingThermostat") {
+ if($hash->{type} =~ /HeatingThermostat.*/) {
#Create numbers from 4.5 to 30.5
my $templistOffset = join(",",map { sprintf("%2.1f",($_-7)/2) } (0..14));
my $boostDurVal = join(",", values(%boost_durations));
@@ -461,7 +546,7 @@ MAX_Parse($$)
$devicetype = $args[0] if($msgtype eq "define");
$devicetype = "ShutterContact" if($msgtype eq "ShutterContactState");
$devicetype = "Cube" if($msgtype eq "CubeClockState" or $msgtype eq "CubeConnectionState");
- $devicetype = "WallMountedThermostat" if($msgtype ~~ ["WallThermostatConfig","WallThermostatState"]);
+ $devicetype = "WallMountedThermostat" if($msgtype ~~ ["WallThermostatConfig","WallThermostatState","WallThermostatControl"]);
$devicetype = "HeatingThermostat" if($msgtype ~~ ["HeatingThermostatConfig", "ThermostatState"]);
if($devicetype) {
return "UNDEFINED MAX_$addr MAX $devicetype $addr";
@@ -481,6 +566,7 @@ MAX_Parse($$)
if($msgtype eq "define"){
my $devicetype = $args[0];
Log 1, "Device changed type from $shash->{type} to $devicetype" if($shash->{type} ne $devicetype);
+ $shash->{type} = $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));
@@ -506,16 +592,11 @@ MAX_Parse($$)
$untilStr = "" if($mode != 2);
$desiredTemperature = ($desiredTemperature&0x7F)/2.0; #convert to degree celcius
- Log 5, "battery $batterylow, rferror $rferror, panel $panel, langateway $langateway, dstsetting $dstsetting, mode $mode, valveposition $valveposition %, desiredTemperature $desiredTemperature, until $untilStr, curTemp $measuredTemperature";
+ Log GetLogLevel($shash->{NAME}, 5), "battery $batterylow, rferror $rferror, panel $panel, langateway $langateway, dstsetting $dstsetting, mode $mode, valveposition $valveposition %, desiredTemperature $desiredTemperature, until $untilStr, curTemp $measuredTemperature";
#Very seldomly, the HeatingThermostat sends us temperatures like 0.2 or 0.3 degree Celcius - ignore them
$measuredTemperature = "" if($measuredTemperature ne "" and $measuredTemperature < 1);
- #The HeatingThermostat uses the measurementOffset during control
- #but does not apply it to measuredTemperature before sending it to us (guessed)
- my $measOffset = MAX_ReadingsVal($shash,"measurementOffset");
- $measuredTemperature -= $measOffset if($measuredTemperature ne "" and $measOffset ne "");
-
$shash->{mode} = $mode;
$shash->{rferror} = $rferror;
$shash->{dstsetting} = $dstsetting;
@@ -535,15 +616,17 @@ MAX_Parse($$)
readingsBulkUpdate($shash, "temperature", sprintf("%2.1f",$measuredTemperature));
}
- }elsif($msgtype eq "WallThermostatState"){
- my ($bits2,$displayActualTemperature,$desiredTemperature,$null1,$heaterTemperature,$null2,$temperature);
- if( length($args[0]) == 4 ) {
+ }elsif($msgtype ~~ ["WallThermostatState", "WallThermostatControl" ]){
+ my ($bits2,$displayActualTemperature,$desiredTemperatureRaw,$null1,$heaterTemperature,$null2,$temperature);
+ if( length($args[0]) == 4 ) { #WallThermostatControl
#This is the message that WallMountedThermostats send to paired HeatingThermostats
- ($desiredTemperature,$temperature) = unpack("CC",pack("H*",$args[0]));
- } elsif( length($args[0]) == 14 or length($args[0]) == 12) {
+ ($desiredTemperatureRaw,$temperature) = unpack("CC",pack("H*",$args[0]));
+ } elsif( length($args[0]) >= 6 and length($args[0]) <= 14) { #WallThermostatState
#len=14: This is the message we get from the Cube over MAXLAN and which is probably send by WallMountedThermostats to the Cube
- #len=12: Payload of the Ack message, last field "temperature" is missing
- ($bits2,$displayActualTemperature,$desiredTemperature,$null1,$heaterTemperature,$null2,$temperature) = unpack("aCCCCCC",pack("H*",$args[0]));
+ #len=12: Payload of an Ack message, last field "temperature" is missing
+ #len=10: Received by MAX_CUL as WallThermostatState
+ #len=6 : Payload of an Ack message, last four fields (especially $heaterTemperature and $temperature) are missing
+ ($bits2,$displayActualTemperature,$desiredTemperatureRaw,$null1,$heaterTemperature,$null2,$temperature) = unpack("aCCCCCC",pack("H*",$args[0]));
#$heaterTemperature/10 is the temperature measured by a paired HeatingThermostat
#we don't do anything with it here, because this value also appears as temperature in the HeatingThermostat's ThermostatState message
my $mode = vec($bits2, 0, 2); #
@@ -552,24 +635,29 @@ MAX_Parse($$)
my $panel = vec($bits2, 5, 1); #1 if the heating thermostat is locked for manually setting the temperature at the device
my $rferror = vec($bits2, 6, 1); #communication with link partner (what does that mean?)
my $batterylow = vec($bits2, 7, 1); #1 if battery is low
- Log 2, "Warning: WallThermostatState null1: $null1 null2: $null2 should be both zero" if($null1 != 0 || $null2 != 0);
- Log 5, "battery $batterylow, rferror $rferror, panel $panel, langateway $langateway, dstsetting $dstsetting, mode $mode, displayActualTemperature $displayActualTemperature, heaterTemperature $heaterTemperature";
+ my $untilStr = "";
+ if(defined($null2) and ($null1 != 0 or $null2 != 0)) {
+ $untilStr = MAX_ParseDateTime($null1,$heaterTemperature,$null2)->{str};
+ $heaterTemperature = "";
+ }
+
+ Log GetLogLevel($shash->{NAME}, 5), "battery $batterylow, rferror $rferror, panel $panel, langateway $langateway, dstsetting $dstsetting, mode $mode, displayActualTemperature $displayActualTemperature, heaterTemperature $heaterTemperature, untilStr $untilStr";
$shash->{rferror} = $rferror;
readingsBulkUpdate($shash, "mode", $ctrl_modes[$mode] );
readingsBulkUpdate($shash, "battery", $batterylow ? "low" : "ok");
readingsBulkUpdate($shash, "displayActualTemperature", ($displayActualTemperature) ? 1 : 0);
} else {
- Log 2, "Invalid WallThermostatState packet"
+ Log 2, "Invalid $msgtype packet"
}
- $desiredTemperature = ($desiredTemperature &0x7F)/2.0; #convert to degree celcius
+ my $desiredTemperature = ($desiredTemperatureRaw &0x7F)/2.0; #convert to degree celcius
if(defined($temperature)) {
- $temperature = ((($desiredTemperature &0x80)<<1) + $temperature)/10; # auch Temperaturen über 25.5 °C werden angezeigt !
- Log 5, "desiredTemperature $desiredTemperature, temperature $temperature";
+ $temperature = ((($desiredTemperatureRaw &0x80)<<1) + $temperature)/10; # auch Temperaturen über 25.5 °C werden angezeigt !
+ Log GetLogLevel($shash->{NAME}, 5), "desiredTemperature $desiredTemperature, temperature $temperature";
readingsBulkUpdate($shash, "temperature", sprintf("%2.1f",$temperature));
} else {
- Log 5, "desiredTemperature $desiredTemperature"
+ Log GetLogLevel($shash->{NAME}, 5), "desiredTemperature $desiredTemperature"
}
#This formatting must match with in MAX_Set:$templist
@@ -581,7 +669,7 @@ MAX_Parse($$)
my $unkbits = vec($bits,2,4);
my $rferror = vec($bits,6,1);
my $batterylow = vec($bits,7,1);
- Log 5, "ShutterContact isopen $isopen, rferror $rferror, battery $batterylow, unkbits $unkbits";
+ Log GetLogLevel($shash->{NAME}, 5), "ShutterContact isopen $isopen, rferror $rferror, battery $batterylow, unkbits $unkbits";
$shash->{rferror} = $rferror;
@@ -589,13 +677,14 @@ MAX_Parse($$)
readingsBulkUpdate($shash,"onoff",$isopen);
}elsif($msgtype eq "PushButtonState") {
- my ($bits2, $onoff) = unpack("CC",pack("H*",$args[0]));
+ my ($bits2, $onoff) = unpack("aC",pack("H*",$args[0]));
#The meaning of $bits2 is completly guessed based on similarity to other devices, TODO: confirm
- my $rferror = vec($bits2, 6, 1); #communication with link partner (what does that mean?)
+ my $gateway = vec($bits2, 4, 1); #Paired to a CUBE?
+ my $rferror = vec($bits2, 6, 1); #communication with link partner (1 if we did not sent an Ack)
my $batterylow = vec($bits2, 7, 1); #1 if battery is low
readingsBulkUpdate($shash, "battery", $batterylow ? "low" : "ok");
- readingsBulkUpdate($shash,"onoff",$onoff);
+ readingsBulkUpdate($shash, "onoff", $onoff);
}elsif($msgtype eq "CubeClockState"){
my $clockset = $args[0];
@@ -611,7 +700,7 @@ MAX_Parse($$)
readingsBulkUpdate($shash, "comfortTemperature", sprintf("%2.1f",$args[1]));
readingsBulkUpdate($shash, "maximumTemperature", sprintf("%2.1f",$args[2]));
readingsBulkUpdate($shash, "minimumTemperature", sprintf("%2.1f",$args[3]));
- if($shash->{type} eq "HeatingThermostat") {
+ if($shash->{type} =~ /HeatingThermostat.*/) {
readingsBulkUpdate($shash, "boostValveposition", $args[4]);
readingsBulkUpdate($shash, "boostDuration", $boost_durations{$args[5]});
readingsBulkUpdate($shash, "measurementOffset", sprintf("%2.1f",$args[6]));
@@ -620,44 +709,12 @@ MAX_Parse($$)
readingsBulkUpdate($shash, "maxValveSetting", $args[9]);
readingsBulkUpdate($shash, "valveOffset", $args[10]);
readingsBulkUpdate($shash, "decalcification", "$decalcDays{$args[11]} $args[12]:00");
- $shash->{internal}{weekProfile} = $args[13];
+ readingsBulkUpdate($shash, ".weekProfile", $args[13]);
} else {
- $shash->{internal}{weekProfile} = $args[4];
+ readingsBulkUpdate($shash, ".weekProfile", $args[4]);
}
- #parse weekprofiles for each day
- for (my $i=0;$i<7;$i++) {
- my (@time_prof, @temp_prof);
- for(my $j=0;$j<13;$j++) {
- $time_prof[$j] = (hex(substr($shash->{internal}{weekProfile},($i*52)+ 4*$j,4))& 0x1FF) * 5;
- $temp_prof[$j] = (hex(substr($shash->{internal}{weekProfile},($i*52)+ 4*$j,4))>> 9 & 0x3F ) / 2;
- }
-
- my @hours;
- my @minutes;
- my $j;
- for($j=0;$j<13;$j++) {
- $hours[$j] = ($time_prof[$j] / 60 % 24);
- $minutes[$j] = ($time_prof[$j]%60);
- #if 00:00 reached, last point in profile was found
- last if(int($hours[$j])==0 && int($minutes[$j])==0 );
- }
-
- my $time_prof_str = "00:00";
- my $temp_prof_str;
- for (my $k=0;$k<=$j;$k++) {
- $time_prof_str .= sprintf("-%02d:%02d", $hours[$k], $minutes[$k]);
- $temp_prof_str .= sprintf("%2.1f °C",$temp_prof[$k]);
- if ($k < $j) {
- $time_prof_str .= " / " . sprintf("%02d:%02d", $hours[$k], $minutes[$k]);
- $temp_prof_str .= " / ";
- }
- }
-
- readingsBulkUpdate($shash, "weekprofile-$i-$decalcDays{$i}-time", $time_prof_str );
- readingsBulkUpdate($shash, "weekprofile-$i-$decalcDays{$i}-temp", $temp_prof_str );
-
- } # Endparse weekprofiles for each day
+ MAX_ParseWeekProfile($shash);
} elsif($msgtype eq "Error") {
if(@args == 0) {
@@ -666,6 +723,27 @@ MAX_Parse($$)
$shash->{ERROR} = join(",",$args[0]);
}
+ } elsif($msgtype eq "AckWakeUp") {
+ my ($duration) = @args;
+ #substract five seconds safety margin
+ $shash->{wakeUpUntil} = gettimeofday() + $duration - 5;
+
+ } elsif($msgtype eq "AckConfigWeekProfile") {
+ my ($day, $part, $profile) = @args;
+
+ my $curWeekProfile = MAX_ReadingsVal($shash, ".weekProfile");
+ substr($curWeekProfile, $day*52+$part*2*2*7, length($profile)) = $profile;
+ readingsBulkUpdate($shash, ".weekProfile", $curWeekProfile);
+ MAX_ParseWeekProfile($shash);
+
+ } elsif($msgtype ~~ ["AckConfigValve", "AckConfigTemperatures", "AckSetDisplayActualTemperature" ]) {
+
+ readingsBulkUpdate($shash, $args[0], $args[1]);
+
+ } elsif($msgtype ~~ ["AckSetGroupId", "AckRemoveGroupId" ]) {
+
+ readingsBulkUpdate($shash, "groupid", $args[0]);
+
} elsif($msgtype eq "Ack") {
#The payload of an Ack is a 2-digit hex number (being "01" for okey and "81" for "invalid command/argument"
if($isToMe and (unpack("C",pack("H*",$args[0])) & 0x80)) {
@@ -675,7 +753,7 @@ MAX_Parse($$)
}
#with unknown meaning plus the data of a State broadcast from the same device
#For HeatingThermostats, it does not contain the last three "until" bytes (or measured temperature)
- if($shash->{type} ~~ "HeatingThermostat" ) {
+ if($shash->{type} =~ /HeatingThermostat.*/ ) {
return MAX_Parse($hash, "MAX,$isToMe,ThermostatState,$addr,". substr($args[0],2));
} elsif($shash->{type} eq "WallMountedThermostat") {
return MAX_Parse($hash, "MAX,$isToMe,WallThermostatState,$addr,". substr($args[0],2));
@@ -763,13 +841,13 @@ MAX_Parse($$)
to set a temporary temperature until that date/time. Make sure that the cube has valid system time!
groupid <id>
For devices of type HeatingThermostat only.
- Writes the given group id the device's memory. It is usually not necessary to change this.
+ Writes the given group id the device's memory. To sync all devices in one room, set them to the same groupid greater than zero.
ecoTemperature <value>
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.
comfortTemperature <value>
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.
measurementOffset <value>
- For devices of type HeatingThermostat only. Writes the given temperature offset to the device's memory. The thermostat tries to match desiredTemperature to (measured temperature at sensor - measurementOffset). Usually, the measured temperature is a bit higher than the overall room temperature (due to closeness to the heater), so one uses a small positive offset. Must be between -3.5 and 3.5 degree.
+ For devices of type HeatingThermostat only. Writes the given temperature offset to the device's memory. The thermostat tries to match desiredTemperature to (temperature = measured temperature at sensor + measurementOffset). 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 celsius.
minimumTemperature <value>
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.
maximumTemperature <value>
@@ -799,10 +877,14 @@ MAX_Parse($$)
WallMountedThermostat for control.
deassociate <value>
Removes the association set by associate.
- fake <device> <parameters...>
- Sends a fake state message of this device over the air to <device>. Works only with CUL_MAX as IODev. For ShutterContacts, sends
- a ShutterContactState message; <parameters...> must be 0 or 1 for "window closed" or "window opened". For WallMountedThermostats.
- sends a WallThermostatState message; <parameters...> must be "$desiredTemperature $measuredTemperature" (both may have one digit after the decimal point, for desiredTemperature it may only by 0 or 5). Make sure you associate the target device with the source device beforehand.
+ weekProfile [<day> <temp1>,<until1>,<temp2>,<until2>] [<day> <temp1>,<until1>,<temp2>,<until2>] ...
+ Allows setting the week profile. For devices of type HeatingThermostat or WallMountedThermostat only. Example:
+ set MAX_12345 weekProfile Fri 24.5,6:00,12,15:00,5 Sat 7,4:30,19,12:55,6
+ sets the profile
+ Friday: 24.5 °C for 0:00 - 6:00, 12 °C for 6:00 - 15:00, 5 °C for 15:00 - 0:00
+ Saturday: 7 °C for 0:00 - 4:30, 19 °C for 4:30 - 12:55, 6 °C for 12:55 - 0:00
+ while keeping the old profile for all other days.
+
@@ -818,6 +900,7 @@ MAX_Parse($$)
do_not_notify
ignore
readingFnAttributes
+ keepAuto Default: 0. If set to 1, it will stay in the auto mode when you set a desiredTemperature while the auto (=weekly program) mode is active.
@@ -827,7 +910,7 @@ MAX_Parse($$)
desiredTemperature Only for HeatingThermostat and WallMountedThermostat
valveposition Only for HeatingThermostat
battery
- temperature The measured(!) temperature, only for HeatingThermostat and WallMountedThermostat
+ temperature The measured temperature (= measured temperature at sensor + measurementOffset), only for HeatingThermostat and WallMountedThermostat
diff --git a/FHEM/10_OWServer.pm b/FHEM/10_OWServer.pm
index 506009aa9..8e72968c3 100644
--- a/FHEM/10_OWServer.pm
+++ b/FHEM/10_OWServer.pm
@@ -102,7 +102,7 @@ OWServer_Initialize($)
$hash->{ReadFn} = "OWServer_Read";
$hash->{DirFn} = "OWServer_Dir";
$hash->{FindFn} = "OWServer_Find";
- $hash->{Clients} = ":OWDevice:";
+ $hash->{Clients} = ":OWDevice:OWAD:OWCOUNT:OWMULTI:OWSWITCH:OWTHERM:";
# Consumer
$hash->{DefFn} = "OWServer_Define";
@@ -169,9 +169,8 @@ OWServer_CloseDev($)
my $name = $hash->{NAME};
return unless(defined($hash->{fhem}{owserver}));
- DoTrigger($name, "DISCONNECTED");
delete $hash->{fhem}{owserver};
-
+ readingsSingleUpdate($hash, "state", "DISCONNECTED", 1);
}
########################
@@ -183,15 +182,13 @@ OWServer_OpenDev($)
OWServer_CloseDev($hash);
my $protocol= $hash->{fhem}{protocol};
- Log 4, "$name: Opening connection to OWServer $protocol...";
+ Log 3, "$name: Opening connection to OWServer $protocol...";
my $owserver= OWNet->new($protocol);
if($owserver) {
- Log 4, "$name: Successfully connected to $protocol.";
+ Log 3, "$name: Successfully connected to $protocol.";
$hash->{fhem}{owserver}= $owserver;
- DoTrigger($name, "CONNECTED") if($owserver);
- $hash->{STATE}= ""; # Allow InitDev to set the state
+ readingsSingleUpdate($hash, "state", "CONNECTED", 1);
my $ret = OWServer_DoInit($hash);
-
}
return $owserver
}
@@ -223,7 +220,6 @@ OWServer_DoInit($)
{
my $hash = shift;
my $name = $hash->{NAME};
- $hash->{STATE} = "Initialized" if(!$hash->{STATE});
my $owserver= $hash->{fhem}{owserver};
foreach my $reading (sort keys %gets) {
@@ -231,7 +227,7 @@ OWServer_DoInit($)
readingsBulkUpdate($hash,"$reading",$owserver->read("$reading"));
readingsEndUpdate($hash,1);
}
-
+ readingsSingleUpdate($hash, "state", "Initialized", 1);
OWServer_Autocreate($hash) if($init_done);
return undef;
}
@@ -244,6 +240,8 @@ OWServer_Read($@)
return undef unless(defined($hash->{fhem}{owserver}));
+ my $ret= undef;
+
if(AttrVal($hash->{NAME},"nonblocking",undef) && $init_done) {
$hash->{".path"}= $path;
pipe(READER,WRITER);
@@ -258,7 +256,7 @@ OWServer_Read($@)
InternalTimer(gettimeofday()+20, "OWServer_TimeoutChild", $pid, 0);
if($pid == 0) {
close READER;
- my $ret= OWNet::read($hash->{DEF},$path);
+ $ret= OWNet::read($hash->{DEF},$path);
$ret =~ s/^\s+//g if(defined($ret));
Log 5, "OWServer child read $path: $ret";
delete $hash->{".path"};
@@ -269,14 +267,18 @@ OWServer_Read($@)
Log 5, "OWServer child ID for reading '$path' is $pid";
close WRITER;
- chomp(my $ret= );
+ chomp($ret= );
close READER;
- return $ret;
} else {
- my $ret= $hash->{fhem}{owserver}->read($path);
+ $ret= $hash->{fhem}{owserver}->read($path);
$ret =~ s/^\s+//g if(defined($ret));
- return $ret;
}
+
+ # if a device does not exist, the server returns undef
+ # therefore it's not a good idea to blame the connection
+ # and remove the server in such a case.
+ #if(!defined($ret)) { OWServer_CloseDev($hash); }
+ return $ret;
}
#####################################
@@ -313,8 +315,10 @@ sub
OWServer_Find($@)
{
my ($hash,$slave)= @_;
- my $owserver= $hash->{fhem}{owserver};
+ return undef unless(defined($hash->{fhem}{owserver}));
+
+ my $owserver= $hash->{fhem}{owserver};
my @dir= split(",",$owserver->dir("/"));
my $path= undef;
for my $entry (@dir) {
@@ -462,10 +466,11 @@ OWServer_Set($@)
# usage check
#my $usage= "Usage: set $name classdef OR set $name reopen";
my $usage= "Unknown argument $a[1], choose one of reopen ".join(" ", sort keys %sets);
- return $usage if($a[1] ne "reopen " && !defined($sets{$a[1]}));
+ return $usage if($a[1] ne "reopen" && !defined($sets{$a[1]}));
if((@a == 2) && ($a[1] eq "reopen")) {
- return OWServer_OpenDev($hash);
+ OWServer_OpenDev($hash);
+ return undef;
} elsif(@a == 3) {
my $cmd= $a[1];
my $value= $a[2];
@@ -568,7 +573,7 @@ OWServer_Set($@)
the corresponding OWDevice if one is defined for the respective 1-wire devices.
errors
- List a (maybe helpful) view of error statistics.
+ List a view of error statistics.
owserver (OWFS) specific settings:
/settings/timeout/directory
diff --git a/FHEM/10_ZWave.pm b/FHEM/10_ZWave.pm
index bc05c687c..b793c4030 100755
--- a/FHEM/10_ZWave.pm
+++ b/FHEM/10_ZWave.pm
@@ -56,8 +56,8 @@ my %zwave_class = (
reportOff => "0300", },
get => { swmStatus => "02", },
#03260363 reported in http://forum.fhem.de/index.php?t=rview&th=10216
- parse => { "032603(.*)"=> '($1 == "00" ? "state:off" :
- ($1 == "ff" ? "state:on" :
+ parse => { "032603(.*)"=> '($1 eq "00" ? "state:off" :
+ ($1 eq "ff" ? "state:on" :
"state:dim ".hex($1)))',}, },
SWITCH_ALL => { id => '27', },
SWITCH_TOGGLE_BINARY => { id => '28', },
@@ -88,7 +88,13 @@ my %zwave_class = (
THERMOSTAT_SETBACK => { id => '47', },
BASIC_WINDOW_COVERING => { id => '50', },
MTP_WINDOW_COVERING => { id => '51', },
- MULTI_INSTANCE => { id => '60', },
+ MULTI_CHANNEL => { id => '60', # Version 2!
+ get => { mcEndpoints => "07", # Endpoints
+ mcCapability=> "09%02x"},
+ parse => { "^046008(..)(..)" => '"mcEndpoints:total ".hex($2).'.
+ '(hex($1)&0x80 ? ", dynamic":"").'.
+ '(hex($1)&0x40 ? ", identical":", different")',
+ "^..600a(.*)"=> 'ZWave_mcCapability($hash, $1)' }, },
DOOR_LOCK => { id => '62', },
USER_CODE => { id => '63', },
CONFIGURATION => { id => '70',
@@ -120,16 +126,17 @@ my %zwave_class = (
get => { wakeupInterval => "05" },
parse => { "028407" => 'wakeup:notification',
"..8406(......)(..)" =>
- '"wakeupReport:interval:".hex($1)." target:".hex($2)',}, },
+ '"wakeupReport:interval ".hex($1)." target ".hex($2)',}, },
ASSOCIATION => { id => '85',
set => { associationAdd => "01%02x%02x*",
associationDel => "04%02x%02x*", },
get => { association => "02%02x", },
- parse => { "..8503(..)(..)..(.*)" => '"assocGroup_$1:Max:$2 Nodes:$3"',}, },
+ parse => { "..8503(..)(..)..(.*)" => '"assocGroup_$1:Max $2 Nodes $3"',}, },
VERSION => { id => '86',
get => { version => "11", },
parse => { "078612(..)(..)(..)(..)(..)" =>
- 'sprintf("Lib:%d Prot:%d.%d App:%d.%d",hex($1),hex($2),hex($3),hex($4),hex($5))', } },
+ 'sprintf("version:Lib %d Prot %d.%d App %d.%d",'.
+ 'hex($1),hex($2),hex($3),hex($4),hex($5))', } },
INDICATOR => { id => '87', },
PROPRIETARY => { id => '88', },
LANGUAGE => { id => '89', },
@@ -138,7 +145,7 @@ my %zwave_class = (
COMPOSITE => { id => '8D', },
MULTI_CMD => { id => '8F', },
TIME => { id => '8a', },
- MULTI_INSTANCE_ASSOCIATION => { id => '8E', },
+ MULTI_CHANNEL_ASSOCIATION=> { id => '8E', },
ENERGY_PRODUCTION => { id => '90', },
MANUFACTURER_PROPRIETARY => { id => '91', },
SCREEN_MD => { id => '92', },
@@ -200,7 +207,7 @@ ZWave_Define($$)
return "define $name: wrong id ($id): need a number"
if( ($id !~ m/^\d+$/i) );
- $id = sprintf("%02x", $id);
+ $id = sprintf("%0*x", ($id > 255 ? 4 : 2), $id);
$hash->{homeId} = $homeId;
$hash->{id} = $id;
@@ -289,6 +296,15 @@ ZWave_Cmd($$@)
return "$type $cmd needs $parTxt" if($nArg != int(@a));
}
$cmdFmt = sprintf($cmdFmt, @a) if($nArg);
+
+ if($id =~ m/(..)(..)/) { # Multi-Channel, encapsulate
+ my ($lid,$ch) = ($1, $2);
+ $id = $lid;
+ $cmdFmt = "0d01$ch$cmdId$cmdFmt";
+ $cmdId = "60"; # MULTI_CHANNEL
+ }
+
+
my $len = sprintf("%02x", length($cmdFmt)/2+1);
my $data = "13$id$len$cmdId${cmdFmt}05";
@@ -379,6 +395,38 @@ ZWave_SetClasses($$$$)
return "";
}
+sub
+ZWave_mcCapability($$)
+{
+ my ($hash, $caps) = @_;
+
+ my $name = $hash->{NAME};
+ my $iodev = $hash->{IODev};
+ return "Missing IODev for $name" if(!$iodev);
+
+ my $homeId = $iodev->{homeId};
+ my @l = grep /../, split(/(..)/, lc($caps));
+ my $chid = shift(@l);
+ my $id = $hash->{id};
+
+ my @classes;
+ for my $classId (@l) {
+ push @classes, $zwave_id2class{$classId} if($zwave_id2class{$classId});
+ }
+ return "mcCapability_$chid:no classes" if(!@classes);
+
+ if(!$modules{ZWave}{defptr}{"$homeId $id$chid"}) {
+ my $lid = hex("$id$chid");
+ my $lcaps = substr($caps, 2);
+ $id = hex($id);
+ DoTrigger("global",
+ "UNDEFINED ZWave_$classes[0]_$id.$chid ZWave $homeId $lid $caps",
+ 1);
+ }
+
+ return "mcCapability_$chid:".join(" ", @classes);
+}
+
###################################
# 0004000a03250300 (sensor binary off for id 11)
sub
@@ -434,7 +482,14 @@ ZWave_Parse($$@)
######################################
# device messages
- return "" if($cmd ne "APPLICATION_COMMAND_HANDLER" || $arg !~ m/^..(..)/);
+ return "" if($cmd ne "APPLICATION_COMMAND_HANDLER");
+
+ if($arg =~ /^..600d(..)(..)(.*)/) { # MULTI_CHANNEL CMD_ENCAP
+ $id = "$id$1";
+ $arg = sprintf("%02x$3", length($3)/2);
+ }
+
+ return if($arg !~ m/^..(..)/);
my $class = $1;
my $hash = $modules{ZWave}{defptr}{"$homeId $id"};
if(!$hash) {
@@ -633,19 +688,32 @@ ZWave_Undef($$)
Class WAKE_UP
- wakeupInterval
return the wakeup interval in seconds, in the form
- wakeupReport:interval:seconds target:id
+ wakeupReport:interval seconds target id
Class ASSOCIATION
- association groupId
return the list of nodeIds in the association group groupId in the form:
- assocGroup_X:Max:Y Nodes:id,id...
+ assocGroup_X:Max Y, Nodes id,id...
Class VERSION
- version
return the version information of this node in the form:
- Lib:A Prot:x.y App:a.b
+ Lib A Prot x.y App a.b
+
+
+
Class MULTI_CHANNEL
+ - mcEndpoints
+ return the list of endpoints available, e.g.:
+ mcEndpoints: total 2, identical
+
+ - mcCapability chid
+ return the classes supported by the endpoint/channel chid. If the channel
+ does not exists, create a FHEM node for it. Example:
+ mcCapability_02:SWITCH_BINARY
+ Note: This is the best way to create the secondary nodes of a
+ MULTI_CHANNEL device. The device is only created for channel 2 or greater.
@@ -709,16 +777,17 @@ ZWave_Undef($$)
wakeupReport:interval:X target:Y
Class ASSOCIATION
- assocGroup_X:Max:Y Nodes:A,B,...
+ assocGroup_X:Max Y Nodes A,B,...
Class VERSION
- Lib:A Prot:x.y App:a.b
+ version:Lib A Prot x.y App a.b
+
+
Class MULTI_CHANNEL
+ endpoints:total X $dynamic $identical
+ mcCapability_X:class1 class2 ...
-
-
-
=end html
=cut
diff --git a/FHEM/11_FHT.pm b/FHEM/11_FHT.pm
index 793e95d7c..07e52abca 100755
--- a/FHEM/11_FHT.pm
+++ b/FHEM/11_FHT.pm
@@ -174,6 +174,7 @@ FHT_Initialize($)
$m2c{$c2m{$k}} = $k;
}
+# { Dispatch($defs{CUL}, "810b04028309830151024130001d", undef) }
# 810c0426 0909a001 1111 1600
# 810c04b3 0909a001 1111 44006900
# 810b0402 83098301 1111 41301d
@@ -305,7 +306,7 @@ FHT_Set($@)
$ncmd++;
$allcmd .=" " if($allcmd);
$allcmd .= $cmd;
- $allcmd .= " $val" if($val);
+ $allcmd .= " $val" if(defined($val));
}
}
@@ -399,6 +400,13 @@ FHT_Parse($$)
my $confirm = 0;
if(!defined($modules{FHT}{defptr}{$dev})) {
+ # it might be our own FHT8v, then be silent
+ foreach my $d (%defs) {
+ my $dp = $defs{$d};
+ next if(!$dp->{TYPE} || $dp->{TYPE} ne "FHT8V");
+ return "" if($dp->{addr} eq $dev);
+ }
+
Log 3, "FHT Unknown device $dev, please define it";
return "UNDEFINED FHT_$dev FHT $dev";
}
@@ -736,26 +744,26 @@ getFhtBuffer($)
set <name> <valuetype> <value>
where value is one of:
-
- desired-temp
- day-temp night-temp
- report1 report2
- refreshvalues
- mode
- holiday1 holiday2 # see mode holiday_short or holiday
- manu-temp # No clue what it does.
- year month day hour minute
- time date
- lowtemp-offset # Alarm-Temp.-Differenz
- windowopen-temp
- mon-from1 mon-to1 mon-from2 mon-to2
- tue-from1 tue-to1 tue-from2 tue-to2
- wed-from1 wed-to1 wed-from2 wed-to2
- thu-from1 thu-to1 thu-from2 thu-to2
- fri-from1 fri-to1 fri-from2 fri-to2
- sat-from1 sat-to1 sat-from2 sat-to2
- sun-from1 sun-to1 sun-from2 sun-to2
-
+
+ desired-temp
+ day-temp night-temp
+ report1 report2
+ refreshvalues
+ mode
+ holiday1 holiday2 # see mode holiday_short or holiday
+ manu-temp # No clue what it does.
+ year month day hour minute
+ time date
+ lowtemp-offset # Alarm-Temp.-Differenz
+ windowopen-temp
+ mon-from1 mon-to1 mon-from2 mon-to2
+ tue-from1 tue-to1 tue-from2 tue-to2
+ wed-from1 wed-to1 wed-from2 wed-to2
+ thu-from1 thu-to1 thu-from2 thu-to2
+ fri-from1 fri-to1 fri-from2 fri-to2
+ sat-from1 sat-to1 sat-from2 sat-to2
+ sun-from1 sun-to1 sun-from2 sun-to2
+
Examples:
diff --git a/FHEM/11_OWDevice.pm b/FHEM/11_OWDevice.pm
index 708b757dc..b88f2b17b 100644
--- a/FHEM/11_OWDevice.pm
+++ b/FHEM/11_OWDevice.pm
@@ -581,7 +581,8 @@ OWDevice_Set($@)
} else {
RemoveInternalTimer($hash);
$hash->{fhem}{interval}= $value;
- InternalTimer(int(gettimeofday())+$hash->{fhem}{interval}, "OWDevice_UpdateValues", $hash, 0)
+ InternalTimer(int(gettimeofday())+$hash->{fhem}{interval}, "OWDevice_UpdateValues", $hash, 0);
+ return undef;
}
} else {
return "Unknown argument $cmdname, choose one of interval " . join(" ", @setters);
@@ -612,7 +613,7 @@ OWDevice_Define($$)
AssignIoPort($hash) if(!defined($hash->{IODev}->{NAME}));
if(defined($hash->{IODev}->{NAME})) {
- Log 3, "$name: I/O device is " . $hash->{IODev}->{NAME};
+ Log 4, "$name: I/O device is " . $hash->{IODev}->{NAME};
} else {
Log 1, "$name: no I/O device";
}
diff --git a/FHEM/14_CUL_MAX.pm b/FHEM/14_CUL_MAX.pm
index 242437311..1f8ffaee6 100644
--- a/FHEM/14_CUL_MAX.pm
+++ b/FHEM/14_CUL_MAX.pm
@@ -5,29 +5,20 @@ package main;
use strict;
use warnings;
-require "10_MAX.pm";
+use MaxCommon;
+use POSIX;
-our %msgId2Cmd;
-our %msgCmd2Id;
-our %device_types;
-
-sub CUL_MAX_Send(@);
sub CUL_MAX_BroadcastTime(@);
sub CUL_MAX_Set($@);
-sub CUL_MAX_SendAck($$$);
sub CUL_MAX_SendTimeInformation(@);
+sub CUL_MAX_GetTimeInformationPayload();
sub CUL_MAX_Send(@);
+sub CUL_MAX_SendQueueHandler($);
my $pairmodeDuration = 60; #seconds
-my $timeBroadcastInterval = 6*60*60; #= 6 hours, the same time that the cube uses
-
-my $resendRetries = 0; #how often resend before giving up?
-
my $ackTimeout = 3; #seconds
-my $defaultWeekprofile = "444855084520452045204520452045204520452045204520452044485508452045204520452045204520452045204520452045204448546c44cc55144520452045204520452045204520452045204448546c44cc55144520452045204520452045204520452045204448546c44cc55144520452045204520452045204520452045204448546c44cc55144520452045204520452045204520452045204448546c44cc5514452045204520452045204520452045204520";
-
sub
CUL_MAX_Initialize($)
{
@@ -43,12 +34,39 @@ CUL_MAX_Initialize($)
$hash->{UndefFn} = "CUL_MAX_Undef";
$hash->{ParseFn} = "CUL_MAX_Parse";
$hash->{SetFn} = "CUL_MAX_Set";
+ $hash->{AttrFn} = "CUL_MAX_Attr";
$hash->{AttrList} = "IODev do_not_notify:1,0 ignore:0,1 " .
"showtime:1,0 loglevel:0,1,2,3,4,5,6 ".
$readingFnAttributes;
}
#############################
+
+sub
+CUL_MAX_SetupCUL($)
+{
+ my $hash = $_[0];
+ AssignIoPort($hash);
+ if(!defined($hash->{IODev})) {
+ Log 1, "$hash->{NAME}: did not find suitable IODev (CUL etc. in rfmode MAX)! You may want to execute 'attr $hash->{NAME} IODev SomeCUL'";
+ return 0;
+ }
+
+ if(CUL_MAX_Check($hash) >= 152) {
+ #Doing this on older firmware disables MAX mode
+ IOWrite($hash, "", "Za". $hash->{addr});
+ #Append to initString, so this is resend if cul disappears and then reappears
+ $hash->{IODev}{initString} .= "\nZa". $hash->{addr};
+ }
+ if(CUL_MAX_Check($hash) >= 153) {
+ #Doing this on older firmware disables MAX mode
+ my $cmd = "Zw". CUL_MAX_fakeWTaddr($hash);
+ IOWrite($hash, "", $cmd);
+ $hash->{IODev}{initString} .= "\n".$cmd;
+ }
+ return 1
+}
+
sub
CUL_MAX_Define($$)
{
@@ -68,20 +86,14 @@ CUL_MAX_Define($$)
$hash->{cnt} = 0;
$hash->{pairmode} = 0;
$hash->{retryCount} = 0;
- $hash->{devices} = ();
- AssignIoPort($hash);
-
- if(CUL_MAX_Check($hash)) {
- #Doing this on older firmware disables MAX mode
- IOWrite($hash, "", "Za". $hash->{addr});
- #Append to initString, so this is resend if cul disappears and then reappears
- $hash->{IODev}{initString} .= "\nZa". $hash->{addr};
- }
+ $hash->{sendQueue} = [];
+ CUL_MAX_SetupCUL($hash);
#This interface is shared with 00_MAXLAN.pm
$hash->{Send} = \&CUL_MAX_Send;
- CUL_MAX_BroadcastTime($hash);
+ #Start broadcasting time after 30 seconds, so there is enough time to parse the config
+ InternalTimer(gettimeofday()+30, "CUL_MAX_BroadcastTime", $hash, 0);
return undef;
}
@@ -115,11 +127,33 @@ CUL_MAX_Check($@)
#Looks like "V 1.49 CUL868"
$version =~ m/V (.*)\.(.*) .*/;
my ($major_version,$minorversion) = ($1, $2);
- if($major_version == 1 and $minorversion < 52) {
- Log 2, "The current firmware of the CUL has known bugs with respect to MAX! support. Please update.";
- return 0;
+ $version = 100*$major_version + $minorversion;
+ if($version < 153) {
+ Log 2, "You are using an old version of the CUL firmware, which has known bugs with respect to MAX! support. Please update.";
}
- return 1;
+ return $version;
+}
+
+sub
+CUL_MAX_Attr(@)
+{
+ my ($hash, $action, $name, $attr, $value) = @_;
+ if ($action eq "set") {
+ return "No such attribute" if($attr !~ ["fakeWTaddr", "fakeSCaddr", "IODev"]);
+ return "Invalid value" if($attr ~~ ["fakeWTaddr", "fakeSCaddr"] && $value !~ /^[0-9a-fA-F]{6}$/);
+ }
+}
+
+sub
+CUL_MAX_fakeWTaddr($)
+{
+ return AttrVal($_[0]->{NAME}, "fakeWTaddr", "111111");
+}
+
+sub
+CUL_MAX_fakeSCaddr($)
+{
+ return AttrVal($_[0]->{NAME}, "fakeSCaddr", "222222");
}
sub
@@ -136,15 +170,48 @@ CUL_MAX_Set($@)
} elsif($setting eq "broadcastTime") {
CUL_MAX_BroadcastTime($hash, 1);
+ } elsif($setting ~~ ["fakeSC", "fakeWT"]) {
+ return "Invalid number of arguments" if(@args == 0);
+ my $dest = $args[0];
+ #$dest may be either a name or an address
+ if(exists($defs{$dest})) {
+ return "Destination is not a MAX device" if($defs{$dest}{TYPE} ne "MAX");
+ $dest = $defs{$dest}{addr};
+ } else {
+ return "No MAX device with address $dest" if(!exists($modules{MAX}{defptr}{$dest}));
+ }
+
+ if($setting eq "fakeSC") {
+ return "Invalid number of arguments" if(@args != 2);
+ return "Invalid fakeSCaddr attribute set (must not be 000000)" if(CUL_MAX_fakeSCaddr($hash) eq "000000");
+
+ my $state = $args[1] ? "12" : "10";
+ return CUL_MAX_Send($hash, "ShutterContactState",$dest,$state, flags => "06", src => CUL_MAX_fakeSCaddr($hash));
+
+ } elsif($setting eq "fakeWT") {
+ return "Invalid number of arguments" if(@args != 3);
+ return "desiredTemperature is invalid" if($args[1] < 4.5 || $args[2] > 30.5);
+ return "Invalid fakeWTaddr attribute set (must not be 000000)" if(CUL_MAX_fakeWTaddr($hash) eq "000000");
+
+ $args[2] = 0 if($args[2] < 0); #Clamp temperature to minimum of 0 degree
+
+ #Encode into binary form
+ my $arg2 = int(10*$args[2]);
+ #First bit is 9th bit of temperature, rest is desiredTemperature
+ my $arg1 = (($arg2&0x100)>>1) | (int(2*$args[1])&0x7F);
+ $arg2 &= 0xFF; #only take the lower 8 bits
+ my $groupid = ReadingsVal($hash,"groupid",0);
+
+ return CUL_MAX_Send($hash,"WallThermostatControl",$dest,
+ sprintf("%02x%02x",$arg1,$arg2), groupId => sprintf("%02x",$groupid), flags => ( $groupid ? "04" : "00" ), src => CUL_MAX_fakeWTaddr($hash));
+ }
+
} else {
return "Unknown argument $setting, choose one of pairmode broadcastTime";
}
return undef;
}
-#Array of all packet that we wait to be ack'ed
-my @waitForAck = ();
-
sub
CUL_MAX_Parse($$)
{
@@ -152,10 +219,11 @@ CUL_MAX_Parse($$)
my ($hash, $rmsg) = @_;
if(!exists($modules{CUL_MAX}{defptr})) {
- Log 5, "No CUL_MAX defined";
+ Log 2, "No CUL_MAX defined";
return "UNDEFINED CULMAX0 CUL_MAX 123456";
}
my $shash = $modules{CUL_MAX}{defptr};
+ my $ll5 = GetLogLevel($shash->{NAME}, 5);
return () if($rmsg !~ m/Z(..)(..)(..)(..)(......)(......)(..)(.*)/);
@@ -172,33 +240,33 @@ CUL_MAX_Parse($$)
$src = lc($src);
$dst = lc($dst);
my $msgType = exists($msgId2Cmd{$msgTypeRaw}) ? $msgId2Cmd{$msgTypeRaw} : $msgTypeRaw;
- Log 5, "CUL_MAX_Parse: len $len, msgcnt $msgcnt, msgflag $msgFlag, msgTypeRaw $msgType, src $src, dst $dst, groupid $groupid, payload $payload";
+ Log $ll5, "CUL_MAX_Parse: len $len, msgcnt $msgcnt, msgflag $msgFlag, msgTypeRaw $msgType, src $src, dst $dst, groupid $groupid, payload $payload";
my $isToMe = ($dst eq $shash->{addr}) ? 1 : 0; # $isToMe is true if that packet was directed at us
if(exists($msgId2Cmd{$msgTypeRaw})) {
if($msgType eq "Ack") {
- return $shash->{NAME} if($src eq $shash->{addr}); #Ignore packets generated by culfw's auto-Ack
+ #Ignore packets generated by culfw's auto-Ack
+ return $shash->{NAME} if($src eq $shash->{addr});
+ return $shash->{NAME} if($src eq CUL_MAX_fakeWTaddr($hash));
+ return $shash->{NAME} if($src eq CUL_MAX_fakeSCaddr($hash));
+
Dispatch($shash, "MAX,$isToMe,Ack,$src,$payload", {RAWMSG => $rmsg});
- return $shash->{NAME} if(!$isToMe);
+ return $shash->{NAME} if(!@{$shash->{sendQueue}}); #we are not waiting for any Ack
- my $i = 0;
- while ($i < @waitForAck) {
- my $packet = $waitForAck[$i];
- if($packet->{dest} eq $src and $packet->{cnt} == hex($msgcnt)) {
- Log 5, "Got matching ack";
- splice @waitForAck, $i, 1;
- return $shash->{NAME};
- } else {
- $i++;
- }
+ my $packet = $shash->{sendQueue}[0];
+ if($packet->{src} eq $dst and $packet->{dst} eq $src and $packet->{cnt} == hex($msgcnt)) {
+ Log $ll5, "Got matching ack";
+ my $isnak = unpack("C",pack("H*",$payload)) & 0x80;
+ $packet->{sent} = $isnak ? 3 : 2;
+ return $shash->{NAME};
}
} elsif($msgType eq "TimeInformation") {
if($isToMe) {
#This is a request for TimeInformation send to us
- Log 5, "Got request for TimeInformation, sending it";
+ Log $ll5, "Got request for TimeInformation, sending it";
CUL_MAX_SendTimeInformation($shash, $src);
} else {
my ($f1,$f2,$f3,$f4,$f5) = unpack("CCCCC",pack("H*",$payload));
@@ -213,37 +281,38 @@ CUL_MAX_Parse($$)
my $unk2 = $f4 >> 6;
my $unk3 = $f5 >> 6;
#I guess the unk1,2,3 encode if we are in DST?
- Log 5, "CUL_MAX_Parse: Got TimeInformation: (in GMT) year $year, mon $month, day $day, hour $hour, min $min, sec $sec, unk ($unk1, $unk2, $unk3)";
+ Log $ll5, "CUL_MAX_Parse: Got TimeInformation: (in GMT) year $year, mon $month, day $day, hour $hour, min $min, sec $sec, unk ($unk1, $unk2, $unk3)";
}
} elsif($msgType eq "PairPing") {
my ($unk1,$type,$unk2,$serial) = unpack("CCCa*",pack("H*",$payload));
- Log 5, "CUL_MAX_Parse: Got PairPing (dst $dst, pairmode $shash->{pairmode}), unk1 $unk1, type $type, unk2 $unk2, serial $serial";
+ Log $ll5, "CUL_MAX_Parse: Got PairPing (dst $dst, pairmode $shash->{pairmode}), unk1 $unk1, type $type, unk2 $unk2, serial $serial";
#There are two variants of PairPing:
#1. It has a destination address of "000000" and can be paired to any device.
#2. It is sent after changing batteries or repressing the pair button (without factory reset) and has a destination address of the last paired device. We can answer it with PairPong and even get an Ack, but it will still not be paired to us. A factory reset (originating from the last paired device) is needed first.
if(($dst ne "000000") and !$isToMe) {
- Log 2, "Device want's to be re-paired to $dst, not to us";
+ Log $ll5, "Device want's to be re-paired to $dst, not to us";
return $shash->{NAME};
}
if($shash->{pairmode}) {
Log 3, "CUL_MAX_Parse: Pairing device $src of type $device_types{$type} with serial $serial";
- CUL_MAX_Send($shash, "PairPong", $src, "00");
Dispatch($shash, "MAX,$isToMe,define,$src,$device_types{$type},$serial,0,0", {RAWMSG => $rmsg});
+ #Send after dispatch the define, otherwise Send will create an invalid device
+ CUL_MAX_Send($shash, "PairPong", $src, "00");
#This are the default values that a device has after factory reset or pairing
- if($device_types{$type} eq "HeatingThermostat") {
- Dispatch($shash, "MAX,$isToMe,HeatingThermostatConfig,$src,17,21,30.5,4.5,80,5,0,12,15,100,0,0,12,$defaultWeekprofile", {RAWMSG => $rmsg});
+ if($device_types{$type} =~ /HeatingThermostat.*/) {
+ Dispatch($shash, "MAX,$isToMe,HeatingThermostatConfig,$src,17,21,30.5,4.5,80,5,0,12,15,100,0,0,12,$defaultWeekProfile", {RAWMSG => $rmsg});
} elsif($device_types{$type} eq "WallMountedThermostat") {
- Dispatch($shash, "MAX,$isToMe,WallThermostatConfig,$src,17,21,30.5,4.5,$defaultWeekprofile", {RAWMSG => $rmsg});
+ Dispatch($shash, "MAX,$isToMe,WallThermostatConfig,$src,17,21,30.5,4.5,$defaultWeekProfile", {RAWMSG => $rmsg});
}
#Todo: CUL_MAX_SendTimeInformation($shash, $src); on Ack for our PairPong
}
- } elsif($msgType ~~ ["ShutterContactState", "WallThermostatState", "ThermostatState", "PushButtonState"]) {
+ } elsif($msgType ~~ ["ShutterContactState", "WallThermostatState", "WallThermostatControl", "ThermostatState", "PushButtonState"]) {
Dispatch($shash, "MAX,$isToMe,$msgType,$src,$payload", {RAWMSG => $rmsg});
} else {
- Log 5, "Unhandled message $msgType";
+ Log $ll5, "Unhandled message $msgType";
}
} else {
Log 2, "CUL_MAX_Parse: Got unhandled message type $msgTypeRaw";
@@ -251,52 +320,50 @@ CUL_MAX_Parse($$)
return $shash->{NAME};
}
-sub
-CUL_MAX_SendAck($$$)
-{
- my ($hash,$msgcnt,$dst) = @_;
- return CUL_MAX_Send($hash, "Ack", $dst, "00", "00", "00", $msgcnt);
-}
-
#All inputs are hex strings, $cmd is one from %msgCmd2Id
sub
CUL_MAX_Send(@)
{
# $cmd is one of
- my ($hash, $cmd, $dst, $payload, $flags, $groupId, $msgcnt, $src) = @_;
+ my ($hash, $cmd, $dst, $payload, %opts) = @_;
- CUL_MAX_Check($hash);
+ my $flags = "00";
+ my $groupId = "00";
+ my $src = $hash->{addr};
+ my $callbackParam = undef;
- $flags = "00" if(!$flags);
- $groupId = "00" if(!defined($groupId));
- $src = $hash->{addr} if(!defined($src));
+ $flags = $opts{flags} if(exists($opts{flags}));
+ $groupId = $opts{groupId} if(exists($opts{groupId}));
+ $src = $opts{src} if(exists($opts{src}));
+ $callbackParam = $opts{callbackParam} if(exists($opts{callbackParam}));
- if(!defined($msgcnt)) {
- my $dhash = CUL_MAX_DeviceHash($dst);
- #replace message counter if not already set
- $dhash->{READINGS}{msgcnt}{VAL} += 1;
- $dhash->{READINGS}{msgcnt}{VAL} &= 0xFF;
- $msgcnt = sprintf("%02x",$dhash->{READINGS}{msgcnt}{VAL});
- }
+ my $dhash = CUL_MAX_DeviceHash($dst);
+ $dhash->{READINGS}{msgcnt}{VAL} += 1;
+ $dhash->{READINGS}{msgcnt}{VAL} &= 0xFF;
+ $dhash->{READINGS}{msgcnt}{TIME} = TimeNow();
+ my $msgcnt = sprintf("%02x",$dhash->{READINGS}{msgcnt}{VAL});
my $packet = $msgcnt . $flags . $msgCmd2Id{$cmd} . $src . $dst . $groupId . $payload;
#prefix length in bytes
$packet = sprintf("%02x",length($packet)/2) . $packet;
- #Send to CUL
- IOWrite($hash, "", "Zs". $packet);
-
- #Schedule checking for Ack
- return undef if($cmd eq "Ack"); #we don't get an Ack for an Ack
- return undef if($src ne $hash->{addr}); #we don't handle Ack's for fake messages
+ Log GetLogLevel($hash->{NAME}, 5), "CUL_MAX_Send: enqueuing $packet";
my $timeout = gettimeofday()+$ackTimeout;
- $waitForAck[@waitForAck] = { "packet" => $packet,
- "dest" => $dst,
- "cnt" => hex($msgcnt),
- "time" => $timeout,
- "resends" => "0" };
- InternalTimer($timeout, "CUL_MAX_Resend", $hash, 0);
+ my $aref = $hash->{sendQueue};
+ push(@{$aref}, { "packet" => $packet,
+ "src" => $src,
+ "dst" => $dst,
+ "cnt" => hex($msgcnt),
+ "time" => $timeout,
+ "sent" => "0",
+ "cmd" => $cmd,
+ "callbackParam" => $callbackParam,
+ });
+
+ #Call CUL_MAX_SendQueueHandler if we just enqueued the only packet
+ #otherwise it is already in the InternalTimer list
+ CUL_MAX_SendQueueHandler($hash) if(@{$hash->{sendQueue}} == 1);
return undef;
}
@@ -307,35 +374,88 @@ CUL_MAX_DeviceHash($)
return $modules{MAX}{defptr}{$addr};
}
+#This can be called for two reasons:
+#1. @sendQueue was empty, CUL_MAX_Send added a packet and then called us
+#2. We sent a packet from @sendQueue and know the ackTimeout is over.
+# The packet my still be in @sendQueue (timed out) or removed when the Ack was received.
sub
-CUL_MAX_Resend($)
+CUL_MAX_SendQueueHandler($)
{
my $hash = shift;
+ Log GetLogLevel($hash->{NAME}, 5), "CUL_MAX_SendQueueHandler: " . @{$hash->{sendQueue}} . " items in queue";
+ return if(!@{$hash->{sendQueue}}); #nothing to do
- my $resendTime = gettimeofday()+60; #some large time
- my $i = 0;
- while ($i < @waitForAck ) {
- my $packet = $waitForAck[$i];
- if( $packet->{time} <= gettimeofday() ) {
- Log 2, "CUL_MAX_Resend: Missing ack from $packet->{dest} for $packet->{packet}";
- if($packet->{resends}++ < $resendRetries) {
- #First resend is one second after original send, second resend it two seconds after first resend, etc
- $packet->{time} = gettimeofday()+$ackTimeout;
- IOWrite($hash, "", "Zs". $packet->{packet});
- readingsSingleUpdate($hash, "retryCount", ReadingsVal($hash->{NAME}, "retryCount", 0) + 1, 1);
- } else {
- Log 1, "CUL_MAX_Resend: Giving up on that packet";
- splice @waitForAck, $i, 1; #Remove from array
- readingsSingleUpdate($hash, "packetsLost", ReadingsVal($hash->{NAME}, "packetsLost", 0) + 1, 1);
- next
- }
- }
- $resendTime = $packet->{time} if($packet->{time} < $resendTime);
- $i++;
+ my $timeout = gettimeofday(); #reschedule immediatly
+
+ #Check if we have an IODev
+ if(!defined($hash->{IODev})) {
+ Log 1, "$hash->{NAME}: did not find suitable IODev (CUL etc. in rfmode MAX), cannot send! You may want to execute 'attr $hash->{NAME} IODev SomeCUL'";
+ #Maybe some CUL will appear magically in some seconds
+ #At least we cannot quit here with an non-empty queue, so we have two alternatives:
+ #1. Delete the packet from queue and quit -> packet is lost
+ #2. Wait, recheck, wait, recheck ... -> a lot of logs
+
+ #InternalTimer($timeout+60, "CUL_MAX_SendQueueHandler", $hash, 0);
+ $hash->{sendQueue} = [];
+ return undef;
}
- return if(!@waitForAck); #no need to recheck
- InternalTimer($resendTime, "CUL_MAX_Resend", $hash, 0);
+ my $packet = $hash->{sendQueue}[0];
+
+ if( $packet->{sent} == 0 ) { #Need to send it first
+ #We can use fast sending without preamble on culfw 1.53 and higher when the devices has been woken up
+ my $needPreamble = ((CUL_MAX_Check($hash) < 153)
+ || !defined($modules{MAX}{defptr}{$packet->{dst}}{wakeUpUntil})
+ || $modules{MAX}{defptr}{$packet->{dst}}{wakeUpUntil} < gettimeofday()) ? 1 : 0;
+
+ #Send to CUL
+ my ($credit10ms) = (CommandGet("","$hash->{IODev}{NAME} credit10ms") =~ /[^ ]* [^ ]* => (.*)/);
+ # We need 1000ms for preamble + len in bits (=hex len * 4) ms for payload. Divide by 10 to get credit10ms units
+ # keep this in sync with culfw's code in clib/rf_moritz.c!
+ my $necessaryCredit = ceil(100*$needPreamble + (length($packet->{packet})*4)/10);
+ Log 5, "needPreamble: $needPreamble, necessaryCredit: $necessaryCredit, credit10ms: $credit10ms";
+ if( defined($credit10ms) && $credit10ms < $necessaryCredit ) {
+ my $waitTime = $necessaryCredit-$credit10ms; #we get one credit10ms every second
+ $timeout += $waitTime;
+ Log 2, "CUL_MAX_SendQueueHandler: Not enough credit! credit10ms is $credit10ms, but we need $necessaryCredit. Waiting $waitTime seconds.";
+ } else {
+ #Update TimeInformation payload. It should reflect the current time when sending,
+ #not the time when it was enqueued. A low credit10ms can defer such a packet for multiple
+ #minutes
+ if( $msgId2Cmd{substr($packet->{packet},6,2)} eq "TimeInformation" ) {
+ Log GetLogLevel($hash->{NAME}, 5), "Updating TimeInformation payload";
+ substr($packet->{packet},22) = CUL_MAX_GetTimeInformationPayload();
+ }
+ IOWrite($hash, "", ($needPreamble ? "Zs" : "Zf") . $packet->{packet});
+
+ $packet->{sent} = 1;
+ $packet->{sentTime} = gettimeofday();
+ $timeout += 0.5; #recheck for Ack
+ }
+
+ } elsif( $packet->{sent} == 1 ) { #Already sent it, got no Ack
+ if( $packet->{sentTime} + $ackTimeout < gettimeofday() ) {
+ # ackTimeout exceeded
+ Log 2, "CUL_MAX_SendQueueHandler: Missing ack from $packet->{dst} for $packet->{packet}";
+ splice @{$hash->{sendQueue}}, 0, 1; #Remove from array
+ readingsSingleUpdate($hash, "packetsLost", ReadingsVal($hash->{NAME}, "packetsLost", 0) + 1, 1);
+ } else {
+ # Recheck for Ack
+ $timeout += 0.5;
+ }
+
+ } elsif( $packet->{sent} == 2 ) { #Got ack
+ if(defined($packet->{callbackParam})) {
+ Dispatch($hash, "MAX,1,Ack$packet->{cmd},$packet->{dst},$packet->{callbackParam}", {RAWMSG => ""});
+ }
+ splice @{$hash->{sendQueue}}, 0, 1; #Remove from array
+
+ } elsif( $packet->{sent} == 3 ) { #Got nack
+ splice @{$hash->{sendQueue}}, 0, 1; #Remove from array
+ }
+
+ return if(!@{$hash->{sendQueue}}); #everything done
+ InternalTimer($timeout, "CUL_MAX_SendQueueHandler", $hash, 0);
}
sub
@@ -353,16 +473,8 @@ CUL_MAX_SendTimeInformation(@)
{
my ($hash,$addr,$payload) = @_;
$payload = CUL_MAX_GetTimeInformationPayload() if(!defined($payload));
- Log 5, "broadcast time to $addr";
- CUL_MAX_Send($hash, "TimeInformation", $addr, $payload, "03");
-}
-
-sub
-CUL_MAX_SendTimeInformationSender($)
-{
- my $args = shift;
- my ($hash,$addr,$payload) = @{$args};
- CUL_MAX_SendTimeInformation($hash, $addr, $payload);
+ Log GetLogLevel($hash->{NAME}, 5), "broadcast time to $addr";
+ CUL_MAX_Send($hash, "TimeInformation", $addr, $payload, flags => "04");
}
sub
@@ -370,22 +482,44 @@ CUL_MAX_BroadcastTime(@)
{
my ($hash,$manual) = @_;
my $payload = CUL_MAX_GetTimeInformationPayload();
- Log 5, "CUL_MAX_BroadcastTime: payload $payload";
+ Log GetLogLevel($hash->{NAME}, 5), "CUL_MAX_BroadcastTime: payload $payload ";
my $i = 1;
+
+ my @used_slots = ( 0, 0, 0, 0, 0, 0 );
+
+ # First, lookup all thermstats for their current TimeInformationHour
+ foreach my $addr (keys %{$modules{MAX}{defptr}}) {
+ my $dhash = $modules{MAX}{defptr}{$addr};
+ if(exists($dhash->{IODev}) && $dhash->{IODev} == $hash
+ && $dhash->{type} =~ /.*Thermostat.*/ ) {
+
+ my $h = ReadingsVal($dhash->{NAME},"TimeInformationHour","");
+ $used_slots[$h]++ if( $h =~ /^[0-5]$/);
+ }
+ }
+
foreach my $addr (keys %{$modules{MAX}{defptr}}) {
my $dhash = $modules{MAX}{defptr}{$addr};
#Check that
#1. the MAX device dhash uses this MAX_CUL as IODev
#2. the MAX device is a Wall/HeatingThermostat
if(exists($dhash->{IODev}) && $dhash->{IODev} == $hash
- && $dhash->{type} ~~ [ "HeatingThermostat", "HeatingThermostatPlus", "WallMountedThermostat"] ) {
- #We queue it at different times and do not send directly, because
- #sending them all in a row makes us not see the Acks
- InternalTimer(gettimeofday()+3*$i++, "CUL_MAX_SendTimeInformationSender", [$hash, $addr, $payload], 0);
+ && $dhash->{type} =~ /.*Thermostat.*/ ) {
+
+ my $h = ReadingsVal($dhash->{NAME},"TimeInformationHour","");
+ if( $h !~ /^[0-5]$/ ) {
+ #Find the used_slot with the smallest number of entries
+ $h = (sort { $used_slots[$a] cmp $used_slots[$b] } 0 .. 5)[0];
+ readingsSingleUpdate($dhash, "TimeInformationHour", $h, 1);
+ $used_slots[$h]++;
+ }
+
+ CUL_MAX_SendTimeInformation($hash, $addr, $payload) if( [gmtime()]->[2] % 6 == $h );
}
}
- InternalTimer(gettimeofday()+$timeBroadcastInterval, "CUL_MAX_BroadcastTime", $hash, 0) unless(defined($manual));
+ #Check again in 1 hour if some thermostats with the right TimeInformationHour need updating
+ InternalTimer(gettimeofday() + 3600, "CUL_MAX_BroadcastTime", $hash, 0) unless(defined($manual));
}
1;
@@ -413,7 +547,16 @@ CUL_MAX_BroadcastTime(@)
- Set
+ Set
+
+ - pairmode
+ Sets the CUL_MAX into pairing mode for 60 seconds where it can be paired with other devices (Thermostats, Buttons, etc.). You also have to set the other device into pairing mode manually. (For Thermostats, this is pressing the "Boost" button for 3 seconds, for example).
+ - fakeSC <device> <open>
+ Sends a fake ShutterContactState message; <open> must be 0 or 1 for "window closed" or "window opened". Make sure you associate the target device with fakeShutterContact beforehand.
+ - fakeWT <device> <desiredTemperature> <measuredTemperature>
+ Sends a fake WallThermostatControl message (parameters both may have one digit after the decimal point, for desiredTemperature it may only by 0 or 5). Make sure you associate the target device with fakeWallThermostat beforehand.
+
+
Get
diff --git a/FHEM/15_CUL_EM.pm b/FHEM/15_CUL_EM.pm
index 38f6b00a4..15a974f49 100755
--- a/FHEM/15_CUL_EM.pm
+++ b/FHEM/15_CUL_EM.pm
@@ -286,14 +286,16 @@ CUL_EM_Parse($$)
daily and mothly fees. Your COST will appear in the log, generated once
daiy (without the basic fee) or month (with the bassic fee included). Your
definition should look like E.g.:
-
- define emwz 1 75 900 0.15 12.50
+
+ define emwz 1 75 900 0.15 12.50
+
and the Log looks like:
-
- CUM_DAY: 6.849 CUM: 60123.4 COST: 1.02
- CUM_MONTH: 212.319 CUM: 60123.4 COST: 44.34
+
+ CUM_DAY: 6.849 CUM: 60123.4 COST: 1.02
+ CUM_MONTH: 212.319 CUM: 60123.4 COST: 44.34
+
- Tipp: You can configure your EMWZ device to show in the CUM column of the
+ Tip: You can configure your EMWZ device to show in the CUM column of the
STATE reading the current reading of your meter. For this purpose: multiply
the current reading (from the real device) with the corr1 value (RperKW),
and substract the RAW CUM value from it. Now set the basis reading of your
diff --git a/FHEM/20_FRM_AD.pm b/FHEM/20_FRM_AD.pm
index 4bdc51f6a..e2d93e88a 100755
--- a/FHEM/20_FRM_AD.pm
+++ b/FHEM/20_FRM_AD.pm
@@ -25,24 +25,23 @@ FRM_AD_Initialize($)
$hash->{InitFn} = "FRM_AD_Init";
$hash->{UndefFn} = "FRM_AD_Undef";
- $hash->{AttrList} = "IODev upper-threshold lower-threshold loglevel:0,1,2,3,4,5 $main::readingFnAttributes";
+ $hash->{AttrList} = "IODev upper-threshold lower-threshold loglevel:0,1,2,3,4,5,6 $main::readingFnAttributes";
}
sub
FRM_AD_Init($$)
{
my ($hash,$args) = @_;
- if (FRM_Init_Pin_Client($hash,$args,PIN_ANALOG)) {
- my $firmata = $hash->{IODev}->{FirmataDevice};
- $firmata->observe_analog($hash->{PIN},\&FRM_AD_observer,$hash);
- $main::defs{$hash->{NAME}}{resolution}=$firmata->{metadata}{analog_resolutions}{$hash->{PIN}} if (defined $firmata->{metadata}{analog_resolutions});
- if (! (defined AttrVal($hash->{NAME},"stateFormat",undef))) {
- $main::attr{$hash->{NAME}}{"stateFormat"} = "reading";
- }
- main::readingsSingleUpdate($hash,"state","Initialized",1);
- return undef;
+ my $ret = FRM_Init_Pin_Client($hash,$args,PIN_ANALOG);
+ return $ret if (defined $ret);
+ my $firmata = $hash->{IODev}->{FirmataDevice};
+ $firmata->observe_analog($hash->{PIN},\&FRM_AD_observer,$hash);
+ $main::defs{$hash->{NAME}}{resolution}=$firmata->{metadata}{analog_resolutions}{$hash->{PIN}} if (defined $firmata->{metadata}{analog_resolutions});
+ if (! (defined AttrVal($hash->{NAME},"stateFormat",undef))) {
+ $main::attr{$hash->{NAME}}{"stateFormat"} = "reading";
}
- return 1;
+ main::readingsSingleUpdate($hash,"state","Initialized",1);
+ return undef;
}
sub
diff --git a/FHEM/20_FRM_I2C.pm b/FHEM/20_FRM_I2C.pm
index c15581543..fcc9c26c7 100755
--- a/FHEM/20_FRM_I2C.pm
+++ b/FHEM/20_FRM_I2C.pm
@@ -26,20 +26,20 @@ FRM_I2C_Init($)
my ($hash,$args) = @_;
my $u = "wrong syntax: define FRM_I2C address register numbytes";
- return $u if(int(@$args) < 5);
+ return $u if(int(@$args) < 3);
- $hash->{"i2c-address"} = @$args[2];
- $hash->{"i2c-register"} = @$args[3];
- $hash->{"i2c-bytestoread"} = @$args[4];
+ $hash->{"i2c-address"} = @$args[0];
+ $hash->{"i2c-register"} = @$args[1];
+ $hash->{"i2c-bytestoread"} = @$args[2];
+
+ return "no IODev set" unless defined $hash->{IODev};
+ return "no FirmataDevice assigned to ".$hash->{IODev}->{NAME} unless defined $hash->{IODev}->{FirmataDevice};
- if (defined $hash->{IODev}) {
- my $firmata = $hash->{IODev}->{FirmataDevice};
- if (defined $firmata) {
- $firmata->i2c_read(@$args[2],@$args[3],@$args[4]);
- }
- return undef;
- }
- return 1;
+ eval {
+ $hash->{IODev}->{FirmataDevice}->i2c_read(@$args[0],@$args[1],@$args[2]);
+ };
+ return "error calling i2c_read: ".$@ if ($@);
+ return undef;
}
sub FRM_I2C_Attr(@) {
diff --git a/FHEM/20_FRM_IN.pm b/FHEM/20_FRM_IN.pm
index a765f7404..c651ffcc5 100755
--- a/FHEM/20_FRM_IN.pm
+++ b/FHEM/20_FRM_IN.pm
@@ -37,17 +37,15 @@ sub
FRM_IN_Init($$)
{
my ($hash,$args) = @_;
- if (FRM_Init_Pin_Client($hash,$args,PIN_INPUT)) {
- my $firmata = $hash->{IODev}->{FirmataDevice};
- $firmata->observe_digital($hash->{PIN},\&FRM_IN_observer,$hash);
- if (! (defined AttrVal($hash->{NAME},"stateFormat",undef))) {
- $main::attr{$hash->{NAME}}{"stateFormat"} = "reading";
- }
- main::readingsSingleUpdate($hash,"state","Initialized",1);
- return undef;
+ my $ret = FRM_Init_Pin_Client($hash,$args,PIN_INPUT);
+ return $ret if (defined $ret);
+ my $firmata = $hash->{IODev}->{FirmataDevice};
+ $firmata->observe_digital($hash->{PIN},\&FRM_IN_observer,$hash);
+ if (! (defined AttrVal($hash->{NAME},"stateFormat",undef))) {
+ $main::attr{$hash->{NAME}}{"stateFormat"} = "reading";
}
- return 1;
-
+ main::readingsSingleUpdate($hash,"state","Initialized",1);
+ return undef;
}
sub
diff --git a/FHEM/20_FRM_LCD.pm b/FHEM/20_FRM_LCD.pm
new file mode 100755
index 000000000..3bafc42b2
--- /dev/null
+++ b/FHEM/20_FRM_LCD.pm
@@ -0,0 +1,274 @@
+#############################################
+package main;
+
+use strict;
+use warnings;
+use Device::Firmata;
+use Device::Firmata::Constants qw/ :all /;
+
+#####################################
+
+my %sets = (
+ "text" => "",
+ "home" => "",
+ "clear" => "",
+ "display" => "on,off",
+ "cursor" => "",
+ "scroll" => "left,right",
+);
+
+my %gets = (
+);
+
+sub
+FRM_LCD_Initialize($)
+{
+ my ($hash) = @_;
+
+ $hash->{DefFn} = "FRM_Client_Define";
+ $hash->{InitFn} = "FRM_LCD_Init";
+ $hash->{SetFn} = "FRM_LCD_Set";
+ $hash->{UndefFn} = "FRM_LCD_Undef";
+ $hash->{AttrFn} = "FRM_LCD_Attr";
+ $hash->{StateFn} = "FRM_LCD_State";
+
+ $hash->{AttrList} = "restoreOnReconnect:on,off restoreOnStartup:on,off IODev model backLight:on,off blink:on,off autoClear:on,off autoBreak:on,off loglevel:0,1,2,3,4,5 $main::readingFnAttributes";
+ # autoScroll:on,off direction:leftToRight,rightToLeft do not work reliably
+}
+
+sub
+FRM_LCD_Init($)
+{
+ my ($hash,$args) = @_;
+ my $u = "wrong syntax: define FRM_LCD []";
+
+ return $u if(int(@$args) < 3);
+
+ $hash->{type} = shift @$args;
+ $hash->{sizex} = shift @$args;
+ $hash->{sizey} = shift @$args;
+ $hash->{address} = shift @$args if (@$args);
+
+ return "no IODev set" unless defined $hash->{IODev};
+ return "no FirmataDevice assigned to ".$hash->{IODev}->{NAME} unless defined $hash->{IODev}->{FirmataDevice};
+
+ my $name = $hash->{NAME};
+ if (($hash->{type} eq "i2c") and defined $hash->{address}) {
+ require LiquidCrystal_I2C;
+ my $lcd = LiquidCrystal_I2C->new($hash->{address},$hash->{sizex},$hash->{sizey});
+ $lcd->attach($hash->{IODev}->{FirmataDevice});
+ $lcd->init();
+ $hash->{lcd} = $lcd;
+ FRM_LCD_Apply_Attribute($name,"backLight");
+# FRM_LCD_Apply_Attribute($name,"autoscroll");
+# FRM_LCD_Apply_Attribute($name,"direction");
+ FRM_LCD_Apply_Attribute($name,"blink");
+ }
+ if (! (defined AttrVal($name,"stateFormat",undef))) {
+ $main::attr{$name}{"stateFormat"} = "text";
+ }
+ my $value = ReadingsVal($name,"text",undef);
+ if (defined $value and AttrVal($hash->{NAME},"restoreOnReconnect","on") eq "on") {
+ FRM_LCD_Set($hash,$name,"text",$value);
+ }
+
+
+ return undef;
+}
+
+sub FRM_LCD_Attr(@) {
+ my ($command,$name,$attribute,$value) = @_;
+ my $hash = $main::defs{$name};
+ if ($command eq "set") {
+ $main::attr{$name}{$attribute}=$value;
+ FRM_LCD_Apply_Attribute($name,$attribute);
+ }
+}
+
+sub FRM_LCD_Apply_Attribute {
+ my ($name,$attribute) = @_;
+ my $lcd = $main::defs{$name}{lcd};
+ if (defined $lcd) {
+ ATTRIBUTE_HANDLER: {
+ $attribute eq "backLight" and do {
+ if (AttrVal($name,"backLight","on") eq "on") {
+ $lcd->backlight();
+ } else {
+ $lcd->noBacklight();
+ }
+ last;
+ };
+ $attribute eq "autoScroll" and do {
+ if (AttrVal($name,"autoScroll","on") eq "on") {
+ $lcd->autoscroll();
+ } else {
+ $lcd->noAutoscroll();
+ }
+ last;
+ };
+ $attribute eq "direction" and do {
+ if (AttrVal($name,"direction","leftToRight") eq "leftToRight") {
+ $lcd->leftToRight();
+ } else {
+ $lcd->rightToLeft();
+ }
+ last;
+ };
+ $attribute eq "blink" and do {
+ if (AttrVal($name,"blink","off") eq "on") {
+ $lcd->blink();
+ } else {
+ $lcd->noBlink();
+ }
+ last;
+ };
+ }
+ }
+}
+
+sub FRM_LCD_Set(@) {
+ my ($hash, @a) = @_;
+ return "Need at least one parameters" if(@a < 2);
+ my $command = $a[1];
+ my $value = $a[2];
+ return "Unknown argument $a[1], choose one of " . join(" ", sort keys %sets)
+ if(!defined($sets{$command}));
+ my $lcd = $hash->{lcd};
+ return unless defined $lcd;
+ COMMAND_HANDLER: {
+ $command eq "text" and do {
+ if (AttrVal($hash->{NAME},"autoClear","on") eq "on") {
+ $lcd->clear();
+ }
+ if (AttrVal($hash->{NAME},"autoBreak","on") eq "on") {
+ my $sizex = $hash->{sizex};
+ my $sizey = $hash->{sizey};
+ my $start = 0;
+ my $len = length $value;
+ for (my $line = 0;$line<$sizey;$line++) {
+ $lcd->setCursor(0,$line);
+ if ($start<$len) {
+ $lcd->print(substr $value, $start, $sizex);
+ } else {
+ last;
+ }
+ $start+=$sizex;
+ }
+ } else {
+ $lcd->print($value);
+ }
+ main::readingsSingleUpdate($hash,"text",$value,1);
+ last;
+ };
+ $command eq "home" and do {
+ $lcd->home();
+ last;
+ };
+ $command eq "clear" and do {
+ $lcd->clear();
+ main::readingsSingleUpdate($hash,"text","",1);
+ last;
+ };
+ $command eq "display" and do {
+ if ($value ne "off") {
+ $lcd->display();
+ } else {
+ $lcd->noDisplay();
+ }
+ last;
+ };
+ $command eq "cursor" and do {
+ my ($x,$y) = split ",",$value;
+ $lcd->setCursor($x,$y);
+ last;
+ };
+ $command eq "scroll" and do {
+ if ($value eq "left") {
+ $lcd->scrollDisplayLeft();
+ } else {
+ $lcd->scrollDisplayRight();
+ }
+ last;
+ };
+ }
+}
+
+sub FRM_LCD_State($$$$)
+{
+ my ($hash, $tim, $sname, $sval) = @_;
+
+STATEHANDLER: {
+ $sname eq "text" and do {
+ if (AttrVal($hash->{NAME},"restoreOnStartup","on") eq "on") {
+ FRM_LCD_Set($hash,$hash->{NAME},$sname,$sval);
+ }
+ last;
+ }
+ }
+}
+
+sub
+FRM_LCD_Undef($$)
+{
+ my ($hash, $name) = @_;
+}
+
+1;
+
+=pod
+=begin html
+
+
+FRM_LCD
+
+ drives LiquidCrystal Displays (LCD) that are connected to Firmata (via I2C).
+ Supported are Displays that use a PCF8574T as I2C Bridge (as found on eBay when searching for
+ 'LCD' and 'I2C'). Tested is the 1602 type (16 characters, 2 Lines), the 2004 type (and other cheap chinise-made
+ I2C-LCDs for Arduino) ship with the same library, so they should work as well.
+ See http://arduino.cc/en/Tutorial/LiquidCrystal for details about
+ how to hook up the LCD to the arduino.
+
+ Requires a defined FRM-device to work.
+ this FRM-device has to be configures for i2c by setting attr 'i2c-config' on the FRM-device
+
+
+ Define
+
+ define <name> FRM_LCD i2c <size-x> <size-y> <i2c-address>
+ Specifies the FRM_LCD device.
+ - size-x is the number of characters per line
+ - size-y is the numbers of rows.
+ - i2c-address is the (device-specific) address of the ic on the i2c-bus
+
+
+
+
+ Set
+
+ set <name> text <text to be displayed>
+
+
+ Get
+
+
+ Attributes
+
+ - backLight <on|off>
+ - autoClear <on|off>
+ - autoBreak <on|off>
+ - restoreOnStartup <on|off>
+ - restoreOnReconnect <on|off>
+ - IODev
+ Specify which FRM to use. (Optional, only required if there is more
+ than one FRM-device defined.)
+
+ - eventMap
+ - readingFnAttributes
+
+
+
+
+=end html
+=cut
diff --git a/FHEM/20_FRM_OUT.pm b/FHEM/20_FRM_OUT.pm
index 146ef4e19..9d9bc968b 100755
--- a/FHEM/20_FRM_OUT.pm
+++ b/FHEM/20_FRM_OUT.pm
@@ -17,28 +17,33 @@ FRM_OUT_Initialize($)
$hash->{DefFn} = "FRM_Client_Define";
$hash->{InitFn} = "FRM_OUT_Init";
$hash->{UndefFn} = "FRM_OUT_Undef";
+ $hash->{StateFn} = "FRM_OUT_State";
- $hash->{AttrList} = "IODev loglevel:0,1,2,3,4,5 $main::readingFnAttributes";
+ $hash->{AttrList} = "restoreOnReconnect:on,off restoreOnStartup:on,off IODev loglevel:0,1,2,3,4,5 $main::readingFnAttributes";
}
sub
FRM_OUT_Init($$)
{
my ($hash,$args) = @_;
- if (FRM_Init_Pin_Client($hash,$args,PIN_OUTPUT)) {
- main::readingsSingleUpdate($hash,"state","Initialized",1);
- return undef;
+ my $ret = FRM_Init_Pin_Client($hash,$args,PIN_OUTPUT);
+ return $ret if (defined $ret);
+ my $name = $hash->{NAME};
+ if (! (defined AttrVal($name,"stateFormat",undef))) {
+ $main::attr{$name}{"stateFormat"} = "value";
}
- return 1;
+ my $value = ReadingsVal($name,"value",undef);
+ if (defined $value and AttrVal($hash->{NAME},"restoreOnReconnect","on") eq "on") {
+ FRM_OUT_Set($hash,$name,$value);
+ }
+ main::readingsSingleUpdate($hash,"state","Initialized",1);
+ return undef;
}
sub
-FRM_OUT_Set($@)
+FRM_OUT_Set($$$)
{
- my ($hash, @a) = @_;
- my $name = $hash->{NAME};
- shift @a;
- my $cmd = $a[0];
+ my ($hash, $name, $cmd, @a) = @_;
my $value;
if ($cmd eq "on") {
$value=PIN_HIGH;
@@ -46,12 +51,12 @@ FRM_OUT_Set($@)
$value=PIN_LOW;
} else {
my $list = "on off";
- return SetExtensions($hash, $list, $name, @a);
+ return SetExtensions($hash, $list, $name, $cmd, @a);
}
my $iodev = $hash->{IODev};
+ main::readingsSingleUpdate($hash,"value",$cmd, 1);
if (defined $iodev and defined $iodev->{FirmataDevice} and defined $iodev->{FD}) {
$iodev->{FirmataDevice}->digital_write($hash->{PIN},$value);
- main::readingsSingleUpdate($hash,"state",$cmd, 1);
} else {
return $name." no IODev assigned" if (!defined $iodev);
return $name.", ".$iodev->{NAME}." is not connected";
@@ -59,6 +64,20 @@ FRM_OUT_Set($@)
return undef;
}
+sub FRM_OUT_State($$$$)
+{
+ my ($hash, $tim, $sname, $sval) = @_;
+
+STATEHANDLER: {
+ $sname eq "value" and do {
+ if (AttrVal($hash->{NAME},"restoreOnStartup","on") eq "on") {
+ FRM_OUT_Set($hash,$hash->{NAME},$sval);
+ }
+ last;
+ }
+ }
+}
+
sub
FRM_OUT_Undef($$)
{
@@ -98,6 +117,8 @@ FRM_OUT_Undef($$)
Attributes
+ - restoreOnStartup <on|off>
+ - restoreOnReconnect <on|off>
- IODev
Specify which FRM to use. (Optional, only required if there is more
than one FRM-device defined.)
diff --git a/FHEM/20_FRM_PWM.pm b/FHEM/20_FRM_PWM.pm
index 7d6df485a..16406eec0 100755
--- a/FHEM/20_FRM_PWM.pm
+++ b/FHEM/20_FRM_PWM.pm
@@ -21,21 +21,29 @@ FRM_PWM_Initialize($)
$hash->{DefFn} = "FRM_Client_Define";
$hash->{InitFn} = "FRM_PWM_Init";
$hash->{UndefFn} = "FRM_PWM_Undef";
+ $hash->{StateFn} = "FRM_PWM_State";
- $hash->{AttrList} = "IODev loglevel:0,1,2,3,4,5 $main::readingFnAttributes";
+ $hash->{AttrList} = "restoreOnReconnect:on,off restoreOnStartup:on,off IODev loglevel:0,1,2,3,4,5 $main::readingFnAttributes";
}
sub
FRM_PWM_Init($$)
{
my ($hash,$args) = @_;
- if (FRM_Init_Pin_Client($hash,$args,PIN_PWM)) {
- my $firmata = $hash->{IODev}->{FirmataDevice};
- $main::defs{$hash->{NAME}}{resolution}=$firmata->{metadata}{pwm_resolutions}{$hash->{PIN}} if (defined $firmata->{metadata}{pwm_resolutions});
- main::readingsSingleUpdate($hash,"state","Initialized",1);
- return undef;
+ my $ret = FRM_Init_Pin_Client($hash,$args,PIN_PWM);
+ return $ret if (defined $ret);
+ my $firmata = $hash->{IODev}->{FirmataDevice};
+ my $name = $hash->{NAME};
+ $main::defs{$name}{resolution}=$firmata->{metadata}{pwm_resolutions}{$hash->{PIN}} if (defined $firmata->{metadata}{pwm_resolutions});
+ if (! (defined AttrVal($name,"stateFormat",undef))) {
+ $main::attr{$name}{"stateFormat"} = "value";
}
- return 1;
+ my $value = ReadingsVal($name,"value",undef);
+ if (defined $value and AttrVal($hash->{NAME},"restoreOnReconnect","on") eq "on") {
+ FRM_OUT_Set($hash,$value);
+ }
+ main::readingsSingleUpdate($hash,"state","Initialized",1);
+ return undef;
}
sub
@@ -48,9 +56,9 @@ FRM_PWM_Set($@)
my $command = $a[1];
my $value = $a[2];
my $iodev = $hash->{IODev};
+ main::readingsSingleUpdate($hash,"value",$value, 1);
if (defined $iodev and defined $iodev->{FirmataDevice} and defined $iodev->{FD}) {
$iodev->{FirmataDevice}->analog_write($hash->{PIN},$value);
- main::readingsSingleUpdate($hash,"state",$value, 1);
} else {
return $hash->{NAME}." no IODev assigned" if (!defined $iodev);
return $hash->{NAME}.", ".$iodev->{NAME}." is not connected";
@@ -58,6 +66,20 @@ FRM_PWM_Set($@)
return undef;
}
+sub FRM_PWM_State($$$$)
+{
+ my ($hash, $tim, $sname, $sval) = @_;
+
+STATEHANDLER: {
+ $sname eq "value" and do {
+ if (AttrVal($hash->{NAME},"restoreOnStartup","on") eq "on") {
+ FRM_PWM_Set($hash,$hash->{NAME},$sval);
+ }
+ last;
+ }
+ }
+}
+
sub
FRM_PWM_Undef($$)
{
@@ -100,6 +122,8 @@ FRM_PWM_Undef($$)
Attributes
+ - restoreOnStartup <on|off>
+ - restoreOnReconnect <on|off>
- IODev
Specify which FRM to use. (Optional, only required if there is more
than one FRM-device defined.)
diff --git a/FHEM/20_FRM_SERVO.pm b/FHEM/20_FRM_SERVO.pm
index 18a8663f1..309ad8f55 100755
--- a/FHEM/20_FRM_SERVO.pm
+++ b/FHEM/20_FRM_SERVO.pm
@@ -30,14 +30,13 @@ sub
FRM_SERVO_Init($$)
{
my ($hash,$args) = @_;
- if (FRM_Init_Pin_Client($hash,$args,PIN_SERVO)) {
- my $firmata = $hash->{IODev}->{FirmataDevice};
- $main::defs{$hash->{NAME}}{resolution}=$firmata->{metadata}{servo_resolutions}{$hash->{PIN}} if (defined $firmata->{metadata}{servo_resolutions});
- FRM_SERVO_apply_attribute($hash,"max-pulse"); #sets min-pulse as well
- main::readingsSingleUpdate($hash,"state","Initialized",1);
- return undef;
- }
- return 1;
+ my $ret = FRM_Init_Pin_Client($hash,$args,PIN_SERVO);
+ return $ret if (defined $ret);
+ my $firmata = $hash->{IODev}->{FirmataDevice};
+ $main::defs{$hash->{NAME}}{resolution}=$firmata->{metadata}{servo_resolutions}{$hash->{PIN}} if (defined $firmata->{metadata}{servo_resolutions});
+ FRM_SERVO_apply_attribute($hash,"max-pulse"); #sets min-pulse as well
+ main::readingsSingleUpdate($hash,"state","Initialized",1);
+ return undef;
}
sub FRM_SERVO_Attr(@) {
diff --git a/FHEM/21_OWAD.pm b/FHEM/21_OWAD.pm
index c66afd465..b0d348f5f 100644
--- a/FHEM/21_OWAD.pm
+++ b/FHEM/21_OWAD.pm
@@ -151,8 +151,8 @@ sub OWAD_Initialize ($) {
$hash->{SetFn} = "OWAD_Set";
$hash->{AttrFn} = "OWAD_Attr";
my $attlist = "IODev do_not_notify:0,1 showtime:0,1 model:DS2450 loglevel:0,1,2,3,4,5 ".
- "event-on-update-reading event-on-change-reading ".
- "stateAL0 stateAL1 stateAH0 stateAH1";
+ "stateAL0 stateAL1 stateAH0 stateAH1 ".
+ $readingFnAttributes;
for( my $i=0;$i{OW_FAMILY} = $fam;
$hash->{PRESENT} = 0;
$hash->{INTERVAL} = $interval;
+ $hash->{ERRCOUNT} = 0;
#-- Couple to I/O device
AssignIoPort($hash);
@@ -253,12 +254,9 @@ sub OWAD_Define ($$) {
#-- Initialization reading according to interface type
my $interface= $hash->{IODev}->{TYPE};
-
- #-- Start timer for initialization in a few seconds
- InternalTimer(time()+10, "OWAD_InitializeDevice", $hash, 0);
#-- Start timer for updates
- InternalTimer(time()+10+$hash->{INTERVAL}, "OWAD_GetValues", $hash, 0);
+ InternalTimer(time()+10, "OWAD_GetValues", $hash, 0);
return undef;
}
@@ -275,6 +273,7 @@ sub OWAD_InitializeDevice($) {
my ($hash) = @_;
my $name = $hash->{NAME};
+ my $interface = $hash->{IODev}->{TYPE};
#-- Initial readings
@owg_val = ("","","","");
@@ -302,24 +301,50 @@ sub OWAD_InitializeDevice($) {
$hash->{READINGS}{"$owg_channel[$i]"}{TYPE} = $cnama[1];
$hash->{READINGS}{"$owg_channel[$i]"}{UNIT} = $unarr[0];
$hash->{READINGS}{"$owg_channel[$i]"}{UNITABBR} = $unarr[1];
- $hash->{tempf}{change} = 0;
-
- #-- Initial readings
- my $alarm = defined($attr{$name}{$owg_fixed[$i]."Alarm"}) ? $attr{$name}{$owg_fixed[$i]."Alarm"} : "none";
- $owg_vlow[$i] = defined($attr{$name}{$owg_fixed[$i]."Low"}) ? $attr{$name}{$owg_fixed[$i]."Low"} : "";
- $owg_vhigh[$i] = defined($attr{$name}{$owg_fixed[$i]."High"}) ? $attr{$name}{$owg_fixed[$i]."High"} : "";
- if( $alarm eq "low" || $alarm eq "both" ){
- $owg_slow[$i]=1;
- }
- if( $alarm eq "high" || $alarm eq "both" ){
- $owg_shigh[$i]=1;
- };
-
- }
-
- #-- Output formatting because of reading attributes
- OWAD_FormatValues($hash);
+ $hash->{ERRCOUNT} = 0;
+ #-- alarm enabling
+ if( AttrVal($name,$owg_fixed[$i]."Alarm",undef) ){
+ my $alarm = AttrVal($name,$owg_fixed[$i]."Alarm",undef);
+ if( $alarm eq "none" ){
+ $owg_slow[$i]=0;
+ $owg_shigh[$i]=0;
+ }elsif( $alarm eq "low" ){
+ $owg_slow[$i]=1;
+ $owg_shigh[$i]=0;
+ }elsif( $alarm eq "high" ){
+ $owg_slow[$i]=0;
+ $owg_shigh[$i]=1;
+ }elsif( $alarm eq "both" ){
+ $owg_slow[$i]=1;
+ $owg_shigh[$i]=1;
+ }
+ }
+ #-- low alarm value - no checking for correct parameters
+ if( AttrVal($name,$owg_fixed[$i]."Low",undef) ){
+ $owg_vlow[$i] = $main::attr{$name}{$owg_fixed[$i]."Low"};
+ }
+ #-- high alarm value
+ if( AttrVal($name,$owg_fixed[$i]."High",undef) ){
+ $owg_vhigh[$i] = $main::attr{$name}{$owg_fixed[$i]."High"};
+ }
+ }
+ #-- resolution in bit - fixed for now
+ @owg_resoln = (16,16,16,16);
+ #-- raw range in mV - fixed for now
+ @owg_range = (5100,5100,5100,5100);
+ #-- OWX interface
+ if( $interface eq "OWX" ){
+ OWXAD_SetPage($hash,"status");
+ OWXAD_SetPage($hash,"alarm");
+ #-- OWFS interface
+ }elsif( $interface eq "OWServer" ){
+ OWFSAD_SetPage($hash,"status");
+ OWFSAD_SetPage($hash,"alarm");
+ }
+
+ #-- Set state to initialized
+ readingsSingleUpdate($hash,"state","initialized",1);
}
########################################################################################
@@ -334,14 +359,18 @@ sub OWAD_FormatValues($) {
my ($hash) = @_;
my $name = $hash->{NAME};
- my ($offset,$factor,$vval,$vlow,$vhigh,$vfunc);
+ my $interface = $hash->{IODev}->{TYPE};
+ my ($offset,$factor,$vval,$vlow,$vhigh,$vfunc,$ret);
my $vfuncall = "";
my $svalue = "";
#-- insert initial values
for( my $k=0;$k{ALARM} = 0;
#-- alarm signatures
@@ -355,6 +384,10 @@ sub OWAD_FormatValues($) {
return if( $owg_val[$i] eq "");
}
+ #-- check if device needs to be initialized
+ OWAD_InitializeDevice($hash)
+ if( $hash->{READINGS}{"state"}{VAL} eq "defined");
+
#-- put into READINGS
readingsBeginUpdate($hash);
@@ -404,14 +437,19 @@ sub OWAD_FormatValues($) {
} else {
$vval = "???";
}
+
+ #-- low alarm value
$vlow =$owg_vlow[$i];
+ $main::attr{$name}{$owg_fixed[$i]."Low"}=$vlow;
+ #-- high alarm value
$vhigh=$owg_vhigh[$i];
-
+ $main::attr{$name}{$owg_fixed[$i]."High"}=$vhigh;
+
#-- string buildup for return value, STATE and alarm
$svalue .= sprintf( "%s: %5.3f %s", $owg_channel[$i], $vval,$unarr[1]);
#-- Test for alarm condition
- my $alarm = "none";
+ $alarm = "none";
#-- alarm signature low
if( $owg_slow[$i] == 0 ) {
} else {
@@ -445,20 +483,6 @@ sub OWAD_FormatValues($) {
#-- put into READINGS
readingsBulkUpdate($hash,"$owg_channel[$i]",$vval);
- #-- well - we may also put these into attributes instead of readings
- if( $alarm eq "low" || $alarm eq "none" ){
- delete($main::attr{$name}{"$owg_fixed[$i]High"});
- }
- if( $alarm eq "high" || $alarm eq "none" ){
- delete($main::attr{$name}{"$owg_fixed[$i]Low"});
- };
- if( $alarm eq "low" || $alarm eq "both" ){
- $main::attr{$name}{"$owg_fixed[$i]Low"}=$vlow;
- }
- if( $alarm eq "high" || $alarm eq "both" ){
- $main::attr{$name}{"$owg_fixed[$i]High"}=$vhigh;
- };
- $main::attr{$name}{"$owg_fixed[$i]Alarm"}=$alarm;
}
}
#-- insert space
@@ -466,12 +490,12 @@ sub OWAD_FormatValues($) {
$svalue .= " ";
}
}
+
#-- STATE
readingsBulkUpdate($hash,"state",$svalue);
readingsEndUpdate($hash,1);
$hash->{ALARM} = 1
if( $galarm == 1);
-
return $svalue;
}
@@ -542,7 +566,11 @@ sub OWAD_Get($@) {
#-- process results
if( defined($ret) ){
- return "OWAD: Could not get values from device $name, reason $ret";
+ $hash->{ERRCOUNT}=$hash->{ERRCOUNT}+1;
+ if( $hash->{ERRCOUNT} > 5 ){
+ $hash->{INTERVAL} = 9999;
+ }
+ return "OWAD: Could not get values from device $name for ".$hash->{ERRCOUNT}." times, reason $ret";
}
$hash->{PRESENT} = 1;
return "OWAD: $name.reading => ".OWAD_FormatValues($hash);
@@ -563,7 +591,11 @@ sub OWAD_Get($@) {
#-- process results
if( defined($ret) ){
- return "OWAD: Could not get values from device $name, reason $ret";
+ $hash->{ERRCOUNT}=$hash->{ERRCOUNT}+1;
+ if( $hash->{ERRCOUNT} > 5 ){
+ $hash->{INTERVAL} = 9999;
+ }
+ return "OWAD: Could not get values from device $name for ".$hash->{ERRCOUNT}." times, reason $ret";
}
$hash->{PRESENT} = 1;
OWAD_FormatValues($hash);
@@ -593,13 +625,17 @@ sub OWAD_Get($@) {
#-- process results
if( defined($ret) ){
- return "OWAD: Could not get values from device $name, reason $ret";
+ $hash->{ERRCOUNT}=$hash->{ERRCOUNT}+1;
+ if( $hash->{ERRCOUNT} > 5 ){
+ $hash->{INTERVAL} = 9999;
+ }
+ return "OWAD: Could not get values from device $name for ".$hash->{ERRCOUNT}." times, reason $ret";
}
$hash->{PRESENT} = 1;
OWAD_FormatValues($hash);
#-- assemble output string
- $value = "";
+ $value = "\n";
for (my $i=0;$i ".$value;
}
-
}
#######################################################################################
@@ -759,10 +794,16 @@ sub OWAD_Set($@) {
#-- set these values depending on interface type
my $interface= $hash->{IODev}->{TYPE};
- #-- check alarm values
+ #-- set status values (alarm on or off)
if( $key =~ m/(.*)(Alarm)/ ) {
return "OWAD: Set with wrong value $value for $key, allowed is none/low/high/both"
if($value ne "none" && $value ne "low" && $value ne "high" && $value ne "both");
+ #-- put into attribute value
+ if( $main::attr{$name}{$owg_fixed[$channo]."Alarm"} ne $value ){
+ #Log 1,"OWAD: Correcting attribute value ".$owg_fixed[$channo]."Alarm";
+ $main::attr{$name}{$owg_fixed[$channo]."Alarm"} = $value
+ }
+ #-- put into device
if( $value eq "low" || $value eq "both" ){
$owg_slow[$channo]=1;
} else{
@@ -787,6 +828,7 @@ sub OWAD_Set($@) {
} else {
return "OWAD: Set with wrong IODev type $interface";
}
+ #-- set alarm values (alarm voltages)
}elsif( $key =~ m/(.*)(Low|High)/ ) {
#-- find upper and lower boundaries for given offset/factor
@@ -799,10 +841,22 @@ sub OWAD_Set($@) {
#-- round to those numbers understood by the device
my $value2 = int($value*255000/$owg_range[$channo])*$owg_range[$channo]/255000;
- #-- set alarm value in the device
if( $key =~ m/(.*)Low/ ){
+ #-- put into attribute value
+ if( $main::attr{$name}{$owg_fixed[$channo]."Low"} != $value2 ){
+ Log 1,"OWAD: Correcting attribute value ".$owg_fixed[$channo]."Low";
+ $main::attr{$name}{$owg_fixed[$channo]."Low"} = $value2
+ }
+ #-- put into device
$owg_vlow[$channo] = $value2;
- } elsif( $key =~ m/(.*)High/ ){
+
+ } elsif( $key =~ m/(.*)High/ ){
+ #-- put into attribute value
+ if( $main::attr{$name}{$owg_fixed[$channo]."High"} != $value2 ){
+ Log 1,"OWAD: Correcting attribute value ".$owg_fixed[$channo]."High";
+ $main::attr{$name}{$owg_fixed[$channo]."High"} = $value2
+ }
+ #-- put into device
$owg_vhigh[$channo] = $value2;
}
@@ -943,27 +997,8 @@ sub OWFSAD_GetPage($$) {
if( (int(@ral) != 4) || (int(@ral2) != 4) );
for( $i=0;$i{tempf}{change} = 1;
- }
- $vn = int($ral2[$i]*1000)/1000;
- #-- first reading from device
- if( $owg_vhigh[$i] eq "" ){
- $owg_vhigh[$i]= $vn;
- #-- change needed
- }elsif( $owg_vhigh[$i] != $vn ){
- $hash->{tempf}{change} = 1;
- }
- }
- #-- change needed
- if( $hash->{tempf}{change} == 1){
- OWFSAD_SetPage($hash,"alarm");
- $hash->{tempf}{change} = 0;
+ $owg_vlow[$i] = int($ral[$i]*1000)/1000;
+ $owg_vhigh[$i] = int($ral2[$i]*1000)/1000;
}
#=============== get the status reading ===============================
@@ -1007,13 +1042,7 @@ sub OWFSAD_GetPage($$) {
$an = 2;
}
}
- #-- first reading from device
- if( $owg_slow[$i] eq "" ){
- $owg_slow[$i]= $an;
- #-- change needed
- }elsif( $owg_slow[$i] != $an ){
- $hash->{tempf}{change} = 1;
- }
+ $owg_slow[$i] = $an;
}
#-- get values - or should we rather use the uncached ones ?
$rel = OWServer_Read($master,"/$owx_add/alarm/high.ALL");
@@ -1042,19 +1071,7 @@ sub OWFSAD_GetPage($$) {
$an = 2;
}
}
- #-- first reading from device
- if( $owg_shigh[$i] eq "" ){
- $owg_shigh[$i]= $an;
- #-- change needed
- }elsif( $owg_shigh[$i] != $an ){
- $hash->{tempf}{change} = 1;
- }
- }
-
- #-- change needed
- if( $hash->{tempf}{change} == 1){
- OWFSAD_SetPage($hash,"status");
- $hash->{tempf}{change} = 0;
+ $owg_shigh[$i] = $an;
}
}
return undef
@@ -1062,7 +1079,7 @@ sub OWFSAD_GetPage($$) {
########################################################################################
#
-# OWXAD_SetPage - Set one page of device
+# OWFSAD_SetPage - Set one page of device
#
# Parameter hash = hash of device addressed
# page = "alarm" or "status"
@@ -1199,27 +1216,8 @@ sub OWXAD_GetPage($$) {
#=============== get the alarm reading ===============================
} elsif ( $page eq "alarm" ) {
for( $i=0;$i{tempf}{change} = 1;
- }
- $vn = int(ord($data[4+2*$i])/255 * $owg_range[$i])/1000;
- #-- first reading from device
- if( $owg_vhigh[$i] eq "" ){
- $owg_vhigh[$i]= $vn;
- #-- change needed
- }elsif( $owg_vhigh[$i] != $vn ){
- $hash->{tempf}{change} = 1;
- }
- }
- #-- change needed
- if( $hash->{tempf}{change} == 1){
- OWXAD_SetPage($hash,"alarm");
- $hash->{tempf}{change} = 0;
+ $owg_vlow[$i] = int(ord($data[3+2*$i])/255 * $owg_range[$i])/1000;
+ $owg_vhigh[$i] = int(ord($data[4+2*$i])/255 * $owg_range[$i])/1000;
}
#=============== get the status reading ===============================
} elsif ( $page eq "status" ) {
@@ -1232,7 +1230,9 @@ sub OWXAD_GetPage($$) {
if( $sb1 && 128) {
#-- put into globals
$owg_mode[$i] = "input";
- $owg_resoln[$i] = ($sb1 & 15) + 1 ;
+ $owg_resoln[$i] = ($sb1 & 15);
+ $owg_resoln[$i] = 16
+ if ($owg_resoln[$i] == 0);
$owg_range[$i] = ($sb2 & 1) ? 5100 : 2550;
#-- low alarm disabled
@@ -1247,13 +1247,7 @@ sub OWXAD_GetPage($$) {
$an = 2;
}
}
- #-- first reading from device
- if( $owg_slow[$i] eq "" ){
- $owg_slow[$i]= $an;
- #-- change needed
- }elsif( $owg_slow[$i] != $an ){
- $hash->{tempf}{change} = 1;
- }
+ $owg_slow[$i]= $an;
#-- high alarm disabled
if( ($sb2 & 8)==0 ){
@@ -1266,19 +1260,8 @@ sub OWXAD_GetPage($$) {
}else{
$an = 2;
}
- }
- #-- first reading from device
- if( $owg_shigh[$i] eq "" ){
- $owg_shigh[$i]= $an;
- #-- change needed
- }elsif( $owg_shigh[$i] != $an ){
- $hash->{tempf}{change} = 1;
- }
- #-- change needed
- if( $hash->{tempf}{change} == 1){
- OWXAD_SetPage($hash,"status");
- $hash->{tempf}{change} = 0;
- }
+ }
+ $owg_shigh[$i]= $an;
#-- output operation
} else {
$owg_mode[$i] = "output";
@@ -1328,9 +1311,10 @@ sub OWXAD_SetPage($$) {
# \x55\x08\x00 reading 8 data bytes and 2 CRC bytes
$select="\x55\x08\x00";
for( $i=0;$i 0){
#-- resolution (TODO: check !)
- $sb1 = $owg_resoln[$i]-1;
+ $sb1 = $owg_resoln[$i] & 15;
#-- alarm enabled
if( defined($owg_slow[$i]) ){
$sb2 = ( $owg_slow[$i] ne 0 ) ? 4 : 0;
@@ -1479,7 +1463,7 @@ sub OWXAD_SetPage($$) {
<string>
arbitrary functional expression involving the values VA,VB,VC,VD. VA is replaced by
the measured voltage in channel A, etc. This attribute allows linearization of measurement
- curves as well as the mixing of various channels. Replacement for Offset/Factor !
+ curves as well as the mixing of various channels. Replacement for Offset/Factor !
attr <name> <channel>Alarm
<string>
alarm setting in this channel, either both, low, high or none (default).
@@ -1493,7 +1477,8 @@ sub OWXAD_SetPage($$) {
alarm.
- Standard attributes alias, comment, event-on-update-reading, event-on-change-reading, event-on-change-reading, stateFormat, room, eventMap, loglevel,
webCmd
diff --git a/FHEM/21_OWCOUNT.pm b/FHEM/21_OWCOUNT.pm
index 801e20a71..5c70e2498 100644
--- a/FHEM/21_OWCOUNT.pm
+++ b/FHEM/21_OWCOUNT.pm
@@ -36,7 +36,7 @@
# Additional attributes are defined in fhem.cfg, in some cases per channel, where =A,B
# Note: attributes are read only during initialization procedure - later changes are not used.
#
-# attr UnitInReading = whether the physical unit is written into the reading = 1 (default) or 0
+# attr LogM = device name (not file name) of monthly log file
# attr Name | = name for the channel | a type description for the measured value
# attr Unit | = unit of measurement for this channel | its abbreviation
# attr Offset = offset added to the reading in this channel
@@ -132,8 +132,8 @@ sub OWCOUNT_Initialize ($) {
$hash->{SetFn} = "OWCOUNT_Set";
#-- see header for attributes
- my $attlist = "IODev do_not_notify:0,1 showtime:0,1 model:DS2423 loglevel:0,1,2,3,4,5 ".
- "event-on-update-reading event-on-change-reading ";
+ my $attlist = "IODev do_not_notify:0,1 showtime:0,1 model:DS2423 loglevel:0,1,2,3,4,5 LogM ".
+ $readingFnAttributes;
for( my $i=0;$i{READINGS}{"$owg_channel[$i]"}{PERIOD};
$runit = $hash->{READINGS}{"$owg_rate[$i]"}{UNITABBR};
- #-- skip some thing if undefined
+ #-- skip some things if undefined
if( $owg_val[$i] eq ""){
$svalue .= $owg_channel[$i].": ???";
}else{
- #-- only if attribute value Mode=daily, take the midnight value from memory
+ #-- only if attribute value mode=daily, take the midnight value from memory
if( defined($attr{$name}{$owg_fixed[$i]."Mode"} )){
if( $attr{$name}{$owg_fixed[$i]."Mode"} eq "daily"){
$midnight = $owg_midnight[$i];
@@ -407,43 +408,44 @@ sub OWCOUNT_FormatValues($) {
#-- safeguard against the case where no previous measurement
if( length($oldtim) > 0 ){
- #-- time difference in seconds
+ #-- previous measurement time
($yearo,$montho,$dayrest) = split(/-/,$oldtim);
$dayo = substr($dayrest,0,2);
($houro,$mino,$seco) = split(/:/,substr($dayrest,3));
- my $delt = ($hour-$houro)*3600 + ($min-$mino)*60 + ($sec-$seco);
- #-- debugging: changing the day every 10 minutes
- #$delt =
- #-- correct time for wraparound at midnight
- if( ($delt<0) && ($present==1)){
+
+ #-- time dfifference to previous measurement and to midnight
+ $delt = ($hour-$houro)*3600 + ($min-$mino)*60 + ($sec-$seco);
+ $delf = $hour *3600 + $min *60 + $sec - 86400;
+ if( ($delf+$hash->{INTERVAL}) >= 0 ){
$daybreak = 1;
- $delt += 86400;
+ #-- Timer data from tomorrow
+ my ($secn,$minn,$hourn,$dayn,$monthn,$yearn,$wdayn,$ydayn,$isdstn) = localtime(time() + 24*60*60);
+ #-- Check, whether we have a new month
+ if( (($delf+$hash->{INTERVAL}) > 0) && ($dayn == 1) ){
+ $monthbreak =1;
+ }
}
+
#-- correct $vval for wraparound of 32 bit counter
if( ($vval < $oldval) && ($daybreak==0) && ($present==1) ){
Log 1,"OWCOUNT TODO: Counter wraparound";
}
if( $daybreak==1 ){
- #-- linear interpolation
- my $dt = ((24-$houro)*3600 -$mino*60 - $seco)/( ($hour+24-$houro)*3600 + ($min-$mino)*60 + ($sec-$seco) );
- my $dv = $oldval*(1-$dt)+$vval*$dt;
- #-- correct reading in daily mode
- if( $midnight > 0.0 ){
- $vval -= $dv;
- $delt *= (1-$dt);
- $oldval = 0.0;
- }
+ #-- linear extrapolation
+ $dt = -$delf/$delt;
+ $dv = ($vval-$oldval)*$dt;
+ $dval = $vval+$dv;
+
#-- in any mode store the interpolated value in the midnight store
- $midnight += $dv;
- OWXCOUNT_SetPage($hash,14+$i,sprintf("%f",$midnight));
+ OWXCOUNT_SetPage($hash,14+$i,sprintf("%f",$dval));
#-- string buildup for monthly logging
- $dvalue .= sprintf( "%s: %5.1f %s", $owg_channel[$i], $dv,$unit);
+ $dvalue .= sprintf( "%s: %5.1f %s", $owg_channel[$i], $dval,$unit);
if( $day<$dayo ){
$monthbreak = 1;
Log 1, "OWCOUNT: Change of month";
#-- string buildup for yearly logging
- $mvalue .= sprintf( "%s: %5.1f %s", $owg_channel[$i], $dv,$unit);
+ $mvalue .= sprintf( "%s: %5.1f %s", $owg_channel[$i], $dval,$unit);
}
}
#-- rate
@@ -480,24 +482,25 @@ sub OWCOUNT_FormatValues($) {
#-- insert space
if( $i{NAME};
+ my $regexp = ".*$name.*";
+ my $val;
+ my @month = ();
+ my @month2 = ();
+ my @channel;
+ my ($total,$total2,$deltim,$av);
+
+ #-- Check current logfile
+ my $ln = $attr{$name}{"LogM"};
+ if( !(defined($ln))){
+ Log 1,"OWCOUNT_GetMonth: Attribute LogM is missing";
+ return undef;
+ } else {
+ my $lf = $defs{$ln}{currentlogfile};
+ my $ret = open(OWXFILE, "< $lf" );
+ if( $ret) {
+ while( ){
+ #-- line looks as
+ # 2013-02-09_23:59:31 day D_09 : 180.0 cts : 180.0 cts etc.
+ my $line = $_;
+ chomp($line);
+ if ( $line =~ m/$regexp/i){
+ my @linarr = split(' ',$line);
+ my $day = $linarr[3];
+ $day =~ s/D_0+//;
+ @channel = ();
+ for (my $i=0;$i{READINGS}{"$owg_channel[$i]"}{VAL}))/100;
+ my $av = int(100*$total2/(int(@month)+$deltim))/100;
+
+ push(@month2,[($total,$total2,$av)]);
+ }
+ return @month2;
+ }
+}
+
#######################################################################################
#
# OWCOUNT_GetValues - Updates the reading from one device
@@ -1168,6 +1239,11 @@ sub OWXCOUNT_SetPage($$$) {
Attributes
+
For each of the following attributes, the channel identification A,B may be used.
attr <name> <channel>Name
@@ -1187,7 +1263,8 @@ sub OWXCOUNT_SetPage($$$) {
factor multiplied to (reading+offset) in this channel.
- Standard attributes alias, comment, event-on-update-reading, event-on-change-reading, event-on-change-reading, stateFormat, room, eventMap, loglevel,
webCmd
diff --git a/FHEM/21_OWID.pm b/FHEM/21_OWID.pm
index 94a20b405..2c9342330 100644
--- a/FHEM/21_OWID.pm
+++ b/FHEM/21_OWID.pm
@@ -10,14 +10,17 @@
#
########################################################################################
#
-# define OWID
+# define OWID [interval] or OWID . [interval]
#
# where may be replaced by any name string
#
-# is a 2 character (1 byte) 1-Wire Family ID
+# is a 2 character (1 byte) 1-Wire Family ID
#
# is a 12 character (6 byte) 1-Wire ROM ID
# without Family ID, e.g. A2D90D000800
+# [interval] is an optional query interval in seconds
+#
+# set interval => set query interval for checking presence
#
# get id => FAM_ID.ROM_ID.CRC
# get present => 1 if device present, 0 if not
@@ -51,10 +54,15 @@ sub Log($$);
#-- declare variables
my %gets = (
"present" => "",
+ "interval" => "",
"id" => ""
);
-my %sets = ();
-my %updates = ();
+my %sets = (
+ "interval" => ""
+);
+my %updates = (
+ "present" => ""
+);
########################################################################################
#
@@ -76,8 +84,9 @@ sub OWID_Initialize ($) {
$hash->{DefFn} = "OWID_Define";
$hash->{UndefFn} = "OWID_Undef";
$hash->{GetFn} = "OWID_Get";
- $hash->{SetFn} = undef;
- my $attlist = "IODev do_not_notify:0,1 showtime:0,1 model loglevel:0,1,2,3,4,5 ";
+ $hash->{SetFn} = "OWID_Set";
+ my $attlist = "IODev do_not_notify:0,1 showtime:0,1 model loglevel:0,1,2,3,4,5 ".
+ $readingFnAttributes;
$hash->{AttrList} = $attlist;
}
@@ -95,30 +104,60 @@ sub OWID_Define ($$) {
#-- define OWID
my @a = split("[ \t][ \t]*", $def);
- my ($name,$fam,$id,$crc,$ret);
+ my ($name,$interval,$model,$fam,$id,$crc,$ret);
#-- default
$name = $a[0];
+ $interval = 300;
$ret = "";
#-- check syntax
- return "OWID: Wrong syntax, must be define OWID "
- if(int(@a) !=4 );
-
- #-- check id
- if( $a[2] =~ m/^[0-9|a-f|A-F]{2}$/ ) {
- $fam = $a[2];
+ return "OWID: Wrong syntax, must be define OWID [] [interval] or OWAD . [interval]"
+ if(int(@a) < 2 || int(@a) > 5);
+
+ #-- different types of definition allowed
+ my $a2 = $a[2];
+ my $a3 = defined($a[3]) ? $a[3] : "";
+ #-- no model, 2+12 characters
+ if( $a2 =~ m/^[0-9|a-f|A-F]{2}\.[0-9|a-f|A-F]{12}$/ ) {
+ $fam = substr($a[2],0,2);
+ $id = substr($a[2],3);
+ if(int(@a)>=4) { $interval = $a[3]; }
+ if( $fam eq "01" ){
+ $model = "DS2401";
+ CommandAttr (undef,"$name model DS2401");
+ }else{
+ $model = "unknown";
+ CommandAttr (undef,"$name model unknown");
+ }
+ #-- model or family id, 12 characters
+ } elsif( $a3 =~ m/^[0-9|a-f|A-F]{12}$/ ) {
+ $id = $a[3];
+ if(int(@a)>=5) { $interval = $a[4]; }
+ #-- family id, 2 characters
+ if( $a2 =~ m/^[0-9|a-f|A-F]{2}$/ ) {
+ $fam = $a[2];
+ if( $fam eq "01" ){
+ $model = "DS2401";
+ CommandAttr (undef,"$name model DS2401");
+ }else{
+ $model = "unknown";
+ CommandAttr (undef,"$name model unknown");
+ }
+ }else{
+ $model = $a[2];
+ if( $model eq "DS2401" ){
+ $fam = "01";
+ CommandAttr (undef,"$name model DS2401");
+ }else{
+ return "OWID: Unknown 1-Wire device model $model";
+ }
+ }
} else {
- return "OWID: $a[0] family id $a[2] invalid, specify a 2 digit value";
- }
- if( $a[3] =~ m/^[0-9|a-f|A-F]{12}$/ ) {
- $id = $a[3];
- } else {
- return "OWID: $a[0] ID $a[3] invalid, specify a 12 digit value";
+ return "OWID: $a[0] ID $a[2] invalid, specify a 12 or 2.12 digit value";
}
- #-- 1-Wire ROM identifier in the form "FF.XXXXXXXXXXXX.YY"
- # determine CRC Code YY - only if this is a direct interface
+ #-- determine CRC Code
$crc = defined($hash->{IODev}->{INTERFACE}) ? sprintf("%02x",OWX_CRC($fam.".".$id."00")) : "00";
#-- Define device internals
@@ -126,22 +165,27 @@ sub OWID_Define ($$) {
$hash->{OW_ID} = $id;
$hash->{OW_FAMILY} = $fam;
$hash->{PRESENT} = 0;
+ $hash->{INTERVAL} = $interval;
#-- Couple to I/O device
AssignIoPort($hash);
if( !defined($hash->{IODev}->{NAME}) | !defined($hash->{IODev}) | !defined($hash->{IODev}->{PRESENT}) ){
- return "OWSWITCH: Warning, no 1-Wire I/O device found for $name.";
+ return "OWID: Warning, no 1-Wire I/O device found for $name.";
}
if( $hash->{IODev}->{PRESENT} != 1 ){
- return "OWSWITCH: Warning, 1-Wire I/O device ".$hash->{IODev}->{NAME}." not present for $name.";
+ return "OWID: Warning, 1-Wire I/O device ".$hash->{IODev}->{NAME}." not present for $name.";
}
$modules{OWID}{defptr}{$id} = $hash;
#--
readingsSingleUpdate($hash,"state","Defined",1);
- Log 3, "OWTHERM: Device $name defined.";
+ Log 3, "OWID: Device $name defined.";
#-- Initialization reading according to interface type
my $interface= $hash->{IODev}->{TYPE};
+
+ #-- Start timer for updates
+ InternalTimer(time()+5+$hash->{INTERVAL}, "OWID_GetValues", $hash, 0);
+
#--
readingsSingleUpdate($hash,"state","Initialized",1);
return undef;
@@ -180,16 +224,104 @@ sub OWID_Get($@) {
return "$name.id => $value";
}
+ #-- get interval
+ if($a[1] eq "interval") {
+ $value = $hash->{INTERVAL};
+ return "$name.interval => $value";
+ }
+
#-- get present
if($a[1] eq "present") {
#-- hash of the busmaster
my $master = $hash->{IODev};
$value = OWX_Verify($master,$hash->{ROM_ID});
$hash->{PRESENT} = $value;
+ if( $value == 0 ){
+ readingsSingleUpdate($hash,"state","not present",1);
+ } else {
+ readingsSingleUpdate($hash,"state","present",1);
+ }
return "$name.present => $value";
}
}
+
+########################################################################################
+#
+# OWID_GetValues - Updates the reading from one device
+#
+# Parameter hash = hash of device addressed
+#
+########################################################################################
+
+sub OWID_GetValues($) {
+ my $hash = shift;
+
+ my $name = $hash->{NAME};
+ my $value = "";
+ my $ret = "";
+ my $offset;
+ my $factor;
+
+ #-- restart timer for updates
+ RemoveInternalTimer($hash);
+ InternalTimer(time()+$hash->{INTERVAL}, "OWID_GetValues", $hash, 1);
+
+ #-- hash of the busmaster
+ my $master = $hash->{IODev};
+ $value = OWX_Verify($master,$hash->{ROM_ID});
+ $hash->{PRESENT} = $value;
+ if( $value == 0 ){
+ readingsSingleUpdate($hash,"state","not present",1);
+ } else {
+ readingsSingleUpdate($hash,"state","present",1);
+ }
+}
+
+#######################################################################################
+#
+# OWID_Set - Set one value for device
+#
+# Parameter hash = hash of device addressed
+# a = argument array
+#
+########################################################################################
+
+sub OWID_Set($@) {
+ my ($hash, @a) = @_;
+
+ my $key = $a[1];
+ my $value = $a[2];
+
+ #-- for the selector: which values are possible
+ if (@a == 2){
+ my $newkeys = join(" ", keys %sets);
+ return $newkeys ;
+ }
+
+ #-- check syntax
+ return "OWID: Set needs at least one parameter"
+ if( int(@a)<3 );
+ #-- check argument
+ if( !defined($sets{$a[1]}) ){
+ return "OWID: Set with unknown argument $a[1]";
+ }
+
+ my $name = $hash->{NAME};
+
+ #-- set new timer interval
+ if($key eq "interval") {
+ # check value
+ return "OWID: Set with short interval, must be > 1"
+ if(int($value) < 1);
+ # update timer
+ $hash->{INTERVAL} = $value;
+ RemoveInternalTimer($hash);
+ InternalTimer(gettimeofday()+$hash->{INTERVAL}, "OWID_GetValues", $hash, 1);
+ return undef;
+ }
+}
+
########################################################################################
#
# OWID_Undef - Implements UndefFn function
@@ -213,24 +345,41 @@ sub OWID_Undef ($) {
OWID
FHEM module for 1-Wire devices that know only their unique ROM ID
- Note: This 1-Wire module so far works only with the OWX interface module.
- Please define an OWX device first.
+ This 1-Wire module works with the OWX interface module or with the OWServer interface module
+ Please define an OWX device or OWServer device first.
Example
- define ROM1 OWX_ID OWCOUNT CE780F000000
+ define ROM1 OWX_ID OWCOUNT 09.CE780F000000 10
Define
- define <name> OWID <id>
+ define <name> OWID <fam> <id> [<interval>] or
+ define <name> OWID <fam>.<id> [<interval>]
Define a 1-Wire device.
+ -
+
<fam>
+ 2-character unique family id, see above
+
-
<id>
12-character unique ROM id of the converter device without family id and CRC
- code
+ code
+
+ -
+
<interval>
+ Interval in seconds for checking the presence of the device. The default is 300 seconds.
+
+
+
+ Set
+
diff --git a/FHEM/21_OWLCD.pm b/FHEM/21_OWLCD.pm
index 8175adce6..c40e15953 100644
--- a/FHEM/21_OWLCD.pm
+++ b/FHEM/21_OWLCD.pm
@@ -10,14 +10,14 @@
#
########################################################################################
#
-# define OWLCD
+# define OWLCD or FF.
#
# where may be replaced by any name string
#
# is a 12 character (6 byte) 1-Wire ROM ID
# without Family ID, e.g. A2D90D000800
#
-# get id => FAM_ID.ROM_ID.CRC
+# get id => FF.ROM_ID.CRC
# get present => 1 if device present, 0 if not
# get gpio => current state of the gpio pins (15 = all off, 0 = all on)
# get counter => four values (16 Bit) of the gpio counter
@@ -148,8 +148,10 @@ sub OWLCD_Define ($$) {
#-- check id
if( $a[2] =~ m/^[0-9|a-f|A-F]{12}$/ ) {
$id = $a[2];
+ } elsif( $a[2] =~ m/^FF\.[0-9|a-f|A-F]{12}$/ ) {
+ $id = substr($a[2],3);
} else {
- return "OWLCD: $a[0] ID $a[2] invalid, specify a 12 digit value";
+ return "OWLCD: $a[0] ID $a[2] invalid, specify a 12 digit or 2.12 digit value";
}
#-- 1-Wire ROM identifier in the form "FF.XXXXXXXXXXXX.YY"
@@ -164,7 +166,7 @@ sub OWLCD_Define ($$) {
#-- Couple to I/O device
AssignIoPort($hash);
- if( !defined($hash->{IODev}->{NAME}) | !defined($hash->{IODev}) | !defined($hash->{IODev}->{PRESENT}) ){
+ if( (!defined($hash->{IODev}->{NAME})) || (!defined($hash->{IODev})) || (!defined($hash->{IODev}->{PRESENT})) ){
return "OWSWITCH: Warning, no 1-Wire I/O device found for $name.";
}
if( $hash->{IODev}->{PRESENT} != 1 ){
@@ -992,7 +994,9 @@ sub OWXLCD_Trans($) {
$msg =~ s/Ö/\x5C/g;
$msg =~ s/Ü/\x5E/g;
$msg =~ s/ß/\xBE/g;
- #--take out degree sign
+ #-- replace other special chars
+ $msg =~s/_/\xC4/g;
+ #--take out HTML degree sign
if( $msg =~ m/.*\°\;.*/ ) {
my @ma = split(/\°\;/,$msg);
$msg = $ma[0]."\x80".$ma[1];
@@ -1053,7 +1057,7 @@ sub OWXLCD_SetMemory($$$) {
=pod
=begin html
-
+
OWLCD
FHEM module to commmunicate with the 1-Wire LCD controller
@@ -1070,7 +1074,8 @@ sub OWXLCD_SetMemory($$$) {
Define
- define <name> OWLCD <id>
+ define <name> OWLCD <id> or
+ define <name> OWLCD FF.<id>
Define a 1-Wire LCD device.
-
@@ -1143,4 +1148,4 @@ sub OWXLCD_SetMemory($$$) {
=end html
-=cut
+=cut
\ No newline at end of file
diff --git a/FHEM/21_OWMULTI.pm b/FHEM/21_OWMULTI.pm
index 6396cf861..bd3c0a5bc 100644
--- a/FHEM/21_OWMULTI.pm
+++ b/FHEM/21_OWMULTI.pm
@@ -118,9 +118,9 @@ sub OWMULTI_Initialize ($) {
#tempOffset = a temperature offset added to the temperature reading for correction
#tempUnit = a unit of measure: C/F/K
$hash->{AttrList}= "IODev do_not_notify:0,1 showtime:0,1 model:DS2438 loglevel:0,1,2,3,4,5 ".
- "event-on-update-reading event-on-change-reading ".
"tempOffset tempUnit:C,Celsius,F,Fahrenheit,K,Kelvin ".
- "VName VUnit VFunction";
+ "VName VUnit VFunction ".
+ $readingFnAttributes;
}
########################################################################################
@@ -985,7 +985,8 @@ sub OWXMULTI_SetValues($@) {
unit of measurement (temperature scale), default is Celsius = °C
Standard attributes alias, comment, event-on-update-reading, event-on-change-reading, event-on-change-reading, stateFormat, room, eventMap, loglevel,
webCmd
diff --git a/FHEM/21_OWSWITCH.pm b/FHEM/21_OWSWITCH.pm
index 17ef0daa9..2d7222277 100644
--- a/FHEM/21_OWSWITCH.pm
+++ b/FHEM/21_OWSWITCH.pm
@@ -130,8 +130,8 @@ sub OWSWITCH_Initialize ($) {
$hash->{SetFn} = "OWSWITCH_Set";
my $attlist = "IODev do_not_notify:0,1 showtime:0,1 model:DS2413,DS2406,DS2408 loglevel:0,1,2,3,4,5 ".
- "event-on-update-reading event-on-change-reading ".
- "stateS ";
+ "stateS ".
+ $readingFnAttributes;
#-- correct list of attributes
for( my $i=0;$i<8;$i++ ){
@@ -1198,7 +1198,8 @@ sub OWXSWITCH_SetState($$) {
display for on | off condition
Standard attributes alias, comment, event-on-update-reading, event-on-change-reading, event-on-change-reading, stateFormat, room, eventMap, loglevel,
webCmd
diff --git a/FHEM/21_OWTHERM.pm b/FHEM/21_OWTHERM.pm
index bf71f8185..9561a07bb 100755
--- a/FHEM/21_OWTHERM.pm
+++ b/FHEM/21_OWTHERM.pm
@@ -120,10 +120,10 @@ sub OWTHERM_Initialize ($) {
$hash->{SetFn} = "OWTHERM_Set";
$hash->{AttrFn} = "OWTHERM_Attr";
$hash->{AttrList}= "IODev model:DS1820,DS18B20,DS1822 loglevel:0,1,2,3,4,5 ".
- "event-on-update-reading event-on-change-reading ".
"stateAL stateAH ".
"tempOffset tempUnit:C,Celsius,F,Fahrenheit,K,Kelvin ".
- "tempLow tempHigh";
+ "tempLow tempHigh ".
+ $readingFnAttributes;
}
########################################################################################
@@ -142,7 +142,6 @@ sub OWTHERM_Define ($$) {
my @a = split("[ \t][ \t]*", $def);
my ($name,$model,$fam,$id,$crc,$interval,$ret);
- my $tn = TimeNow();
#-- default
$name = $a[0];
@@ -210,6 +209,7 @@ sub OWTHERM_Define ($$) {
$hash->{PRESENT} = 0;
$hash->{ROM_ID} = $fam.".".$id.$crc;
$hash->{INTERVAL} = $interval;
+ $hash->{ERRCOUNT} = 0;
#-- Couple to I/O device, exit if not possible
AssignIoPort($hash);
@@ -223,12 +223,9 @@ sub OWTHERM_Define ($$) {
#--
readingsSingleUpdate($hash,"state","defined",1);
Log 3, "OWTHERM: Device $name defined.";
-
- #-- Start timer for initialization in a few seconds
- InternalTimer(time()+10, "OWTHERM_InitializeDevice", $hash, 0);
#-- Start timer for updates
- InternalTimer(time()+10+$hash->{INTERVAL}, "OWTHERM_GetValues", $hash, 0);
+ InternalTimer(time()+10, "OWTHERM_GetValues", $hash, 0);
return undef;
}
@@ -245,9 +242,9 @@ sub OWTHERM_InitializeDevice($) {
my ($hash) = @_;
my $name = $hash->{NAME};
- my @args;
- my $ret;
- my ($unit,$offset,$factor,$abbr,$vval,$vlow,$vhigh,$statef);
+ my $interface = $hash->{IODev}->{TYPE};
+ my @a = ($name,"",0);
+ my ($unit,$offset,$factor,$abbr,$value);
#-- attributes defined ?
$stateal = defined($attr{$name}{stateAL}) ? $attr{$name}{stateAL} : "▾";
@@ -273,14 +270,37 @@ sub OWTHERM_InitializeDevice($) {
$hash->{READINGS}{"temperature"}{TYPE} = "temperature";
$hash->{READINGS}{"temperature"}{UNIT} = $unit;
$hash->{READINGS}{"temperature"}{UNITABBR} = $abbr;
+ $hash->{ERRCOUNT} = 0;
$hash->{tempf}{offset} = $offset;
$hash->{tempf}{factor} = $factor;
- #-- Initial readings temperature sensor
- $owg_temp = "";
- $owg_tl = "";
- $owg_th = "",
-
+ #-- Set the attribute values if defined
+ if( defined($attr{$name}{"tempLow"}) ){
+ $value = $attr{$name}{"tempLow"};
+ $a[1] = "tempLow";
+ $a[2] = floor($value/$factor-$offset+0.5);
+ #-- put into device
+ #-- OWX interface
+ if( $interface eq "OWX" ){
+ OWXTHERM_SetValues($hash,@a);
+ #-- OWFS interface
+ }elsif( $interface eq "OWServer" ){
+ OWFSTHERM_SetValues($hash,@a);
+ }
+ }
+ if( defined($attr{$name}{"tempHigh"}) ){
+ $value = $attr{$name}{"tempHigh"};
+ $a[1] = "tempHigh";
+ $a[2] = floor($value/$factor-$offset+0.5);
+ #-- put into device
+ #-- OWX interface
+ if( $interface eq "OWX" ){
+ OWXTHERM_SetValues($hash,@a);
+ #-- OWFS interface
+ }elsif( $interface eq "OWServer" ){
+ OWFSTHERM_SetValues($hash,@a);
+ }
+ }
#-- Set state to initialized
readingsSingleUpdate($hash,"state","initialized",1);
}
@@ -330,31 +350,17 @@ sub OWTHERM_FormatValues($) {
#-- no change in any value if invalid reading
return if( $owg_temp eq "");
+ #-- check if device needs to be initialized
+ OWTHERM_InitializeDevice($hash)
+ if( $hash->{READINGS}{"state"}{VAL} eq "defined");
+
#-- correct values for proper offset, factor
$vval = ($owg_temp + $offset)*$factor;
-
- #-- correct alarm values for proper offset, factor
- $vlow = ($owg_tl + $offset)*$factor;
- $vhigh = ($owg_th + $offset)*$factor;
+ $vlow = floor(($owg_tl + $offset)*$factor+0.5);
+ $vhigh = floor(($owg_th + $offset)*$factor+0.5);
- #-- check if the device has to be corrected
- if( AttrVal($name,"tempLow",undef) ){
- if( $main::attr{$name}{"tempLow"} != $vlow ){
- OWTHERM_Set( $hash,("tempLow",$main::attr{$name}{"tempLow"}));
- $vlow = $main::attr{$name}{"tempLow"};
- }
- } else {
- $main::attr{$name}{"tempLow"} = $vlow;
- }
-
- if( AttrVal($name,"tempHigh",undef) ){
- if( $main::attr{$name}{"tempHigh"} != $vhigh ){
- OWTHERM_Set( $hash,("tempHigh",$main::attr{$name}{"tempHigh"}));
- $vhigh = $main::attr{$name}{"tempHigh"};
- }
- } else {
- $main::attr{$name}{"tempHigh"} = $vhigh;
- }
+ $main::attr{$name}{"tempLow"} = $vlow;
+ $main::attr{$name}{"tempHigh"} = $vhigh;
#-- formats for output
$statef = "T: %5.2f ".$abbr;
@@ -509,8 +515,11 @@ sub OWTHERM_GetValues($@) {
#-- process results
if( defined($ret) ){
- Log 3, "OWTHERM: Could not get values from device $name, reason $ret";
- return 1;
+ $hash->{ERRCOUNT}=$hash->{ERRCOUNT}+1;
+ if( $hash->{ERRCOUNT} > 5 ){
+ $hash->{INTERVAL} = 9999;
+ }
+ return "OWTHERM: Could not get values from device $name for ".$hash->{ERRCOUNT}." times, reason $ret";
}
$hash->{PRESENT} = 1;
@@ -562,19 +571,38 @@ sub OWTHERM_Set($@) {
#-- set tempLow or tempHigh
if( (lc($key) eq "templow") || (lc($key) eq "temphigh")) {
+
my $interface = $hash->{IODev}->{TYPE};
my $offset = defined($hash->{tempf}{offset}) ? $hash->{tempf}{offset} : 0.0;
my $factor = defined($hash->{tempf}{factor}) ? $hash->{tempf}{factor} : 1.0;
+ #-- Only integer values are allowed
+ $value = floor($value+0.5);
+
+ #-- First we have to read the current data, because alarms may not be set independently
+ $owg_tl = floor($main::attr{$name}{"tempLow"}/$factor-$offset+0.5);
+ $owg_th = floor($main::attr{$name}{"tempHigh"}/$factor-$offset+0.5);
+
#-- find upper and lower boundaries for given offset/factor
- my $mmin = (-55+$offset)*$factor;
- my $mmax = (125+$offset)*$factor;
+ my $mmin = floor((-55+$offset)*$factor+0.5);
+ my $mmax = floor((125+$offset)*$factor+0.5);
return sprintf("OWTHERM: Set with wrong value $value for $key, range is [%3.1f,%3.1f]",$mmin,$mmax)
if($value < $mmin || $value > $mmax);
- #-- seems to be ok, put into the device after correcting for offset and factor
- $a[2] = int($value/$factor-$offset);
-
+ #-- seems to be ok, correcting for offset and factor
+ $a[2] = floor($value/$factor-$offset+0.5);
+ #-- put into attribute value
+ if( lc($key) eq "templow" ){
+ if( $main::attr{$name}{"tempLow"} != $value ){
+ $main::attr{$name}{"tempLow"} = $value;
+ }
+ }
+ if( lc($key) eq "temphigh" ){
+ if( $main::attr{$name}{"tempHigh"} != $value ){
+ $main::attr{$name}{"tempHigh"} = $value;
+ }
+ }
+ #-- put into device
#-- OWX interface
if( $interface eq "OWX" ){
$ret = OWXTHERM_SetValues($hash,@a);
@@ -588,10 +616,9 @@ sub OWTHERM_Set($@) {
if(defined($ret));
}
- #-- process results - we have to reread the device
+ #-- process results
$hash->{PRESENT} = 1;
- #OWTHERM_GetValues($hash);
- #OWTHERM_FormatValues($hash);
+ OWTHERM_FormatValues($hash);
Log 4, "OWTHERM: Set $hash->{NAME} $key $value";
return undef;
@@ -705,7 +732,20 @@ sub OWFSTHERM_SetValues($@) {
my $master = $hash->{IODev};
my $name = $hash->{NAME};
- OWServer_Write($master, "/$owx_add/".lc($a[0]),$a[1] );
+ #-- define vars
+ my $key = $a[1];
+ my $value = $a[2];
+ return undef
+ if( !defined($value));
+ return undef
+ if( $value eq "");
+
+ #-- $owg_tl and $owg_th are preset and may be changed here
+ $owg_tl = $value if( lc($key) eq "templow" );
+ $owg_th = $value if( lc($key) eq "temphigh");
+
+ OWServer_Write($master, "/$owx_add/".lc($key),$value );
+
return undef
}
@@ -731,7 +771,7 @@ sub OWXTHERM_GetValues($) {
my ($i,$j,$k,@data,$ow_thn,$ow_tln);
my $change = 0;
- #-- For default, perform the conversion NOT now
+ #-- For default, perform the conversion now
my $con=1;
#-- ID of the device
@@ -1001,7 +1041,8 @@ sub OWXTHERM_SetValues($@) {
value).
Standard attributes alias, comment, event-on-update-reading, event-on-change-reading, event-on-change-reading, stateFormat, room, eventMap, loglevel,
webCmd
diff --git a/FHEM/30_HUEBridge.pm b/FHEM/30_HUEBridge.pm
new file mode 100644
index 000000000..660a44e0f
--- /dev/null
+++ b/FHEM/30_HUEBridge.pm
@@ -0,0 +1,490 @@
+
+# "Hue Personal Wireless Lighting" is a trademark owned by Koninklijke Philips Electronics N.V.,
+# see www.meethue.com for more information.
+# I am in no way affiliated with the Philips organization.
+
+package main;
+
+use strict;
+use warnings;
+use POSIX;
+use JSON;
+use Try::Tiny;
+use Data::Dumper;
+use MIME::Base64;
+
+sub HUEBridge_Initialize($)
+{
+ my ($hash) = @_;
+
+ # Provider
+ $hash->{ReadFn} = "HUEBridge_Read";
+ $hash->{WriteFn} = "HUEBridge_Read";
+ $hash->{Clients} = ":HUEDevice:";
+
+ #Consumer
+ $hash->{DefFn} = "HUEBridge_Define";
+ $hash->{SetFn} = "HUEBridge_Set";
+ $hash->{GetFn} = "HUEBridge_Get";
+ $hash->{UndefFn} = "HUEBridge_Undefine";
+ $hash->{AttrList}= "key loglevel:0,1,2,3,4,5";
+}
+
+sub
+HUEBridge_Read($@)
+{
+ my ($hash,$name,$id,$obj)= @_;
+
+ return HUEBridge_Call($hash, 'lights/' . $id, $obj);
+}
+
+sub HUEBridge_Define($$)
+{
+ my ($hash, $def) = @_;
+
+ my @args = split("[ \t]+", $def);
+
+ return "Usage: define HUEBridge [] [interval]" if(@args < 2);
+
+ my ($name, $type, $host, $interval) = @args;
+
+ if( !defined($host) ) {
+ my $ret = HUEBridge_HTTP_Request(0,"http://www.meethue.com/api/nupnp","GET",undef,undef,undef);
+
+ if( defined($ret) && $ret ne '' )
+ {
+ my $obj = decode_json($ret);
+
+ if( defined($obj->[0])
+ && defined($obj->[0]->{'internalipaddress'}) ) {
+ }
+ $host = $obj->[0]->{'internalipaddress'};
+ }
+
+ if( !defined($host) ) {
+ return 'error detecting bridge.';
+ }
+
+ $hash->{DEF} = $host;
+ }
+
+ $interval= 300 unless defined($interval);
+ if( $interval < 60 ) { $interval = 60; }
+
+ $hash->{STATE} = 'Initialized';
+
+ $hash->{Host} = $host;
+ $hash->{INTERVAL} = $interval;
+
+ $attr{$name}{"key"} = join "",map { unpack "H*", chr(rand(256)) } 1..16 unless defined( AttrVal($name, "key", undef) );
+
+ #HUEBridge_OpenDev($hash);
+ InternalTimer(gettimeofday()+10, "HUEBridge_OpenDev", $hash, 0);
+
+ return undef;
+}
+
+sub HUEBridge_Undefine($$)
+{
+ my ($hash,$arg) = @_;
+
+ RemoveInternalTimer($hash);
+ return undef;
+}
+
+sub HUEBridge_OpenDev($)
+{
+ my ($hash) = @_;
+
+ my $result = HUEBridge_Call($hash, 'config', undef);
+ if( !defined($result) ) {
+ return undef;
+ }
+
+ $hash->{name} = $result->{'name'};
+ $hash->{swversion} = $result->{'swversion'};
+
+ if( !defined($result->{'mac'}) )
+ {
+ HUEBridge_Pair($hash);
+ return;
+ }
+
+ $hash->{mac} = $result->{'mac'};
+
+ $hash->{STATE} = 'Connected';
+ HUEBridge_GetUpdate($hash);
+
+ HUEBridge_Autocreate($hash);
+
+ return undef;
+}
+sub HUEBridge_Pair($)
+{
+ my ($hash) = @_;
+
+ $hash->{STATE} = 'Pairing';
+
+ my $result = HUEBridge_Register($hash);
+ if( $result->{'error'} )
+ {
+ RemoveInternalTimer($hash);
+ InternalTimer(gettimeofday()+5, "HUEBridge_Pair", $hash, 0);
+
+ return undef;
+ }
+
+ $hash->{STATE} = 'Paired';
+
+ HUEBridge_OpenDev($hash);
+
+ return undef;
+}
+
+
+sub
+HUEBridge_Set($@)
+{
+ my ($hash, $name, $cmd) = @_;
+
+ # usage check
+ if($cmd eq 'statusRequest') {
+ RemoveInternalTimer($hash);
+ HUEBridge_GetUpdate($hash);
+ return undef;
+ } else {
+ return "Unknown argument $cmd, choose one of statusRequest";
+ }
+}
+
+sub
+HUEBridge_Get($@)
+{
+ my ($hash, $name, $cmd) = @_;
+
+ return "$name: get needs at least one parameter" if( !defined($cmd) );
+
+ # usage check
+ if($cmd eq 'devices') {
+ my $result = HUEBridge_Call($hash, 'lights', undef);
+ my $ret = "";
+ foreach my $key ( sort keys %$result ) {
+ $ret .= $key .": ". $result->{$key}{name} ."\n";
+ }
+ return $ret;
+ } else {
+ return "Unknown argument $cmd, choose one of devices";
+ }
+}
+
+sub
+HUEBridge_GetUpdate($)
+{
+ my ($hash) = @_;
+
+ if(!$hash->{LOCAL}) {
+ RemoveInternalTimer($hash);
+ InternalTimer(gettimeofday()+$hash->{INTERVAL}, "HUEBridge_GetUpdate", $hash, 1);
+ }
+
+ my $text='';
+
+ return($text);
+}
+
+sub
+HUEBridge_Autocreate($)
+{
+ my ($hash)= @_;
+ my $name = $hash->{NAME};
+
+ foreach my $d (keys %defs) {
+ next if($defs{$d}{TYPE} ne "autocreate");
+ return undef if(AttrVal($defs{$d}{NAME},"disable",undef));
+ }
+
+ my $result = HUEBridge_Call($hash, 'lights', undef);
+
+ my @defined = ();
+ foreach my $d (keys %defs) {
+ next if($defs{$d}{TYPE} ne "HUEDevice");
+ if(defined($defs{$d}{fhem}) && defined($defs{$d}{fhem}{id})) {
+ push(@defined,$defs{$d}{fhem}{id});
+ }
+ }
+
+ foreach my $key ( keys %$result )
+ {
+ my $id= $key;
+
+ my $found = 0;
+ foreach my $d (keys %defs) {
+ next if($defs{$d}{TYPE} ne "HUEDevice");
+ if(defined($defs{$d}{fhem}) &&
+ defined($defs{$d}{fhem}{id}) && $defs{$d}{fhem}{id} eq $id) {
+ Log 5, "$name id '$id' already defined as '$defs{$d}{NAME}'";
+ $found = 1;
+ last;
+ }
+ }
+
+ if( !$found ) {
+ my $devname= "HUEDevice" . $id;
+ my $define= "$devname HUEDevice $id";
+
+ Log 5, "$name create new device '$devname' for address '$id'";
+
+ my $cmdret= CommandDefine(undef,$define);
+ if($cmdret) {
+ Log 1, "$name: Autocreate: An error occurred while creating device for id '$id': $cmdret";
+ } else {
+ $cmdret= CommandAttr(undef,"$devname alias ".$result->{$id}{name});
+ $cmdret= CommandAttr(undef,"$devname room HUEDevice");
+ }
+ }
+ }
+
+ return undef;
+}
+
+sub HUEBridge_ProcessResponse($$)
+{
+ my ($hash,$obj) = @_;
+
+ #Log 3, ref($obj);
+ #Log 3, "Receiving: " . Dumper $obj;
+
+ if( ref($obj) eq 'ARRAY' )
+ {
+ if( defined($obj->[0]->{error}))
+ {
+ my $error = $obj->[0]->{error}->{'description'};
+
+ $hash->{STATE} = $error;
+
+ Log 3, $error;
+ }
+
+ return ($obj->[0]);
+ }
+ elsif( ref($obj) eq 'HASH' )
+ {
+ return $obj;
+ }
+
+ return undef;
+}
+
+sub HUEBridge_Register($)
+{
+ my ($hash) = @_;
+
+ my $obj = {
+ 'username' => AttrVal($hash->{NAME}, "key", ""),
+ 'devicetype' => 'fhem',
+ };
+
+ return HUEBridge_Call($hash, undef, $obj);
+}
+
+#Executes a JSON RPC
+sub
+HUEBridge_Call($$$)
+{
+ my ($hash,$path,$obj) = @_;
+
+ #Log 3, "Sending: " . Dumper $obj;
+
+ my $json = undef;
+ $json = encode_json($obj) if $obj;
+
+ return HUEBridge_HTTP_Call($hash,$path,$json);
+}
+
+#JSON RPC over HTTP
+sub HUEBridge_HTTP_Call($$$)
+{
+ my ($hash,$path,$obj) = @_;
+ my $uri = "http://" . $hash->{Host} . "/api";
+ my $method = 'GET';
+ if( defined($obj) ) {
+ $method = 'PUT';
+
+ if( $hash->{STATE} eq 'Pairing' ) {
+ $method = 'POST';
+ } else {
+ $uri .= "/" . AttrVal($hash->{NAME}, "key", "");
+ }
+ } else {
+ $uri .= "/" . AttrVal($hash->{NAME}, "key", "");
+ }
+ if( defined $path) {
+ $uri .= "/" . $path;
+ }
+ #Log 3, "Url: " . $uri;
+ my $ret = HUEBridge_HTTP_Request(0,$uri,$method,undef,$obj,undef);
+ #Log 3, Dumper $ret;
+ if( !defined($ret) ) {
+ return undef;
+ } elsif($ret eq '') {
+ return undef;
+ } elsif($ret =~ /^error:(\d){3}$/) {
+ return "HTTP Error Code " . $1;
+ }
+
+# try {
+# decode_json($ret);
+# } catch {
+# return undef;
+# }
+
+ return HUEBridge_ProcessResponse($hash,decode_json($ret));
+}
+
+#adapted version of the CustomGetFileFromURL subroutine from HttpUtils.pm
+sub
+HUEBridge_HTTP_Request($$$@)
+{
+ my ($quiet, $url, $method, $timeout, $data, $noshutdown) = @_;
+ $timeout = 4.0 if(!defined($timeout));
+
+ my $displayurl= $quiet ? "" : $url;
+ if($url !~ /^(http|https):\/\/([^:\/]+)(:\d+)?(\/.*)$/) {
+ Log 1, "HUEBridge_HTTP_Request $displayurl: malformed or unsupported URL";
+ return undef;
+ }
+
+ my ($protocol,$host,$port,$path)= ($1,$2,$3,$4);
+
+ if(defined($port)) {
+ $port =~ s/^://;
+ } else {
+ $port = ($protocol eq "https" ? 443: 80);
+ }
+ $path= '/' unless defined($path);
+
+
+ my $conn;
+ if($protocol eq "https") {
+ eval "use IO::Socket::SSL";
+ if($@) {
+ Log 1, $@;
+ } else {
+ $conn = IO::Socket::SSL->new(PeerAddr=>"$host:$port", Timeout=>$timeout);
+ }
+ } else {
+ $conn = IO::Socket::INET->new(PeerAddr=>"$host:$port", Timeout=>$timeout);
+ }
+ if(!$conn) {
+ Log 1, "HUEBridge_HTTP_Request $displayurl: Can't connect to $protocol://$host:$port\n";
+ undef $conn;
+ return undef;
+ }
+
+ $host =~ s/:.*//;
+ #my $hdr = ($data ? "POST" : "GET")." $path HTTP/1.0\r\nHost: $host\r\n";
+ my $hdr = $method." $path HTTP/1.0\r\nHost: $host\r\n";
+ if(defined($data)) {
+ $hdr .= "Content-Length: ".length($data)."\r\n";
+ $hdr .= "Content-Type: application/json";
+ }
+ $hdr .= "\r\n\r\n";
+ syswrite $conn, $hdr;
+ syswrite $conn, $data if(defined($data));
+ shutdown $conn, 1 if(!$noshutdown);
+
+ my ($buf, $ret) = ("", "");
+ $conn->timeout($timeout);
+ for(;;) {
+ my ($rout, $rin) = ('', '');
+ vec($rin, $conn->fileno(), 1) = 1;
+ my $nfound = select($rout=$rin, undef, undef, $timeout);
+ if($nfound <= 0) {
+ Log 1, "HUEBridge_HTTP_Request $displayurl: Select timeout/error: $!";
+ undef $conn;
+ return undef;
+ }
+
+ my $len = sysread($conn,$buf,65536);
+ last if(!defined($len) || $len <= 0);
+ $ret .= $buf;
+ }
+
+ $ret=~ s/(.*?)\r\n\r\n//s; # Not greedy: switch off the header.
+ my @header= split("\r\n", $1);
+ my $hostpath= $quiet ? "" : $host . $path;
+ Log 4, "HUEBridge_HTTP_Request $displayurl: Got data, length: ".length($ret);
+ if(!length($ret)) {
+ Log 4, "HUEBridge_HTTP_Request $displayurl: Zero length data, header follows...";
+ for (@header) {
+ Log 4, "HUEBridge_HTTP_Request $displayurl: $_";
+ }
+ }
+ undef $conn;
+ if($header[0] =~ /^[^ ]+ ([\d]{3})/ && $1 != 200) {
+ return "error:" . $1;
+ }
+ return $ret;
+}
+
+1;
+
+=pod
+=begin html
+
+
+HUEBridge
+
+ Module to access the bridge of the phillips hue lighting system.
+
+ The actual hue bulbs, living colors or living whites devices are defined as HUEDevice devices.
+
+
+ All newly found devices are autocreated at startup and added to the room HUEDevice.
+
+
+ Notes:
+
+ - This module needs
JSON.
+ Pleease install with 'cpan install JSON' or your method of choice.
+
+
+
+
+
+ Define
+
+ define <name> HUEBridge [<host>] [<interval>]
+
+
+ Defines a HUEBridge device with address <host>.
+
+ If [<host>] is not given the module will try to autodetect the bridge with the hue portal services.
+
+ The bridge status will be updated every <interval> seconds. The default and minimum is 60.
+
+ After a new bridge is created the pair button on the bridge has to be pressed.
+
+ Examples:
+
+ define bridge HUEBridge 10.0.1.1
+
+
+
+
+ Set
+
+ - devices
+ list the devices known to the bridge.
+
+
+
+ Set
+
+ - statusRequest
+ Update bridge status.
+
+
+
+=end html
+=cut
diff --git a/FHEM/31_HUEDevice.pm b/FHEM/31_HUEDevice.pm
new file mode 100644
index 000000000..f7a458e37
--- /dev/null
+++ b/FHEM/31_HUEDevice.pm
@@ -0,0 +1,609 @@
+
+# "Hue Personal Wireless Lighting" is a trademark owned by Koninklijke Philips Electronics N.V.,
+# see www.meethue.com for more information.
+# I am in no way affiliated with the Philips organization.
+
+package main;
+
+use strict;
+use warnings;
+
+use POSIX;
+use JSON;
+use SetExtensions;
+
+my %hueModels = (
+ LCT001 => {name => 'HUE Bulb' ,type => 'Extended color light' ,subType => 'colordimmer',},
+ LLC001 => {name => 'LivingColors G2' ,type => 'Color Light' ,subType => 'colordimmer',},
+ LLC006 => {name => 'LivingColors Iris' ,type => 'Color Light' ,subType => 'colordimmer',},
+ LLC007 => {name => 'LivingColors Bloom' ,type => 'Color Light' ,subType => 'colordimmer',},
+ LWB001 => {name => 'LivingWhites Bulb' ,type => 'Dimmable light' ,subType => 'dimmer',},
+ LWL001 => {name => 'LivingWhites Outlet' ,type => 'Dimmable plug-in unit' ,subType => 'dimmer',},
+);
+
+my %dim_values = (
+ 0 => "dim06%",
+ 1 => "dim12%",
+ 2 => "dim18%",
+ 3 => "dim25%",
+ 4 => "dim31%",
+ 5 => "dim37%",
+ 6 => "dim43%",
+ 7 => "dim50%",
+ 8 => "dim56%",
+ 9 => "dim62%",
+ 10 => "dim68%",
+ 11 => "dim75%",
+ 12 => "dim81%",
+ 13 => "dim87%",
+ 14 => "dim93%",
+);
+
+
+
+sub HUEDevice_Initialize($)
+{
+ my ($hash) = @_;
+
+ # Provide
+
+ #Consumer
+ $hash->{DefFn} = "HUEDevice_Define";
+ $hash->{UndefFn} = "HUEDevice_Undefine";
+ $hash->{SetFn} = "HUEDevice_Set";
+ $hash->{GetFn} = "HUEDevice_Get";
+ $hash->{AttrList} = "IODev ".
+ "$readingFnAttributes ".
+ "model:".join(",", sort keys %hueModels)." ".
+ "subType:colordimmer,dimmer,switch";
+}
+
+sub HUEDevice_Define($$)
+{
+ my ($hash, $def) = @_;
+
+ my @args = split("[ \t]+", $def);
+
+ return "Usage: define HUEDevice [interval]" if(@args < 3);
+
+ my ($name, $type, $id, $interval) = @args;
+
+ $interval= 60 unless defined($interval);
+ if( $interval < 10 ) { $interval = 60; }
+
+
+ $hash->{STATE} = 'Initialized';
+ $hash->{fhem}{interfaces}= "dimmer";
+
+ $hash->{fhem}{id} = $id;
+ $hash->{INTERVAL} = $interval;
+
+ $hash->{fhem}{on} = -1;
+ $hash->{fhem}{colormode} = '';
+ $hash->{fhem}{bri} = -1;
+ $hash->{fhem}{ct} = -1;
+ $hash->{fhem}{hue} = -1;
+ $hash->{fhem}{sat} = -1;
+ $hash->{fhem}{xy} = '';
+
+
+ CommandAttr(undef,$name.' webCmd rgb:toggle:on:off') if( !defined( AttrVal($hash->{NAME}, "webCmd", undef) ) );
+ CommandAttr(undef,$name.' devStateIcon {CommandGet("","'.$name.' devStateIcon")}') if( !defined( AttrVal($hash->{NAME}, "devStateIcon", undef) ) );
+
+ AssignIoPort($hash);
+ if(defined($hash->{IODev}->{NAME})) {
+ Log 3, "$name: I/O device is " . $hash->{IODev}->{NAME};
+ } else {
+ Log 1, "$name: no I/O device";
+ }
+
+ #HUEDevice_GetUpdate($hash);
+ InternalTimer(gettimeofday()+10, "HUEDevice_GetUpdate", $hash, 0);
+
+ return undef;
+}
+
+sub HUEDevice_Undefine($$)
+{
+ my ($hash,$arg) = @_;
+
+ RemoveInternalTimer($hash);
+
+ delete($hash->{fhem}{id});
+
+ return undef;
+}
+
+sub
+HUEDevice_SetParam($$@)
+{
+ my ($name, $obj, $cmd, $value, $value2) = @_;
+
+ if( $cmd eq "color" ) {
+ $value = int(1000000/$value);
+ $cmd = 'ct';
+ } elsif( $cmd eq "toggle" ) {
+ $cmd = ReadingsVal($name,"state","on") eq "off" ? "on" :"off";
+ } elsif( $cmd =~ m/^dim(\d+)/ ) {
+ $value = $1 unless defined($value);
+ if( $value < 0 ) { $value = 0; }
+ if( $value > 100 ) { $value = 100; }
+ $cmd = 'pct';
+ } elsif( !defined($value) && $cmd =~ m/^(\d+)/) {
+ $value = $1;
+ $value = 254 if( $value > 254 );
+ $cmd = 'bri';
+ }
+
+ if($cmd eq 'on') {
+ $obj->{'on'} = JSON::true;
+ $obj->{'bri'} = 254 if( ReadingsVal($name,"bri","0") eq 0 );
+ $obj->{'transitiontime'} = $value / 10 if( defined($value) );
+ } elsif($cmd eq 'off') {
+ $obj->{'on'} = JSON::false;
+ $obj->{'transitiontime'} = $value / 10 if( defined($value) );
+ } elsif($cmd eq "pct") {
+ $obj->{'on'} = JSON::true;
+ $obj->{'bri'} = int(2.54 * $value);
+ $obj->{'transitiontime'} = $value2 / 10 if( defined($value2) );
+ } elsif($cmd eq "bri") {
+ $obj->{'on'} = JSON::true;
+ $obj->{'bri'} = 0+$value;
+ $obj->{'transitiontime'} = $value2 / 10 if( defined($value2) );
+ } elsif($cmd eq "ct") {
+ $obj->{'on'} = JSON::true;
+ $obj->{'ct'} = 0+$value;
+ $obj->{'transitiontime'} = $value2 / 10 if( defined($value2) );
+ } elsif($cmd eq "hue") {
+ $obj->{'on'} = JSON::true;
+ $obj->{'hue'} = 0+$value;
+ $obj->{'transitiontime'} = $value2 / 10 if( defined($value2) );
+ } elsif($cmd eq "sat") {
+ $obj->{'on'} = JSON::true;
+ $obj->{'sat'} = 0+$value;
+ $obj->{'transitiontime'} = $value2 / 10 if( defined($value2) );
+ } elsif($cmd eq "xy" && $value =~ m/^(.+),(.+)/) {
+ my ($x,$y) = ($1, $2);
+ $obj->{'on'} = JSON::true;
+ $obj->{'xy'} = [0+$x, 0+$y];
+ $obj->{'transitiontime'} = $value2 / 10 if( defined($value2) );
+ } elsif( $cmd eq "rgb" && $value =~ m/^(..)(..)(..)/) {
+ # calculation from http://www.everyhue.com/vanilla/discussion/94/rgb-to-xy-or-hue-sat-values/p1
+ my( $r, $g, $b ) = (hex($1)/255.0, hex($2)/255.0, hex($3)/255.0);
+#Log 3, "rgb: ". $r . " " . $g ." ". $b;
+
+ my $X = 1.076450 * $r - 0.237662 * $g + 0.161212 * $b;
+ my $Y = 0.410964 * $r + 0.554342 * $g + 0.034694 * $b;
+ my $Z = -0.010954 * $r - 0.013389 * $g + 1.024343 * $b;
+#Log 3, "XYZ: ". $X . " " . $Y ." ". $Y;
+
+ if( $X != 0
+ || $Y != 0
+ || $Z != 0 ) {
+ my $x = $X / ($X + $Y + $Z);
+ my $y = $Y / ($X + $Y + $Z);
+#Log 3, "xyY:". $x . " " . $y ." ". $Y;
+
+ #$x = 0 if( $x < 0 );
+ #$x = 1 if( $x > 1 );
+ #$y = 0 if( $y < 0 );
+ #$y = 1 if( $y > 1 );
+ $Y = 1 if( $Y > 1 );
+
+ my $bri = max($r,max($g,$b));
+ #my $bri = $Y;
+
+ $obj->{'on'} = JSON::true;
+ $obj->{'xy'} = [0+$x, 0+$y];
+ $obj->{'bri'} = int(254*$bri);
+ }
+ } elsif( $cmd eq "hsv" && $value =~ m/^(..)(..)(..)/) {
+ my( $h, $s, $v ) = (hex($1), hex($2), hex($3));
+
+ $s = 254 if( $s > 254 );
+ $v = 254 if( $v > 254 );
+
+ $obj->{'on'} = JSON::true;
+ $obj->{'hue'} = int($h*256);
+ $obj->{'sat'} = 0+$s;
+ $obj->{'bru'} = 0+$v;
+ } elsif( $cmd eq "effect" ) {
+ $obj->{'effect'} = $value;
+ } elsif( $cmd eq "transitiontime" ) {
+ $obj->{'transitiontime'} = 0+$value;
+ } else {
+ return 0;
+ }
+
+ return 1;
+}
+sub HUEDevice_Set($@);
+sub
+HUEDevice_Set($@)
+{
+ my ($hash, $name, @aa) = @_;
+
+ my %obj;
+
+ if( (my $joined = join(" ", @aa)) =~ /:/ ) {
+ my @cmds = split(":", $joined);
+ for( my $i = 0; $i <= $#cmds; ++$i ) {
+ HUEDevice_SetParam($name, \%obj, split(" ", $cmds[$i]) );
+ }
+ } else {
+ my ($cmd, $value, $value2, @a) = @aa;
+
+ if( $cmd eq "statusRequest" ) {
+ RemoveInternalTimer($hash);
+ HUEDevice_GetUpdate($hash);
+ return undef;
+ }
+
+ HUEDevice_SetParam($name, \%obj, $cmd, $value, $value2);
+ }
+
+
+ if( scalar keys %obj ) {
+ my $result = HUEDevice_ReadFromServer($hash,$hash->{fhem}{id}."/state",\%obj);
+ if( $result->{'error'} ) {
+ $hash->{STATE} = $result->{'error'}->{'description'};
+ return undef;
+ }
+
+ $hash->{LOCAL} = 1;
+ HUEDevice_GetUpdate($hash);
+ delete $hash->{LOCAL};
+
+ return undef;
+ }
+
+ my $list = "off on toggle statusRequest";
+ $list .= " pct:slider,0,1,100 bri:slider,0,1,254" if( AttrVal($hash->{NAME}, "subType", "colordimmer") =~ m/dimmer/ );
+ #$list .= " dim06% dim12% dim18% dim25% dim31% dim37% dim43% dim50% dim56% dim62% dim68% dim75% dim81% dim87% dim93% dim100%" if( AttrVal($hash->{NAME}, "subType", "colordimmer") =~ m/dimmer/ );
+ $list .= " rgb:colorpicker,RGB color:slider,2000,1,6500 ct:slider,154,1,500 hue:slider,0,1,65535 sat:slider,0,1,254 xy effect:none,colorloop" if( AttrVal($hash->{NAME}, "subType", "colordimmer") =~ m/color/ );
+ return SetExtensions($hash, $list, $name, @aa);
+}
+
+sub
+HUEDevice_Get($@)
+{
+ my ($hash, @a) = @_;
+
+ my $name = $a[0];
+ return "$name: get needs at least one parameter" if(@a < 2);
+
+ my $cmd= $a[1];
+
+ if($cmd eq "rgb") {
+ my $r = 0;
+ my $g = 0;
+ my $b = 0;
+
+ if( ReadingsVal($name,"colormode","") eq "ct" ) {
+ if( ReadingsVal($name,"ct","") =~ m/(\d+) .*/ ) {
+ # calculation from http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code
+ # adjusted by 1000K
+ my $temp = (1000000/$1)/100 + 10;
+
+ $r = 255;
+ $r = 329.698727446 * ($temp - 60) ** -0.1332047592 if( $temp > 66 );
+ $r = 0 if( $r < 0 );
+ $r = 255 if( $r > 255 );
+
+ if( $temp <= 66 ) {
+ $g = 99.4708025861 * log($temp) - 161.1195681661;
+ } else {
+ $g = 288.1221695283 * ($temp - 60) ** -0.0755148492;
+ }
+ $g = 0 if( $g < 0 );
+ $g = 255 if( $g > 255 );
+
+ $b = 255;
+ $b = 0 if( $temp <= 19 );
+ if( $temp < 66 ) {
+ $b = 138.5177312231 * log($temp-10) - 305.0447927307;
+ }
+ $b = 0 if( $b < 0 );
+ $b = 255 if( $b > 255 );
+ }
+ } elsif( ReadingsVal($name,"xy","") =~ m/(.+),(.+)/ ) {
+ # calculation from http://www.brucelindbloom.com/index.html
+ my ($x,$y) = ($1, $2);
+ my $Y = ReadingsVal($name,"bri","") / 254.0;
+#Log 3, "xyY:". $x . " " . $y ." ". $Y;
+
+ if( $y > 0 ) {
+ my $X = $x * $Y / $y;
+ my $Z = (1 - $x - $y)*$Y / $y;
+
+ if( $X > 1
+ || $Y > 1
+ || $Z > 1 ) {
+ my $f = max($X,max($Y,$Z));
+ $X /= $f;
+ $Y /= $f;
+ $Z /= $f;
+ }
+#Log 3, "XYZ: ". $X . " " . $Y ." ". $Y;
+
+ $r = 0.7982 * $X + 0.3389 * $Y - 0.1371 * $Z;
+ $g = -0.5918 * $X + 1.5512 * $Y + 0.0406 * $Z;
+ $b = 0.0008 * $X + 0.0239 * $Y + 0.9753 * $Z;
+
+ if( $r > 1
+ || $g > 1
+ || $b > 1 ) {
+ my $f = max($r,max($g,$b));
+ $r /= $f;
+ $g /= $f;
+ $b /= $f;
+ }
+#Log 3, "rgb: ". $r . " " . $g ." ". $b;
+
+ $r *= 255;
+ $g *= 255;
+ $b *= 255;
+ }
+ }
+ return sprintf( "%02x%02x%02x", $r+0.5, $g+0.5, $b+0.5 );
+ } elsif ( $cmd eq "devStateIcon" ) {
+ return ' '
+ if( ReadingsVal($name,"state","off") eq "off" || ReadingsVal($name,"bri","0") eq 0 );
+
+ return ' '
+ if( AttrVal($hash->{NAME}, "model", "") eq "LWL001" );
+
+ return ''.
+ '  '.
+ ' ' if( ReadingsVal($name,"colormode","") eq "ct" );
+
+ return '';
+ }
+
+ return "Unknown argument $cmd, choose one of rgb devStateIcon";
+}
+
+
+###################################
+# This could be IORead in fhem, But there is none.
+# Read http://forum.fhem.de/index.php?t=tree&goto=54027&rid=10#msg_54027
+# to find out why.
+sub
+HUEDevice_ReadFromServer($@)
+{
+ my ($hash,@a) = @_;
+
+ my $dev = $hash->{NAME};
+ no strict "refs";
+ my $ret;
+ unshift(@a,$dev);
+ $ret = IOWrite($hash, @a);
+ use strict "refs";
+ return $ret;
+ return if(IsDummy($dev) || IsIgnored($dev));
+ my $iohash = $hash->{IODev};
+ if(!$iohash ||
+ !$iohash->{TYPE} ||
+ !$modules{$iohash->{TYPE}} ||
+ !$modules{$iohash->{TYPE}}{ReadFn}) {
+ Log 5, "No I/O device or ReadFn found for $dev";
+ return;
+ }
+
+ no strict "refs";
+ #my $ret;
+ unshift(@a,$dev);
+ $ret = &{$modules{$iohash->{TYPE}}{ReadFn}}($iohash, @a);
+ use strict "refs";
+ return $ret;
+}
+
+sub
+HUEDevice_GetUpdate($)
+{
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+
+ if(!$hash->{LOCAL}) {
+ RemoveInternalTimer($hash);
+ InternalTimer(gettimeofday()+$hash->{INTERVAL}, "HUEDevice_GetUpdate", $hash, 1);
+ }
+
+ my $result = HUEDevice_ReadFromServer($hash,$hash->{fhem}{id});
+ if( !defined($result) ) {
+ $hash->{STATE} = "unknown";
+ return;
+ } elsif( $result->{'error'} ) {
+ $hash->{STATE} = $result->{'error'}->{'description'};
+ return;
+ }
+
+ $hash->{modelid} = $result->{'modelid'};
+ $hash->{name} = $result->{'name'};
+ $hash->{type} = $result->{'type'};
+ $hash->{swversion} = $result->{'swversion'};
+
+ $attr{$name}{model} = $result->{'modelid'} unless( defined($attr{$name}{model}) || $result->{'modelid'} eq '' );
+ $attr{$name}{subType} = $hueModels{$attr{$name}{model}}{subType} unless( defined($attr{$name}{subType})
+ || !defined($attr{$name}{model})
+ || !defined($hueModels{$attr{$name}{model}}{subType}) );
+
+ readingsBeginUpdate($hash);
+
+ my $state = $result->{'state'};
+
+ my $on = $state->{on};
+ my $colormode = $state->{'colormode'};
+ my $bri = $state->{'bri'};
+ my $ct = $state->{'ct'};
+ my $hue = $state->{'hue'};
+ my $sat = $state->{'sat'};
+ my $xy = ",";
+ $xy = $state->{'xy'}->[0] .",". $state->{'xy'}->[1] if( defined($state->{'xy'}) );
+
+ if( defined($colormode) && $colormode ne $hash->{fhem}{colormode} ) {readingsBulkUpdate($hash,"colormode",$colormode);}
+ if( defined($bri) && $bri != $hash->{fhem}{bri} ) {readingsBulkUpdate($hash,"bri",$bri);}
+ if( defined($ct) && $ct != $hash->{fhem}{ct} ) {
+ if( $ct == 0 ) {
+ readingsBulkUpdate($hash,"ct",$ct);
+ }
+ else {
+ readingsBulkUpdate($hash,"ct",$ct . " (".int(1000000/$ct)."K)");
+ }
+ }
+ if( defined($hue) && $hue != $hash->{fhem}{hue} ) {readingsBulkUpdate($hash,"hue",$hue);}
+ if( defined($sat) && $sat != $hash->{fhem}{sat} ) {readingsBulkUpdate($hash,"sat",$sat);}
+ if( $xy eq "," ) {readingsBulkUpdate($hash,"xy","");}
+ elsif( $xy ne $hash->{fhem}{xy} ) {readingsBulkUpdate($hash,"xy",$xy);}
+
+ my $s = '';
+ if( $on )
+ {
+ $s = 'on';
+ if( $on != $hash->{fhem}{on} ) {readingsBulkUpdate($hash,"onoff",1);}
+
+ my $percent = int( $state->{'bri'} * 100 / 254 );
+ if( $bri != $hash->{fhem}{bri} ) {readingsBulkUpdate($hash,"level", $percent . ' %');}
+ if( $bri != $hash->{fhem}{bri} ) {readingsBulkUpdate($hash,"pct", $percent);}
+ if( $percent > 0
+ && $percent < 100 ) {
+ $s = $dim_values{int($percent/7)};
+ }
+ $s = 'off' if( $percent == 0 );
+ }
+ else
+ {
+ $s = 'off';
+ if( $on != $hash->{fhem}{on} ) {readingsBulkUpdate($hash,"onoff",0);}
+ }
+
+ if( $s ne $hash->{STATE} ) {readingsBulkUpdate($hash,"state",$s);}
+ readingsEndUpdate($hash,defined($hash->{LOCAL} ? 0 : 1));
+
+ $hash->{fhem}{on} = $on;
+ $hash->{fhem}{colormode} = $colormode;
+ $hash->{fhem}{bri} = $bri;
+ $hash->{fhem}{ct} = $ct;
+ $hash->{fhem}{hue} = $hue;
+ $hash->{fhem}{sat} = $sat;
+ $hash->{fhem}{xy} = $xy;
+}
+
+1;
+
+=pod
+=begin html
+
+
+HUEDevice
+
+
+
+ Define
+
+ define <name> HUEDevice <id> [<interval>]
+
+
+ Defines a device connected to a HUEBridge.
+
+ This can be a hue bulb, a living colors light or a living whites bulb or dimmer plug.
+
+ The device status will be updated every <interval> seconds. The default and minimum is 60.
+
+ Examples:
+
+ define bulb HUEDevice 1
+ define LC HUEDevice 2
+
+
+
+
+ Readings
+
+ - bri
+ the brightness reported from the device. the value can be betwen 1 and 254
+ - colormode
+ the current colormode
+ - ct
+ the colortemperature in mireds and kelvin
+ - hue
+ the current hue
+ - level
+ the current brightness in percent
+ - onoff
+ the current on/off state as 0 or 1
+ - sat
+ the current saturation
+ - xy
+ the current xy color coordinates
+ - state
+ the current state
+
+ Notes:
+
+ - not all readings show the actual device state. all readings not related to the current colormode have to be ignored.
+ - the actual state of a device controlled by a living colors or living whites remote can be different and will
+ be updated after some time.
+
+
+
+
+ Set
+
+ - on [<ramp-time>]
+ - off [<ramp-time>]
+ - toggle [<ramp-time>]
+ - statusRequest
+ Request device status update.
+ - pct <value> [<ramp-time>]
+ dim to <value>
+ Note: the FS20 compatible dimXX% commands are also accepted.
+ - color <value>
+ set colortemperature to <value> kelvin.
+ - bri <value>
+ set brighness to <value>; range is 1-254.
+ - ct <value>
+ set colortemperature to <value> mireds; range is 154-500.
+ - hue <value>
+ set hue to <value>; range is 0-65535.
+ - sat <value>
+ set saturation to <value>; range is 0-254.
+ - xy <x>,<y>
+ set the xy color coordinates to <x>,<y>
+ - effect [none|colorloop]
+ - transitiontime <time>
+ set the transitiontime to <time> 1/10s
+ - rgb <rrggbb>
+
+ Note:
+
+ - multiple paramters can be set at once separated by
:
+ Examples:
+ set LC on : transitiontime 100
+ set bulb on : bri 100 : color 4000
+
+
+
+
+ Get
+
+ - rgb
+ - devStateIcon
+ returns html code that can be used to create an icon that represents the device color in the room overview.
+
+
+
+ Attributes
+
+ - subType
+ dimmer or switch, default is dimmer.
+ - devStateIcon
+ will be initialized to {CommandGet("","<name> devStateIcon")} to show device color as default in room overview.
+
+
+
+
+=end html
+=cut
diff --git a/FHEM/31_LightScene.pm b/FHEM/31_LightScene.pm
new file mode 100644
index 000000000..2394263d3
--- /dev/null
+++ b/FHEM/31_LightScene.pm
@@ -0,0 +1,329 @@
+
+package main;
+
+use strict;
+use warnings;
+use POSIX;
+use JSON;
+
+sub LightScene_Initialize($)
+{
+ my ($hash) = @_;
+
+ $hash->{DefFn} = "LightScene_Define";
+ $hash->{NotifyFn} = "LightScene_Notify";
+ $hash->{UndefFn} = "LightScene_Undefine";
+ $hash->{SetFn} = "LightScene_Set";
+ $hash->{GetFn} = "LightScene_Get";
+}
+
+sub LightScene_Define($$)
+{
+ my ($hash, $def) = @_;
+
+ my @args = split("[ \t]+", $def);
+
+ return "Usage: define LightScene +" if(@args < 3);
+
+ my $name = shift(@args);
+ my $tyoe = shift(@args);
+
+ my %list;
+ foreach my $a (@args) {
+ foreach my $d (devspec2array($a)) {
+ $list{$d} = 1;
+ }
+ }
+ $hash->{CONTENT} = \%list;
+
+ my %scenes;
+ $hash->{SCENES} = \%scenes;
+
+ LightScene_Load($hash);
+
+ $hash->{STATE} = 'Initialized';
+
+ return undef;
+}
+
+sub LightScene_Undefine($$)
+{
+ my ($hash,$arg) = @_;
+
+ LightScene_Save();
+
+ return undef;
+}
+
+sub
+LightScene_Notify($$)
+{
+ my ($hash,$dev) = @_;
+ my $name = $hash->{NAME};
+ my $type = $hash->{TYPE};
+
+ return if($dev->{NAME} ne "global");
+
+ if( grep(m/^INITIALIZED$/, @{$dev->{CHANGED}}) ) {
+ } elsif( grep(m/^SAVE$/, @{$dev->{CHANGED}}) ) {
+ LightScene_Save();
+ }
+
+ return undef;
+}
+
+sub
+myStatefileName()
+{
+ my $statefile = $attr{global}{statefile};
+ $statefile = substr $statefile,0,rindex($statefile,'/')+1;
+ return $statefile ."LightScenes.save";
+}
+my $LightScene_LastSaveTime="";
+sub
+LightScene_Save()
+{
+ my $time_now = TimeNow();
+ return if( $time_now eq $LightScene_LastSaveTime);
+ $LightScene_LastSaveTime = $time_now ;
+
+ return "No statefile specified" if(!$attr{global}{statefile});
+ my $statefile = myStatefileName();
+
+ my $hash;
+ for my $d (keys %defs) {
+ next if($defs{$d}{TYPE} ne "LightScene");
+ next if( !defined($defs{$d}{SCENES}) );
+
+ $hash->{$d} = $defs{$d}{SCENES} if( keys(%{$defs{$d}{SCENES}}) );
+ }
+
+ if(open(FH, ">$statefile")) {
+ my $t = localtime;
+ print FH "#$t\n";
+
+ print FH encode_json($hash) if( defined($hash) );
+
+ close(FH);
+ } else {
+
+ my $msg = "LightScene_Save: Cannot open $statefile: $!";
+ Log 1, $msg;
+ }
+
+ return undef;
+}
+sub
+LightScene_Load($)
+{
+ my ($hash) = @_;
+
+ return "No statefile specified" if(!$attr{global}{statefile});
+ my $statefile = myStatefileName();
+
+ if(open(FH, "<$statefile")) {
+ my $json;
+ while (my $line = ) {
+ chomp $line;
+ next if($line =~ m/^#.*$/);
+ $json .= $line;
+ }
+
+ close(FH);
+
+ return if( !defined($json) );
+
+ my $decoded = decode_json( $json );
+
+ if( defined($decoded->{$hash->{NAME}}) ) {
+ $hash->{SCENES} = $decoded->{$hash->{NAME}};
+ }
+ } else {
+ my $msg = "LightScene_Load: Cannot open $statefile: $!";
+ Log 1, $msg;
+ }
+ return undef;
+}
+
+
+sub
+LightScene_Set($@)
+{
+ my ($hash, $name, $cmd, $scene, @a) = @_;
+ my $ret = "";
+
+ if( !defined($cmd) ){ return "$name: set needs at least one parameter" };
+
+ if( $cmd eq "?" ){ return "Unknown argument ?, choose one of remove save set scene:".join(",", sort keys %{$hash->{SCENES}}) };
+
+ if( $cmd eq "save" && !defined( $scene ) ) { return "Usage: set $name save " };
+ if( $cmd eq "scene" && !defined( $scene ) ) { return "Usage: set $name scene " };
+ if( $cmd eq "remove" && !defined( $scene ) ) { return "Usage: set $name remove " };
+
+ if( $cmd eq "remove" ) {
+ delete( $hash->{SCENES}{$scene} );
+ return undef;
+ } elsif( $cmd eq "set" ) {
+ my ($d, @args) = @a;
+
+ if( !defined( $scene ) || !defined( $d ) || !defined( @args ) ) { return "Usage: set $name set " };
+
+ if( defined($hash->{SCENES}{$scene})
+ && defined($hash->{SCENES}{$scene}{$d}) )
+ {
+ $hash->{SCENES}{$scene}{$d} = join(" ", @args);
+ return undef;
+ }
+ }
+
+
+ $hash->{INSET} = 1;
+
+ foreach my $d (sort keys %{ $hash->{CONTENT} }) {
+ next if(!$defs{$d});
+ if($defs{$d}{INSET}) {
+ Log 1, "ERROR: endless loop detected for $d in " . $hash->{NAME};
+ next;
+ }
+
+ if( $cmd eq "save" ) {
+ my $status = "";
+ if( $defs{$d}{TYPE} eq 'CUL_HM' ) {
+ my $subtype = AttrVal($d,"subType","");
+ if( $subtype eq "switch" ) {
+ $status = Value($d);
+ } elsif( $subtype eq "dimmer" ) {
+ $status = Value($d);
+ } else {
+ $status = Value($d);
+ }
+ } elsif( $defs{$d}{TYPE} eq 'FS20' ) {
+ $status = Value($d);
+ } elsif( $defs{$d}{TYPE} eq 'HUEDevice' ) {
+ my $subtype = AttrVal($d,"subType","");
+ if( $subtype eq "switch" || Value($d) eq "off" ) {
+ $status = Value($d);
+ } elsif( $subtype eq "dimmer" ) {
+ $status = "bri ". ReadingsVal($d,'bri',"0");
+ } elsif( $subtype eq "colordimmer" ) {
+ if( ReadingsVal($d,"colormode","") eq "ct" ) {
+ ReadingsVal($d,"ct","") =~ m/(\d+) .*/;
+ $status = "bri ". ReadingsVal($d,'bri',"0") ." : ct ". $1;
+ } else {
+ $status = "bri ". ReadingsVal($d,'bri',"0") ." : xy ". ReadingsVal($d,'xy',"");
+ }
+ }
+ } elsif( $defs{$d}{TYPE} eq 'IT' ) {
+ my $subtype = AttrVal($d,"model","");
+ if( $subtype eq "itswitch" ) {
+ $status = Value($d);
+ } elsif( $subtype eq "itdimmer" ) {
+ $status = Value($d);
+ } else {
+ $status = Value($d);
+ }
+ } elsif( $defs{$d}{TYPE} eq 'TRX_LIGHT' ) {
+ $status = Value($d);
+ } else {
+ $status = Value($d);
+ }
+
+ $hash->{SCENES}{$scene}{$d} = $status;
+ $ret .= $d .": ". $status ."\n";
+
+ } elsif ( $cmd eq "scene" ) {
+ $hash->{STATE} = $scene;
+ $ret .= " ". CommandSet(undef,"$d $hash->{SCENES}{$scene}{$d}");
+ } else {
+ $ret = "Unknown argument $cmd, choose one of save scene";
+ }
+ }
+
+ delete($hash->{INSET});
+ Log GetLogLevel($hash->{NAME},5), "SET: $ret" if($ret);
+
+ return $ret;
+
+ return undef;
+}
+
+sub
+LightScene_Get($@)
+{
+ my ($hash, @a) = @_;
+
+ my $name = $a[0];
+ return "$name: get needs at least one parameter" if(@a < 2);
+
+ my $cmd= $a[1];
+ if( $cmd eq "scene" && @a < 3 ) { return "Usage: get scene " };
+
+ my $ret = "";
+ if( $cmd eq "scenes" ) {
+ foreach my $scene (sort keys %{ $hash->{SCENES} }) {
+ $ret .= $scene ."\n";
+ }
+ return $ret;
+ } elsif( $cmd eq "scene" ) {
+ my $ret = "";
+ my $scene = $a[2];
+ if( defined($hash->{SCENES}{$scene}) ) {
+ foreach my $d (sort keys %{ $hash->{SCENES}{$scene} }) {
+ $ret .= $d .": ". $hash->{SCENES}{$scene}{$d} ."\n";
+ }
+ } else {
+ $ret = "no scene <$scene> defined";
+ }
+ return $ret;
+ }
+
+ return "Unknown argument $cmd, choose one of scenes scene";
+}
+
+1;
+
+=pod
+=begin html
+
+
+LightScene
+
+ Allows to store the state of a group of lights and recall it later. Multiple states for one group can be stored.
+
+
+
+ Define
+
+ define <name> LightScene [<dev1>] [<dev2>] [<dev3>] ...
+
+
+ Examples:
+
+ define light_group LightScene Lampe1 Lampe2 Dimmer1
+
+
+
+
+ Set
+
+ - save <scene_name>
+ save current state for alle devices in this LightScene to <scene_name>
+ - scene <scene_name>
+ shows scene <scene_name> - all devices are switched to the previously saved state
+ - set <scene_name> <device> <cmd>
+ set the saved state of <device> in <scene_name> to <cmd>
+ - remove <scene_name>
+ remove <scene_name> from list of saved scenes
+
+
+
+ Get
+
+ - scenes
+ - scene <scene_name>
+
+
+
+
+=end html
+=cut
diff --git a/FHEM/32_SYSSTAT.pm b/FHEM/32_SYSSTAT.pm
index 62caae85c..951643cb2 100644
--- a/FHEM/32_SYSSTAT.pm
+++ b/FHEM/32_SYSSTAT.pm
@@ -3,7 +3,8 @@ package main;
use strict;
use warnings;
-use Sys::Statistics::Linux;
+use Sys::Statistics::Linux::LoadAVG;
+use Sys::Statistics::Linux::DiskUsage;
sub
SYSSTAT_Initialize($)
@@ -12,8 +13,9 @@ SYSSTAT_Initialize($)
$hash->{DefFn} = "SYSSTAT_Define";
$hash->{UndefFn} = "SYSSTAT_Undefine";
+ $hash->{GetFn} = "SYSSTAT_Get";
$hash->{AttrFn} = "SYSSTAT_Attr";
- $hash->{AttrList} = "filesystems showpercent useregex loglevel:0,1,2,3,4,5,6 ".
+ $hash->{AttrList} = "filesystems showpercent:1 useregex:1 ssh_user loglevel:0,1,2,3,4,5,6 ".
$readingFnAttributes;
}
@@ -26,21 +28,55 @@ SYSSTAT_Define($$)
my @a = split("[ \t][ \t]*", $def);
- return "Usage: define SYSSTAT [interval]" if(@a < 2);
+ return "Usage: define SYSSTAT [interval [interval_fs [host]]]" if(@a < 2);
my $interval = 60;
if(int(@a)>=3) { $interval = $a[2]; }
if( $interval < 60 ) { $interval = 60; }
+ my $interval_fs = $interval * 60;
+ if(int(@a)>=4) { $interval_fs = $a[3]; }
+ if( $interval_fs < $interval ) { $interval_fs = $interval; }
+ if( $interval_fs == $interval ) { $interval_fs = undef; }
+
+ my $host = $a[4] if(int(@a)>=5);;
+
+ delete( $hash->{INTERVAL_FS} );
+ delete( $hash->{HOST} );
+
$hash->{STATE} = "Initialized";
$hash->{INTERVAL} = $interval;
+ $hash->{INTERVAL_FS} = $interval_fs if( defined( $interval_fs ) );
- $hash->{xls} = Sys::Statistics::Linux->new( loadavg => 1 );
+ $hash->{HOST} = $host if( defined( $host ) );
+
+ $hash->{interval_fs} = $interval_fs;
+ SYSSTAT_InitSys( $hash );
InternalTimer(gettimeofday()+$hash->{INTERVAL}, "SYSSTAT_GetUpdate", $hash, 0);
return undef;
}
+sub
+SYSSTAT_InitSys( $ )
+{
+ my ($hash) = @_;
+
+ if( defined($hash->{HOST}) ) {
+ my $cmd = qx(which ssh);
+ chomp( $cmd );
+ my $user = AttrVal($hash->{NAME}, "ssh_user", undef );
+ $cmd .= ' ';
+ $cmd .= $user."\@" if( defined($user) );
+ $cmd .= $hash->{HOST}." df -kP 2>/dev/null";
+ $hash->{loadavg} = Sys::Statistics::Linux::LoadAVG->new;
+ $hash->{diskusage} = Sys::Statistics::Linux::DiskUsage->new( cmd => { path => '',
+ df => $cmd } );
+ } else {
+ $hash->{loadavg} = Sys::Statistics::Linux::LoadAVG->new;
+ $hash->{diskusage} = Sys::Statistics::Linux::DiskUsage->new;
+ }
+}
sub
SYSSTAT_Undefine($$)
@@ -51,50 +87,94 @@ SYSSTAT_Undefine($$)
return undef;
}
+sub
+SYSSTAT_Get($@)
+{
+ my ($hash, @a) = @_;
+
+ my $name = $a[0];
+ return "$name: get needs at least one parameter" if(@a < 2);
+
+ my $cmd= $a[1];
+
+ if($cmd eq "filesystems") {
+
+ my $filesystems = $hash->{diskusage}->get;
+
+ my $ret;
+ $ret .= " <= \n";
+ foreach my $filesystem (keys %$filesystems ) {
+ $ret .= $filesystem ." <= ". $filesystems->{$filesystem}->{mountpoint} ."\n";
+ }
+ return $ret;
+ }
+
+ return "Unknown argument $cmd, choose one of filesystems";
+}
+
sub
SYSSTAT_Attr($$$)
{
my ($cmd, $name, $attrName, $attrVal) = @_;
- my $hash = $defs{$name};
$attrVal= "" unless defined($attrVal);
- $attrVal= "" if($cmd eq "useregex");
- $attrVal= "" if($cmd eq "showpercent");
+ my $orig = $attrVal;
+ $attrVal= "1" if($attrName eq "useregex");
+ $attrVal= "1" if($attrName eq "showpercent");
if( $attrName eq "filesystems") {
+ my $hash = $defs{$name};
my @filesystems = split(",",$attrVal);
@{$hash->{filesystems}} = @filesystems;
+ } elsif( $attrName eq "ssh_user") {
+ $attr{$name}{$attrName} = $attrVal;
+ my $hash = $defs{$name};
+ SYSSTAT_InitSys( $hash );
+ }
- if( $#filesystems >= 0 ) {
- $hash->{xls}->set( loadavg => 1,
- diskusage => 1 );
- } else {
- $hash->{xls}->set( loadavg => 1,
- diskusage => 0 );
+ if( $cmd eq "set" ) {
+ if( $orig ne $attrVal ) {
+ $attr{$name}{$attrName} = $attrVal;
+ return $attrName ." set to ". $attrVal;
}
}
return;
}
+sub SYSSTAT_getLoadAVG( $ );
sub
SYSSTAT_GetUpdate($)
{
my ($hash) = @_;
if(!$hash->{LOCAL}) {
+ RemoveInternalTimer($hash);
InternalTimer(gettimeofday()+$hash->{INTERVAL}, "SYSSTAT_GetUpdate", $hash, 1);
}
- my $stat = $hash->{xls}->get;
-
- my $load = $stat->{loadavg};
+ #my $load = $hash->{loadavg}->get;
+ my $load = SYSSTAT_getLoadAVG( $hash );
$hash->{STATE} = $load->{avg_1} . " " . $load->{avg_5} . " " . $load->{avg_15};
readingsSingleUpdate($hash,"load",$load->{avg_1},defined($hash->{LOCAL} ? 0 : 1));
- if( defined(my $usage = $stat->{diskusage}) ){
+ my $do_diskusage = 1;
+ if( defined($hash->{INTERVAL_FS} ) ) {
+ $do_diskusage = 0;
+ $hash->{interval_fs} -= $hash->{INTERVAL};
+
+ if( $hash->{interval_fs} <= 0 ) {
+ $do_diskusage = 1;
+ $hash->{interval_fs} += $hash->{INTERVAL_FS};
+ }
+ }
+
+ if( $do_diskusage
+ && $#{$hash->{filesystems}} >= 0 ) {
+
+ my $usage = $hash->{diskusage}->get;
my $type = 'free';
if( AttrVal($hash->{NAME}, "showpercent", "") ne "" ) {
@@ -119,6 +199,44 @@ SYSSTAT_GetUpdate($)
}
}
+sub SYSSTAT_getLoadAVG( $ )
+{
+ my ($hash) = @_;
+
+ if( defined($hash->{HOST}) ) {
+ no strict;
+ no warnings 'redefine';
+ local *Sys::Statistics::Linux::LoadAVG::get = sub {
+ my $self = shift;
+ my $class = ref $self;
+ my $file = $self->{files};
+ my %lavg = ();
+
+ my $cmd = qx(which ssh);
+ chomp( $cmd );
+ my $user = AttrVal($hash->{NAME}, "ssh_user", undef );
+ $cmd .= ' ';
+ $cmd .= $user."\@" if( defined($user) );
+ $cmd .= $hash->{HOST}." cat /proc/loadavg 2>/dev/null";
+ my $fh;
+ if( open($fh, "$cmd|" ) ) {
+ ( $lavg{avg_1}
+ , $lavg{avg_5}
+ , $lavg{avg_15}
+ ) = (split /\s+/, <$fh>)[0..2];
+
+ close($fh);
+ }
+ return \%lavg;
+ };
+
+ return $hash->{loadavg}->get;
+ }
+
+ return $hash->{loadavg}->get;
+}
+
+
1;
=pod
@@ -127,7 +245,7 @@ SYSSTAT_GetUpdate($)
SYSSTAT
- Provides system statistics for the host FHEM runs on.
+ Provides system statistics for the host FHEM runs on or a remote Linux system that is reachable by preconfigured passwordless ssh access.
Notes:
@@ -135,6 +253,7 @@ SYSSTAT_GetUpdate($)
- This module needs
Sys::Statistics::Linux on Linux.
It can be installed with 'cpan install Sys::Statistics::Linux'
or on debian with 'apt-get install libsys-statistics-linux-perl'
+
- To plot the load values the following code can be used:
define sysstatlog FileLog /usr/local/FHEM/var/log/sysstat-%Y-%m.log sysstat
@@ -143,22 +262,30 @@ SYSSTAT_GetUpdate($)
attr wl_sysstat label "Load Min: $data{min1}, Max: $data{max1}, Aktuell: $data{currval1}"
attr wl_sysstat room System
+ - to match the root filesystem (mount point '/') in diskusage plots use
+ '
#FileLog 4:/\x3a:0:' or '#FileLog 4:\s..\s:0:'
+ and not '#FileLog 4:/:0:' as the later will match all mount points .
Define
- define <name> SYSSTAT [<interval>]
+ define <name> SYSSTAT [<interval> [<interval_fs>] [<host>]]
Defines a SYSSTAT device.
- The statistics are updated <interval> seconds. The default is 60.
+ The load is updated every <interval> seconds. The default and minimum is 60.
+ The diskusage is updated every <interval_fs> seconds. The default is <interval>*60 and the minimum is 60.
+ <interval_fs> is only aproximated and works best if <interval_fs> is an integral multiple of <interval>.
+
+ If <host> is given it has to be accessible by ssh without the need for a password.
Examples:
define sysstat SYSSTAT
- define sysstat SYSSTAT 30
+ define sysstat SYSSTAT 300
+ define sysstat SYSSTAT 60 600
@@ -173,19 +300,34 @@ SYSSTAT_GetUpdate($)
free bytes for <mountpoint>
+
+ Get
+
+ get <name> <value>
+
+ where value is one of
+ - filesystems
+ Lists the filesystems that can be monitored.
+
+
+
Attributes
+
- filesystems
- List of comma separated filesystems that should be monitored.
+ List of comma separated filesystems (not mountpoints) that should be monitored.
Examples:
attr sysstat filesystems /dev/md0,/dev/md2
attr sysstat filesystems /dev/.*
-
+
showpercent
If set the usage is shown in percent. If not set the remaining free space in bytes is shown.
useregex
If set the entries of the filesystems list are treated as regex.
+ ssh_user
+ The username for ssh remote access.
+
=end html
diff --git a/FHEM/45_TRX.pm b/FHEM/45_TRX.pm
index 4c8d1fcd8..ad6308cc3 100755
--- a/FHEM/45_TRX.pm
+++ b/FHEM/45_TRX.pm
@@ -51,12 +51,12 @@ TRX_Initialize($)
$hash->{ReadFn} = "TRX_Read";
$hash->{WriteFn} = "TRX_Write";
$hash->{Clients} =
- ":TRX_WEATHER:TRX_SECURITY:TRX_LIGHT:";
+ ":TRX_WEATHER:TRX_SECURITY:TRX_LIGHT:TRX_ELSE:";
my %mc = (
"1:TRX_WEATHER" => "^..(50|51|52|54|55|56|57|5a|5d).*",
"2:TRX_SECURITY" => "^..(20).*",
- "3:TRX_LIGHT" => "^..(10|11|12|13).*",
- "4:TRX_ELSE" => "^...*",
+ "3:TRX_LIGHT" => "^..(10|11|12|13|14).*",
+ "4:TRX_ELSE" => "^..(0[0-f]|1[5-f]|2[1-f]|3[0-f]|4[0-f]|53|58|59|5b|5c|5e|5f|[6-f][0-f]).*",
);
$hash->{MatchList} = \%mc;
@@ -284,7 +284,16 @@ TRX_DoInit($)
$status .= $freq;
$status .= ", " . sprintf "firmware=%d",$msg2;
$status .= ", protocols enabled: ";
- $status .= sprintf "undecoded, " if ($msg3 & 0x80);
+ $status .= "undecoded " if ($msg3 & 0x80);
+ $status .= "RFU6 " if ($msg3 & 0x40);
+ $status .= "RFU5 " if ($msg3 & 0x20);
+ $status .= "RFU4 " if ($msg3 & 0x10);
+ $status .= "Lighting4 " if ($msg3 & 0x08);
+ $status .= "FineOffset/Viking " if ($msg3 & 0x04);
+ $status .= "Rubicson " if ($msg3 & 0x02);
+ $status .= "AE/Blyss " if ($msg3 & 0x01);
+ $status .= "BlindsT1/T2/T3/T4 " if ($msg4 & 0x80);
+ $status .= "BlindsT0 " if ($msg4 & 0x40);
$status .= "ProGuard " if ($msg4 & 0x20);
$status .= "FS20 " if ($msg4 & 0x10);
$status .= "LaCrosse " if ($msg4 & 0x08);
diff --git a/FHEM/46_TRX_ELSE.pm b/FHEM/46_TRX_ELSE.pm
index b0fcf49ea..b623e97d8 100755
--- a/FHEM/46_TRX_ELSE.pm
+++ b/FHEM/46_TRX_ELSE.pm
@@ -44,11 +44,11 @@ TRX_ELSE_Initialize($)
{
my ($hash) = @_;
- $hash->{Match} = "^...*";
+ $hash->{Match} = "^..(0[0-f]|1[5-f]|2[1-f]|3[0-f]|4[0-f]|53|58|59|5b|5c|5e|5f|[6-f][0-f]).*";
$hash->{DefFn} = "TRX_ELSE_Define";
$hash->{UndefFn} = "TRX_ELSE_Undef";
$hash->{ParseFn} = "TRX_ELSE_Parse";
- $hash->{AttrList} = "IODev do_not_notify:1,0 loglevel:0,1,2,3,4,5,6 ".
+ $hash->{AttrList} = "IODev ignore:1,0 do_not_notify:1,0 loglevel:0,1,2,3,4,5,6 ".
$readingFnAttributes;
Log 1, "TRX_ELSE: Initialize" if ($TRX_ELSE_debug == 1);
@@ -70,7 +70,7 @@ TRX_ELSE_Define($$)
my $device_name = "TRX_UNKNOWN".$DOT.$code;
- $hash->{TRX_LIGHT_CODE} = $code;
+ $hash->{CODE} = $code;
$modules{TRX_ELSE}{defptr}{$device_name} = $hash;
AssignIoPort($hash);
@@ -136,23 +136,24 @@ TRX_ELSE_Parse($$)
my $def = $modules{TRX_ELSE}{defptr}{$device_name};
if (!$def) {
- Log 1, "UNDEFINED $device_name TRX_ELSE $type_hex";
- Log 3, "TRX_ELSE: TRX_ELSE Unknown device $device_name, please define it";
- return "UNDEFINED $device_name TRX_ELSE $type_hex";
+ Log 3, "TRX_ELSE: Unknown device $device_name, please define it";
+ return "UNDEFINED $device_name TRX_ELSE $type_hex";
+
}
+ my $name = $def->{NAME};
+ return "" if(IsIgnored($name));
readingsBeginUpdate($def);
- #my $sensor = "hexline";
my $current = $msg;
+ #my $sensor = "hexline";
#readingsBulkUpdate($def, $sensor, $current);
readingsBulkUpdate($def, "state", $current);
-
readingsEndUpdate($def, 1);
- return "";
+ return $name;
}
1;
diff --git a/FHEM/46_TRX_LIGHT.pm b/FHEM/46_TRX_LIGHT.pm
index 6172070d3..c2820442b 100755
--- a/FHEM/46_TRX_LIGHT.pm
+++ b/FHEM/46_TRX_LIGHT.pm
@@ -524,13 +524,8 @@ sub TRX_LIGHT_parse_X10 {
my $sensor = "";
if ($device_type eq "MS14A") {
- # for ms14a behave like x10, but flip second deviceid
+ # for ms14a behave like x10
$device_type = "X10";
- if ($firstdevice == 1) {
- $command = ($command eq "on") ? "alert" : "normal" ;
- } else {
- $command = ($command eq "on") ? "off" : "on" ;
- }
}
if (lc($def->{TRX_LIGHT_devicelog}) eq "window" || lc($def->{TRX_LIGHT_devicelog}) eq "door") {
diff --git a/FHEM/57_Calendar.pm b/FHEM/57_Calendar.pm
index c87c85007..d70ca3fac 100644
--- a/FHEM/57_Calendar.pm
+++ b/FHEM/57_Calendar.pm
@@ -1019,8 +1019,8 @@ sub Calendar_Undef($$) {
Examples:
- define MyCalendar Calendar ical url https://www.google.com/calendar/ical/john.doe%40example.com/private-foo4711/basic.ics
- define YourCalendar Calendar ical url http://www.google.com/calendar/ical/jane.doe%40example.com/private-bar0815/basic.ics 86400
+ define MyCalendar Calendar ical url https://www.google.com/calendar/ical/john.doe%40example.com/private-foo4711/basic.ics
+ define YourCalendar Calendar ical url http://www.google.com/calendar/ical/jane.doe%40example.com/private-bar0815/basic.ics 86400
@@ -1130,8 +1130,8 @@ sub Calendar_Undef($$) {
get MyCalendar full all
- 2767324dsfretfvds7dsfn3e4dsa234r234sdfds6bh874googlecom known alarm 31.05.2012 17:00:00 07.06.2012 16:30:00-07.06.2012 18:00:00 Erna for coffee
- 992hydf4y44awer5466lhfdsrgl7tin6b6mckf8glmhui4googlecom known upcoming 08.06.2012 00:00:00-09.06.2012 00:00:00 Vacation
+ 2767324dsfretfvds7dsfn3e4dsa234r234sdfds6bh874googlecom known alarm 31.05.2012 17:00:00 07.06.2012 16:30:00-07.06.2012 18:00:00 Erna for coffee
+ 992hydf4y44awer5466lhfdsrgl7tin6b6mckf8glmhui4googlecom known upcoming 08.06.2012 00:00:00-09.06.2012 00:00:00 Vacation
@@ -1151,15 +1151,15 @@ sub Calendar_Undef($$) {
First find the UID of the calendar event:
get MyCalendar find .*Erna.*
- 2767324dsfretfvds7dsfn3e4dsa234r234sdfds6bh874googlecom
+ 2767324dsfretfvds7dsfn3e4dsa234r234sdfds6bh874googlecom
Then define a notify:
- define ErnaComes notify MyCalendar:modeStarted.*2767324dsfretfvds7dsfn3e4dsa234r234sdfds6bh874googlecom.* set MyLight on
+ define ErnaComes notify MyCalendar:modeStarted.*2767324dsfretfvds7dsfn3e4dsa234r234sdfds6bh874googlecom.* set MyLight on
You can also do some logging:
- define LogErna notify MyCalendar:modeAlarmed.*2767324dsfretfvds7dsfn3e4dsa234r234sdfds6bh874googlecom.* { Log 1, "ALARM name=%NAME event=%EVENT part1=%EVTPART0 part2=%EVTPART1" }
+ define LogErna notify MyCalendar:modeAlarmed.*2767324dsfretfvds7dsfn3e4dsa234r234sdfds6bh874googlecom.* { Log 1, "ALARM name=%NAME event=%EVENT part1=%EVTPART0 part2=%EVTPART1" }
diff --git a/FHEM/59_Weather.pm b/FHEM/59_Weather.pm
index 4d83eaf61..af5a38034 100755
--- a/FHEM/59_Weather.pm
+++ b/FHEM/59_Weather.pm
@@ -253,7 +253,7 @@ sub Weather_RetrieveData($)
### current condition and forecast
if (($tag eq "yweather:condition" ) || ($tag eq "yweather:forecast" )) {
my $code = (($value =~/code="([0-9]*?)".*/) ? $1 : undef);
- if (defined($code)) {
+ if(defined($code)) {
readingsBulkUpdate($hash, $prefix . "code", $code);
my $text = $YahooCodes_i18n[$code];
if ($text) { readingsBulkUpdate($hash, $prefix . "condition", $text); }
@@ -266,7 +266,7 @@ sub Weather_RetrieveData($)
### current condition
if ($tag eq "yweather:condition" ) {
my $temp = (($value =~/temp="(-?[0-9.]*?)".*/) ? $1 : undef);
- if ($temp) {
+ if(defined($temp)) {
readingsBulkUpdate($hash, "temperature", $temp);
readingsBulkUpdate($hash, "temp_c", $temp); # compatibility
$temp = int(( $temp * 9 / 5 ) + 32.5); # Celsius to Fahrenheit
@@ -277,7 +277,7 @@ sub Weather_RetrieveData($)
readingsBulkUpdate($hash, "current_date_time", $datum) if (defined($1));
my $day = (($value =~/date="(.*?), .*/) ? $1 : undef);
- if ($day) {
+ if(defined($day)) {
readingsBulkUpdate($hash, "day_of_week", $wdays_txt_i18n{$day});
}
}
@@ -285,11 +285,11 @@ sub Weather_RetrieveData($)
### forecast
if ($tag eq "yweather:forecast" ) {
my $low_c = (($value =~/low="(-?[0-9.]*?)".*/) ? $1 : undef);
- if ($low_c) { readingsBulkUpdate($hash, $prefix . "low_c", $low_c); }
+ if(defined($low_c)) { readingsBulkUpdate($hash, $prefix . "low_c", $low_c); }
my $high_c = (($value =~/high="(-?[0-9.]*?)".*/) ? $1 : undef);
- if ($high_c) { readingsBulkUpdate($hash, $prefix . "high_c", $high_c); }
+ if(defined($high_c)) { readingsBulkUpdate($hash, $prefix . "high_c", $high_c); }
my $day1 = (($value =~/day="(.*?)" .*/) ? $1 : undef); # forecast
- if ($day1) {
+ if(defined($day1)) {
readingsBulkUpdate($hash, $prefix . "day_of_week", $wdays_txt_i18n{$day1});
}
}
@@ -345,8 +345,7 @@ sub Weather_GetUpdate($)
my $wind= $hash->{READINGS}{wind}{VAL};
my $val= "T: $temperature H: $humidity W: $wind";
Log GetLogLevel($hash->{NAME},4), "Weather ". $hash->{NAME} . ": $val";
- $hash->{STATE}= $val;
- addEvent($hash, $val);
+ readingsBulkUpdate($hash, "state", $val);
readingsEndUpdate($hash, defined($hash->{LOCAL} ? 0 : 1)); # DoTrigger, because sub is called by a timer instead of dispatch
return 1;
diff --git a/FHEM/70_VIERA.pm b/FHEM/70_VIERA.pm
new file mode 100644
index 000000000..7a4451cda
--- /dev/null
+++ b/FHEM/70_VIERA.pm
@@ -0,0 +1,658 @@
+##############################################################################
+#
+# 70_VIERA.pm
+#
+# a module to send messages or commands to a Panasonic TV
+# inspired by Samsung TV Module from Gabriel Bentele
+# written 2013 by Tobias Vaupel
+#
+#
+# Version = 1.00
+#
+##############################################################################
+#
+# define VIERA
+#
+# set
+#
+# where is one of mute, volume, remoteControl or off
+# examples:
+# set mute on < This will switch mute on
+# set volume 20 < This will set volume level to 20, mute will be set to off if enabled
+# set remoteControl mute < This is equal to push the mute button at remote control. State of muting will be toggeled!
+# set remoteControl ? < Print an overview of remotecontrol buttons
+#
+##############################################################################
+
+package main;
+use strict;
+use warnings;
+use IO::Socket::INET;
+
+my %VIERA_remoteControl_args = (
+ "NRC_CH_DOWN-ONOFF" => "Channel down",
+ "NRC_CH_UP-ONOFF" => "Channel up",
+ "NRC_VOLUP-ONOFF" => "Volume up",
+ "NRC_VOLDOWN-ONOFF" => "Volume down",
+ "NRC_MUTE-ONOFF" => "Mute",
+ "NRC_TV-ONOFF" => "TV",
+ "NRC_CHG_INPUT-ONOFF" => "AV",
+ "NRC_RED-ONOFF" => "Red",
+ "NRC_GREEN-ONOFF" => "Green",
+ "NRC_YELLOW-ONOFF" => "Yellow",
+ "NRC_BLUE-ONOFF" => "Blue",
+ "NRC_VTOOLS-ONOFF" => "VIERA tools",
+ "NRC_CANCEL-ONOFF" => "Cancel / Exit",
+ "NRC_SUBMENU-ONOFF" => "Option",
+ "NRC_RETURN-ONOFF" => "Return",
+ "NRC_ENTER-ONOFF" => "Control Center click / enter",
+ "NRC_RIGHT-ONOFF" => "Control RIGHT",
+ "NRC_LEFT-ONOFF" => "Control LEFT",
+ "NRC_UP-ONOFF" => "Control UP",
+ "NRC_DOWN-ONOFF" => "Control DOWN",
+ "NRC_3D-ONOFF" => "3D button",
+ "NRC_SD_CARD-ONOFF" => "SD-card",
+ "NRC_DISP_MODE-ONOFF" => "Display mode / Aspect ratio",
+ "NRC_MENU-ONOFF" => "Menu",
+ "NRC_INTERNET-ONOFF" => "VIERA connect",
+ "NRC_VIERA_LINK-ONOFF"=> "VIERA link",
+ "NRC_EPG-ONOFF" => "Guide / EPG",
+ "NRC_TEXT-ONOFF" => "Text / TTV",
+ "NRC_STTL-ONOFF" => "STTL / Subtitles",
+ "NRC_INFO-ONOFF" => "Info",
+ "NRC_INDEX-ONOFF" => "TTV index",
+ "NRC_HOLD-ONOFF" => "TTV hold / image freeze",
+ "NRC_R_TUNE-ONOFF" => "Last view",
+ "NRC_POWER-ONOFF" => "Power off",
+ "NRC_REW-ONOFF" => "Rewind",
+ "NRC_PLAY-ONOFF" => "Play",
+ "NRC_FF-ONOFF" => "Fast forward",
+ "NRC_SKIP_PREV-ONOFF" => "Skip previous",
+ "NRC_PAUSE-ONOFF" => "Pause",
+ "NRC_SKIP_NEXT-ONOFF" => "Skip next",
+ "NRC_STOP-ONOFF" => "Stop",
+ "NRC_REC-ONOFF" => "Record",
+ "NRC_D1-ONOFF" => "Digit 1",
+ "NRC_D2-ONOFF" => "Digit 2",
+ "NRC_D3-ONOFF" => "Digit 3",
+ "NRC_D4-ONOFF" => "Digit 4",
+ "NRC_D5-ONOFF" => "Digit 5",
+ "NRC_D6-ONOFF" => "Digit 6",
+ "NRC_D7-ONOFF" => "Digit 7",
+ "NRC_D8-ONOFF" => "Digit 8",
+ "NRC_D9-ONOFF" => "Digit 9",
+ "NRC_D0-ONOFF" => "Digit 0",
+ "NRC_P_NR-ONOFF" => "P-NR (Noise reduction)",
+ "NRC_R_TUNE-ONOFF" => "Seems to do the same as INFO",
+);
+
+
+sub VIERA_Initialize($){
+ my ($hash) = @_;
+ $hash->{DefFn} = "VIERA_Define";
+ $hash->{SetFn} = "VIERA_Set";
+ $hash->{GetFn} = "VIERA_Get";
+ $hash->{UndefFn} = "VIERA_Undefine";
+ $hash->{AttrList} = "loglevel:0,1,2,3,4,5 " . $readingFnAttributes;
+}
+
+sub VIERA_Undefine($$){
+ my($hash, $name) = @_;
+
+ # Stop the internal GetStatus-Loop and exist
+ RemoveInternalTimer($hash);
+ return undef;
+}
+
+sub VIERA_Define($$){
+ my ($hash, $def) = @_;
+ my @args = split("[ \t][ \t]*", $def);
+ my $name = $hash->{NAME};
+
+ if(int(@args) < 3 && int(@args) > 4) {
+ my $msg = "wrong syntax: define VIERA []";
+ Log GetLogLevel($name, 2), $msg;
+ return $msg;
+ }
+
+ $hash->{helper}{HOST} = $args[2];
+ readingsSingleUpdate($hash,"state","Initialized",1);
+
+ if(defined($args[3]) and $args[3] > 10) {
+ $hash->{helper}{INTERVAL}=$args[3];
+ }
+ else{
+ $hash->{helper}{INTERVAL}=30;
+ }
+
+ CommandAttr(undef,$name.' webCmd off') if( !defined( AttrVal($hash->{NAME}, "webCmd", undef) ) );
+ Log GetLogLevel($name, 2), "VIERA: defined with host: $hash->{helper}{HOST} and interval: $hash->{helper}{INTERVAL}";
+ InternalTimer(gettimeofday()+$hash->{helper}{INTERVAL}, "VIERA_GetStatus", $hash, 0);
+
+ return undef;
+}
+
+sub connection($$){
+ my $tmp = shift ;
+ my $TV = shift;
+ my $buffer = "";
+ my $tmp2 = "";
+ my $sock = new IO::Socket::INET (
+ PeerAddr => $TV,
+ PeerPort => '55000',
+ Proto => 'tcp',
+ Timeout => 2
+ );
+
+ #Log 5, "VIERA: connection message: $tmp";
+
+ if(defined ($sock)){
+ print $sock $tmp;
+ my $buff ="";
+
+ while ((read $sock, $buff, 1) > 0){
+ $buffer .= $buff;
+ }
+
+ my @tmp2 = split (/\n/,$buffer);
+ #Log 4, "VIERA: $TV response: $tmp2[0]";
+ #Log 5, "VIERA: $TV buffer response: $buffer";
+ $sock->close();
+ return $buffer;
+ }
+ else{
+ #Log 4, "VIERA: $TV: not able to open socket";
+ return undef;
+ }
+}
+
+sub VIERA_GetStatus($;$){
+ my ($hash, $local) = @_;
+ my $name = $hash->{NAME};
+ my $host = $hash->{helper}{HOST};
+
+ InternalTimer(gettimeofday()+$hash->{helper}{INTERVAL}, "VIERA_GetStatus", $hash, 0);
+
+ return "" if(!defined($hash->{helper}{HOST}) or !defined($hash->{helper}{INTERVAL}));
+
+ my $returnVol = connection(VIERA_BuildXML_RendCtrl($hash, "Get", "Volume", ""), $host);
+ Log GetLogLevel($name, 5), "VIERA: GetStatusVol-Request returned: $returnVol" if(defined($returnVol));
+ if(not defined($returnVol) or $returnVol eq "") {
+ Log GetLogLevel($name, 4), "VIERA: GetStatusVol-Request NO SOCKET!";
+ #readingsSingleUpdate($hash,"state","off",1);
+ if( $hash->{STATE} ne "off") {readingsSingleUpdate($hash,"state","off",1);}
+ return;
+ }
+
+ my $returnMute = connection(VIERA_BuildXML_RendCtrl($hash, "Get", "Mute", ""), $host);
+ Log GetLogLevel($name, 5), "VIERA: GetStatusMute-Request returned: $returnMute" if(defined($returnMute));
+ if(not defined($returnMute) or $returnMute eq "") {
+ Log GetLogLevel($name, 4), "VIERA: GetStatusMute-Request NO SOCKET!";
+ #readingsSingleUpdate($hash,"state","off",1);
+ if( $hash->{STATE} ne "off") {readingsSingleUpdate($hash,"state","off",1);}
+ return;
+ }
+
+ readingsBeginUpdate($hash);
+ if($returnVol =~ /(.+)<\/CurrentVolume>/){
+ Log GetLogLevel($name, 4), "VIERA: GetStatus-Set reading volume to $1";
+ if( $1 != $hash->{READINGS}{volume}{VAL} ) {readingsBulkUpdate($hash, "volume", $1);}
+ }
+
+ if($returnMute =~ /(.+)<\/CurrentMute>/){
+ my $myMute = $1;
+ if ($myMute == 0) { $myMute = "off"; } else { $myMute = "on";}
+ Log GetLogLevel($name, 4), "VIERA: GetStatus-Set reading volume to $myMute";
+ if( $myMute ne $hash->{READINGS}{mute}{VAL} ) {readingsBulkUpdate($hash, "mute", $myMute);}
+ }
+ #readingsBulkUpdate($hash, "state", "on");
+ if( $hash->{STATE} ne "on") {readingsBulkUpdate($hash, "state", "on");}
+ readingsEndUpdate($hash, 1);
+
+ Log GetLogLevel($name,4), "VIERA $name: $hash->{STATE}";
+ return $hash->{STATE};
+}
+
+sub VIERA_Get($@){
+ my ($hash, @a) = @_;
+ my $what;
+
+ return "argument is missing" if(int(@a) != 2);
+
+ $what = $a[1];
+
+ if($what =~ /^(volume|mute)$/) {
+ if (defined($hash->{READINGS}{$what})) {
+ return $hash->{READINGS}{$what}{VAL};
+ }
+ else{
+ return "no such reading: $what";
+ }
+ }
+ else{
+ return "Unknown argument $what, choose one of param: volume, mute";
+ }
+}
+
+sub VIERA_Set($@){
+ my ($hash, @a) = @_;
+ my $name = $hash->{NAME};
+ my $host = $hash->{helper}{HOST};
+ my $count = @a;
+ my $ret = "";
+ my $key = "";
+ my $tab = "";
+ my $usage = "choose one of off mute:on,off volume:slider,0,1,100 remoteControl:" . join(",", sort keys %VIERA_remoteControl_args);
+ $usage =~ s/(NRC_|-ONOFF)//g;
+
+
+ return "VIERA: No argument given, $usage" if(!defined($a[1]));
+ my $what = $a[1];
+
+ return "VIERA: No state given, $usage" if(!defined($a[2]) && $what ne "off");
+ my $state = $a[2];
+
+
+ if($what eq "mute"){
+ Log GetLogLevel($name, 3), "VIERA: Set mute $state";
+
+ if ($state eq "on") {$state = 1;} else {$state = 0;}
+ $ret = connection(VIERA_BuildXML_RendCtrl($hash, "Set", "Mute", $state), $host);
+ }
+ elsif($what eq "volume"){
+ if($state < 0 || $state > 100){
+ return "Range is too high! Use Value 0 till 100 for volume.";
+ }
+ Log GetLogLevel($name, 3), "VIERA: Set volume $state";
+ $ret = connection(VIERA_BuildXML_RendCtrl($hash, "Set", "Volume", $state), $host);
+ }
+ elsif($what eq "remoteControl"){
+ if($state eq "?"){
+ $usage = "choose one of the states:\n";
+ foreach $key (sort keys %VIERA_remoteControl_args){
+ if(length($key) < 17){ $tab = "\t\t"; }else{ $tab = "\t"; }
+ $usage .= "$key $tab=> $VIERA_remoteControl_args{$key}\n";
+ }
+ $usage =~ s/(NRC_|-ONOFF)//g;
+ return $usage;
+ }
+ else{
+ $state = uc($state);
+ Log GetLogLevel($name, 3), "VIERA: Set remoteControl $state";
+ $ret = connection(VIERA_BuildXML_NetCtrl($hash,$state), $host);
+ }
+ }
+ elsif($what eq "off"){
+ Log GetLogLevel($name, 3), "VIERA: Set off";
+ $ret = connection(VIERA_BuildXML_NetCtrl($hash,"POWER"), $host);
+ }
+ else{
+ Log GetLogLevel($name, 3), "VIERA: $usage";
+ return "Unknown argument $what, $usage";
+ }
+ return;
+}
+
+sub VIERA_BuildXML_NetCtrl($$){
+ my ($hash, $command) = @_;
+ my $host = $hash->{helper}{HOST};
+
+ my $callsoap = "";
+ my $message = "";
+ my $head = "";
+ my $size = "";
+
+ #Log 1, "DEBUG: $command, $host";
+ $callsoap .= "";
+ $callsoap .= "";
+ $callsoap .= "";
+ $callsoap .= "";
+ $callsoap .= "NRC_$command-ONOFF";
+ $callsoap .= "";
+ $callsoap .= "";
+ $callsoap .= "";
+
+ $size = length($callsoap);
+
+ $head .= "POST /nrc/control_0 HTTP/1.1\r\n";
+ $head .= "Host: $host:55000\r\n";
+ $head .= "SOAPACTION: \"urn:panasonic-com:service:p00NetworkControl:1#X_SendKey\"\r\n";
+ $head .= "Content-Type: text/xml; charset=\"utf-8\"\r\n";
+ $head .= "Content-Length: $size\r\n";
+ $head .= "\r\n";
+
+ $message .= $head;
+ $message .= $callsoap;
+ return $message;
+}
+
+sub VIERA_BuildXML_RendCtrl($$$$){
+ my ($hash, $methode, $command, $value) = @_;
+ my $host = $hash->{helper}{HOST};
+
+ my $callsoap = "";
+ my $message = "";
+ my $head = "";
+ my $size = "";
+
+ #Log 1, "DEBUG: $command with $value to $host";
+
+ $callsoap .= "\r\n";
+ $callsoap .= "\r\n";
+ $callsoap .= "\r\n";
+ $callsoap .= "\r\n";
+ $callsoap .= "0\r\n";
+ $callsoap .= "Master\r\n";
+ $callsoap .= "$value\r\n" if(defined($value));
+ $callsoap .= "\r\n";
+ $callsoap .= "\r\n";
+ $callsoap .= "\r\n";
+
+ $size = length($callsoap);
+
+ $head .= "POST /dmr/control_0 HTTP/1.1\r\n";
+ $head .= "Host: $host:55000\r\n";
+ $head .= "SOAPACTION: \"urn:schemas-upnp-org:service:RenderingControl:1#$methode$command\"\r\n";
+ $head .= "Content-Type: text/xml; charset=\"utf-8\"\r\n";
+ $head .= "Content-Length: $size\r\n";
+ $head .= "\r\n";
+
+ $message .= $head;
+ $message .= $callsoap;
+ return $message;
+}
+1;
+
+=pod
+=begin html
+
+
+VIERA
+
+
+ Define
+
+ define <name> VIERA <host> [<interval>]
+
+ This module controls Panasonic TV device over ethernet. It's possible to
+ power down the tv, change volume or mute/unmute the TV. Also this modul is simulating
+ the remote control and you are able to send different command buttons actions of remote control.
+ The module is tested with Panasonic plasma TV tx-p50vt30e
+
+ Defining a VIERA device will schedule an internal task (interval can be set
+ with optional parameter <interval> in seconds, if not set, the value is 30
+ seconds), which periodically reads the status of volume and mute status and triggers
+ notify/filelog commands.
+ Example:
+
+ define myTV1 VIERA 192.168.178.20
+ define myTV1 VIERA 192.168.178.20 60 #with custom interval of 60 seconds
+
+
+
+
+
+ Set
+
+ set <name> <command> [<value>]
+
+ Currently, the following commands are defined.
+
+ off
+ mute [on|off]
+ volume <value>
+ remoteControl <command>
+
+
+
+
+ Remote control (depending on your model, maybe)
+ For this application the following commands are available:
+
+ 3D => 3D button
+ BLUE => Blue
+ CANCEL => Cancel / Exit
+ CHG_INPUT => AV
+ CH_DOWN => Channel down
+ CH_UP => Channel up
+ D0 => Digit 0
+ D1 => Digit 1
+ D2 => Digit 2
+ D3 => Digit 3
+ D4 => Digit 4
+ D5 => Digit 5
+ D6 => Digit 6
+ D7 => Digit 7
+ D8 => Digit 8
+ D9 => Digit 9
+ DISP_MODE => Display mode / Aspect ratio
+ DOWN => Control DOWN
+ ENTER => Control Center click / enter
+ EPG => Guide / EPG
+ FF => Fast forward
+ GREEN => Green
+ HOLD => TTV hold / image freeze
+ INDEX => TTV index
+ INFO => Info
+ INTERNET => VIERA connect
+ LEFT => Control LEFT
+ MENU => Menu
+ MUTE => Mute
+ PAUSE => Pause
+ PLAY => Play
+ POWER => Power off
+ P_NR => P-NR (Noise reduction)
+ REC => Record
+ RED => Red
+ RETURN => Return
+ REW => Rewind
+ RIGHT => Control RIGHT
+ R_TUNE => Seems to do the same as INFO
+ SD_CARD => SD-card
+ SKIP_NEXT => Skip next
+ SKIP_PREV => Skip previous
+ STOP => Stop
+ STTL => STTL / Subtitles
+ SUBMENU => Option
+ TEXT => Text / TTV
+ TV => TV
+ UP => Control UP
+ VIERA_LINK => VIERA link
+ VOLDOWN => Volume down
+ VOLUP => Volume up
+ VTOOLS => VIERA tools
+ YELLOW => Yellow
+
+
+
+ Example:
+
+ set <name> mute on
+ set <name> volume 20
+ set <name> remoteControl CH_DOWN
+
+
+
+ Notes:
+ Activate volume remotecontrol by DLNA: Menu -> Setup -> Network Setup -> Network Link Settings -> DLNA RemoteVolume -> On
+
+
+
+
+ Get
+
+ get <name> <what>
+
+ Currently, the following commands are defined and return the current state of the TV.
+
+
+
+
+
+ Attributes
+
+
+
+
+ Generated events:
+
+ - on
+ - off
+ - volume
+ - mute
+
+
+
+=end html
+
+
+=begin html_DE
+
+
+VIERA
+
+
+ Define
+
+ define <name> VIERA <host> [<interval>]
+
+ Dieses Modul steuert einen Panasonic Fernseher über das Netzwerk. Es ist möglich den Fernseher
+ auszuschalten, die Lautstärke zu ändern oder zu muten bzw. unmuten. Dieses Modul kann zusätzlich
+ die Fernbedienung simulieren. Somit können also die Schaltaktionen einer Fernbedienung simuliert werden.
+ Getestet wurde das Modul mit einem Panasonic Plasma TV tx-p50vt30e
+
+ Beim definieren des Gerätes in FHEM wird ein interner Timer gestartet, welcher zyklisch alle 30 Sekunden
+ den Status der Lautstärke und des Mute-Zustand ausliest. Das Intervall des Timer kann über den Parameter <interval>
+ geändert werden. Wird kein Interval angegeben, liest das Modul alle 30 Sekunden die Werte aus und triggert ein notify.
+
+ Beispiel:
+
+ define myTV1 VIERA 192.168.178.20
+ define myTV1 VIERA 192.168.178.20 60 #Mit einem Interval von 60 Sekunden
+
+
+
+
+
+ Set
+
+ set <name> <command> [<value>]
+
+ Zur Zeit sind die folgenden Befehle implementiert:
+
+ off
+ mute [on|off]
+ volume <Wert>
+ remoteControl <Befehl>
+
+
+
+
+ Fernbedienung (Kann vielleicht nach Modell variieren)
+ Das Modul hat die folgenden Fernbedienbefehle implementiert:
+
+ 3D => 3D Knopf
+ BLUE => Blau
+ CANCEL => Cancel / Exit
+ CHG_INPUT => AV
+ CH_DOWN => Kanal runter
+ CH_UP => Kanal hoch
+ D0 => Ziffer 0
+ D1 => Ziffer 1
+ D2 => Ziffer 2
+ D3 => Ziffer 3
+ D4 => Ziffer 4
+ D5 => Ziffer 5
+ D6 => Ziffer 6
+ D7 => Ziffer 7
+ D8 => Ziffer 8
+ D9 => Ziffer 9
+ DISP_MODE => Anzeigemodus / Seitenverhältnis
+ DOWN => Navigieren runter
+ ENTER => Navigieren enter
+ EPG => Guide / EPG
+ FF => Vorspulen
+ GREEN => Grün
+ HOLD => Bild einfrieren
+ INDEX => TTV index
+ INFO => Info
+ INTERNET => VIERA connect
+ LEFT => Navigieren links
+ MENU => Menü
+ MUTE => Mute
+ PAUSE => Pause
+ PLAY => Play
+ POWER => Power off
+ P_NR => P-NR (Geräuschreduzierung)
+ REC => Aufnehmen
+ RED => Rot
+ RETURN => Enter
+ REW => Zurückspulen
+ RIGHT => Navigieren Rechts
+ R_TUNE => Vermutlich die selbe Funktion wie INFO
+ SD_CARD => SD-card
+ SKIP_NEXT => Skip next
+ SKIP_PREV => Skip previous
+ STOP => Stop
+ STTL => Untertitel
+ SUBMENU => Option
+ TEXT => TeleText
+ TV => TV
+ UP => Navigieren Hoch
+ VIERA_LINK => VIERA link
+ VOLDOWN => Lauter
+ VOLUP => Leiser
+ VTOOLS => VIERA tools
+ YELLOW => Gelb
+
+
+
+ Beispiel:
+
+ set <name> mute on
+ set <name> volume 20
+ set <name> remoteControl CH_DOWN
+
+
+
+ Anmerkung:
+ Aktivieren von Fernbedienung der Lautstärke per DLNA: Menü -> Setup -> Netzwerk-Setup -> Netzwerkverbindungsein. -> DLNA-Fernbed. Lautst. -> Ein
+
+
+
+
+ Get
+
+ get <name> <what>
+
+ Die folgenden Befehle sind definiert und geben den entsprechenden Wert zurück, der vom Fernseher zurückgegeben wurde.
+
+
+
+
+
+ Attribute
+
+
+
+
+ Generierte events:
+
+ - on
+ - off
+ - volume
+ - mute
+
+
+
+=end html_DE
+
+
+
+=cut
diff --git a/FHEM/71_YAMAHA_AVR.pm b/FHEM/71_YAMAHA_AVR.pm
index 6087d0be0..42411eb35 100755
--- a/FHEM/71_YAMAHA_AVR.pm
+++ b/FHEM/71_YAMAHA_AVR.pm
@@ -93,7 +93,7 @@ YAMAHA_AVR_GetStatus($;$)
if(not defined($zone))
{
- InternalTimer(gettimeofday()+$hash->{helper}{INTERVAL}, "YAMAHA_AVR_GetStatus", $hash, 1) unless($local == 1);
+ InternalTimer(gettimeofday()+$hash->{helper}{INTERVAL}, "YAMAHA_AVR_GetStatus", $hash, 0) unless($local == 1);
return "No Zone available";
}
@@ -103,7 +103,7 @@ YAMAHA_AVR_GetStatus($;$)
if(not defined($return) or $return eq "")
{
- InternalTimer(gettimeofday()+$hash->{helper}{INTERVAL}, "YAMAHA_AVR_GetStatus", $hash, 1) unless($local == 1);
+ InternalTimer(gettimeofday()+$hash->{helper}{INTERVAL}, "YAMAHA_AVR_GetStatus", $hash, 0) unless($local == 1);
return;
}
@@ -150,7 +150,7 @@ YAMAHA_AVR_GetStatus($;$)
readingsEndUpdate($hash, 1);
- InternalTimer(gettimeofday()+$hash->{helper}{INTERVAL}, "YAMAHA_AVR_GetStatus", $hash, 1) unless($local == 1);
+ InternalTimer(gettimeofday()+$hash->{helper}{INTERVAL}, "YAMAHA_AVR_GetStatus", $hash, 0) unless($local == 1);
Log GetLogLevel($name,4), "YAMAHA_AVR $name: $hash->{STATE}";
@@ -769,13 +769,15 @@ sub YAMAHA_AVR_getInputs($)
seconds), which periodically reads the status of the AV receiver (power state, selected
input, volume and mute status) and triggers notify/filelog commands.
- Example:
-
+ Example:
+
define AV_Receiver YAMAHA_AVR 192.168.0.10
-
- define AV_Receiver YAMAHA_AVR 192.168.0.10 mainzone 60 # With custom interval of 60 seconds
-
+
+ define AV_Receiver YAMAHA_AVR 192.168.0.10 mainzone 60 # With custom interval of 60 seconds
+
+
+
Zone Selection
If your receiver supports zone selection (e.g. RX-V671, RX-V673,... and the AVANTAGE series)
@@ -793,15 +795,15 @@ sub YAMAHA_AVR_getInputs($)
The module just offers the real available inputs.
Example:
-
-
- define AV_Receiver YAMAHA_AVR 192.168.0.10 # If no zone is specified, the "Main Zone" will be used.
- attr AV_Receiver YAMAHA_AVR room Livingroom
-
- # Define the second zone
- define AV_Receiver_Zone2 YAMAHA_AVR 192.168.0.10 zone2
+
+
+ define AV_Receiver YAMAHA_AVR 192.168.0.10 # If no zone is specified, the "Main Zone" will be used.
+ attr AV_Receiver YAMAHA_AVR room Livingroom
+
+ # Define the second zone
+ define AV_Receiver_Zone2 YAMAHA_AVR 192.168.0.10 zone2
attr AV_Receiver_Zone2 room Bedroom
-
+
For each Zone you will need an own YAMAHA_AVR device, which can be assigned to a different room.
Each zone can be controlled separatly from all other available zones.
@@ -814,79 +816,79 @@ sub YAMAHA_AVR_getInputs($)
Currently, the following commands are defined; the available inputs are depending on the used receiver.
The module only offers the real available inputs. The following input commands are just an example and can differ.
-
-on
-off
-input hdmi1
-input hdmi2
-input hdmi3
-input hdmi4
-input av1
-input av2
-input av3
-input av3
-input av4
-input av5
-input av6
-input usb
-input airplay
-input tuner
-input v-aux
-input audio
-input server
-volume -80..16 (volume between -80 and +16 dB)
-mute on
-mute off
-
+
+on
+off
+input hdmi1
+input hdmi2
+input hdmi3
+input hdmi4
+input av1
+input av2
+input av3
+input av3
+input av4
+input av5
+input av6
+input usb
+input airplay
+input tuner
+input v-aux
+input audio
+input server
+volume -80..16 (volume between -80 and +16 dB)
+mute on
+mute off
+
Remote control (not in all zones available, depending on your model)
In many receiver models, inputs exist, which can't be used just by selecting them. These inputs needs
a manual interaction with the remote control to activate the playback (e.g. Internet Radio, Network Streaming).
For this application the following commands are available:
- Cursor Selection:
-
- remoteControl up
- remoteControl down
- remoteControl left
- remoteControl right
- remoteControl enter
- remoteControl return
-
+ Cursor Selection:
+
+ remoteControl up
+ remoteControl down
+ remoteControl left
+ remoteControl right
+ remoteControl enter
+ remoteControl return
+
- Menu Selection:
-
- remoteControl setup
- remoteControl option
- remoteControl display
-
+ Menu Selection:
+
+ remoteControl setup
+ remoteControl option
+ remoteControl display
+
The button names are the same as on your remote control.
- A typical example is the automatical turn on and play an internet radio broadcast:
-
- # the initial definition.
+ A typical example is the automatical turn on and play an internet radio broadcast:
+
+ And in your 99_MyUtils.pm the following function:
+
+ sub startNetRadio()
+ {
+ fhem "set AV_Receiver on";
+ sleep 5;
+ fhem "set AV_Receiver input netradio";
+ sleep 4;
+ fhem "set AV_Receiver remoteControl enter";
+ sleep 2;
+ fhem "set AV_Receiver remoteControl enter";
}
-
+
The remote control commands must be separated with a sleep, because the receiver is loading meanwhile and don't accept commands.
- Now you can use this function by typing the following line in your FHEM command line or in your notify-definitions:
-
+ Now you can use this function by typing the following line in your FHEM command line or in your notify-definitions:
+
@@ -897,11 +899,11 @@ mute off
get <name> <what>
- Currently, the following commands are defined and return the current state of the receiver.
- power
-input
-mute
-volume_level
+ Currently, the following commands are defined and return the current state of the receiver.
+power
+input
+mute
+volume_level
Attributes
@@ -951,15 +953,15 @@ volume_level
die Lautstärke zu ändern, den Receiver "Stumm" zu schalten, sowie den aktuellen Status abzufragen.
Bei der Definition eines YAMAHA_AVR-Moduls wird eine interne Routine in Gang gesetzt, welche regelmäßig
- (einstellbar durch den optionalen Parameter <Status_Interval>; falls nicht gesetzt ist der Standardwert 30 Sekunden)
+ (einstellbar durch den optionalen Parameter <Status_Interval>; falls nicht gesetzt ist der Standardwert 30 Sekunden)
den Status des Receivers abfragt und entsprechende Notify-/FileLog-Geräte triggert..
- Beispiel:
-
- define AV_Receiver YAMAHA_AVR 192.168.0.10
+ Beispiel:
+
+ define AV_Receiver YAMAHA_AVR 192.168.0.10
- define AV_Receiver YAMAHA_AVR 192.168.0.10 mainzone 60 # Mit modifiziertem Status Interval (60 Sekunden)
-
+ define AV_Receiver YAMAHA_AVR 192.168.0.10 mainzone 60 # Mit modifiziertem Status Interval (60 Sekunden)
+
Zonenauswahl
@@ -977,16 +979,15 @@ volume_level
Je nach Receiver-Modell stehen in den verschiedenen Zonen nicht immer alle Eingänge zur Verfügung.
Dieses Modul bietet nur die tatsächlich verfügbaren Eingänge an.
- Beispiel:
-
-
- define AV_Receiver YAMAHA_AVR 192.168.0.10 # Wenn keine Zone angegeben ist, wird
- attr AV_Receiver YAMAHA_AVR room Wohnzimmer # standardmäßig "mainzone" verwendet
-
- # Definition der zweiten Zone
- define AV_Receiver_Zone2 YAMAHA_AVR 192.168.0.10 zone2
- attr AV_Receiver_Zone2 room Schlafzimmer
-
+ Beispiel:
+
+ define AV_Receiver YAMAHA_AVR 192.168.0.10 # Wenn keine Zone angegeben ist, wird
+ attr AV_Receiver YAMAHA_AVR room Wohnzimmer # standardmäßig "mainzone" verwendet
+
+ # Definition der zweiten Zone
+ define AV_Receiver_Zone2 YAMAHA_AVR 192.168.0.10 zone2
+ attr AV_Receiver_Zone2 room Schlafzimmer
+
Für jede Zone muss eine eigene YAMAHA_AVR Definition erzeugt werden, welche dann unterschiedlichen Räumen zugeordnet werden kann.
Jede Zone kann unabhängig von allen anderen Zonen (inkl. der Main Zone) gesteuert werden.
@@ -1000,29 +1001,30 @@ volume_level
Aktuell werden folgende Kommandos unterstützt. Die verfügbaren Eingänge können je nach Receiver-Modell variieren.
Die folgenden Eingänge stehen beispielhaft an einem RX-V473 Receiver zur Verfügung.
Aktuell stehen folgende Kommandos zur Verfügung.
-
-on
-off
-input hdmi1
-input hdmi2
-input hdmi3
-input hdmi4
-input av1
-input av2
-input av3
-input av3
-input av4
-input av5
-input av6
-input usb
-input airplay
-input tuner
-input v-aux
-input audio
-input server
-volume -80..16 (Lautstärke zwischen -80 und +16 dB)
-mute on
-mute off
+
+
+on
+off
+input hdmi1
+input hdmi2
+input hdmi3
+input hdmi4
+input av1
+input av2
+input av3
+input av3
+input av4
+input av5
+input av6
+input usb
+input airplay
+input tuner
+input v-aux
+input audio
+input server
+volume -80..16 (Lautstärke zwischen -80 und +16 dB)
+mute on
+mute off
Fernbedienung (je nach Modell nicht in allen Zonen verfügbar)
@@ -1031,49 +1033,49 @@ mute off
bedürfen manueller Interaktion mit der Fernbedienung um die Wiedergabe zu starten (z.B. Internet Radio, Netzwerk Streaming, usw.).
Für diesen Fall gibt es folgende Befehle:
- Cursor Steuerung:
-
- remoteControl up
- remoteControl down
- remoteControl left
- remoteControl right
- remoteControl enter
- remoteControl return
-
+ Cursor Steuerung:
+
+ remoteControl up
+ remoteControl down
+ remoteControl left
+ remoteControl right
+ remoteControl enter
+ remoteControl return
+
- Menü Auswahl:
-
- remoteControl setup
- remoteControl option
- remoteControl display
-
+ Menü Auswahl:
+
+ remoteControl setup
+ remoteControl option
+ remoteControl display
+
Die Befehlsnamen entsprechen den Tasten auf der Fernbedienung.
- Ein typisches Beispiel ist das automatische Einschalten und Abspielen eines Internet Radio Sender:
-
- # Die Gerätedefinition
+ Ein typisches Beispiel ist das automatische Einschalten und Abspielen eines Internet Radio Sender:
+
+ Und in der 99_MyUtils.pm die folgende Funktion:
+
+ sub startNetRadio
+ {
+ fhem "set AV_Receiver on";
+ sleep 5;
+ fhem "set AV_Receiver input netradio";
+ sleep 4;
+ fhem "set AV_Receiver remoteControl enter";
+ sleep 2;
+ fhem "set AV_Receiver remoteControl enter";
}
-
+
Die Kommandos der Fernbedienung müssen mit einem sleep pausiert werden, da der Receiver in der Zwischenzeit arbeitet und keine Befehle annimmt..
- Nun kann man diese Funktion in der FHEM Kommandozeile oder in notify-Definitionen wie folgt verwenden.:
-
+ Nun kann man diese Funktion in der FHEM Kommandozeile oder in notify-Definitionen wie folgt verwenden.:
+
diff --git a/FHEM/72_FB_CALLMONITOR.pm b/FHEM/72_FB_CALLMONITOR.pm
index 60e0c8e33..4e555d834 100755
--- a/FHEM/72_FB_CALLMONITOR.pm
+++ b/FHEM/72_FB_CALLMONITOR.pm
@@ -87,8 +87,11 @@ FB_CALLMONITOR_Initialize($)
$hash->{ReadFn} = "FB_CALLMONITOR_Read";
$hash->{ReadyFn} = "FB_CALLMONITOR_Ready";
$hash->{GetFn} = "FB_CALLMONITOR_Get";
+ $hash->{SetFn} = "FB_CALLMONITOR_Set";
$hash->{DefFn} = "FB_CALLMONITOR_Define";
$hash->{UndefFn} = "FB_CALLMONITOR_Undef";
+ $hash->{AttrFn} = "FB_CALLMONITOR_Attr";
+
$hash->{AttrList}= "do_not_notify:0,1 loglevel:1,2,3,4,5 unique-call-ids:0,1 local-area-code remove-leading-zero:0,1 reverse-search-cache-file reverse-search:all,internal,klicktel.de,dasoertliche.de,search.ch,none reverse-search-cache:0,1 reverse-search-phonebook-file ".
@@ -113,14 +116,13 @@ FB_CALLMONITOR_Define($$)
my $dev = $a[2];
$dev .= ":1012" if($dev !~ m/:/ && $dev ne "none" && $dev !~ m/\@/);
- InternalTimer(gettimeofday()+3, "FB_CALLMONITOR_loadInternalPhonebookFile", $hash, 0);
- InternalTimer(gettimeofday()+2, "FB_CALLMONITOR_loadCacheFile", $hash, 0);
-
$hash->{DeviceName} = $dev;
my $ret = DevIo_OpenDev($hash, 0, "FB_CALLMONITOR_DoInit");
+
+
return $ret;
}
@@ -171,6 +173,26 @@ else
}
+sub
+FB_CALLMONITOR_Set($@)
+{
+ my ($hash, @a) = @_;
+
+
+ my $usage = (defined($hash->{helper}{PHONEBOOK}) ? "Unknown argument ".$a[1].", choose one of rereadPhonebook" : "");
+
+ if($a[1] eq "rereadPhonebook")
+ {
+ FB_CALLMONITOR_loadInternalPhonebookFile($hash);
+ return undef;
+ }
+ else
+ {
+ return $usage;
+ }
+}
+
+
#####################################
# Receives an event and creates several readings for event triggering
sub
@@ -221,6 +243,34 @@ FB_CALLMONITOR_Read($)
readingsBulkUpdate($hash, "internal_connection", $connection_type{$array[3]}) if($array[1] eq "CALL" or $array[1] eq "CONNECT" and defined($connection_type{$array[3]}));
readingsBulkUpdate($hash, "call_duration", $array[3]) if($array[1] eq "DISCONNECT");
+
+ if ($array[1] eq "RING")
+ {
+ $hash->{helper}{MISSED_CALL_DETECTION}{$array[2]}{EVENT} = $array[1];
+ my $no = "unknown";
+
+ if (defined($external_number))
+ {
+ $no = $external_number;
+ if (defined($reverse_search))
+ {
+ $no .= " (".$reverse_search.")";
+ }
+ }
+
+ $hash->{helper}{MISSED_CALL_DETECTION}{$array[2]}{NUMBER} = $no;
+ }
+ elsif ($array[1] eq "DISCONNECT")
+ {
+ if (($array[3] eq "0") and ($hash->{helper}{MISSED_CALL_DETECTION}{$array[2]}{EVENT} eq "RING"))
+ {
+ readingsBulkUpdate($hash, "missed_call", $hash->{helper}{MISSED_CALL_DETECTION}{$array[2]}{NUMBER})
+ }
+
+ delete($hash->{helper}{MISSED_CALL_DETECTION}{$array[2]}) if(defined($hash->{helper}{MISSED_CALL_DETECTION}{$array[2]}));
+ }
+
+
if(AttrVal($name, "unique-call-ids", "0") eq "1")
{
if($array[1] eq "RING" or $array[1] eq "CALL")
@@ -263,6 +313,46 @@ FB_CALLMONITOR_Ready($)
}
+sub
+FB_CALLMONITOR_Attr($@)
+{
+
+ my (@a) = @_;
+ my $hash = $defs{$a[1]};
+ my $name = $hash->{NAME};
+
+ if($a[0] eq "set")
+ {
+
+ if($a[2] eq "reverse-search" or $a[2] eq "reverse-search-phonebook-file")
+ {
+ $attr{$name}{"reverse-search-phonebook-file"} = $a[3] if($a[2] eq "reverse-search-phonebook-file");
+
+ FB_CALLMONITOR_loadInternalPhonebookFile($hash);
+ }
+
+ if($a[2] eq "reverse-search-cache-file")
+ {
+ $attr{$name}{"reverse-search-cache-file"} = $a[3];
+
+ FB_CALLMONITOR_loadCacheFile($hash);
+ }
+
+ }
+ elsif($a[0] eq "del")
+ {
+
+ if($a[2] eq "reverse-search" or $a[2] eq "reverse-search-phonebook-file")
+ {
+ delete($hash->{helper}{PHONEBOOK}) if(defined($hash->{helper}{PHONEBOOK}));
+ }
+
+ }
+
+ return undef;
+
+}
+
sub
FB_CALLMONITOR_reverseSearch($$)
{
@@ -446,10 +536,10 @@ sub FB_CALLMONITOR_writeToCache($$$)
}
-sub FB_CALLMONITOR_loadInternalPhonebookFile($)
+sub FB_CALLMONITOR_loadInternalPhonebookFile($@)
{
- my ($hash) = @_;
+ my ($hash, $overwrite) = @_;
my $name = $hash->{NAME};
my $phonebook = undef;
my $contact;
@@ -460,10 +550,18 @@ sub FB_CALLMONITOR_loadInternalPhonebookFile($)
my $area_code = AttrVal($name, "local-area-code", "");
my $phonebook_file = AttrVal($name, "reverse-search-phonebook-file", "/var/flash/phonebook");
- delete $hash->{helper}{PHONEBOOK} if(defined($hash->{helper}{PHONEBOOK}));
+ $overwrite = 1 unless(defined($overwrite));
+
+
+ return if($overwrite == 0 and defined($hash->{helper}{PHONEBOOK}));
+
+
+
+
if(-r $phonebook_file)
{
+ delete $hash->{helper}{PHONEBOOK} if(defined($hash->{helper}{PHONEBOOK}));
if(open(PHONEBOOK, "<$phonebook_file"))
{
@@ -530,15 +628,21 @@ sub FB_CALLMONITOR_loadInternalPhonebookFile($)
sub FB_CALLMONITOR_loadCacheFile($)
{
my ($hash) = @_;
+
my $file = AttrVal($hash->{NAME}, "reverse-search-cache-file", "");
my @cachefile;
my @tmpline;
+ my $count_contacts;
+ my $name = $hash->{NAME};
$file =~ s/(^\s+|\s+$)//g;
if($file ne "")
{
- Log 2, "FB_CALLMONITOR: loading cache file $file";
+
+ delete($hash->{helper}{CACHE}) if(defined($hash->{helper}{CACHE}));
+
+ Log GetLogLevel($hash->{NAME}, 3), "FB_CALLMONITOR: loading cache file $file";
if(open(CACHEFILE, "$file"))
{
@cachefile = ;
@@ -557,11 +661,14 @@ sub FB_CALLMONITOR_loadCacheFile($)
$hash->{helper}{CACHE}{$tmpline[0]} = $tmpline[1];
}
}
- }
+ }
+
+ $count_contacts = scalar keys %{$hash->{helper}{CACHE}};
+ Log GetLogLevel($name, 2), "FB_CALLMONITOR: $name read ".($count_contacts > 0 ? $count_contacts : "no")." contact".($count_contacts == 1 ? "" : "s")." from Cache";
}
else
{
- Log 2, "FB_CALLMONITOR: could not open cache file";
+ Log GetLogLevel($hash->{NAME}, 3), "FB_CALLMONITOR: could not open cache file";
}
}
}
@@ -605,14 +712,14 @@ sub FB_CALLMONITOR_loadCacheFile($)
Set
- N/A
+ - rereadPhonebook - Reloads the FritzBox phonebook (from given file or directly if available)
Get
- N/A
+ - search <telephone-number> - returns the name of the given number via reverse-search (internal phonebook, cache or internet research)
@@ -663,6 +770,7 @@ sub FB_CALLMONITOR_loadCacheFile($)
external_connection: $connection - The external connection (fixed line, VoIP account) which is used to take the call
call_duration: $seconds - The call duration in seconds. Is only generated at a disconnect event. The value 0 means, the call was not taken by anybody.
call_id: $id - The call identification number to separate events of two or more different calls at the same time. This id number is equal for all events relating to one specific call.
+ missed_call $number - This event will be raised in case of a missing incoming call. If available, also the name of the calling number will be displayed.
@@ -703,14 +811,14 @@ sub FB_CALLMONITOR_loadCacheFile($)
Set-Kommandos
- N/A
+ - rereadPhonebook - Liest das Telefonbuch der FritzBox neu ein (per Datei oder direkt lokal)
Get-Kommandos
- N/A
+ - search <Rufnummer> - gibt den Namen der Telefonnummer zurück (aus Cache, Telefonbuch oder Rückwärtssuche)
@@ -767,6 +875,7 @@ sub FB_CALLMONITOR_loadCacheFile($)
external_connection: $connection - Der externe Anschluss welcher genutzt wird um das Gespräch durchzuführen (Festnetz, VoIP Nummer, ...)
call_duration: $seconds - Die Gesprächsdauer in Sekunden. Dieser Wert wird nur bei einem disconnect-Event erzeugt. Ist der Wert 0, so wurde das Gespräch von niemandem angenommen.
call_id: $id - Die Identifizierungsnummer eines einzelnen Gesprächs. Dient der Zuordnung bei 2 oder mehr parallelen Gesprächen, damit alle Events eindeutig einem Gespräch zugeordnet werden können
+ missed_call: $number - Dieses Event wird nur generiert, wenn ein eingehender Anruf nicht beantwortet wird. Sofern der Name dazu bekannt ist, wird dieser ebenfalls mit angezeigt.
diff --git a/FHEM/73_PRESENCE.pm b/FHEM/73_PRESENCE.pm
index 6828f6505..daa460a7c 100755
--- a/FHEM/73_PRESENCE.pm
+++ b/FHEM/73_PRESENCE.pm
@@ -1,4 +1,4 @@
-# $Id$
+# $Id$
##############################################################################
#
# 73_PRESENCE.pm
@@ -35,6 +35,8 @@ use DevIo;
+
+
sub
PRESENCE_Initialize($)
{
@@ -45,11 +47,11 @@ PRESENCE_Initialize($)
# Provider
$hash->{ReadFn} = "PRESENCE_Read";
$hash->{ReadyFn} = "PRESENCE_Ready";
- $hash->{GetFn} = "PRESENCE_Get";
+ $hash->{SetFn} = "PRESENCE_Set";
$hash->{DefFn} = "PRESENCE_Define";
$hash->{UndefFn} = "PRESENCE_Undef";
$hash->{AttrFn} = "PRESENCE_Attr";
- $hash->{AttrList}= "do_not_notify:0,1 disable:0,1 loglevel:1,2,3,4,5 ".$readingFnAttributes;
+ $hash->{AttrList}= "do_not_notify:0,1 disable:0,1 fritzbox_repeater:0,1 loglevel:1,2,3,4,5 ".$readingFnAttributes;
}
@@ -61,9 +63,9 @@ PRESENCE_Define($$)
my @a = split("[ \t]+", $def);
my $dev;
- if($a[2] ne "lan-bluetooth" and not (@a == 4 or @a == 5))
+ if($a[2] ne "lan-bluetooth" and not (@a == 4 or @a == 5 or @a == 6))
{
- my $msg = "wrong syntax: define PRESENCE [ ]";
+ my $msg = "wrong syntax: define PRESENCE [ [ ] ]";
Log 2, $msg;
return $msg;
}
@@ -80,16 +82,18 @@ PRESENCE_Define($$)
my $address = $a[3];
my $timeout = (defined($a[4]) ? $a[4] : 30);
+ my $presence_timeout = (defined($a[5]) ? $a[5] : $timeout);
+
$timeout = (defined($a[5]) ? $a[5] : 30) if($destination eq "lan-bluetooth");
-
+ $presence_timeout = (defined($a[6]) ? $a[6] : 30) if($destination eq "lan-bluetooth");
$hash->{ADDRESS} = $address;
- $hash->{TIMEOUT} = $timeout;
-
+ $hash->{TIMEOUT_NORMAL} = $timeout;
+ $hash->{TIMEOUT_PRESENT} = $presence_timeout;
if(defined($timeout) and not $timeout =~ /^\d+$/)
{
- my $msg = "timeout must be a number";
+ my $msg = "check-interval must be a number";
Log 2, "PRESENCE: ".$msg;
return $msg;
}
@@ -97,11 +101,27 @@ PRESENCE_Define($$)
if(defined($timeout) and not $timeout > 0)
{
- my $msg = "timeout must be greater than zero";
+ my $msg = "check-interval must be greater than zero";
Log 2, "PRESENCE: ".$msg;
return $msg;
}
+
+ if(defined($presence_timeout) and not $presence_timeout =~ /^\d+$/)
+ {
+ my $msg = "presence-check-interval must be a number";
+ Log 2, "PRESENCE: ".$msg;
+ return $msg;
+ }
+
+
+ if(defined($presence_timeout) and not $presence_timeout > 0)
+ {
+ my $msg = "presence-check-interval must be greater than zero";
+ Log 2, "PRESENCE: ".$msg;
+ return $msg;
+ }
+
if(($destination eq "local-bluetooth" or $destination eq "lan-bluetooth") and not $address =~ /^\s*([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}\s*$/)
{
my $msg = "given address is not a bluetooth hardware address";
@@ -116,7 +136,7 @@ PRESENCE_Define($$)
return $msg;
}
- if($destination eq "fritzbox" and not $< == 0)
+ if(-X "/usr/bin/ctlmgr_ctl" and ($destination eq "fritzbox" or $destination eq "lan-ping") and not $< == 0)
{
my $msg = "FHEM is not running under root (currently ".(getpwuid($<))[0].") This check can only performed with root access to the FritzBox";
@@ -127,9 +147,14 @@ PRESENCE_Define($$)
if($destination eq "lan-ping" or $destination eq "local-bluetooth" or $destination eq "fritzbox")
{
- $hash->{MODE} = $destination;
- PRESENCE_StartLocalScan($hash);
- return;
+ $hash->{MODE} = $destination;
+
+ delete $hash->{helper}{cachednr} if(defined($hash->{helper}{cachednr}));
+
+ RemoveInternalTimer($hash);
+ InternalTimer(gettimeofday()+2, "PRESENCE_StartLocalScan", $hash, 0) unless(exists($hash->{helper}{DISABLED}) and $hash->{helper}{DISABLED});
+
+ return;
}
elsif($destination eq "lan-bluetooth")
@@ -180,6 +205,34 @@ PRESENCE_Undef($$)
return undef;
}
+sub
+PRESENCE_Set($@)
+{
+ my ($hash, @a) = @_;
+
+ return "No Argument given" if(!defined($a[1]));
+
+ my $usage = (defined($hash->{MODE}) and $hash->{MODE} ne "lan-bluetooth" ? "Unknown argument ".$a[1].", choose one of statusRequest " : undef);
+
+ if($a[1] eq "statusRequest")
+ {
+ if($hash->{MODE} ne "lan-bluetooth")
+ {
+ PRESENCE_StartLocalScan($hash, 1);
+ return "";
+ }
+
+ }
+ else
+ {
+ return $usage;
+ }
+
+
+
+
+
+}
##########################
@@ -194,14 +247,23 @@ PRESENCE_Attr(@)
if($a[3] eq "0")
{
$hash->{helper}{DISABLED} = 0;
- if(defined($hash->{FD}))
+ readingsSingleUpdate($hash, "state", "defined",0);
+ if(defined($hash->{DeviceName}))
{
- PRESENCE_DoInit($hash);
+ if(defined($hash->{FD}))
+ {
+ PRESENCE_DoInit($hash);
+ }
+ else
+ {
+ DevIo_OpenDev($hash, 0, "PRESENCE_DoInit");
+ }
}
else
{
PRESENCE_StartLocalScan($hash);
}
+
}
elsif($a[3] eq "1")
{
@@ -221,9 +283,17 @@ PRESENCE_Attr(@)
elsif($a[0] eq "del" && $a[2] eq "disable")
{
$hash->{helper}{DISABLED} = 0;
- if(defined($hash->{FD}))
+ readingsSingleUpdate($hash, "state", "defined",0);
+ if(defined($hash->{DeviceName}))
{
- PRESENCE_DoInit($hash);
+ if(defined($hash->{FD}))
+ {
+ PRESENCE_DoInit($hash);
+ }
+ else
+ {
+ DevIo_OpenDev($hash, 0, "PRESENCE_DoInit");
+ }
}
else
{
@@ -305,8 +375,14 @@ PRESENCE_DoInit($)
my ($hash) = @_;
- DevIo_SimpleWrite($hash, $hash->{ADDRESS}."|".$hash->{TIMEOUT}."\n", 0) unless($hash->{helper}{DISABLED});
-
+ unless($hash->{helper}{DISABLED})
+ {
+ DevIo_SimpleWrite($hash, $hash->{ADDRESS}."|".$hash->{TIMEOUT_NORMAL}."\n", 0);
+ }
+ else
+ {
+ readingsSingleUpdate($hash, "state", "disabled",1);
+ }
}
@@ -315,7 +391,8 @@ PRESENCE_Ready($)
{
my ($hash) = @_;
- return DevIo_OpenDev($hash, 1, "PRESENCE_DoInit");
+
+ return DevIo_OpenDev($hash, 1, "PRESENCE_DoInit") if($hash->{MODE} eq "lan-bluetooth");
}
@@ -325,40 +402,40 @@ PRESENCE_Ready($)
# Functions for local testing with Blocking.pm to ensure a smooth FHEM processing
#
#
-sub
-PRESENCE_StartLocalScan($)
+sub PRESENCE_StartLocalScan($;$)
{
-my ($hash) = @_;
+ my ($hash, $local) = @_;
+ $local = 0 unless(defined($local));
if($hash->{MODE} eq "local-bluetooth")
{
-
- BlockingCall("PRESENCE_DoLocalBluetoothScan", $hash->{NAME}."|".$hash->{ADDRESS}, "PRESENCE_ProcessLocalScan", 20);
+ BlockingCall("PRESENCE_DoLocalBluetoothScan", $hash->{NAME}."|".$hash->{ADDRESS}."|".$local, "PRESENCE_ProcessLocalScan", 60);
}
elsif($hash->{MODE} eq "lan-ping")
{
- BlockingCall("PRESENCE_DoLocalPingScan", $hash->{NAME}."|".$hash->{ADDRESS}, "PRESENCE_ProcessLocalScan", 20);
-
+ BlockingCall("PRESENCE_DoLocalPingScan", $hash->{NAME}."|".$hash->{ADDRESS}."|".$local, "PRESENCE_ProcessLocalScan", 60);
}
elsif($hash->{MODE} eq "fritzbox")
{
- BlockingCall("PRESENCE_DoLocalFritzBoxScan", $hash->{NAME}."|".$hash->{ADDRESS}, "PRESENCE_ProcessLocalScan", 20);
+ BlockingCall("PRESENCE_DoLocalFritzBoxScan", $hash->{NAME}."|".$hash->{ADDRESS}."|".$local."|".AttrVal($hash->{NAME}, "fritzbox_repeater", "0"), "PRESENCE_ProcessLocalScan", 60);
}
}
sub
-PRESENCE_DoLocalPingScan($$)
+PRESENCE_DoLocalPingScan($)
{
my ($string) = @_;
- my ($name, $device) = split("\\|", $string);
+ my ($name, $device, $local) = split("\\|", $string);
+ Log GetLogLevel($defs{$name}{NAME}, 5), "PRESENCE_DoLocalPingScan: $string";
+
my $retcode;
my $return;
my $temp;
- if($^O =~ m/Win/)
+ if($^O =~ m/(Win|cygwin)/)
{
eval "require Net::Ping;";
my $pingtool = Net::Ping->new("syn");
@@ -366,18 +443,23 @@ PRESENCE_DoLocalPingScan($$)
if($pingtool)
{
$retcode = $pingtool->ping($device, 5);
- $return = "$name|".($retcode ? "present" : "absent");
+
+ Log GetLogLevel($name, 5), "PRESENCE ($name) - pingtool returned $retcode";
+
+ $return = "$name|$local|".($retcode ? "present" : "absent");
}
else
{
- $return = "$name|error|Could not create a Net::Ping object.";
+ $return = "$name|$local|error|Could not create a Net::Ping object.";
}
}
else
{
$temp = qx(ping -c 4 $device);
- $return = "$name|".($temp =~ /\d+ bytes from/ ? "present" : "absent");
+
+ Log GetLogLevel($name, 5), "PRESENCE ($name) - ping command returned with output:\n$temp";
+ $return = "$name|$local|".($temp =~ /\d+ [Bb]ytes (from|von)/ ? "present" : "absent");
}
return $return;
@@ -388,32 +470,69 @@ sub
PRESENCE_DoLocalFritzBoxScan($)
{
my ($string) = @_;
- my ($name, $device) = split("\\|", $string);
-
+ my ($name, $device, $local, $repeater) = split("\\|", $string);
+
+ Log GetLogLevel($defs{$name}{NAME}, 5), "PRESENCE_DoLocalFritzBoxScan: $string";
my $number=0;
+ my $check_command = ($repeater ? "active" : "speed");
+
+
+ my $status=0;
+
+ if (defined($defs{$name}{helper}{cachednr}))
+ {
+ $number = $defs{$name}{helper}{cachednr};
+
+ Log GetLogLevel($name, 5), "PRESENCE_DoLocalFritzBoxScan: name=$name device=$device cachednr=$number";
+
+ my $cached_name = qx(/usr/bin/ctlmgr_ctl r landevice settings/landevice$number/name);
+ chomp $cached_name;
+
+ # only use the cached $number if it has still the correct device name
+ if($cached_name eq $device)
+ {
+ Log GetLogLevel($name, 5), "PRESENCE ($name) - checking with cached number the $check_command state ($number)";
+ $status = qx(/usr/bin/ctlmgr_ctl r landevice settings/landevice$number/$check_command);
+ if(not $status =~ /^\s*\d+\s*$/)
+ {
+ return "$name|$local|error|could not execute ctlmgr_ctl (cached)";
+ }
+ return ($status == 0)? "$name|$local|absent|$number" : "$name|$local|present|$number"; ###MH
+ }
+ else
+ {
+ Log GetLogLevel($name, 5), "PRESENCE ($name) - cached device name ($cached_name) does not match expected name ($device). perform a full scan";
+ }
+ }
+
my $max = qx(/usr/bin/ctlmgr_ctl r landevice settings/landevice/count);
chomp $max;
if(not $max =~ /^\s*\d+\s*$/)
{
- return "$name|error|could not execute ctlmgr_ctl";
+ return "$name|$local|error|could not execute ctlmgr_ctl";
}
- my $status=0;
+
my $net_device;
+ $number = 0;
+
while($number <= $max)
{
$net_device=qx(/usr/bin/ctlmgr_ctl r landevice settings/landevice$number/name);
chomp $net_device;
+ Log GetLogLevel($name, 5), "PRESENCE ($name) - checking with device number $number the $check_command state ($net_device)";
if($net_device eq $device)
{
- $status=qx(/usr/bin/ctlmgr_ctl r landevice settings/landevice$number/active);
+ $status=qx(/usr/bin/ctlmgr_ctl r landevice settings/landevice$number/$check_command);
+
+ Log GetLogLevel($name, 5), "PRESENCE ($name) - $check_command for device number $net_device is $status";
last;
}
@@ -422,31 +541,26 @@ PRESENCE_DoLocalFritzBoxScan($)
}
chomp $status;
-
- if($status == 1)
- {
- return "$name|present";
- }
- else
- {
- return "$name|absent";
- }
-
+ return ($status == 0 ? "$name|$local|absent" : "$name|$local|present").($number <= $max ? "|$number" : "");
}
sub
PRESENCE_DoLocalBluetoothScan($)
{
my ($string) = @_;
- my ($name, $device) = split("\\|", $string);
+ my ($name, $device, $local) = split("\\|", $string);
my $hcitool = qx(which hcitool);
my $devname;
my $return;
my $wait = 1;
my $ps;
- if($hcitool)
+ Log GetLogLevel($name, 4), "PRESENCE ($name): 'which hcitool' returns: $hcitool";
+ chomp $hcitool;
+
+
+ if(-x $hcitool)
{
while($wait)
{ # check if another hcitool process is running
@@ -454,6 +568,7 @@ PRESENCE_DoLocalBluetoothScan($)
if(not $ps =~ /^\s*$/)
{
# sleep between 1 and 5 seconds and try again
+ Log GetLogLevel($name, 5), "PRESENCE ($name) - another hcitool command is running. waiting...";
sleep(rand(4)+1);
}
else
@@ -463,20 +578,22 @@ PRESENCE_DoLocalBluetoothScan($)
}
$devname = qx(hcitool name $device);
+
chomp($devname);
-
+ Log GetLogLevel($name, 4), "PRESENCE ($name) - hcitool returned: $devname";
+
if(not $devname =~ /^\s*$/)
{
- $return = "$name|present|$devname";
+ $return = "$name|$local|present|$devname";
}
else
{
- $return = "$name|absent";
+ $return = "$name|$local|absent";
}
}
else
{
- $return = "$name|error|no hcitool binary found. Please check that the bluez-package is properly installed";
+ $return = "$name|$local|error|no hcitool binary found. Please check that the bluez-package is properly installed";
}
return $return;
@@ -493,31 +610,45 @@ PRESENCE_ProcessLocalScan($)
my @a = split("\\|",$string);
my $hash = $defs{$a[0]};
+ my $local = $a[1];
return if($hash->{helper}{DISABLED});
+ Log GetLogLevel($hash->{NAME}, 5), "PRESENCE_ProcessLocalScan: $string";
+
+ if($hash->{MODE} eq "fritzbox" and defined($a[3]))
+ {
+ $hash->{helper}{cachednr} = $a[3] if(($a[2] eq "present") || ($a[2] eq "absent"));
+ }
+ elsif($hash->{MODE} eq "fritzbox" and defined($hash->{helper}{cachednr}))
+ {
+ delete($hash->{helper}{cachednr});
+ }
readingsBeginUpdate($hash);
- if($a[1] eq "present")
+ if($a[2] eq "present")
{
readingsBulkUpdate($hash, "state", "present");
- readingsBulkUpdate($hash, "device_name", $a[2]) if(defined($a[2]));
+ readingsBulkUpdate($hash, "device_name", $a[3]) if(defined($a[3]) and $hash->{MODE} =~ /^(lan-bluetooth|local-bluetooth)$/ );
}
- elsif($a[1] eq "absent")
+ elsif($a[2] eq "absent")
{
readingsBulkUpdate($hash, "state", "absent");
}
- elsif($a[1] eq "error")
+ elsif($a[2] eq "error")
{
- Log GetLogLevel($hash->{NAME}, 2), "PRESENCE: error while processing device ".$hash->{NAME}." - ".$a[2];
+ Log GetLogLevel($hash->{NAME}, 2), "PRESENCE: error while processing device ".$hash->{NAME}." - ".$a[3];
}
readingsEndUpdate($hash, 1);
- #Schedule the next check withing $timeout
- RemoveInternalTimer($hash);
- InternalTimer(gettimeofday()+$hash->{TIMEOUT}, "PRESENCE_StartLocalScan", $hash, 1) unless($hash->{helper}{DISABLED});
+ #Schedule the next check withing $timeout if it is a regular run
+ unless($local)
+ {
+ RemoveInternalTimer($hash);
+ InternalTimer(gettimeofday()+($a[2] eq "present" ? $hash->{TIMEOUT_PRESENT} : $hash->{TIMEOUT_NORMAL}), "PRESENCE_StartLocalScan", $hash, 0) unless($hash->{helper}{DISABLED});
+ }
}
1;
@@ -533,7 +664,7 @@ PRESENCE_ProcessLocalScan($)
This module provides several operational modes to serve your needs. These are:
- lan-ping - A presence check of a device via network ping in your LAN/WLAN
- - fritzbox - A presence check by requesting the device state from the FritzBox internals (only available when running FHEM on a FritzBox!)
+
- fritzbox - A presence check by requesting the device state from the FritzBox internals (only available when running FHEM on a FritzBox!)
- local-bluetooth - A presence check by searching directly for a given bluetooth device nearby
- lan-bluetooth - A presence check of a bluetooth device via LAN network by connecting to a presenced or collectord instance
@@ -541,17 +672,17 @@ PRESENCE_ProcessLocalScan($)
Define
Mode: lan-ping
- define <name> PRESENCE lan-ping <ip-address> [ <check-interval> ]
+ define <name> PRESENCE lan-ping <ip-address> [ <check-interval> [ <present-check-interval> ] ]
Checks for a network device via PING requests and reports its presence state.
Mode: fritzbox
- define <name> PRESENCE fritzbox <device-name> [ <check-interval> ]
+ define <name> PRESENCE fritzbox <device-name> [ <check-interval> [ <present-check-interval> ] ]
Checks for a network device by requesting the internal state on a FritzBox via ctlmgr_ctl. The device-name must be the same as shown in the network overview of the FritzBox
Mode: local-bluetooth
- define <name> PRESENCE local-bluetooth <bluetooth-address> [ <check-interval> ]
+ define <name> PRESENCE local-bluetooth <bluetooth-address> [ <check-interval> [ <present-check-interval> ] ]
Checks for a bluetooth device and reports its presence state. For this mode the shell command "hcitool" is required (provided with a bluez installation under Debian via APT), as well
as a functional bluetooth device directly attached to your machine.
@@ -650,7 +781,7 @@ Options:
@@ -659,7 +790,7 @@ Options:
Set
- N/A
+ - statusRequest - (Only for local-bluetooth, lan-ping and fritzbox) - Schedules an immediatly check.
@@ -680,6 +811,16 @@ Options:
If this attribute is activated, an active check will be disabled.
Possible values: 0 => not disabled , 1 => disabled
Default Value is 0 (not disabled)
+ fritzbox_repeater (Only in Mode "fritzbox" applicable)
+ If your FritzBox is part of a network using repeaters, than this attribute needs to be enabled to ensure a correct recognition for devices, which are connected via repeater.
+
+ This attribute is also needed, if your network device has no speed information on the FritzBox website (Home Network).
+ BE AWARE: The recognition of device going absent in a repeated network can take about 15 - 20 minutes!!
+
+ Possible values: 0 => Use default recognition, 1 => Use repeater-supported recognition
+ Default Value is 0 (Use default recognition)
+
+
@@ -724,18 +865,18 @@ Options:
Define
Modus: lan-ping
- define <name> PRESENCE lan-ping <IP-Addresse oder Hostname> [ <Interval> ]
+ define <name> PRESENCE lan-ping <IP-Addresse oder Hostname> [ <Interval> [ <Anwesend-Interval> ] ]
Prüft ob ein Gerät über Netzwerk (üblicherweise WLAN) auf Ping-Anfragen reagiert und setzt entsprechend den Anwesenheitsstatus.
Modus: fritzbox
- define <name> PRESENCE fritzbox <device-name> [ <Interval> ]
+ define <name> PRESENCE fritzbox <device-name> [ <Interval> [ <Anwesend-Interval> ] ]
Prüft ob ein Gerät welches per WLAN mit der FritzBox verbunden ist, erreichbar durch Abfrage des Status mit dem Befehl ctlmgr_ctl.
Der Gerätename (Parameter: <device-name>) muss dem Namen entsprechen, welcher im Menüpunkt "Heimnetz" auf der FritzBox-Oberfläche angezeigt wird.
Modus: local-bluetooth
- define <name> PRESENCE local-bluetooth <Bluetooth-Adresse> [ <Interval> ]
+ define <name> PRESENCE local-bluetooth <Bluetooth-Adresse> [ <Interval> [ <Anwesend-Interval> ] ]
Prüft ob ein Bluetooth-Gerät abgefragt werden kann und meldet dies als Anwesenheit. Für diesen Modus wird der Shell-Befehl "hcitool" benötigt
(wird durch das Paket bluez bereitgestellt), sowie ein funktionierender Bluetooth-Empfänger (intern oder als USB-Stick)
@@ -836,7 +977,7 @@ Options:
@@ -845,7 +986,8 @@ Options:
Set
- N/A
+
+ - statusRequest - (Nu für local-bluetooth, lan-ping and fritzbox) - Startet einen sofortigen Check.
@@ -866,6 +1008,17 @@ Options:
Wenn dieses Attribut aktiviert ist, wird die Anwesenheitserkennung nicht mehr durchgeführt.
Mögliche Werte: 0 => Erkennung durchführen , 1 => Keine Erkennungen durchführen
Standardwert ist 0 (Erkennung durchführen)
+ fritzbox_repeater (Nur im Modus "fritzbox" anwendbar)
+ Wenn die FritzBox Teil eines Netzwerkes ist, welches mit Repeatern arbeitet, dann muss dieses Attribut gesetzt sein um die Erkennung von Geräten zu gewährleisten,
+ welche über einen Repeater erreichbar sind.
+
+ Dies gilt ebenso für Devices, welche keine Geschwindigkeitsangaben auf der FritzBox Seite (Heimnetz) anzeigen können.
+ ACHTUNG: Die Erkennung der Abwesenheit eines Gerätes in einem Repeater-Netzwerk kann ca. 15 - 20 Minuten dauern!!
+
+ Mögliche Werte: 0 => Standarderkennung verwenden, 1 => Erkennung für Repeatergeräte verwenden
+ Standardwert ist 0 (Standarderkennung verwenden)
+
+
diff --git a/FHEM/91_notify.pm b/FHEM/91_notify.pm
index c4c099238..ca82015f7 100755
--- a/FHEM/91_notify.pm
+++ b/FHEM/91_notify.pm
@@ -127,44 +127,62 @@ notify_Attr(@)
Examples:
- define b3lampV1 notify btn3 set lamp %
- define b3lampV2 notify btn3 { fhem "set lamp %" }
- define b3lampV3 notify btn3 "/usr/local/bin/setlamp "%""
- define b3lampV3 notify btn3 set lamp1 %;;set lamp2 %
- define wzMessLg notify wz:measured.* "/usr/local/bin/logfht @ "%""
-
- define LogUndef notify global:UNDEFINED.* "send-me-mail.sh "%""
+ define b3lampV1 notify btn3 set lamp $EVENT
+ define b3lampV2 notify btn3 { fhem "set lamp $EVENT" }
+ define b3lampV3 notify btn3 "/usr/local/bin/setlamp "$EVENT""
+ define b3lampV3 notify btn3 set lamp1 $EVENT;;set lamp2 $EVENT
+ define wzMessLg notify wz:measured.* "/usr/local/bin/logfht $NAME "$EVENT""
+
+ define LogUndef notify global:UNDEFINED.* "send-me-mail.sh "$EVENT""
Notes:
- - The character
% will be replaced with the received event,
- e.g. with on or off or measured-temp: 21.7
- (Celsius) It is advisable to put the % into double
- quotes, else the shell may get a syntax error.
-
- - The character
@ will be replaced with the device
- name.
-
- - To use % or @ in the text itself, use the double mode (%% or @@).
-
- - Instead of
% and @, the parameters
- %EVENT (same as %), %NAME (same as
- @) and %TYPE (contains the device type, e.g.
- FHT) can be used. The space separated event "parts" are
- available as %EVTPART0, %EVTPART1, etc. A single % looses
- its special meaning if any of these parameters appears in the
- definition.
-
- <pattern> may also be a compound of
- definition:event to filter for events.
+ <pattern> is either the name of the triggering
+ device, or devicename:event.
<pattern> must completely (!)
- match either the device name, or the compound of the device name and the
- event. The event is either the string you see in the list output in paranthesis after the device name, or the
- string you see when you do a detailed list of the device.
+ match either the device name, or the compound of the device name and the
+ event. To identify the events use "inform" command in telnet or "Event
+ Monitor" in FHEMWEB.
+ - in the command section you can access the event:
+
+ - The variable $EVENT will contain the complete event, e.g.
+
measured-temp: 21.7 (Celsius)
+ - $EVTPART0,$EVTPART1,$EVTPART2,etc contain the space separated event
+ parts (e.g.
$EVTPART0="measured-temp:", $EVTPART1="21.7",
+ $EVTPART2="(Celsius)". This data is available as a local
+ variable in perl, as environment variable for shell scripts, and will
+ be textually replaced for FHEM commands.
+ - $NAME contains the device triggering the event, e.g.
+
myFht
+
+
+ - Note: the following is deprecated and will be removed in a future
+ release. The described replacement is attempted if none of the above
+ variables ($NAME/$EVENT/etc) found in the command.
+
+ - The character
% will be replaced with the received
+ event, e.g. with on or off or
+ measured-temp: 21.7 (Celsius) It is advisable to put
+ the % into double quotes, else the shell may get a syntax
+ error.
+
+ - The character
@ will be replaced with the device
+ name.
+
+ - To use % or @ in the text itself, use the double mode (%% or
+ @@).
+
+ - Instead of
% and @, the parameters
+ %EVENT (same as %), %NAME (same
+ as @) and %TYPE (contains the device type,
+ e.g. FHT) can be used. The space separated event "parts"
+ are available as %EVTPART0, %EVTPART1, etc. A single %
+ looses its special meaning if any of these parameters appears in the
+ definition.
+
- To use database logging, copy the file contrib/91_DbLog.pm into your
modules directory, and change the $dbconn parameter in the file.
diff --git a/FHEM/91_watchdog.pm b/FHEM/91_watchdog.pm
index 7d6a0c21c..a18316902 100755
--- a/FHEM/91_watchdog.pm
+++ b/FHEM/91_watchdog.pm
@@ -65,7 +65,7 @@ watchdog_Notify($$)
my ($watchdog, $dev) = @_;
my $ln = $watchdog->{NAME};
- return "" if($attr{$ln} && $attr{$ln}{disable});
+ return "" if(AttrVal($ln, "disable", 0));
my $dontReAct = AttrVal($ln, "regexp1WontReactivate", 0);
my $n = $dev->{NAME};
@@ -116,7 +116,14 @@ sub
watchdog_Trigger($)
{
my ($watchdog) = @_;
- Log(3, "Watchdog $watchdog->{NAME} triggered");
+ my $name = $watchdog->{NAME};
+
+ if(AttrVal($name, "disable", 0)) {
+ $watchdog->{STATE} = "defined";
+ return "";
+ }
+
+ Log(3, "Watchdog $name triggered");
my $exec = SemicolonEscape($watchdog->{CMD});;
$watchdog->{STATE} = "triggered";
@@ -171,23 +178,23 @@ watchdog_Undef($$)
Examples:
-
- # Request data from the FHT80 _once_ if we do not receive any message for
- # 15 Minutes.
- define w watchdog FHT80 00:15:00 SAME set FHT80 date
+
+ # Request data from the FHT80 _once_ if we do not receive any message for
+ # 15 Minutes.
+ define w watchdog FHT80 00:15:00 SAME set FHT80 date
- # Request data from the FHT80 _each_ time we do not receive any message for
- # 15 Minutes, i.e. reactivate the watchdog after it triggered. Might be
- # dangerous, as it can trigger in a loop.
- define w watchdog FHT80 00:15:00 SAME set FHT80 date;; trigger w .
+ # Request data from the FHT80 _each_ time we do not receive any message for
+ # 15 Minutes, i.e. reactivate the watchdog after it triggered. Might be
+ # dangerous, as it can trigger in a loop.
+ define w watchdog FHT80 00:15:00 SAME set FHT80 date;; trigger w .
- # Shout once if the HMS100-FIT is not alive
- define w watchdog HMS100-FIT 01:00:00 SAME "alarm-fit.sh"
+ # Shout once if the HMS100-FIT is not alive
+ define w watchdog HMS100-FIT 01:00:00 SAME "alarm-fit.sh"
- # Send mail if the window is left open
- define w watchdog contact1:open 00:15 contact1:closed "mail_me close window1"
- attr w regexp1WontReactivate
-
+ # Send mail if the window is left open
+ define w watchdog contact1:open 00:15 contact1:closed "mail_me close window1"
+ attr w regexp1WontReactivate
+
Notes:
diff --git a/FHEM/92_FileLog.pm b/FHEM/92_FileLog.pm
index c945dc5b4..3d571ccce 100755
--- a/FHEM/92_FileLog.pm
+++ b/FHEM/92_FileLog.pm
@@ -309,7 +309,7 @@ RESCAN:
my $v = $fld[$col]-$h->{last1};
$v = 0 if($v < 0); # Skip negative delta
$dte = "$lda[0]_$ts";
- $val = sprintf("%0.1f", $v);
+ $val = sprintf("%g", $v);
if($hd == 13) { # Generate missing 0 values / hour
my @cda = split("[_:]", $ld);
for(my $mi = $lda[1]+1; $mi < $cda[1]; $mi++) {
@@ -506,8 +506,9 @@ seekTo($$$$)
Log events to <filename>. The log format is
-
- YYYY:MM:DD_HH:MM:SS <device> <event>
+
+ YYYY:MM:DD_HH:MM:SS <device> <event>
+
The regexp will be checked against the device name
devicename:event or timestamp:devicename:event combination.
The regexp must match the complete string, not just a part of it.
@@ -613,8 +614,10 @@ seekTo($$$$)
Example:
- get outlog out-2008.log - 2008-01-01 2008-01-08 4:IR:int: 9:IR::
-
+
+ get outlog out-2008.log - 2008-01-01 2008-01-08 4:IR:int: 9:IR::
+
+
diff --git a/FHEM/93_DbLog.pm b/FHEM/93_DbLog.pm
index d18fc5e18..8b87af547 100644
--- a/FHEM/93_DbLog.pm
+++ b/FHEM/93_DbLog.pm
@@ -42,9 +42,9 @@ DbLog_Define($@)
my @a = split("[ \t][ \t]*", $def);
return "wrong syntax: define DbLog configuration regexp"
- if(int(@a) != 4);
+ if(int(@a) != 4);
- my $regexp = $a[3];
+ my $regexp = $a[3];
eval { "Hallo" =~ m/^$regexp$/ };
return "Bad regexp: $@" if($@);
@@ -100,94 +100,94 @@ DbLog_Attr(@)
sub
DbLog_ParseEvent($$)
{
- my ($type, $event)= @_;
- my @result;
+ my ($type, $event)= @_;
+ my @result;
- # split the event into reading and argument
- # "day-temp: 22.0 (Celsius)" -> "day-temp", "22.0 (Celsius)"
- my @parts = split(/: /,$event);
- my $reading = shift @parts;
- my $value = join(": ", @parts);
- my $unit = "";
+ # split the event into reading and argument
+ # "day-temp: 22.0 (Celsius)" -> "day-temp", "22.0 (Celsius)"
+ my @parts = split(/: /,$event);
+ my $reading = shift @parts;
+ my $value = join(": ", @parts);
+ my $unit = "";
- #default
- if(!defined($reading)) { $reading = ""; }
- if(!defined($value)) { $value = ""; }
+ #default
+ if(!defined($reading)) { $reading = ""; }
+ if(!defined($value)) { $value = ""; }
- # the interpretation of the argument depends on the device type
- # EMEM, M232Counter, M232Voltage return plain numbers
- if(($type eq "M232Voltage") ||
- ($type eq "M232Counter") ||
- ($type eq "EMEM")) {
+ # the interpretation of the argument depends on the device type
+ # EMEM, M232Counter, M232Voltage return plain numbers
+ if(($type eq "M232Voltage") ||
+ ($type eq "M232Counter") ||
+ ($type eq "EMEM")) {
}
- # Onewire
- elsif(($type eq "OWAD") ||
- ($type eq "OWSWITCH") ||
- ($type eq "OWMULTI")) {
- $reading = "data";
- $value = $event;
- }
- # FS20
- elsif(($type eq "FS20") ||
+ # Onewire
+ elsif(($type eq "OWAD") ||
+ ($type eq "OWSWITCH") ||
+ ($type eq "OWMULTI")) {
+ $reading = "data";
+ $value = $event;
+ }
+ # FS20
+ elsif(($type eq "FS20") ||
($type eq "X10")) {
- #@parts = split(/ /,$event);
- #$reading = shift @parts;
- #$value = join(" ", shift @parts);
+ #@parts = split(/ /,$event);
+ #$reading = shift @parts;
+ #$value = join(" ", shift @parts);
- if($reading =~ m/^dim(\d+).*/o) {
- $value = $1;
- $reading= "dim";
- $unit= "%";
- }
- if(!defined($value) || $value eq "") {$value=$reading; $reading="data";}
- }
+ if($reading =~ m/^dim(\d+).*/o) {
+ $value = $1;
+ $reading= "dim";
+ $unit= "%";
+ }
+ if(!defined($value) || $value eq "") {$value=$reading; $reading="data";}
+ }
# FHT
elsif($type eq "FHT") {
if($reading =~ m(-from[12]\ ) || $reading =~ m(-to[12]\ )) {
- @parts= split(/ /,$event);
- $reading= $parts[0];
- $value= $parts[1];
- $unit= "";
+ @parts= split(/ /,$event);
+ $reading= $parts[0];
+ $value= $parts[1];
+ $unit= "";
}
if($reading =~ m(-temp)) { $value=~ s/ \(Celsius\)//; $unit= "°C"; }
if($reading =~ m(temp-offset)) { $value=~ s/ \(Celsius\)//; $unit= "°C"; }
if($reading =~ m(^actuator[0-9]*)) {
- if($value eq "lime-protection") {
- $reading= "actuator-lime-protection";
- undef $value;
- }
- elsif($value =~ m(^offset:)) {
- $reading= "actuator-offset";
- @parts= split(/: /,$value);
- $value= $parts[1];
- if(defined $value) {
- $value=~ s/%//; $value= $value*1.; $unit= "%";
- }
- }
- elsif($value =~ m(^unknown_)) {
- @parts= split(/: /,$value);
- $reading= "actuator-" . $parts[0];
- $value= $parts[1];
- if(defined $value) {
- $value=~ s/%//; $value= $value*1.; $unit= "%";
- }
- }
- elsif($value eq "synctime") {
- $reading= "actuator-synctime";
- undef $value;
- }
- elsif($value eq "test") {
- $reading= "actuator-test";
- undef $value;
- }
- elsif($value eq "pair") {
- $reading= "actuator-pair";
- undef $value;
- }
- else {
- $value=~ s/%//; $value= $value*1.; $unit= "%";
- }
+ if($value eq "lime-protection") {
+ $reading= "actuator-lime-protection";
+ undef $value;
+ }
+ elsif($value =~ m(^offset:)) {
+ $reading= "actuator-offset";
+ @parts= split(/: /,$value);
+ $value= $parts[1];
+ if(defined $value) {
+ $value=~ s/%//; $value= $value*1.; $unit= "%";
+ }
+ }
+ elsif($value =~ m(^unknown_)) {
+ @parts= split(/: /,$value);
+ $reading= "actuator-" . $parts[0];
+ $value= $parts[1];
+ if(defined $value) {
+ $value=~ s/%//; $value= $value*1.; $unit= "%";
+ }
+ }
+ elsif($value eq "synctime") {
+ $reading= "actuator-synctime";
+ undef $value;
+ }
+ elsif($value eq "test") {
+ $reading= "actuator-test";
+ undef $value;
+ }
+ elsif($value eq "pair") {
+ $reading= "actuator-pair";
+ undef $value;
+ }
+ else {
+ $value=~ s/%//; $value= $value*1.; $unit= "%";
+ }
}
}
# KS300
@@ -201,7 +201,7 @@ DbLog_ParseEvent($$)
if($reading eq "rain_raw") { $value=~ s/ \(counter\)//; $unit= ""; }
if($reading eq "humidity") { $value=~ s/ \(\%\)//; $unit= "%"; }
if($reading eq "israining") {
- $value=~ s/ \(yes\/no\)//;
+ $value=~ s/ \(yes\/no\)//;
$value=~ s/no/0/;
$value=~ s/yes/1/;
}
@@ -209,10 +209,10 @@ DbLog_ParseEvent($$)
# HMS
elsif($type eq "HMS" ||
$type eq "CUL_WS" ||
- $type eq "OWTHERM") {
+ $type eq "OWTHERM") {
if($event =~ m(T:.*)) { $reading= "data"; $value= $event; }
if($reading eq "temperature") { $value=~ s/ \(Celsius\)//; $unit= "°C"; }
- if($reading eq "temperature") { $value=~ s/([-\.\d]+).*/$1/; $unit= "°C"; } #OWTHERM
+ if($reading eq "temperature") { $value=~ s/([-\.\d]+).*/$1/; $unit= "°C"; } #OWTHERM
if($reading eq "humidity") { $value=~ s/ \(\%\)//; $unit= "%"; }
if($reading eq "battery") {
$value=~ s/ok/1/;
@@ -223,11 +223,11 @@ DbLog_ParseEvent($$)
# BS
elsif($type eq "BS") {
- if($event =~ m(brightness:.*)) {
- @parts= split(/ /,$event);
- $reading= "lux";
- $value= $parts[4]*1.;
- $unit= "lux";
+ if($event =~ m(brightness:.*)) {
+ @parts= split(/ /,$event);
+ $reading= "lux";
+ $value= $parts[4]*1.;
+ $unit= "lux";
}
}
@@ -250,8 +250,8 @@ DbLog_ParseEvent($$)
if($reading =~ m(^pressure_trend)) { $unit= ""; }
}
- @result= ($reading,$value,$unit);
- return @result;
+ @result= ($reading,$value,$unit);
+ return @result;
}
@@ -359,8 +359,8 @@ DbLog_Connect($)
my $configfilename= $hash->{CONFIGURATION};
if(!open(CONFIG, $configfilename)) {
- Log 1, "Cannot open database configuration file $configfilename.";
- return 0; }
+ Log 1, "Cannot open database configuration file $configfilename.";
+ return 0; }
my @config=;
close(CONFIG);
@@ -395,6 +395,25 @@ DbLog_Connect($)
}
Log 3, "Connection to db $dbconn established";
$hash->{DBH}= $dbh;
+
+ if ($hash->{DBMODEL} eq "SQLITE") {
+ $dbh->do("PRAGMA temp_store=MEMORY");
+ $dbh->do("PRAGMA synchronous=NORMAL");
+ $dbh->do("PRAGMA journal_mode=WAL");
+ $dbh->do("CREATE TEMP TABLE IF NOT EXISTS current (TIMESTAMP TIMESTAMP, DEVICE varchar(32), TYPE varchar(32), EVENT varchar(512), READING varchar(32), VALUE varchar(32), UNIT varchar(32))");
+ $dbh->do("CREATE TABLE IF NOT EXISTS history (TIMESTAMP TIMESTAMP, DEVICE varchar(32), TYPE varchar(32), EVENT varchar(512), READING varchar(32), VALUE varchar(32), UNIT varchar(32))");
+ $dbh->do("CREATE INDEX IF NOT EXISTS Search_Idx ON `history` (DEVICE, READING, TIMESTAMP)");
+ }
+
+ # creating an own connection for the webfrontend, saved as DBHF in Hash
+ # this makes sure that the connection doesnt get lost due to other modules
+ my $dbhf = DBI->connect_cached("dbi:$dbconn", $dbuser, $dbpassword);
+ if(!$dbhf) {
+ Log 2, "Can't connect to $dbconn: $DBI::errstr";
+ return 0;
+ }
+ Log 3, "Connection to db $dbconn established";
+ $hash->{DBHF}= $dbhf;
return 1;
}
@@ -469,6 +488,9 @@ DbLog_Get($@)
if($outf eq "INT") {
$outf = "-";
$internal = 1;
+ } elsif (uc($outf) eq "WEBCHART") {
+ # redirect the get request to the chartQuery function
+ return chartQuery($hash, @_);
}
my @readings = ();
@@ -552,7 +574,7 @@ DbLog_Get($@)
DEVICE,
READING,
VALUE
- $sqlspec{all}
+ $sqlspec{all}
FROM history
WHERE 1=1
AND $sqlspec{reading_clause} = ('".$readings[$i]->[0]."|".$readings[$i]->[1]."')
@@ -570,8 +592,8 @@ DbLog_Get($@)
return "Cannot execute statement $stm: $DBI::errstr";
if(uc($outf) eq "ALL") {
- $retval .= "Timestamp: Device, Type, Event, Reading, Value, Unit\n";
- $retval .= "=====================================================\n";
+ $retval .= "Timestamp: Device, Type, Event, Reading, Value, Unit\n";
+ $retval .= "=====================================================\n";
}
while( ($sql_timestamp,$sql_dev,$sql_reading,$sql_value, $type, $event, $unit)= $sth->fetchrow_array) {
$writeout = 0;
@@ -642,12 +664,12 @@ DbLog_Get($@)
###################### Ausgabe ###########################
if($writeout) {
- if(uc($outf) eq "ALL") {
- $retval .= sprintf("%s: %s, %s, %s, %s, %s, %s\n", $out_tstamp, $sql_dev, $type, $event, $sql_reading, $out_value, $unit);
- } else {
- $out_tstamp =~ s/\ /_/g; #needed by generating plots
- $retval .= "$out_tstamp $out_value\n";
- }
+ if(uc($outf) eq "ALL") {
+ $retval .= sprintf("%s: %s, %s, %s, %s, %s, %s\n", $out_tstamp, $sql_dev, $type, $event, $sql_reading, $out_value, $unit);
+ } else {
+ $out_tstamp =~ s/\ /_/g; #needed by generating plots
+ $retval .= "$out_tstamp $out_value\n";
+ }
}
if(defined($sql_value) || $sql_value =~ m/^[-\.\d]+$/o){
@@ -676,11 +698,11 @@ DbLog_Get($@)
$out_tstamp = DbLog_implode_datetime($lasttstamp{year}, $lasttstamp{month}, $lasttstamp{day}, $lasttstamp{hour}, "30", "00") if($readings[$i]->[3] eq "delta-h");
$out_tstamp = DbLog_implode_datetime($lasttstamp{year}, $lasttstamp{month}, $lasttstamp{day}, "00", "00", "00") if($readings[$i]->[3] eq "delta-d");
if(uc($outf) eq "ALL") {
- $retval .= sprintf("%s: %s %s %s %s %s %s\n", $out_tstamp, $sql_dev, $type, $event, $sql_reading, $out_value, $unit);
- } else {
- $out_tstamp =~ s/\ /_/g; #needed by generating plots
- $retval .= "$out_tstamp $out_value\n";
- }
+ $retval .= sprintf("%s: %s %s %s %s %s %s\n", $out_tstamp, $sql_dev, $type, $event, $sql_reading, $out_value, $unit);
+ } else {
+ $out_tstamp =~ s/\ /_/g; #needed by generating plots
+ $retval .= "$out_tstamp $out_value\n";
+ }
}
# DatenTrenner setzen
$retval .= "#$readings[$i]->[0]";
@@ -713,6 +735,273 @@ DbLog_Get($@)
}
return $retval;
}
+
+################################################################
+#
+# Charting Specific functions start here
+#
+################################################################
+
+################################################################
+#
+# Error handling, returns a JSON String
+#
+################################################################
+sub jsonError($) {
+ my $errormsg = $_[0];
+ my $json = '{"success": "false", "msg":"'.$errormsg.'"}';
+ return $json;
+}
+
+
+################################################################
+#
+# Prepare the SQL String
+#
+################################################################
+sub prepareSql(@_) {
+
+ my ($hash, @a) = @_;
+ my $starttime = $_[5];
+ $starttime =~ s/_/ /;
+ my $endtime = $_[6];
+ $endtime =~ s/_/ /;
+ my $device = $_[7];
+ my $userquery = $_[8];
+ my $xaxis = $_[9];
+ my $yaxis = $_[10];
+ my $savename = $_[11];
+ my $jsonChartConfig = $_[12];
+ my $pagingstart = $_[13];
+ my $paginglimit = $_[14];
+ my $dbmodel = $hash->{DBMODEL};
+ my ($sql, $jsonstring, $countsql, $hourstats, $daystats, $weekstats, $monthstats, $yearstats);
+
+ if ($dbmodel eq "POSTGRESQL") {
+ ### POSTGRESQL Queries for Statistics ###
+ ### hour:
+ $hourstats = "SELECT to_char(timestamp, 'YYYY-MM-DD HH24:00:00') AS TIMESTAMP, SUM(VALUE::float) AS SUM, ";
+ $hourstats .= "AVG(VALUE::float) AS AVG, MIN(VALUE::float) AS MIN, MAX(VALUE::float) AS MAX, ";
+ $hourstats .= "COUNT(VALUE) AS COUNT FROM history WHERE READING = '$yaxis' AND DEVICE = '$device' ";
+ $hourstats .= "AND TIMESTAMP Between '$starttime' AND '$endtime' GROUP BY 1 ORDER BY 1;";
+
+ ### day:
+ $daystats = "SELECT to_char(timestamp, 'YYYY-MM-DD 00:00:00') AS TIMESTAMP, SUM(VALUE::float) AS SUM, ";
+ $daystats .= "AVG(VALUE::float) AS AVG, MIN(VALUE::float) AS MIN, MAX(VALUE::float) AS MAX, ";
+ $daystats .= "COUNT(VALUE) AS COUNT FROM history WHERE READING = '$yaxis' AND DEVICE = '$device' ";
+ $daystats .= "AND TIMESTAMP Between '$starttime' AND '$endtime' GROUP BY 1 ORDER BY 1;";
+
+ ### week:
+ $weekstats = "SELECT date_trunc('week',timestamp) AS TIMESTAMP, SUM(VALUE::float) AS SUM, ";
+ $weekstats .= "AVG(VALUE::float) AS AVG, MIN(VALUE::float) AS MIN, MAX(VALUE::float) AS MAX, ";
+ $weekstats .= "COUNT(VALUE) AS COUNT FROM history WHERE READING = '$yaxis' AND DEVICE = '$device' ";
+ $weekstats .= "AND TIMESTAMP Between '$starttime' AND '$endtime' GROUP BY 1 ORDER BY 1;";
+
+ ### month:
+ $monthstats = "SELECT to_char(timestamp, 'YYYY-MM-01 00:00:00') AS TIMESTAMP, SUM(VALUE::float) AS SUM, ";
+ $monthstats .= "AVG(VALUE::float) AS AVG, MIN(VALUE::float) AS MIN, MAX(VALUE::float) AS MAX, ";
+ $monthstats .= "COUNT(VALUE) AS COUNT FROM history WHERE READING = '$yaxis' AND DEVICE = '$device' ";
+ $monthstats .= "AND TIMESTAMP Between '$starttime' AND '$endtime' GROUP BY 1 ORDER BY 1;";
+
+ ### year:
+ $yearstats = "SELECT to_char(timestamp, 'YYYY-01-01 00:00:00') AS TIMESTAMP, SUM(VALUE::float) AS SUM, ";
+ $yearstats .= "AVG(VALUE::float) AS AVG, MIN(VALUE::float) AS MIN, MAX(VALUE::float) AS MAX, ";
+ $yearstats .= "COUNT(VALUE) AS COUNT FROM history WHERE READING = '$yaxis' AND DEVICE = '$device' ";
+ $yearstats .= "AND TIMESTAMP Between '$starttime' AND '$endtime' GROUP BY 1 ORDER BY 1;";
+
+ } elsif ($dbmodel eq "MYSQL") {
+ ### MYSQL Queries for Statistics ###
+ ### hour:
+ $hourstats = "SELECT date_format(timestamp, '%Y-%m-%d %H:00:00') AS TIMESTAMP, SUM(CAST(VALUE AS DECIMAL(12,4))) AS SUM, ";
+ $hourstats .= "AVG(CAST(VALUE AS DECIMAL(12,4))) AS AVG, MIN(CAST(VALUE AS DECIMAL(12,4))) AS MIN, ";
+ $hourstats .= "MAX(CAST(VALUE AS DECIMAL(12,4))) AS MAX, COUNT(VALUE) AS COUNT FROM history WHERE READING = '$yaxis' ";
+ $hourstats .= "AND DEVICE = '$device' AND TIMESTAMP Between '$starttime' AND '$endtime' GROUP BY 1 ORDER BY 1;";
+
+ ### day:
+ $daystats = "SELECT date_format(timestamp, '%Y-%m-%d 00:00:00') AS TIMESTAMP, SUM(CAST(VALUE AS DECIMAL(12,4))) AS SUM, ";
+ $daystats .= "AVG(CAST(VALUE AS DECIMAL(12,4))) AS AVG, MIN(CAST(VALUE AS DECIMAL(12,4))) AS MIN, ";
+ $daystats .= "MAX(CAST(VALUE AS DECIMAL(12,4))) AS MAX, COUNT(VALUE) AS COUNT FROM history WHERE READING = '$yaxis' ";
+ $daystats .= "AND DEVICE = '$device' AND TIMESTAMP Between '$starttime' AND '$endtime' GROUP BY 1 ORDER BY 1;";
+
+ ### week:
+ $weekstats = "SELECT date_format(timestamp, '%Y-%m-%d 00:00:00') AS TIMESTAMP, SUM(CAST(VALUE AS DECIMAL(12,4))) AS SUM, ";
+ $weekstats .= "AVG(CAST(VALUE AS DECIMAL(12,4))) AS AVG, MIN(CAST(VALUE AS DECIMAL(12,4))) AS MIN, ";
+ $weekstats .= "MAX(CAST(VALUE AS DECIMAL(12,4))) AS MAX, COUNT(VALUE) AS COUNT FROM history WHERE READING = '$yaxis' ";
+ $weekstats .= "AND DEVICE = '$device' AND TIMESTAMP Between '$starttime' AND '$endtime' ";
+ $weekstats .= "GROUP BY date_format(timestamp, '%Y-%u 00:00:00') ORDER BY 1;";
+
+ ### month:
+ $monthstats = "SELECT date_format(timestamp, '%Y-%m-01 00:00:00') AS TIMESTAMP, SUM(CAST(VALUE AS DECIMAL(12,4))) AS SUM, ";
+ $monthstats .= "AVG(CAST(VALUE AS DECIMAL(12,4))) AS AVG, MIN(CAST(VALUE AS DECIMAL(12,4))) AS MIN, ";
+ $monthstats .= "MAX(CAST(VALUE AS DECIMAL(12,4))) AS MAX, COUNT(VALUE) AS COUNT FROM history WHERE READING = '$yaxis' ";
+ $monthstats .= "AND DEVICE = '$device' AND TIMESTAMP Between '$starttime' AND '$endtime' GROUP BY 1 ORDER BY 1;";
+
+ ### year:
+ $yearstats = "SELECT date_format(timestamp, '%Y-01-01 00:00:00') AS TIMESTAMP, SUM(CAST(VALUE AS DECIMAL(12,4))) AS SUM, ";
+ $yearstats .= "AVG(CAST(VALUE AS DECIMAL(12,4))) AS AVG, MIN(CAST(VALUE AS DECIMAL(12,4))) AS MIN, ";
+ $yearstats .= "MAX(CAST(VALUE AS DECIMAL(12,4))) AS MAX, COUNT(VALUE) AS COUNT FROM history WHERE READING = '$yaxis' ";
+ $yearstats .= "AND DEVICE = '$device' AND TIMESTAMP Between '$starttime' AND '$endtime' GROUP BY 1 ORDER BY 1;";
+
+ } elsif ($hash->{DBMODEL} eq "SQLITE") {
+ ### SQLITE Queries for Statistics ###
+ ### hour:
+ $hourstats = "SELECT TIMESTAMP, SUM(CAST(VALUE AS FLOAT)) AS SUM, AVG(CAST(VALUE AS FLOAT)) AS AVG, ";
+ $hourstats .= "MIN(CAST(VALUE AS FLOAT)) AS MIN, MAX(CAST(VALUE AS FLOAT)) AS MAX, COUNT(VALUE) AS COUNT ";
+ $hourstats .= "FROM history WHERE READING = '$yaxis' AND DEVICE = '$device' ";
+ $hourstats .= "AND TIMESTAMP Between '$starttime' AND '$endtime' GROUP BY strftime('%Y-%m-%d %H:00:00', TIMESTAMP);";
+
+ ### day:
+ $daystats = "SELECT TIMESTAMP, SUM(CAST(VALUE AS FLOAT)) AS SUM, AVG(CAST(VALUE AS FLOAT)) AS AVG, ";
+ $daystats .= "MIN(CAST(VALUE AS FLOAT)) AS MIN, MAX(CAST(VALUE AS FLOAT)) AS MAX, COUNT(VALUE) AS COUNT ";
+ $daystats .= "FROM history WHERE READING = '$yaxis' AND DEVICE = '$device' ";
+ $daystats .= "AND TIMESTAMP Between '$starttime' AND '$endtime' GROUP BY strftime('%Y-%m-%d 00:00:00', TIMESTAMP);";
+
+ ### week:
+ $weekstats = "SELECT TIMESTAMP, SUM(CAST(VALUE AS FLOAT)) AS SUM, AVG(CAST(VALUE AS FLOAT)) AS AVG, ";
+ $weekstats .= "MIN(CAST(VALUE AS FLOAT)) AS MIN, MAX(CAST(VALUE AS FLOAT)) AS MAX, COUNT(VALUE) AS COUNT ";
+ $weekstats .= "FROM history WHERE READING = '$yaxis' AND DEVICE = '$device' ";
+ $weekstats .= "AND TIMESTAMP Between '$starttime' AND '$endtime' GROUP BY strftime('%Y-%W 00:00:00', TIMESTAMP);";
+
+ ### month:
+ $monthstats = "SELECT TIMESTAMP, SUM(CAST(VALUE AS FLOAT)) AS SUM, AVG(CAST(VALUE AS FLOAT)) AS AVG, ";
+ $monthstats .= "MIN(CAST(VALUE AS FLOAT)) AS MIN, MAX(CAST(VALUE AS FLOAT)) AS MAX, COUNT(VALUE) AS COUNT ";
+ $monthstats .= "FROM history WHERE READING = '$yaxis' AND DEVICE = '$device' ";
+ $monthstats .= "AND TIMESTAMP Between '$starttime' AND '$endtime' GROUP BY strftime('%Y-%m 00:00:00', TIMESTAMP);";
+
+ ### year:
+ $yearstats = "SELECT TIMESTAMP, SUM(CAST(VALUE AS FLOAT)) AS SUM, AVG(CAST(VALUE AS FLOAT)) AS AVG, ";
+ $yearstats .= "MIN(CAST(VALUE AS FLOAT)) AS MIN, MAX(CAST(VALUE AS FLOAT)) AS MAX, COUNT(VALUE) AS COUNT ";
+ $yearstats .= "FROM history WHERE READING = '$yaxis' AND DEVICE = '$device' ";
+ $yearstats .= "AND TIMESTAMP Between '$starttime' AND '$endtime' GROUP BY strftime('%Y 00:00:00', TIMESTAMP);";
+
+ } else {
+ $sql = "errordb";
+ }
+
+ if($userquery eq "getreadings") {
+ $sql = "SELECT distinct(reading) FROM history WHERE device = '".$device."'";
+ } elsif($userquery eq "getdevices") {
+ $sql = "SELECT distinct(device) FROM history";
+ } elsif($userquery eq "timerange") {
+ $sql = "SELECT ".$xaxis.", VALUE FROM history WHERE READING = '$yaxis' AND DEVICE = '$device' AND TIMESTAMP Between '$starttime' AND '$endtime';";
+ } elsif($userquery eq "hourstats") {
+ $sql = $hourstats;
+ } elsif($userquery eq "daystats") {
+ $sql = $daystats;
+ } elsif($userquery eq "weekstats") {
+ $sql = $weekstats;
+ } elsif($userquery eq "monthstats") {
+ $sql = $monthstats;
+ } elsif($userquery eq "yearstats") {
+ $sql = $yearstats;
+ } elsif($userquery eq "savechart") {
+ $sql = "INSERT INTO frontend (TYPE, NAME, VALUE) VALUES ('savedchart', '$savename', '$jsonChartConfig')";
+ } elsif($userquery eq "deletechart") {
+ $sql = "DELETE FROM frontend WHERE TYPE = 'savedchart' AND ID = '".$savename."'";
+ } elsif($userquery eq "getcharts") {
+ $sql = "SELECT * FROM frontend WHERE TYPE = 'savedchart'";
+ } elsif($userquery eq "getTableData") {
+ $countsql = "SELECT count(*) FROM history";
+ $sql = "SELECT * FROM history LIMIT ".$paginglimit." OFFSET ".$pagingstart;
+ return ($sql, $countsql);
+ } else {
+ $sql = "error";
+ }
+
+ return $sql;
+}
+
+################################################################
+#
+# Do the query
+#
+################################################################
+sub chartQuery($@) {
+
+ my ($sql, $countsql) = prepareSql(@_);
+
+ if ($sql eq "error") {
+ return jsonError("Could not setup SQL String. Maybe the Database is busy, please try again!");
+ } elsif ($sql eq "errordb") {
+ return jsonError("The Database Type is not supported!");
+ }
+
+ my ($hash, @a) = @_;
+ my $dbhf= $hash->{DBHF};
+
+ my $totalcount;
+
+ if (defined $countsql && $countsql ne "") {
+ my $query_handle = $dbhf->prepare($countsql)
+ or return jsonError("Could not prepare statement: " . $dbhf->errstr . ", SQL was: " .$countsql);
+
+ $query_handle->execute()
+ or return jsonError("Could not execute statement: " . $query_handle->errstr);
+
+ my @data = $query_handle->fetchrow_array();
+ $totalcount = join(", ", @data);
+
+ }
+
+ # prepare the query
+ my $query_handle = $dbhf->prepare($sql)
+ or return jsonError("Could not prepare statement: " . $dbhf->errstr . ", SQL was: " .$sql);
+
+ # execute the query
+ $query_handle->execute()
+ or return jsonError("Could not execute statement: " . $query_handle->errstr);
+
+ my $columns = $query_handle->{'NAME'};
+ my $columncnt;
+
+ # When columns are empty but execution was successful, we have done a successful INSERT, UPDATE or DELETE
+ if($columns) {
+ $columncnt = scalar @$columns;
+ } else {
+ return '{"success": "true", "msg":"All ok"}';
+ }
+
+ my $i = 0;
+ my $jsonstring = '{"data":[';
+
+ while ( my @data = $query_handle->fetchrow_array()) {
+
+ if($i == 0) {
+ $jsonstring .= '{';
+ } else {
+ $jsonstring .= ',{';
+ }
+
+ for ($i = 0; $i < $columncnt; $i++) {
+ $jsonstring .= '"';
+ $jsonstring .= uc($query_handle->{NAME}->[$i]);
+ $jsonstring .= '":';
+
+ if (defined $data[$i]) {
+ my $fragment = substr($data[$i],0,1);
+ if ($fragment eq "{") {
+ $jsonstring .= $data[$i];
+ } else {
+ $jsonstring .= '"'.$data[$i].'"';
+ }
+ } else {
+ $jsonstring .= '""'
+ }
+
+ if($i != ($columncnt -1)) {
+ $jsonstring .= ',';
+ }
+ }
+ $jsonstring .= '}';
+ }
+ $jsonstring .= ']';
+ if (defined $totalcount && $totalcount ne "") {
+ $jsonstring .= ',"totalCount": '.$totalcount.'}';
+ } else {
+ $jsonstring .= '}';
+ }
+ return $jsonstring;
+}
################################################################
@@ -808,8 +1097,8 @@ DbLog_Get($@)
A dummy parameter for FileLog compatibility. Always set to -
<out>
A dummy parameter for FileLog compatibility. Set it to -
- to check the output for plot-computing. Set it to the special keyword
- all to get all columns from Database.
+ to check the output for plot-computing. Set it to the special keyword
+ all to get all columns from Database.
<from> / <to>
Used to select the data. Please use the following timeformat or
an initial substring of it:
@@ -861,10 +1150,80 @@ DbLog_Get($@)
get myDbLog - all 2012-11-10 2012-11-20 KS300:temperature
get myDbLog - - 2012-11-10 2012-11-20 KS300:temperature KS300:rain::delta-h KS300:rain::delta-d
get myDbLog - - 2012-11-10 2012-11-20 MyFS20:data:::$val=~s/(on|off).*/$1eq"on"?1:0/eg
- return 1 for all occurance of on* (on|on-for-timer etc) and 0 for all off*
- get myDbLog - - 2012-11-10 2012-11-20 Bodenfeuchte:data:::$val=~s/.*B:\s([-\.\d]+).*/$1/eg
- Example of OWAD: value like this: "A: 49.527 % B: 66.647 % C: 9.797 % D: 0.097 V"
- and output for port B is like this: 2012-11-20_10:23:54 66.647
+ return 1 for all occurance of on* (on|on-for-timer etc) and 0 for all off*
+ get myDbLog - - 2012-11-10 2012-11-20 Bodenfeuchte:data:::$val=~s/.*B:\s([-\.\d]+).*/$1/eg
+ Example of OWAD: value like this: "A: 49.527 % B: 66.647 % C: 9.797 % D: 0.097 V"
+ and output for port B is like this: 2012-11-20_10:23:54 66.647
+
+
+
+
+ Get when used for webcharts
+
+ get <name> <infile> <outfile> <from>
+ <to> <device> <querytype> <xaxis> <yaxis> <savename>
+
+ Query the Database to retrieve JSON-Formatted Data, which is used by the charting frontend.
+
+
+ |