mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-03-05 18:16:44 +00:00
1967 lines
64 KiB
Perl
1967 lines
64 KiB
Perl
##############################################
|
||
# $Id$
|
||
# ABU 20160307 First release
|
||
# ABU 20160309 Fixed issue for sending group-indexed with dpt1. Added debug-information. Fixed issue for indexed get. Fixed regex-replace-issue.
|
||
# ABU 20160312 Fixed error while receiving numeric DPT with value 0. Added factor for dpt 08.010.
|
||
# ABU 20160312 Fixed Regex-Attributes. Syntax changed from space-seperated to " /".
|
||
# ABU 20160322 Fixed dpt1.008
|
||
# ABU 20160326 Added fix for stateFormat
|
||
# ABU 20160327 Removed readingRegex, writingRegex, created stateRegex, stateCmd, added reading-name support, fixed dblog-split
|
||
# ABU 20160403 Fixed various minor perl warnings
|
||
# ABU 20160413 Changed SplitFn
|
||
# ABU 20160414 Changed SplitFn again
|
||
# ABU 20160416 Changed SplitFn again
|
||
# ABU 20160422 Added dpt9.021 - mA
|
||
# ABU 20160529 Changed Doku
|
||
# ABU 20160605 Changed Doku, changed autocreate-naming, fixed dpt10-sending-now
|
||
# ABU 20160608 changed sprintf for int-dpt from %d to %.0f
|
||
# ABU 20160624 corrected Doku: till->until
|
||
|
||
package main;
|
||
|
||
use strict;
|
||
use warnings;
|
||
|
||
#set to 1 for debug
|
||
my $debug = 0;
|
||
|
||
#string constant for autocreate
|
||
my $modelErr = "MODEL_NOT_DEFINED";
|
||
|
||
#valid set commands
|
||
my %sets = (
|
||
#"off" => "noArg",
|
||
#"on" => "noArg",
|
||
"off" => "",
|
||
"on" => "",
|
||
"on-for-timer" => "",
|
||
"on-until" => "",
|
||
"value" => "",
|
||
"string" => "",
|
||
"raw" => ""
|
||
);
|
||
|
||
#identifier for TUL
|
||
my $id = 'C';
|
||
|
||
#regex patterns
|
||
my $PAT_GAD = qr/^[0-9]{1,2}\/[0-9]{1,2}\/[0-9]{1,3}$/;
|
||
my $PAT_GAD_HEX = qr/^[0-9a-f]{4}$/;
|
||
my $PAT_GNO = qr/[gG][1-9][0-9]?/;
|
||
|
||
#CODE is the identifier for the en- and decode algos. See encode and decode functions
|
||
#UNIT is appended to state for a better reading
|
||
#FACTOR and OFFSET are used to normalize a value. value = FACTOR * (RAW - OFFSET). Must be undef for non-numeric values.
|
||
#PATTERN is used to check an trim the input-values
|
||
#MIN and MAX are used to cast numeric values. Must be undef for non-numeric dpt. Special Usecase: DPT1 - MIN represents 00, MAX represents 01
|
||
my %dpttypes = (
|
||
#Binary value
|
||
"dpt1" => {CODE=>"dpt1", UNIT=>"", FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/([oO][nN])|([oO][fF][fF])|(0?1)|(0?0)/, MIN=>"off", MAX=>"on"},
|
||
"dpt1.001" => {CODE=>"dpt1", UNIT=>"", FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/([oO][nN])|([oO][fF][fF])|(0?1)|(0?0)/, MIN=>"off", MAX=>"on"},
|
||
"dpt1.002" => {CODE=>"dpt1", UNIT=>"", FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/([tT][rR][uU][eE])|([fF][aA][lL][sS][eE])|(0?1)|(0?0)/, MIN=>"false", MAX=>"true"},
|
||
"dpt1.003" => {CODE=>"dpt1", UNIT=>"", FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/(([eE][nN]|[dD][iI][sS])[aA][bB][lL][eE])|(0?1)|(0?0)/, MIN=>"disable", MAX=>"enable"},
|
||
"dpt1.008" => {CODE=>"dpt1", UNIT=>"", FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/([uU][pP])|([dD][oO][wW][nN])|(0?1)|(0?0)/, MIN=>"up", MAX=>"down"},
|
||
"dpt1.009" => {CODE=>"dpt1", UNIT=>"", FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/([cC][lL][oO][sS][eE][dD])|([oO][pP][eE][nN])|(0?1)|(0?0)/, MIN=>"open", MAX=>"closed"},
|
||
"dpt1.019" => {CODE=>"dpt1", UNIT=>"", FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/([cC][lL][oO][sS][eE][dD])|([oO][pP][eE][nN])|(0?1)|(0?0)/, MIN=>"closed", MAX=>"open"},
|
||
|
||
#Step value (four-bit)
|
||
"dpt3" => {CODE=>"dpt3", UNIT=>"", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,3}/, MIN=>-100, MAX=>100},
|
||
|
||
# 1-Octet unsigned value
|
||
"dpt5" => {CODE=>"dpt5", UNIT=>"", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,3}/, MIN=>0, MAX=>255},
|
||
"dpt5.001" => {CODE=>"dpt5", UNIT=>"%", FACTOR=>100/255, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,3}/, MIN=>0, MAX=>100},
|
||
"dpt5.003" => {CODE=>"dpt5", UNIT=>"°", FACTOR=>360/255, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,3}/, MIN=>0, MAX=>360},
|
||
"dpt5.004" => {CODE=>"dpt5", UNIT=>"%", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,3}/, MIN=>0, MAX=>255},
|
||
|
||
# 1-Octet signed value
|
||
"dpt6" => {CODE=>"dpt6", UNIT=>"", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,3}/, MIN=>-127, MAX=>127},
|
||
"dpt6.001" => {CODE=>"dpt6", UNIT=>"%", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,3}/, MIN=>0, MAX=>100},
|
||
|
||
# 2-Octet unsigned Value
|
||
"dpt7" => {CODE=>"dpt7", UNIT=>"", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,5}/, MIN=>0, MAX=>65535},
|
||
"dpt7.005" => {CODE=>"dpt7", UNIT=>"s", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,5}/, MIN=>0, MAX=>65535},
|
||
"dpt7.006" => {CODE=>"dpt7", UNIT=>"m", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,5}/, MIN=>0, MAX=>65535},
|
||
"dpt7.013" => {CODE=>"dpt7", UNIT=>"lux", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,5}/, MIN=>0, MAX=>65535},
|
||
|
||
# 2-Octet signed Value
|
||
"dpt8" => {CODE=>"dpt8", UNIT=>"", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,5}/, MIN=>-32768, MAX=>32768},
|
||
"dpt8.005" => {CODE=>"dpt8", UNIT=>"s", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,5}/, MIN=>-32768, MAX=>32768},
|
||
"dpt8.010" => {CODE=>"dpt8", UNIT=>"%", FACTOR=>0.01, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,5}/, MIN=>-32768, MAX=>32768},
|
||
"dpt8.011" => {CODE=>"dpt8", UNIT=>"°", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,5}/, MIN=>-32768, MAX=>32768},
|
||
|
||
# 2-Octet Float value
|
||
"dpt9" => {CODE=>"dpt9", UNIT=>"", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,6}[.,]?\d{1,2}/, MIN=>-670760, MAX=>670760},
|
||
"dpt9.001" => {CODE=>"dpt9", UNIT=>"°C", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,6}[.,]?\d{1,2}/, MIN=>-670760, MAX=>670760},
|
||
"dpt9.004" => {CODE=>"dpt9", UNIT=>"lux", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,6}[.,]?\d{1,2}/, MIN=>-670760, MAX=>670760},
|
||
"dpt9.006" => {CODE=>"dpt9", UNIT=>"Pa", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,6}[.,]?\d{1,2}/, MIN=>-670760, MAX=>670760},
|
||
"dpt9.005" => {CODE=>"dpt9", UNIT=>"m/s", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,6}[.,]?\d{1,2}/, MIN=>-670760, MAX=>670760},
|
||
"dpt9.009" => {CODE=>"dpt9", UNIT=>"m³/h", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,6}[.,]?\d{1,2}/, MIN=>-670760, MAX=>670760},
|
||
"dpt9.010" => {CODE=>"dpt9", UNIT=>"s", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,6}[.,]?\d{1,2}/, MIN=>-670760, MAX=>670760},
|
||
"dpt9.021" => {CODE=>"dpt9", UNIT=>"mA", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,6}[.,]?\d{1,2}/, MIN=>-670760, MAX=>670760},
|
||
"dpt9.024" => {CODE=>"dpt9", UNIT=>"kW", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,6}[.,]?\d{1,2}/, MIN=>-670760, MAX=>670760},
|
||
"dpt9.025" => {CODE=>"dpt9", UNIT=>"l/h", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,6}[.,]?\d{1,2}/, MIN=>-670760, MAX=>670760},
|
||
"dpt9.026" => {CODE=>"dpt9", UNIT=>"l/h", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,6}[.,]?\d{1,2}/, MIN=>-670760, MAX=>670760},
|
||
"dpt9.028" => {CODE=>"dpt9", UNIT=>"km/h", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,6}[.,]?\d{1,2}/, MIN=>-670760, MAX=>670760},
|
||
|
||
# Time of Day
|
||
"dpt10" => {CODE=>"dpt10", UNIT=>"", FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/((2[0-4]|[0?1][0-9]):(60|[0?1-5]?[0-9]):(60|[0?1-5]?[0-9]))|([nN][oO][wW])/, MIN=>undef, MAX=>undef},
|
||
|
||
# Date
|
||
"dpt11" => {CODE=>"dpt11", UNIT=>"", FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/((3[01]|[0-2]?[0-9]).(1[0-2]|0?[0-9]).(19[0-9][0-9]|2[01][0-9][0-9]))|([nN][oO][wW])/, MIN=>undef, MAX=>undef},
|
||
|
||
# 4-Octet unsigned value (handled as dpt7)
|
||
"dpt12" => {CODE=>"dpt12", UNIT=>"", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,10}/, MIN=>0, MAX=>4294967295},
|
||
|
||
# 4-Octet Signed Value
|
||
"dpt13" => {CODE=>"dpt13", UNIT=>"", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,10}/, MIN=>-2147483647, MAX=>2147483647},
|
||
"dpt13.010" => {CODE=>"dpt13", UNIT=>"Wh", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,10}/, MIN=>-2147483647, MAX=>2147483647},
|
||
"dpt13.013" => {CODE=>"dpt13", UNIT=>"kWh", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,10}/, MIN=>-2147483647, MAX=>2147483647},
|
||
|
||
# 4-Octet single precision float
|
||
"dpt14" => {CODE=>"dpt14", UNIT=>"", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,40}[.,]?\d{1,4}/, MIN=>undef, MAX=>undef},
|
||
"dpt14.019" => {CODE=>"dpt14", UNIT=>"A", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,40}[.,]?\d{1,4}/, MIN=>undef, MAX=>undef},
|
||
"dpt14.027" => {CODE=>"dpt14", UNIT=>"V", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,40}[.,]?\d{1,4}/, MIN=>undef, MAX=>undef},
|
||
"dpt14.056" => {CODE=>"dpt14", UNIT=>"W", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,40}[.,]?\d{1,4}/, MIN=>undef, MAX=>undef},
|
||
"dpt14.068" => {CODE=>"dpt14", UNIT=>"°C", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,40}[.,]?\d{1,4}/, MIN=>undef, MAX=>undef},
|
||
"dpt14.076" => {CODE=>"dpt14", UNIT=>"m³", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,40}[.,]?\d{1,4}/, MIN=>undef, MAX=>undef},
|
||
|
||
# 14-Octet String
|
||
"dpt16" => {CODE=>"dpt16", UNIT=>"", FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/.{1,14}/, MIN=>undef, MAX=>undef},
|
||
);
|
||
|
||
#Init this device
|
||
#This declares the interface to fhem
|
||
#############################
|
||
sub
|
||
KNX_Initialize($) {
|
||
my ($hash) = @_;
|
||
|
||
$hash->{Match} = "^$id.*";
|
||
$hash->{GetFn} = "KNX_Get";
|
||
$hash->{SetFn} = "KNX_Set";
|
||
$hash->{StateFn} = "KNX_State";
|
||
$hash->{DefFn} = "KNX_Define";
|
||
$hash->{UndefFn} = "KNX_Undef";
|
||
$hash->{ParseFn} = "KNX_Parse";
|
||
$hash->{AttrFn} = "KNX_Attr";
|
||
$hash->{NotifyFn} = "KNX_Notify";
|
||
$hash->{DbLog_splitFn} = "KNX_DbLog_split";
|
||
$hash->{AttrList} = "IODev " . #tells the module the IO-Device to communicate with. Optionally set within definition.
|
||
"do_not_notify:1,0 " . #supress any notification (including log)
|
||
"listenonly:1,0 " . #device may not send any messages. answering is prohibited. get is prohibited.
|
||
"readonly:1,0 " . #device may not send any messages. answering is prohibited. get is allowed.
|
||
"showtime:1,0 " . #shows time instead of received value in state
|
||
"answerReading:1,0 " . #allows FHEM to answer a read telegram
|
||
"stateRegex " . #modifies state value
|
||
"stateCmd " . #modify state value
|
||
"format " . #supplies post-string
|
||
"slider " . #creates slider. Syntax: min, step, max
|
||
"$readingFnAttributes "; #standard attributes
|
||
}
|
||
|
||
#Define this device
|
||
#Is called at every define
|
||
#############################
|
||
sub
|
||
KNX_Define($$) {
|
||
my ($hash, $def) = @_;
|
||
my @a = split("[ \t][ \t]*", $def);
|
||
#device name
|
||
my $name = $a[0];
|
||
|
||
#set verbose to 5, if debug enabled
|
||
$attr{$name}{verbose} = 5 if ($debug eq 1);
|
||
|
||
my $tempStr = join (", ", @a);
|
||
Log3 ($name, 5, "define $name: enter $hash, attributes: $tempStr");
|
||
|
||
#too less arguments
|
||
return "wrong syntax - define <name> KNX <group:model[:reading-name]> [<group:model[:reading-name]>*] [<IODev>]" if (int(@a) < 3);
|
||
|
||
#check for IODev
|
||
#is last argument a group or a group:model pair?
|
||
my $lastGroupDef = int(@a);
|
||
#if (($a[int(@a) - 1] !~ m/^[0-9]{1,2}\/[0-9]{1,2}\/[0-9]{1,3}$/i) and ($a[int(@a) - 1] !~ m/^[0-9a-f]{4}$/i) and ($a[int(@a) - 1] !~ m/[0-9a-fA-F]:[dD][pP][tT]/i))
|
||
if (($a[int(@a) - 1] !~ m/${PAT_GAD}/i) and ($a[int(@a) - 1] !~ m/${PAT_GAD_HEX}/i) and ($a[int(@a) - 1] !~ m/[0-9a-fA-F]:[dD][pP][tT]/i))
|
||
{
|
||
$attr{$name}{IODev} = $a[int(@a) - 1];
|
||
$lastGroupDef--;
|
||
}
|
||
|
||
#create groups and models, iterate through all possible args
|
||
for (my $i = 2; $i < $lastGroupDef; $i++)
|
||
{
|
||
#backup actual GAD
|
||
my $inp = lc($a[$i]);
|
||
my ($group, $model, $rdname) = split /:/, $inp;
|
||
my $groupc;
|
||
#G-nr
|
||
my $gno = $i - 1;
|
||
|
||
#GAD not defined
|
||
return "GAD not defined for group-number $gno" if (!defined($group));
|
||
|
||
#GAD wrong syntax
|
||
#either 1/2/3 or 1203
|
||
#return "wrong group name format: specify as 0-15/0-15/0-255 or as hex" if (($group !~ m/^[0-9]{1,2}\/[0-9]{1,2}\/[0-9]{1,3}$/i) && (lc($group) !~ m/^[0-9a-f]{4}$/i));
|
||
return "wrong group name format: specify as 0-15/0-15/0-255 or as hex" if (($group !~ m/${PAT_GAD}/i) && (lc($group) !~ m/${PAT_GAD_HEX}/i));
|
||
|
||
#check if model supplied
|
||
return "no model defined" if (!defined($model));
|
||
|
||
#within autocreate no model is supplied - throw warning
|
||
if ($model eq $modelErr)
|
||
{
|
||
Log3 ($name, 2, "define $name: autocreate defines no model - only restricted functions are available");
|
||
}
|
||
else
|
||
{
|
||
#check model-type
|
||
return "invalid model. Use " .join(",", keys %dpttypes) if (!defined($dpttypes{$model}));
|
||
}
|
||
|
||
#convert to string, if supplied in Hex
|
||
$group = hexToName ($group) if ($group =~ m/^[0-9a-f]{4}$/i);
|
||
$groupc = nameToHex ($group);
|
||
|
||
Log3 ($name, 5, "define $name: found GAD: $group, NO: $gno, HEX: $groupc, DPT: $model");
|
||
Log3 ($name, 5, "define $name: found Readings-Name: $rdname") if (defined ($rdname));
|
||
|
||
#add indexed group to hash. Index starts with one
|
||
#readable GAD
|
||
$hash->{GADDR}{$gno} = $group;
|
||
#same as hex
|
||
$hash->{GCODE}{$gno} = $groupc;
|
||
#model
|
||
$hash->{MODEL}{$gno} = $model;
|
||
#backup readings-name
|
||
$hash->{READINGSNAME}{$gno} = $rdname if (defined ($rdname) and !($rdname eq ""));
|
||
}
|
||
|
||
#common name
|
||
$hash->{NAME} = $name;
|
||
#backup name for a later rename
|
||
$hash->{DEVNAME} = $name;
|
||
|
||
#finally create decice
|
||
#defptr is needed to supress FHEM input
|
||
$modules{KNX}{defptr}{$name} = $hash;
|
||
|
||
#assign io-dev automatically, if not given via definition
|
||
AssignIoPort($hash);
|
||
|
||
Log3 ($name, 5, "exit define");
|
||
return undef;
|
||
}
|
||
|
||
#Release this device
|
||
#Is called at every delete / shutdown
|
||
#############################
|
||
sub
|
||
KNX_Undef($$) {
|
||
my ($hash, $name) = @_;
|
||
|
||
Log3 ($name, 5, "enter undef $name: hash: $hash name: $name");
|
||
|
||
#remove all groups
|
||
foreach my $group (keys %{$hash->{GCODE}})
|
||
{
|
||
Log3 ($name, 5, "undef $name: remove name: $hash->{NAME}, orig.-Name: $hash->{DEVNAME}, GAD: $group");
|
||
delete $hash->{GADDR}{$group};
|
||
delete $hash->{GCODE}{$group};
|
||
delete $hash->{MODEL}{$group};
|
||
}
|
||
|
||
#remove module. Refer to DevName, because module may be renamed
|
||
delete $modules{KNX}{defptr}{$hash->{DEVNAME}};
|
||
|
||
#remove name
|
||
delete $hash->{NAME};
|
||
#remove backuped name
|
||
delete $hash->{DEVNAME};
|
||
|
||
Log3 ($name, 5, "exit undef");
|
||
return undef;
|
||
}
|
||
|
||
#Places a "read" Message on the KNX-Bus
|
||
#The answer is treated as regular telegram
|
||
#############################
|
||
sub
|
||
KNX_Get($@) {
|
||
my ($hash, @a) = @_;
|
||
my $name = $hash->{NAME};
|
||
my $groupnr = 1;
|
||
|
||
my $tempStr = join (", ", @a);
|
||
Log3 ($name, 5, "enter get $name: hash: $hash, attributes: $tempStr");
|
||
|
||
#FHEM asks with a ? at startup - no action, no log
|
||
#return "" if($a[1] && $a[1] eq "?");
|
||
return "Unknown argument ?, choose one of -" if($a[1] && $a[1] eq "?");
|
||
|
||
splice(@a, 1, 1) if (defined ($a[1]) and ($a[1] =~ m/-/));
|
||
my $na = int(@a);
|
||
|
||
#not more then 2 arguments allowed
|
||
return "too much arguments. Maximum two arguments allowed" if($na>2);
|
||
|
||
# the command can be send to any of the defined groups indexed starting by 1
|
||
# optional last argument starting with g indicates the group
|
||
if(defined ($a[1]))
|
||
{
|
||
#check syntax
|
||
if ($a[1]=~ m/${PAT_GNO}/)
|
||
{
|
||
#assign group-no
|
||
$groupnr = $a[1];
|
||
$groupnr =~ s/^g//;
|
||
} else
|
||
{
|
||
return "$a[1] is invalid. Second argument only may be a group g<no>";
|
||
}
|
||
}
|
||
|
||
#get group from hash (hex)
|
||
my $groupc = $hash->{GCODE}{$groupnr};
|
||
#get group from hash
|
||
my $group = $hash->{GADDR}{$groupnr};
|
||
|
||
#return, if unknown group
|
||
return "groupnr: $groupnr not known" if(!$groupc);
|
||
|
||
#exit, if reat is prohibited
|
||
return "did not request a value - \"listenonly\" is set." if (AttrVal ($name, "listenonly", 0) =~ m/1/);
|
||
|
||
#send read-request to the bus
|
||
Log3 ($name, 5, "get $name: request value for GAD $group");
|
||
IOWrite($hash, $id, "r" . $groupc);
|
||
|
||
Log3 ($name, 5, "exit get");
|
||
|
||
return "current value for $name ($group) requested";
|
||
}
|
||
|
||
#Does something according the given cmd...
|
||
#############################
|
||
sub
|
||
KNX_Set($@) {
|
||
my ($hash, @a) = @_;
|
||
my $name = $hash->{NAME};
|
||
my $ret = "";
|
||
my $na = int(@a);
|
||
|
||
my $tempStr = join (", ", @a);
|
||
#log only, if not called with cmd = ?
|
||
Log3 ($name, 5, "enter set $name: hash: $hash, attributes: $tempStr") if (not ($a[1] eq "?"));
|
||
|
||
#return, if no set value specified
|
||
return "no set value specified" if($na < 2);
|
||
|
||
#return, if this is a readonly-device
|
||
return "this device is readonly" if(defined($hash->{readonly}));
|
||
|
||
#backup values
|
||
my $cmd = lc($a[1]);
|
||
#remove whitespaces
|
||
$cmd =~ s/^\s+|\s+$//g;
|
||
|
||
#get slider definition
|
||
my $slider = AttrVal ($name, "slider", undef);
|
||
|
||
#check command
|
||
#append slider, if wanted
|
||
if(!defined($sets{$cmd}))
|
||
{
|
||
#my $resp = "unknown argument, choose one of " . join(" ", sort keys %sets);
|
||
my $resp = "unknown argument, choose one of";
|
||
|
||
foreach my $key (sort keys %sets)
|
||
{
|
||
my $value = $sets{$key};
|
||
|
||
$resp = $resp . " " . $key;
|
||
$resp = $resp . ":" . $sets{$key} if (defined($value) and not ($value eq ""));
|
||
}
|
||
|
||
#append slider-definition, if set...Necessary for FHEM
|
||
$resp = $resp . ":slider,$slider" if(defined $slider);
|
||
|
||
return $resp;
|
||
}
|
||
|
||
#the command can be send to any of the defined groups indexed starting by 1
|
||
#optional last argument starting with g indicates the group
|
||
#default
|
||
my $groupnr = 1;
|
||
my $lastArg = $na - 1;
|
||
#select another group, if the last arg starts with a g
|
||
if($na > 2 && $a[$lastArg]=~ m/${PAT_GNO}/)
|
||
{
|
||
$groupnr = $a[$lastArg];
|
||
#remove "g"
|
||
$groupnr =~ s/^g//g;
|
||
|
||
$lastArg--;
|
||
}
|
||
|
||
#unknown groupnr
|
||
return "group-no. not found" if(!defined($groupnr));
|
||
|
||
#group
|
||
my $group = $hash->{GADDR}{$groupnr};
|
||
my $groupc = $hash->{GCODE}{$groupnr};
|
||
|
||
#unknown groupnr
|
||
return "group-no. $groupnr not known" if(!defined($group));
|
||
|
||
#get model
|
||
my $model = $hash->{MODEL}{$groupnr};
|
||
my $code = $dpttypes{$model}{CODE};
|
||
|
||
Log3 ($name, 5, "set $name: model: $model, GAD: $group, GAD hex: $groupc, gno: $groupnr");
|
||
|
||
#This contains the input
|
||
my $value = "";
|
||
|
||
#delete any running timers
|
||
if ($hash->{"ON-FOR-TIMER_G$groupnr"})
|
||
{
|
||
CommandDelete(undef, $name . "_timer_$groupnr");
|
||
delete $hash->{"ON-FOR-TIMER_G$groupnr"};
|
||
}
|
||
if($hash->{"ON-UNTIL_G$groupnr"})
|
||
{
|
||
CommandDelete(undef, $name . "_until_$groupnr");
|
||
delete $hash->{"ON-UNTIL_G$groupnr"};
|
||
}
|
||
|
||
#set on-for-timer
|
||
if ($cmd =~ m/on-for-timer/)
|
||
{
|
||
return "\"on-for-timer\" only allowed for dpt1" if (not($code eq "dpt1"));
|
||
#get duration
|
||
my $duration = sprintf("%02d:%02d:%02d", $a[2]/3600, ($a[2]%3600)/60, $a[2]%60);
|
||
#$modules{KNX}{"on-for-timer"}{$name} = $duration;
|
||
$hash->{"ON-FOR-TIMER_G$groupnr"} = $duration;
|
||
Log3 ($name, 5, "set $name: \"on-for-timer\" for $duration");
|
||
#set to on
|
||
$value = 1;
|
||
#place at-command for switching off
|
||
CommandDefine(undef, $name . "_timer_$groupnr at +$duration set $name off g$groupnr");
|
||
}
|
||
#set on-till
|
||
elsif ($cmd =~ m/on-until/)
|
||
{
|
||
return "\"on\" only allowed for dpt1" if (not($code eq "dpt1"));
|
||
#get off-time
|
||
my ($err, $hr, $min, $sec, $fn) = GetTimeSpec($a[2]);
|
||
|
||
return "Error trying to parse timespec for $a[2]: $err" if (defined($err));
|
||
|
||
#build of-time
|
||
my @lt = localtime;
|
||
my $hms_til = sprintf("%02d:%02d:%02d", $hr, $min, $sec);
|
||
my $hms_now = sprintf("%02d:%02d:%02d", $lt[2], $lt[1], $lt[0]);
|
||
|
||
return "Won't switch - now ($hms_now) is later than $hms_til" if($hms_now ge $hms_til);
|
||
|
||
#$modules{KNX}{"on-until"}{$name} = $hms_til;
|
||
$hash->{"ON-UNTIL_G$groupnr"} = $hms_til;
|
||
Log3 ($name, 5, "set $name: \"on-until\" up to $hms_til");
|
||
#set to on
|
||
$value = 1;
|
||
#place at-command for switching off
|
||
CommandDefine(undef, $name . "_until_$groupnr at $hms_til set $name off g$groupnr");
|
||
}
|
||
#set on
|
||
elsif ($cmd =~ m/on/)
|
||
{
|
||
return "\"on\" only allowed for dpt1" if (not($code eq "dpt1"));
|
||
$value = 1;
|
||
}
|
||
#set off
|
||
elsif ($cmd =~ m/off/)
|
||
{
|
||
return "\"off\" only allowed for dpt1" if (not($code eq "dpt1"));
|
||
$value = 0;
|
||
}
|
||
#set raw <value>
|
||
elsif ($cmd =~ m/raw/)
|
||
{
|
||
return "no data for cmd $cmd" if ($lastArg < 2);
|
||
|
||
#check for 1-16 hex-digits
|
||
if ($a[2] =~ m/[0-9a-fA-F]{1,16}/)
|
||
{
|
||
$value = lc($a[2]);
|
||
} else
|
||
{
|
||
return "$a[2] has wrong syntax. Use hex-format only.";
|
||
}
|
||
}
|
||
#set value <value>
|
||
elsif ($cmd =~ m/value/)
|
||
{
|
||
#return "\"value\" not allowed for dpt1 and dpt16" if (($code eq "dpt1") or ($code eq "dpt16"));
|
||
return "\"value\" not allowed for dpt1 and dpt16" if ($code eq "dpt16");
|
||
return "no data for cmd $cmd" if ($lastArg < 2);
|
||
|
||
$value = $a[2];
|
||
#replace , with .
|
||
$value =~ s/,/\./g;
|
||
}
|
||
#set string <val1 val2 valn>
|
||
elsif ($cmd =~ m/string/)
|
||
{
|
||
return "\"string\" only allowed for dpt16" if (not($code eq "dpt16"));
|
||
return "no data for cmd $cmd" if ($lastArg < 2);
|
||
|
||
#join string
|
||
for (my $i=2; $i<=$lastArg; $i++)
|
||
{
|
||
$value.= $a[$i]." ";
|
||
}
|
||
}
|
||
|
||
#check and cast value
|
||
my $transval = checkAndClean($hash, $value, $groupnr);
|
||
|
||
return "invalid value: $value" if (!defined($transval));
|
||
|
||
#exit, if sending is prohibited
|
||
return "did not send value - \"listenonly\" is set." if (AttrVal ($name, "listenonly", 0) =~ m/1/);
|
||
return "did not send value - \"readonly\" is set." if (AttrVal ($name, "readonly", 0) =~ m/1/);
|
||
|
||
#send value
|
||
$transval = encodeByDpt($hash, $transval, $groupnr);
|
||
IOWrite($hash, $id, "w" . $groupc . $transval);
|
||
|
||
Log3 ($name, 5, "set $name: cmd: $cmd, value: $value, translated: $transval");
|
||
|
||
#build readingsName
|
||
my $rdName = $hash->{READINGSNAME}{$groupnr};
|
||
if (defined ($rdName) and !($rdName eq ""))
|
||
{
|
||
Log3 ($name, 5, "set name: $name, replaced \"getG\" with custom readingName \"$rdName\"");
|
||
$rdName = $rdName . "-set";
|
||
}
|
||
else
|
||
{
|
||
$rdName = "setG" . $groupnr;
|
||
}
|
||
|
||
#re-read value, do not modify variable name due to usage in cmdAttr
|
||
$transval = decodeByDpt($hash, $transval, $groupnr);
|
||
#append post-string, if supplied
|
||
my $suffix = AttrVal($name, "format",undef);
|
||
$transval = $transval . " " . $suffix if (defined($suffix));
|
||
#execute regex, if defined
|
||
my $regAttr = AttrVal($name, "stateRegex", undef);
|
||
my $state = replaceByRegex ($regAttr, $rdName . ":", $transval);
|
||
Log3 ($name, 5, "set name: $name - replaced $rdName:$transval to $state") if (not ($transval eq $state));
|
||
|
||
if (defined($state))
|
||
{
|
||
readingsBeginUpdate($hash);
|
||
readingsBulkUpdate($hash, $rdName, $transval);
|
||
|
||
#execute state-command if defined
|
||
#must be placed after first reading, because it may have a reference
|
||
my $cmdAttr = AttrVal($name, "stateCmd", undef);
|
||
if (defined ($cmdAttr) and !($cmdAttr eq ""))
|
||
{
|
||
$state = eval $cmdAttr;
|
||
Log3 ($name, 5, "set name: $name - state replaced via command, result: state:$state");
|
||
}
|
||
|
||
readingsBulkUpdate($hash, "state", $state);
|
||
readingsEndUpdate($hash, 1);
|
||
}
|
||
|
||
Log3 ($name, 5, "exit set");
|
||
return undef;
|
||
}
|
||
|
||
#In case setstate is executed, a readingsupdate is initiated
|
||
#############################
|
||
sub
|
||
KNX_State($$$$) {
|
||
my ($hash, $time, $reading, $value) = @_;
|
||
my $name = $hash->{NAME};
|
||
|
||
my $tempStr = join (", ", @_);
|
||
Log3 ($name, 5, "enter state: hash: $hash name: $name, attributes: $tempStr");
|
||
|
||
#in some cases state is submitted within value - if found, take only the stuff after state
|
||
#my @strings = split("[sS][tT][aA][tT][eE]", $val);
|
||
#$val = $strings[int(@strings) - 1];
|
||
|
||
return undef if (not (defined($value)));
|
||
return undef if (not (defined($reading)));
|
||
|
||
#remove whitespaces
|
||
$value =~ s/^\s+|\s+$//g;
|
||
$reading =~ s/^\s+|\s+$//g;
|
||
|
||
$reading = lc ($reading) if ($reading =~ m/[sS][tT][aA][tT][eE]/);
|
||
|
||
Log3 ($name, 5, "state $name: update $reading with value: $value");
|
||
|
||
#write value and update reading
|
||
readingsSingleUpdate($hash, $reading, $value, 1);
|
||
|
||
return undef;
|
||
}
|
||
|
||
#Get the chance to qualify attributes
|
||
#############################
|
||
sub
|
||
KNX_Attr(@) {
|
||
my ($cmd,$name,$aName,$aVal) = @_;
|
||
|
||
return undef;
|
||
}
|
||
|
||
#Split reading for DBLOG
|
||
#############################
|
||
sub KNX_DbLog_split($) {
|
||
my ($event) = @_;
|
||
my ($reading, $value, $unit);
|
||
|
||
my $tempStr = join (", ", @_);
|
||
Log (5, "splitFn - enter, attributes: $tempStr");
|
||
|
||
#detect reading - real reading or state?
|
||
my $isReading = "false";
|
||
$isReading = "true" if ($event =~ m/: /);
|
||
|
||
#split input-string
|
||
my @strings = split (" ", $event);
|
||
|
||
my $startIndex = undef;
|
||
$unit = "";
|
||
|
||
return undef if (not defined ($strings[0]));
|
||
|
||
#real reading?
|
||
if ($isReading =~ m/true/)
|
||
{
|
||
#first one is always reading
|
||
$reading = $strings[0];
|
||
$reading =~ s/:?$//;
|
||
$startIndex = 1;
|
||
}
|
||
#plain state
|
||
else
|
||
{
|
||
#for reading state nothing is supplied
|
||
$reading = "state";
|
||
$startIndex = 0;
|
||
}
|
||
|
||
return undef if (not defined ($strings[$startIndex]));
|
||
|
||
#per default join all single pieces
|
||
$value = join(" ", @strings[$startIndex..(int(@strings) - 1)]);
|
||
|
||
#numeric value?
|
||
#if ($strings[$startIndex] =~ /^[+-]?\d*[.,]?\d+/)
|
||
if ($strings[$startIndex] =~ /^[+-]?\d*[.,]?\d+$/)
|
||
{
|
||
$value = $strings[$startIndex];
|
||
#single numeric value? Assume second par is unit...
|
||
if ((defined ($strings[$startIndex + 1])) && !($strings[$startIndex+1] =~ /^[+-]?\d*[.,]?\d+/))
|
||
{
|
||
$unit = $strings[$startIndex + 1] if (defined ($strings[$startIndex + 1]));
|
||
}
|
||
}
|
||
|
||
#numeric value?
|
||
#if ($strings[$startIndex] =~ /^[+-]?\d*[.,]?\d+/)
|
||
#{
|
||
# $value = $strings[$startIndex];
|
||
# $unit = $strings[$startIndex + 1] if (defined ($strings[$startIndex + 1]));
|
||
#}
|
||
#string or raw
|
||
#else
|
||
#{
|
||
# $value = join(" ", @strings[$startIndex..(int(@strings) - 1)]);
|
||
#}
|
||
|
||
Log (5, "splitFn - READING: $reading, VALUE: $value, UNIT: $unit");
|
||
|
||
return ($reading, $value, $unit);
|
||
}
|
||
|
||
#Handle incoming messages
|
||
#############################
|
||
sub
|
||
KNX_Parse($$) {
|
||
my ($hash, $msg) = @_;
|
||
|
||
#Msg format:
|
||
#C(w/r/p)<group><value> i.e. Bw00000101
|
||
#we will also take reply telegrams into account,
|
||
#as they will be sent if the status is asked from bus
|
||
#split message into parts
|
||
$msg =~ m/^$id(.{4})(.{1})(.{4})(.*)$/;
|
||
my $src = $1;
|
||
my $cmd = $2;
|
||
my $dest = $3;
|
||
my $val = $4;
|
||
|
||
my @foundMsgs;
|
||
|
||
Log3 ($hash->{NAME}, 5, "enter parse: hash: $hash name: $hash->{NAME}, msg: $msg");
|
||
|
||
#check if the code is within the read groups
|
||
foreach my $deviceName (keys %{$modules{KNX}{defptr}})
|
||
{
|
||
#fetch device
|
||
my $deviceHash = $modules{KNX}{defptr}{$deviceName};
|
||
|
||
#skip, if name not defined
|
||
next if (!defined($deviceHash));
|
||
|
||
#loop through all defined group-numbers
|
||
foreach my $gno (keys %{$deviceHash->{GCODE}})
|
||
{
|
||
#fetch groupcode
|
||
my $groupc = $deviceHash->{GCODE}{$gno};
|
||
|
||
#GAD in message is matching GAD in device
|
||
if (defined ($groupc) and ($groupc eq $dest))
|
||
{
|
||
#get details
|
||
my $name = $deviceHash->{NAME};
|
||
my $groupAddr = $deviceHash->{GADDR}{$gno};
|
||
my $model = $deviceHash->{MODEL}{$gno};
|
||
|
||
Log3 ($name, 5, "parse device hash: $deviceHash name: $name, GADDR: $groupAddr, GCODE: $groupc, MODEL: $model");
|
||
|
||
#handle write and reply messages
|
||
if ($cmd =~ /[w|p]/)
|
||
{
|
||
#decode message
|
||
my $transval = decodeByDpt ($deviceHash, $val, $gno);
|
||
#message invalid
|
||
if (not defined($transval) or ($transval eq ""))
|
||
{
|
||
Log3 ($name, 2, "parse device hash: $deviceHash name: $name, message could not be decoded - see log for details");
|
||
next;
|
||
}
|
||
|
||
Log3 ($name, 5, "received hash: $deviceHash name: $name, STATE: $transval, GNO: $gno, SENDER: $src");
|
||
|
||
#build readingsName
|
||
my $rdName = $deviceHash->{READINGSNAME}{$gno};
|
||
if (defined ($rdName) and !($rdName eq ""))
|
||
{
|
||
Log3 ($name, 5, "parse device hash: $deviceHash name: $name, replaced \"getG\" with custom readingName \"$rdName\"");
|
||
$rdName = $rdName . "-get";
|
||
}
|
||
else
|
||
{
|
||
$rdName = "getG" . $gno;
|
||
}
|
||
|
||
#append post-string, if supplied
|
||
my $suffix = AttrVal($name, "format",undef);
|
||
$transval = $transval . " " . $suffix if (defined($suffix));
|
||
#execute regex, if defined
|
||
my $regAttr = AttrVal($name, "stateRegex", undef);
|
||
my $state = replaceByRegex ($regAttr, $rdName . ":", $transval);
|
||
Log3 ($name, 5, "parse device hash: $deviceHash name: $name - replaced $rdName:$transval to $state") if (not ($transval eq $state));
|
||
|
||
if (defined($state))
|
||
{
|
||
readingsBeginUpdate($deviceHash);
|
||
readingsBulkUpdate($deviceHash, $rdName, $transval);
|
||
readingsBulkUpdate($deviceHash, "last-sender", hexToName($src));
|
||
|
||
#execute state-command if defined
|
||
#must be placed after first readings, because it may have a reference
|
||
my $cmdAttr = AttrVal($name, "stateCmd", undef);
|
||
if (defined ($cmdAttr) and !($cmdAttr eq ""))
|
||
{
|
||
$state = eval $cmdAttr;
|
||
Log3 ($name, 5, "parse device hash: $deviceHash name: $name - state replaced via command - result: state:$state");
|
||
}
|
||
|
||
readingsBulkUpdate($deviceHash, "state", $state);
|
||
readingsEndUpdate($deviceHash, 1);
|
||
}
|
||
}
|
||
#handle read messages, if Attribute is set
|
||
elsif (($cmd =~ /[r]/) && (AttrVal($name, "answerReading",0) =~ m/1/))
|
||
{
|
||
Log3 ($name, 5, "received hash: $deviceHash name: $name, GET");
|
||
my $transval = encodeByDpt($deviceHash, $deviceHash->{STATE}, $gno);
|
||
|
||
if (defined($transval))
|
||
{
|
||
Log3 ($name, 5, "received hash: $deviceHash name: $name, GET: $transval, GNO: $gno");
|
||
IOWrite ($deviceHash, "B", "p" . $groupc . $transval);
|
||
}
|
||
}
|
||
|
||
#skip, if this is ignored
|
||
next if (IsIgnored($name));
|
||
#save to list
|
||
push(@foundMsgs, $name);
|
||
}
|
||
}
|
||
}
|
||
|
||
Log3 ($hash->{NAME}, 5, "exit parse");
|
||
|
||
#return values
|
||
if (int(@foundMsgs))
|
||
{
|
||
return @foundMsgs;
|
||
} else
|
||
{
|
||
my $gad = hexToName($dest);
|
||
#remove slashes
|
||
#$name =~ s/\///g;
|
||
#my $name = "KNX_" . $gad;
|
||
my ($line, $area, $device) = split ("/", $gad);
|
||
my $name = sprintf("KNX_%.2d%.2d%.3d", $line, $area, $device);
|
||
|
||
my $ret = "KNX Unknown device $dest ($gad), Value $val, please define it";
|
||
Log3 ($name, 3, "KNX Unknown device $dest ($gad), Value $val, please define it");
|
||
|
||
#needed for autocreate
|
||
return "UNDEFINED $name KNX $gad:$modelErr";
|
||
}
|
||
}
|
||
|
||
#Function is called at every notify
|
||
#############################
|
||
sub
|
||
KNX_Notify($$)
|
||
{
|
||
my ($ownHash, $callHash) = @_;
|
||
#own name / hash
|
||
my $ownName = $ownHash->{NAME};
|
||
#Device that created the events
|
||
my $callName = $callHash->{NAME};
|
||
|
||
return undef;
|
||
}
|
||
|
||
#Private function to convert GAD from hex to readable version
|
||
#############################
|
||
sub
|
||
hexToName ($)
|
||
{
|
||
my $v = shift;
|
||
|
||
my $p1 = hex(substr($v,0,1));
|
||
my $p2 = hex(substr($v,1,1));
|
||
my $p3 = hex(substr($v,2,2));
|
||
|
||
my $r = sprintf("%d/%d/%d", $p1,$p2,$p3);
|
||
|
||
return $r;
|
||
}
|
||
|
||
#Private function to convert GAD from readable version to hex
|
||
#############################
|
||
sub
|
||
nameToHex ($)
|
||
{
|
||
my $v = shift;
|
||
my $r = $v;
|
||
|
||
if($v =~ /^([0-9]{1,2})\/([0-9]{1,2})\/([0-9]{1,3})$/)
|
||
{
|
||
$r = sprintf("%01x%01x%02x",$1,$2,$3);
|
||
}
|
||
#elsif($v =~ /^([0-9]{1,2})\.([0-9]{1,2})\.([0-9]{1,3})$/)
|
||
#{
|
||
# $r = sprintf("%01x%01x%02x",$1,$2,$3);
|
||
#}
|
||
|
||
return $r;
|
||
}
|
||
|
||
#Private function to clean input string according DPT
|
||
#############################
|
||
sub
|
||
checkAndClean ($$$)
|
||
{
|
||
my ($hash, $value, $gno) = @_;
|
||
my $name = $hash->{NAME};
|
||
my $orgValue = $value;
|
||
|
||
Log3 ($name, 5, "check value: $value, gno: $gno");
|
||
|
||
#get model
|
||
my $model = $hash->{MODEL}{$gno};
|
||
|
||
#return unchecked, if this is a autocreate-device
|
||
return $value if ($model eq $modelErr);
|
||
|
||
#get pattern
|
||
my $pattern = $dpttypes{$model}{PATTERN};
|
||
|
||
#trim whitespaces at the end
|
||
$value =~ s/^\s+|\s+$//g;
|
||
|
||
#match against model pattern
|
||
my @tmp = ($value =~ m/$pattern/g);
|
||
#loop through results
|
||
my $found = 0;
|
||
foreach my $str (@tmp)
|
||
{
|
||
#assign first match and exit loop
|
||
if (defined($str))
|
||
{
|
||
$found = 1;
|
||
$value = $str;
|
||
last;
|
||
}
|
||
}
|
||
|
||
return undef if ($found == 0);
|
||
|
||
#get min
|
||
my $min = $dpttypes{"$model"}{MIN};
|
||
#if min is numeric, cast to min
|
||
$value = $min if (defined ($min) and ($min =~ /^[+-]?\d*[.,]?\d+/) and ($value < $min));
|
||
|
||
#get max
|
||
my $max = $dpttypes{"$model"}{MAX};
|
||
#if max is numeric, cast to max
|
||
$value = $max if (defined ($max) and ($max =~ /^[+-]?\d*[.,]?\d+/) and ($value > $max));
|
||
|
||
Log3 ($name, 3, "check value: input-value $orgValue was casted to $value") if (not($orgValue eq $value));
|
||
Log3 ($name, 5, "check value: $value, gno: $gno, model: $model, pattern: $pattern");
|
||
|
||
return $value;
|
||
}
|
||
|
||
|
||
#Private function to encode KNX-Message according DPT
|
||
#############################
|
||
sub
|
||
encodeByDpt ($$$) {
|
||
my ($hash, $value, $gno) = @_;
|
||
my $name = $hash->{NAME};
|
||
|
||
Log3 ($name, 5, "encode value: $value, gno: $gno");
|
||
|
||
#get model
|
||
my $model = $hash->{MODEL}{$gno};
|
||
my $code = $dpttypes{$model}{CODE};
|
||
|
||
#return unchecked, if this is a autocreate-device
|
||
return $value if ($model eq $modelErr);
|
||
|
||
#this one stores the translated value (readble)
|
||
my $numval = undef;
|
||
#this one stores the translated hex-value
|
||
my $hexval = undef;
|
||
|
||
Log3 ($name, 5, "encode model: $model, code: $code, value: $value");
|
||
|
||
#get correction details
|
||
my $factor = $dpttypes{$model}{FACTOR};
|
||
my $offset = $dpttypes{$model}{OFFSET};
|
||
|
||
#correct value
|
||
$value /= $factor if (defined ($factor));
|
||
$value -= $offset if (defined ($offset));
|
||
|
||
Log3 ($name, 5, "encode normalized value: $value");
|
||
|
||
#Binary value
|
||
if ($code eq "dpt1")
|
||
{
|
||
$numval = "00" if ($value eq 0);
|
||
$numval = "01" if ($value eq 1);
|
||
$numval = "00" if ($value eq $dpttypes{$model}{MIN});
|
||
$numval = "01" if ($value eq $dpttypes{$model}{MAX});
|
||
|
||
$hexval = $numval;
|
||
}
|
||
#Step value (four-bit)
|
||
elsif ($code eq "dpt3")
|
||
{
|
||
$numval = 0;
|
||
|
||
#get dim-direction
|
||
my $sign = 0;
|
||
$sign = 1 if ($value >= 0);
|
||
|
||
#trim sign
|
||
$value =~ s/^-//g;
|
||
|
||
#get dim-value
|
||
$numval = 7 if ($value >= 1);
|
||
$numval = 6 if ($value >= 3);
|
||
$numval = 5 if ($value >= 6);
|
||
$numval = 4 if ($value >= 12);
|
||
$numval = 3 if ($value >= 25);
|
||
$numval = 2 if ($value >= 50);
|
||
$numval = 1 if ($value >= 75);
|
||
|
||
#assign dim direction
|
||
$numval += 8 if ($sign == 1);
|
||
|
||
#get hex representation
|
||
$hexval = sprintf("%.2x",$numval);
|
||
}
|
||
#1-Octet unsigned value
|
||
elsif ($code eq "dpt5")
|
||
{
|
||
$numval = $value;
|
||
$hexval = sprintf("00%.2x",($numval));
|
||
}
|
||
#1-Octet signed value
|
||
elsif ($code eq "dpt6")
|
||
{
|
||
#build 2-complement
|
||
$numval = $value;
|
||
$numval += 0x100 if ($numval < 0);
|
||
$numval = 0x00 if ($numval < 0x00);
|
||
$numval = 0xFF if ($numval > 0xFF);
|
||
|
||
#get hex representation
|
||
$hexval = sprintf("00%.2x",$numval);
|
||
}
|
||
#2-Octet unsigned Value
|
||
elsif ($code eq "dpt7")
|
||
{
|
||
$numval = $value;
|
||
$hexval = sprintf("00%.4x",($numval));
|
||
}
|
||
#2-Octet signed Value
|
||
elsif ($code eq "dpt8")
|
||
{
|
||
#build 2-complement
|
||
$numval = $value;
|
||
$numval += 0x10000 if ($numval < 0);
|
||
$numval = 0x00 if ($numval < 0x00);
|
||
$numval = 0xFFFF if ($numval > 0xFFFF);
|
||
|
||
#get hex representation
|
||
$hexval = sprintf("00%.4x",$numval);
|
||
}
|
||
#2-Octet Float value
|
||
elsif ($code eq "dpt9")
|
||
{
|
||
my $sign = ($value <0 ? 0x8000 : 0);
|
||
my $exp = 0;
|
||
my $mant = 0;
|
||
|
||
$mant = int($value * 100.0);
|
||
while (abs($mant) > 0x7FF)
|
||
{
|
||
$mant /= 2;
|
||
$exp++;
|
||
}
|
||
$numval = $sign | ($exp << 11) | ($mant & 0x07ff);
|
||
|
||
#get hex representation
|
||
$hexval = sprintf("00%.4x",$numval);
|
||
}
|
||
#Time of Day
|
||
elsif ($code eq "dpt10")
|
||
{
|
||
if (lc($value) eq "now")
|
||
{
|
||
#get actual time
|
||
my ($secs,$mins,$hours,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
|
||
my $hoffset;
|
||
|
||
#add offsets
|
||
$year+=1900;
|
||
$mon++;
|
||
# calculate offset for weekday
|
||
$wday = 7 if ($wday eq "0");
|
||
$hoffset = 32*$wday;
|
||
$hours += $hoffset;
|
||
|
||
$value = "$hours:$mins:$secs";
|
||
$numval = $secs + ($mins<<8) + ($hours<<16);
|
||
} else
|
||
{
|
||
my ($hh, $mm, $ss) = split (":", $value);
|
||
$numval = $ss + ($mm<<8) + (($hh)<<16);
|
||
}
|
||
|
||
#get hex representation
|
||
$hexval = sprintf("00%.6x",$numval);
|
||
}
|
||
#Date
|
||
elsif ($code eq "dpt11")
|
||
{
|
||
if (lc($value) eq "now")
|
||
{
|
||
#get actual time
|
||
my ($secs,$mins,$hours,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
|
||
my $hoffset;
|
||
|
||
#add offsets
|
||
$year+=1900;
|
||
$mon++;
|
||
# calculate offset for weekday
|
||
$wday = 7 if ($wday eq "0");
|
||
|
||
$value = "$mday.$mon.$year";
|
||
$numval = ($year - 2000) + ($mon<<8) + ($mday<<16);
|
||
} else
|
||
{
|
||
my ($dd, $mm, $yyyy) = split (/\./, $value);
|
||
|
||
if ($yyyy >= 2000)
|
||
{
|
||
$yyyy -= 2000;
|
||
} else
|
||
{
|
||
$yyyy -= 1900;
|
||
}
|
||
|
||
$numval = ($yyyy) + ($mm<<8) + ($dd<<16);
|
||
}
|
||
|
||
#get hex representation
|
||
$hexval = sprintf("00%.6x",$numval);
|
||
}
|
||
#4-Octet unsigned value (handled as dpt7)
|
||
elsif ($code eq "dpt12")
|
||
{
|
||
$numval = $value;
|
||
$hexval = sprintf("00%.8x",($numval));
|
||
}
|
||
#4-Octet Signed Value
|
||
elsif ($code eq "dpt13")
|
||
{
|
||
#build 2-complement
|
||
$numval = $value;
|
||
$numval += 4294967296 if ($numval < 0);
|
||
$numval = 0x00 if ($numval < 0x00);
|
||
$numval = 0xFFFFFFFF if ($numval > 0xFFFFFFFF);
|
||
|
||
#get hex representation
|
||
$hexval = sprintf("00%.8x",$numval);
|
||
}
|
||
#4-Octet single precision float
|
||
elsif ($code eq "dpt14")
|
||
{
|
||
$numval = unpack("L", pack("f", $value));
|
||
|
||
#get hex representation
|
||
$hexval = sprintf("00%.8x",$numval);
|
||
}
|
||
#14-Octet String
|
||
elsif ($code eq "dpt16")
|
||
{
|
||
#convert to hex-string
|
||
my $dat = unpack "H*", $value;
|
||
#format for 14-byte-length
|
||
$dat = sprintf("%-028s",$dat);
|
||
#append leading zeros
|
||
$dat = "00" . $dat;
|
||
|
||
$numval = $value;
|
||
$hexval = $dat;
|
||
}
|
||
else
|
||
{
|
||
Log3 ($name, 2, "encode model: $model, no vaild model defined");
|
||
return undef;
|
||
}
|
||
|
||
Log3 ($name, 5, "encode model: $model, code: $code, value: $value, numval: $numval, hexval: $hexval");
|
||
return $hexval;
|
||
}
|
||
|
||
#Private function to replace state-values
|
||
#############################
|
||
sub
|
||
replaceByRegex ($$$) {
|
||
my ($regAttr, $prefix, $input) = @_;
|
||
my $retVal = $input;
|
||
|
||
#execute regex, if defined
|
||
if (defined($regAttr))
|
||
{
|
||
#get array of given attributes
|
||
my @reg = split(" /", $regAttr);
|
||
|
||
my $tempVal = $prefix . $input;
|
||
|
||
#loop over all regex
|
||
foreach my $regex (@reg)
|
||
{
|
||
#trim leading and trailing slashes
|
||
$regex =~ s/^\/|\/$//g;
|
||
#get pairs
|
||
my @regPair = split("\/", $regex);
|
||
|
||
#skip if not at least 2 values supplied
|
||
#next if (int(@regPair < 2));
|
||
next if (not defined($regPair[0]));
|
||
|
||
if (not defined ($regPair[1]))
|
||
{
|
||
#cut value
|
||
$tempVal =~ s/$regPair[0]//g;
|
||
}
|
||
else
|
||
{
|
||
#replace value
|
||
$tempVal =~ s/$regPair[0]/$regPair[1]/g;
|
||
}
|
||
|
||
#restore value
|
||
$retVal = $tempVal;
|
||
}
|
||
}
|
||
|
||
return $retVal;
|
||
}
|
||
|
||
#Private function to decode KNX-Message according DPT
|
||
#############################
|
||
sub
|
||
decodeByDpt ($$$) {
|
||
my ($hash, $value, $gno) = @_;
|
||
my $name = $hash->{NAME};
|
||
|
||
Log3 ($name, 5, "decode value: $value, gno: $gno");
|
||
|
||
#get model
|
||
my $model = $hash->{MODEL}{$gno};
|
||
my $code = $dpttypes{$model}{CODE};
|
||
|
||
#return unchecked, if this is a autocreate-device
|
||
return $value if ($model eq $modelErr);
|
||
|
||
#this one stores the translated value (readble)
|
||
my $numval = undef;
|
||
#this one contains the return-value
|
||
my $state = undef;
|
||
|
||
Log3 ($name, 5, "decode model: $model, code: $code, value: $value");
|
||
|
||
#get correction details
|
||
my $factor = $dpttypes{$model}{FACTOR};
|
||
my $offset = $dpttypes{$model}{OFFSET};
|
||
|
||
#Binary value
|
||
if ($code eq "dpt1")
|
||
{
|
||
my $min = $dpttypes{"$model"}{MIN};
|
||
my $max = $dpttypes{"$model"}{MAX};
|
||
|
||
$numval = $min if (lc($value) eq "00");
|
||
$numval = $max if (lc($value) eq "01");
|
||
$state = $numval;
|
||
}
|
||
#Step value (four-bit)
|
||
elsif ($code eq "dpt3")
|
||
{
|
||
#get numeric value
|
||
$numval = hex ($value);
|
||
|
||
$state = 1 if ($numval & 7);
|
||
$state = 3 if ($numval & 6);
|
||
$state = 6 if ($numval & 5);
|
||
$state = 12 if ($numval & 4);
|
||
$state = 25 if ($numval & 3);
|
||
$state = 50 if ($numval & 2);
|
||
$state = 100 if ($numval & 1);
|
||
|
||
#get dim-direction
|
||
$state = 0 - $state if (not ($numval & 8));
|
||
|
||
#correct value
|
||
$state -= $offset if (defined ($offset));
|
||
$state *= $factor if (defined ($factor));
|
||
|
||
$state = sprintf ("%.0f", $state);
|
||
}
|
||
#1-Octet unsigned value
|
||
elsif ($code eq "dpt5")
|
||
{
|
||
$numval = hex ($value);
|
||
$state = $numval;
|
||
|
||
#correct value
|
||
$state -= $offset if (defined ($offset));
|
||
$state *= $factor if (defined ($factor));
|
||
|
||
$state = sprintf ("%.0f", $state);
|
||
}
|
||
#1-Octet signed value
|
||
elsif ($code eq "dpt6")
|
||
{
|
||
$numval = hex ($value);
|
||
$numval -= 0x100 if ($numval >= 0x80);
|
||
$state = $numval;
|
||
|
||
#correct value
|
||
$state -= $offset if (defined ($offset));
|
||
$state *= $factor if (defined ($factor));
|
||
|
||
$state = sprintf ("%.0f", $state);
|
||
}
|
||
#2-Octet unsigned Value
|
||
elsif ($code eq "dpt7")
|
||
{
|
||
$numval = hex ($value);
|
||
$state = $numval;
|
||
|
||
#correct value
|
||
$state -= $offset if (defined ($offset));
|
||
$state *= $factor if (defined ($factor));
|
||
|
||
$state = sprintf ("%.0f", $state);
|
||
}
|
||
#2-Octet signed Value
|
||
elsif ($code eq "dpt8")
|
||
{
|
||
$numval = hex ($value);
|
||
$numval -= 0x10000 if ($numval >= 0x8000);
|
||
$state = $numval;
|
||
|
||
#correct value
|
||
$state -= $offset if (defined ($offset));
|
||
$state *= $factor if (defined ($factor));
|
||
|
||
$state = sprintf ("%.0f", $state);
|
||
}
|
||
#2-Octet Float value
|
||
elsif ($code eq "dpt9")
|
||
{
|
||
$numval = hex($value);
|
||
my $sign = 1;
|
||
$sign = -1 if(($numval & 0x8000) > 0);
|
||
my $exp = ($numval & 0x7800) >> 11;
|
||
my $mant = ($numval & 0x07FF);
|
||
$mant = -(~($mant-1) & 0x07FF) if($sign == -1);
|
||
$numval = (1 << $exp) * 0.01 * $mant;
|
||
|
||
#correct value
|
||
$state -= $offset if (defined ($offset));
|
||
$state *= $factor if (defined ($factor));
|
||
|
||
$state = sprintf ("%.2f","$numval");
|
||
}
|
||
#Time of Day
|
||
elsif ($code eq "dpt10")
|
||
{
|
||
$numval = hex($value);
|
||
my $hours = ($numval & 0x1F0000)>>16;
|
||
my $mins = ($numval & 0x3F00)>>8;
|
||
my $secs = ($numval & 0x3F);
|
||
|
||
$state = sprintf("%02d:%02d:%02d",$hours,$mins,$secs);
|
||
}
|
||
#Date
|
||
elsif ($code eq "dpt11")
|
||
{
|
||
$numval = hex($value);
|
||
my $day = ($numval & 0x1F0000) >> 16;
|
||
my $month = ($numval & 0x0F00) >> 8;
|
||
my $year = ($numval & 0x7F);
|
||
#translate year (21st cent if <90 / else 20th century)
|
||
$year += 1900 if($year >= 90);
|
||
$year += 2000 if($year < 90);
|
||
|
||
$state = sprintf("%02d.%02d.%04d",$day,$month,$year);
|
||
}
|
||
#4-Octet unsigned value (handled as dpt7)
|
||
elsif ($code eq "dpt12")
|
||
{
|
||
$numval = hex ($value);
|
||
$state = $numval;
|
||
|
||
#correct value
|
||
$state -= $offset if (defined ($offset));
|
||
$state *= $factor if (defined ($factor));
|
||
|
||
$state = sprintf ("%.0f", $state);
|
||
}
|
||
#4-Octet Signed Value
|
||
elsif ($code eq "dpt13")
|
||
{
|
||
$numval = hex ($value);
|
||
$numval -= 4294967296 if ($numval >= 0x80000000);
|
||
$state = $numval;
|
||
|
||
#correct value
|
||
$state -= $offset if (defined ($offset));
|
||
$state *= $factor if (defined ($factor));
|
||
|
||
$state = sprintf ("%.0f", $state);
|
||
}
|
||
#4-Octet single precision float
|
||
elsif ($code eq "dpt14")
|
||
{
|
||
$numval = unpack "f", pack "L", hex ($value);
|
||
|
||
#correct value
|
||
$state -= $offset if (defined ($offset));
|
||
$state *= $factor if (defined ($factor));
|
||
|
||
$state = sprintf ("%.3f","$numval");
|
||
}
|
||
#14-Octet String
|
||
elsif ($code eq "dpt16")
|
||
{
|
||
$numval = 0;
|
||
$state = "";
|
||
|
||
for (my $i = 0; $i < 14; $i++)
|
||
{
|
||
my $c = hex(substr($value, $i * 2, 2));
|
||
|
||
#exit at string terminator, otherwise append current char
|
||
if ($c eq 0)
|
||
{
|
||
$i = 14;
|
||
}
|
||
else
|
||
{
|
||
$state .= sprintf("%c", $c);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Log3 ($name, 2, "decode model: $model, no valid model defined");
|
||
return undef;
|
||
}
|
||
|
||
#append unit, if supplied
|
||
my $unit = $dpttypes{$model}{UNIT};
|
||
$state = $state . " " . $unit if (defined ($unit) and not($unit eq ""));
|
||
|
||
Log3 ($name, 5, "decode model: $model, code: $code, value: $value, numval: $numval, state: $state");
|
||
return $state;
|
||
}
|
||
|
||
1;
|
||
|
||
=pod
|
||
=begin html
|
||
|
||
<a name="KNX"></a>
|
||
<h3>KNX</h3>
|
||
<ul>
|
||
<p>KNX is a standard for building automation / home automation.
|
||
It is mainly based on a twisted pair wiring, but also other mediums (ip, wireless) are specified.</p>
|
||
For getting started, please refer to this document: <a href="http://www.knx.org/media/docs/Flyers/KNX-Basics/KNX-Basics_de.pdf">KNX-Basics</a>
|
||
|
||
<p>While the module <a href="#TUL">TUL</a> represents the connection to the KNX network, the KNX modules represent individual KNX devices. This module provides a basic set of operations (on, off, on-until, on-for-timer)
|
||
to switch on/off KNX devices. For numeric DPT you can use value (set <devname> value <177.45>). For string-DPT you can use string (set <devname> string <Hello World>). For other, non-defined
|
||
dpt you can send raw hex values to the network (set <devname> raw <hexval>).<br>
|
||
Sophisticated setups can be achieved by combining a number of KNX module instances. Therefore you can define a number of different GAD/DPT combinations per each device.</p>
|
||
|
||
<p>KNX defines a series of Datapoint Type as standard data types used to allow general interpretation of values of devices manufactured by different companies.
|
||
These datatypes are used to interpret the status of a device, so the state in FHEM will then show the correct value. For each received telegram there will be a reading with state, getG<group> and the sender
|
||
address. For every set, there will be a reading with state and setG<group>.</p>
|
||
|
||
<p><a name="KNXdefine"></a> <b>Define</b></p>
|
||
<ul>
|
||
<code>define <name> KNX <group>:<DPT>:<[;readingName]> [<group>:<DPT> ..] [IODev]</code>
|
||
|
||
<p>A KNX device need a concrete DPT. Please refer to <a href="#KNXdpt">Available DPT</a>. Otherwise the system cannot en- or decode the messages. Furthermore you can supply a IO-Device directly at startup. This can be done later on via attribute as well.</p>
|
||
|
||
<p>Define an KNX device, connected via a <a href="#TUL">TUL</a>. The <group> parameters are either a group name notation (0-15/0-15/0-255) or the hex representation of the value (0-f0-f0-ff).
|
||
All of the defined groups can be used for bus-communication. Without further attributes, all incoming messages are translated into state. Per default, the first group is used for sending. If you want to send
|
||
via a different group, you have to index it (set <devname> value <17.0> <g2>).<br>
|
||
If you use the readingName, readings are based on this name (e.g. hugo-set, hugo-get for name hugo).</p>
|
||
|
||
<p>The module <a href="#autocreate">autocreate</a> is creating a new definition for any unknown sender. The device itself will be NOT fully available, until you added a DPT to the definition. The name will be
|
||
KNX_nnmmooo where nn is the line adress, mm the area and ooo the device.</p>
|
||
|
||
<p>Example:</p>
|
||
<pre>
|
||
define lamp1 KNX 0/10/12:dpt1
|
||
define lamp1 KNX 0/10/12:dpt1:meinName 0/0/5:dpt1.001
|
||
define lamp1 KNX 0A0C:dpt1.003 myTul
|
||
</pre>
|
||
|
||
One hint regarding dpt1 (binary): all the sub-types have to be used with keyword value. Received telegrams are already encoded to their representation.
|
||
Having the on/off button (for send values) without keyword value is an absolutely special use-case and only valid for dpt1 (not the subs).<br>
|
||
|
||
<p>Example:</p>
|
||
<pre>
|
||
define rollo KNX 0/10/12:dpt1.008
|
||
set rollo value up
|
||
set rollo value down
|
||
</pre>
|
||
|
||
</ul>
|
||
|
||
<p><a name="KNXset"></a> <b>Set</b></p>
|
||
<ul>
|
||
<code>set <name> <on, off></code> [g<groupnr>]
|
||
<code>set <name> <on-for-timer, on-until> <time> [g<groupnr>]</code>
|
||
<code>set <name> <value> [g<groupnr>]</code>
|
||
<code>set <name> <string> [g<groupnr>]</code>
|
||
<code>set <name> <raw> [g<groupnr>]</code>
|
||
|
||
<p>Example:</p>
|
||
<pre>
|
||
set lamp1 on
|
||
set lamp1 off
|
||
set lamp1 on-for-timer 10
|
||
set lamp1 on-until 13:15:00
|
||
set foobar raw 234578
|
||
set thermo value 23.44
|
||
set message value Hallo Welt
|
||
</pre>
|
||
|
||
<p>When as last argument a g<groupnr> is present, the command will be sent
|
||
to the KNX group indexed by the groupnr (starting by 1, in the order as given in define).</p>
|
||
<pre>
|
||
define lamp1 KNX 0/10/01:dpt1 0/10/02:dpt1
|
||
set lamp1 on g2 (will send "on" to 0/10/02)
|
||
</pre>
|
||
|
||
<p>A dimmer can be used with a slider as shown in following example:</p>
|
||
<pre>
|
||
define dim1 KNX 0/0/5:dpt5.001
|
||
attr dim1 slider 0,1,100
|
||
attr dim1 webCmd value
|
||
</pre>
|
||
|
||
<p>The current date and time can be sent to the bus by the following settings:</p>
|
||
<pre>
|
||
define timedev KNX 0/0/7:dpt10
|
||
attr timedev eventMap /value now:now/
|
||
attr timedev webCmd now
|
||
|
||
define datedev KNX 0/0/8:dpt11
|
||
attr datedev eventMap /value now:now/
|
||
attr datedev webCmd now
|
||
|
||
# send every midnight the new date
|
||
define dateset at *00:00:00 set datedev value now
|
||
|
||
# send every hour the current time
|
||
define timeset at +*01:00:00 set timedev value now
|
||
</pre>
|
||
</ul>
|
||
|
||
<p><a name="KNXget"></a> <b>Get</b></p>
|
||
<ul>
|
||
<p>If you execute get for a KNX-Element the status will be requested a state from the device. The device has to be able to respond to a read - this is not given for all devices.<br>
|
||
The answer from the bus-device is not shown in the toolbox, but is treated like a regular telegram.</p>
|
||
</ul>
|
||
|
||
<p><a name="KNXattr"></a> <b>Attributes</b></p>
|
||
<ul><br>
|
||
Common attributes:<br>
|
||
<a href="#DbLogInclude">DbLogInclude</a><br>
|
||
<a href="#DbLogExclude">DbLogExclude</a><br>
|
||
<a href="#IODev">IODev</a><br>
|
||
<a href="#alias">alias</a><br>
|
||
<a href="#comment">comment</a><br>
|
||
<a href="#devStateIcon">devStateIcon</a><br>
|
||
<a href="#devStateStyle">devStateStyle</a><br>
|
||
<a href="#do_not_notify">do_not_notify</a><br>
|
||
<a href="#readingFnAttributes">readingFnAttributes</a><br>
|
||
<a href="#event-aggregator">event-aggregator</a><br>
|
||
<a href="#event-min-interval">event-min-interval</a><br>
|
||
<a href="#event-on-change-reading">event-on-change-reading</a><br>
|
||
<a href="#event-on-update-reading">event-on-update-reading</a><br>
|
||
<a href="#eventMap">eventMap</a><br>
|
||
<a href="#group">group</a><br>
|
||
<a href="#icon">icon</a><br>
|
||
<a href="#room">room</a><br>
|
||
<a href="#showtime">showtime</a><br>
|
||
<a href="#sortby">sortby</a><br>
|
||
<a href="#stateFormat">stateFormat</a><br>
|
||
<a href="#userReadings">userReadings</a><br>
|
||
<a href="#userattr">userattr</a><br>
|
||
<a href="#verbose">verbose</a><br>
|
||
<a href="#webCmd">webCmd</a><br>
|
||
<a href="#widgetOverride">widgetOverride</a><br>
|
||
<br>
|
||
</ul>
|
||
|
||
<p><a name="KNXformat"></a> <b>format</b></p>
|
||
<ul>
|
||
The content of this attribute is added to every received value, before this is copied to state.
|
||
<p>Example:</p>
|
||
<pre>
|
||
define myTemperature KNX 0/1/1:dpt5
|
||
attr myTemperature format °C;
|
||
</pre>
|
||
</ul>
|
||
|
||
<p><a name="KNXstateRegex"></a> <b>stateRegex</b></p>
|
||
<ul>
|
||
You can pass n pairs of regex-pattern and string to replace, seperated by a slash. Internally the "new" state is always in the format getG<group>:<state-value>. The substitution is done every time,
|
||
a new object is received. You can use this function for converting, adding units, having more fun with icons, ...
|
||
This function has only an impact on the content of state - no other functions are disturbed. It is executed directly after replacing the reading-names and setting the formats, but before stateCmd
|
||
<p>Example:</p>
|
||
<pre>
|
||
define myLamp KNX 0/1/1:dpt1 0/1/2:dpt1 0/1/2:dpt1
|
||
attr myLamp stateRegex /getG1:/steuern:/ /getG2:/status:/ /getG3:/sperre:/ /setG[13]:/steuern:/ /setG[3]://
|
||
attr myLamp devStateIcon status.on:general_an status.off:general_aus sperre.on:lock steuern.*:hourglass
|
||
</pre>
|
||
</ul>
|
||
|
||
<p><a name="KNXstateCmd"></a> <b>stateCmd</b></p>
|
||
<ul>
|
||
You can supply a perl-command for modifying state. This command is executed directly before updating the reading - so after renaming, format and regex.
|
||
Please supply a valid perl command like using the attribute stateFormat.
|
||
Unlike stateFormat the stateCmd modifies also the content of the reading, not only the hash-conten for visualization.
|
||
<p>Example:</p>
|
||
<pre>
|
||
define myLamp KNX 0/1/1:dpt1 0/1/2:dpt1 0/1/2:dpt1
|
||
attr myLamp stateCmd {$state = sprintf("%s", ReadingsVal($name,"getG2","undef"))}
|
||
</pre>
|
||
</ul>
|
||
|
||
<p><a name="KNXanswerReading"></a> <b>answerReading</b></p>
|
||
<ul>
|
||
If enabled, FHEM answers on read requests. The content of state is send to the bus as answer.
|
||
<p>If set to 1, read-requests are answered</p>
|
||
</ul>
|
||
|
||
<p><a name="KNXlistenonly"></a> <b>listenonly</b></p>
|
||
<ul>
|
||
If set to 1, the device may not send any messages. As well answering requests although get is prohibited.
|
||
</ul>
|
||
|
||
<p><a name="KNXreadonly"></a> <b>readonly</b></p>
|
||
<ul>
|
||
If set to 1, the device may not send any messages. Answering requests are prohibited.Get is allowed.
|
||
</ul>
|
||
|
||
<p><a name="KNXslider"></a> <b>slider</b></p>
|
||
<ul>
|
||
slider <min>,<step>,<max><br>
|
||
With this attribute you can add a slider to any device.
|
||
<p>Example:</p>
|
||
<pre>
|
||
define myDimmer KNX 0/1/1:dpt5
|
||
attr myDimmer slider 0,1,100
|
||
attr myDimmer webCmd value
|
||
</pre>
|
||
</ul>
|
||
|
||
<p><a name="KNXdpt"></a> <b>DPT - datapoint-types</b></p>
|
||
<ul>
|
||
<p>The following dpt are implemented and have to be assigned within the device definition.</p>
|
||
dpt1 on, off<br>
|
||
dpt1.001 on, off<br>
|
||
dpt1.002 true, false<br>
|
||
dpt1.003 enable, disable<br>
|
||
dpt1.008 up, down<br>
|
||
dpt1.009 open, closed<br>
|
||
dpt1.019 closed, open<br>
|
||
dpt3 -100..+100<br>
|
||
dpt5 0..255<br>
|
||
dpt5.001 0..100 %<br>
|
||
dpt5.003 0..360 °<br>
|
||
dpt5.004 0..255 %<br>
|
||
dpt6 -127..+127<br>
|
||
dpt6.001 0..100 %<br>
|
||
dpt7 0..65535<br>
|
||
dpt7.005 0..65535 s<br>
|
||
dpt7.005 0..65535 m<br>
|
||
dpt7.013 0..65535 lux<br>
|
||
dpt8 -32768..32768<br>
|
||
dpt8.005 -32768..32768 s<br>
|
||
dpt8.010 -32768..32768 %<br>
|
||
dpt8.011 -32768..32768 °<br>
|
||
dpt9 -670760.0..+670760.0<br>
|
||
dpt9.001 -670760.0..+670760.0 °<br>
|
||
dpt9.004 -670760.0..+670760.0 lux<br>
|
||
dpt9.006 -670760.0..+670760.0 Pa<br>
|
||
dpt9.005 -670760.0..+670760.0 m/s<br>
|
||
dpt9.009 -670760.0..+670760.0 m<>/h<br>
|
||
dpt9.010 -670760.0..+670760.0 s<br>
|
||
dpt9.021 -670760.0..+670760.0 mA<br>
|
||
dpt9.024 -670760.0..+670760.0 kW<br>
|
||
dpt9.025 -670760.0..+670760.0 l/h<br>
|
||
dpt9.026 -670760.0..+670760.0 l/h<br>
|
||
dpt9.028 -670760.0..+670760.0 km/h<br>
|
||
dpt10 01:00:00<br>
|
||
dpt11 01.01.2000<br>
|
||
dpt12 0..+Inf<br>
|
||
dpt13 -Inf..+Inf<br>
|
||
dpt13.010 -Inf..+Inf Wh<br>
|
||
dpt13.013 -Inf..+Inf kWh<br>
|
||
dpt14 -Inf.0..+Inf.0<br>
|
||
dpt14.019 -Inf.0..+Inf.0 A<br>
|
||
dpt14.027 -Inf.0..+Inf.0 V<br>
|
||
dpt14.056 -Inf.0..+Inf.0 W<br>
|
||
dpt14.068 -Inf.0..+Inf.0 °C;<br>
|
||
dpt14.076 -Inf.0..+Inf.0 m³<br>
|
||
dpt16 String<br>
|
||
</ul>
|
||
</ul>
|
||
=end html
|
||
|
||
=begin html_DE
|
||
|
||
<a name="KNX"></a>
|
||
<h3>KNX</h3>
|
||
<ul>
|
||
<p>KNX ist ein Standard zur Haus- und Gebäudeautomatisierung.
|
||
Der Standard begründet sich hauptsächlich auf twisted pair, findet aber auch zunehmende Verbreitung auf andere Medien (Funk, Ethernet, ...)</p>
|
||
Für Anfänger sei folgende Lektüre empfohlen: <a href="http://www.knx.org/media/docs/Flyers/KNX-Basics/KNX-Basics_de.pdf">KNX-Basics</a>
|
||
|
||
<p>Das Modul <a href="#TUL">TUL</a> stellt die Verbindung zum Bus her, Das KNX-Modul stellt die Verbindung zu den einzelnen KNX-/EIB-Geräten her. Das Modul stellt Befehle (on, off, on-until, on-for-timer)
|
||
zum ein- und Ausschalten von Geräten zur Verfügung. Für numerische DPT nutzt bitte value (set <devname> value <177.45>). Für string-DPT nutzt bitte string
|
||
(set <devname> string <Hello World>). Für andere, undefinierte DPT könnt Ihr raw hex Werte ans Netzwerk senden (set <devname> raw <hexval>).<br>
|
||
Komplexe Konfigurationen können aufgebaut werden, indem mehrere Modulinstanzen in einem Gerät definiert werden. Dafür werden mehrere Kombinationen aus GAD und DPT in einem Gerät definiert werden.</p>
|
||
|
||
<p>Der KNX-Standard stellt eine Reihe vordefinierter Datentypen zur Verfügung. Dies sichert die Herstellerübergreifende Kompatibilität.
|
||
Basierend auf diesen DPT wird der Status eines Gerätes interpretiert und in FHEM angezeigt. Für jedes empfangene Telegramm wird ein reading mit state, getG<group> und der Absenderadresse angelegt.
|
||
Für jedes ser-command wird ein Reading mit state und setG<group> angelegt.</p>
|
||
|
||
<p><a name="KNXdefine"></a> <b>Define</b></p>
|
||
<ul>
|
||
<code>define <name> KNX <group>:<DPT>:<[;readingName]> [<group>:<DPT> ..] [IODev]</code>
|
||
|
||
<p>Ein KNX-device benötigt einen konkreten DPT. Bitte schaut die verfügbaren DPT unter <a href="#KNXdpt">Available DPT</a> nach. Wird kein korrekter DPT angegeben, kann das system die Nachrichten nicht korrekt de- / codieren.
|
||
Weiterhin kann bei der Gerätedefinition eine IO-Schnittstelle angegeben werden. Dies kann später ebenfalls per Attribut erfolgen.</p>
|
||
|
||
<p>Jedes Device muss an eine <a href="#TUL">TUL</a> gebunden sein. Die <group> Parameter werden entweder als Gruppenadresse (0-15/0-15/0-255) oder als Hex-notation angegeben (0-f0-f0-ff).
|
||
Alle definierten Gruppen können für die Buskommunikation verwendet werden. Ohne weitere Attribute, werden alle eingehenden Nachrichten in state übersetzt.
|
||
Per default wird über die erste Gruppe gesendet.<br>
|
||
Wenn Ihr einen readingNamen angebt, wird dieser als Basis f<>r die Readings benutzt (z.B. hugo-set, hugo-get for name hugo).<br>
|
||
Wollt Ihr über eine andere Gruppe senden. müsst Ihr diese indizieren (set <devname> value <17.0> <g2>).</p>
|
||
|
||
<p>Das Modul <a href="#autocreate">autocreate</a> generiert eine Instanz für jede unbekannte Gruppenadresse. Das Gerät selbst wird jedoch NICHT korrekt funktionieren, so lange noch kein korrekter
|
||
DPT angelegt ist. Der Name ist immer KNX_nnmmooo wobei nn die Linie ist, mm der Bereich und ooo die Ger<65>teadresse.</p>
|
||
|
||
<p>Example:</p>
|
||
<pre>
|
||
define lamp1 KNX 0/10/12:dpt1
|
||
define lamp1 KNX 0/10/12:dpt1:meinName 0/0/5:dpt1.001
|
||
define lamp1 KNX 0A0C:dpt1.003 myTul
|
||
</pre>
|
||
|
||
Ein Hinweis bez<65>glich dem bin<69>ren Datentyp dpt1: alle Untertypen m<>ssen <20>ber das Schl<68>sselwort value gesetzt werden. Empfangene Telegramme werden entsprechend ihrer Definition automatisch
|
||
umbenannt. Die zur Verf<72>gung stehenden on/off Schaltfl<66>chen ohne den Schl<68>ssel value sind ein absoluter Sonderfall und gelten nur f<>r den dpt1 selbst (nicht die Untertypen).
|
||
|
||
<p>Example:</p>
|
||
<pre>
|
||
define rollo KNX 0/10/12:dpt1.008
|
||
set rollo value up
|
||
set rollo value down
|
||
</pre>
|
||
|
||
</ul>
|
||
|
||
<p><a name="KNXset"></a> <b>Set</b></p>
|
||
<ul>
|
||
<code>set <name> <on, off></code> [g<groupnr>]
|
||
<code>set <name> <on-for-timer, on-until> <time> [g<groupnr>]</code>
|
||
<code>set <name> <value> [g<groupnr>]</code>
|
||
<code>set <name> <string> [g<groupnr>]</code>
|
||
<code>set <name> <raw> [g<groupnr>]</code>
|
||
|
||
<p>Example:</p>
|
||
<pre>
|
||
set lamp1 on
|
||
set lamp1 off
|
||
set lamp1 on-for-timer 10
|
||
set lamp1 on-until 13:15:00
|
||
set foobar raw 234578
|
||
set thermo value 23.44
|
||
set message value Hallo Welt
|
||
</pre>
|
||
|
||
<p>Wenn eine Gruppe angegeben wurde (g<groupnr>) wird das Telegramm an de indizierte Gruppe gesendet (start bei 1, wie in der Definition angegeben).</p>
|
||
<pre>
|
||
define lamp1 KNX 0/10/01:dpt1 0/10/02:dpt1
|
||
set lamp1 on g2 (will send "on" to 0/10/02)
|
||
</pre>
|
||
|
||
<p>Ein Dimmer mit Slider:</p>
|
||
<pre>
|
||
define dim1 KNX 0/0/5:dpt5.001
|
||
attr dim1 slider 0,1,100
|
||
attr dim1 webCmd value
|
||
</pre>
|
||
|
||
<p>Aktuelle Uhrzeit / Datum können wie folgt auf den Bus gelegt werden:</p>
|
||
<pre>
|
||
define timedev KNX 0/0/7:dpt10
|
||
attr timedev eventMap /value now:now/
|
||
attr timedev webCmd now
|
||
|
||
define datedev KNX 0/0/8:dpt11
|
||
attr datedev eventMap /value now:now/
|
||
attr datedev webCmd now
|
||
|
||
# send every midnight the new date
|
||
define dateset at *00:00:00 set datedev value now
|
||
|
||
# send every hour the current time
|
||
define timeset at +*01:00:00 set timedev value now
|
||
</pre>
|
||
</ul>
|
||
|
||
<p><a name="KNXget"></a> <b>Get</b></p>
|
||
<ul>
|
||
<p>Bei jeder Ausführung wird eine Leseanfrage an die entsprechende Gruppe geschickt. Die Gruppe muss in der Lage sein, auf diese Anfrage zu antworten (dies ist nicht immer der Fall).<br>
|
||
Die Antwort der Gruppe wird nicht im FHEMWEB angezeigt. Das empfangene Telegramm wird (wie jedes andere) ausgewertet.</p>
|
||
</ul>
|
||
|
||
<p><a name="KNXattr"></a> <b>Attributes</b></p>
|
||
<ul><br>
|
||
Common attributes:<br>
|
||
<a href="#DbLogInclude">DbLogInclude</a><br>
|
||
<a href="#DbLogExclude">DbLogExclude</a><br>
|
||
<a href="#IODev">IODev</a><br>
|
||
<a href="#alias">alias</a><br>
|
||
<a href="#comment">comment</a><br>
|
||
<a href="#devStateIcon">devStateIcon</a><br>
|
||
<a href="#devStateStyle">devStateStyle</a><br>
|
||
<a href="#do_not_notify">do_not_notify</a><br>
|
||
<a href="#readingFnAttributes">readingFnAttributes</a><br>
|
||
<a href="#event-aggregator">event-aggregator</a><br>
|
||
<a href="#event-min-interval">event-min-interval</a><br>
|
||
<a href="#event-on-change-reading">event-on-change-reading</a><br>
|
||
<a href="#event-on-update-reading">event-on-update-reading</a><br>
|
||
<a href="#eventMap">eventMap</a><br>
|
||
<a href="#group">group</a><br>
|
||
<a href="#icon">icon</a><br>
|
||
<a href="#room">room</a><br>
|
||
<a href="#showtime">showtime</a><br>
|
||
<a href="#sortby">sortby</a><br>
|
||
<a href="#stateFormat">stateFormat</a><br>
|
||
<a href="#userReadings">userReadings</a><br>
|
||
<a href="#userattr">userattr</a><br>
|
||
<a href="#verbose">verbose</a><br>
|
||
<a href="#webCmd">webCmd</a><br>
|
||
<a href="#widgetOverride">widgetOverride</a><br>
|
||
<br>
|
||
</ul>
|
||
|
||
<p><a name="KNXformat"></a> <b>format</b></p>
|
||
<ul>
|
||
Der Inhalt dieses Attributes wird bei jedem empfangenen Wert angehangen, bevor der Wert in state kopeiert wird.
|
||
<p>Example:</p>
|
||
<pre>
|
||
define myTemperature KNX 0/1/1:dpt5
|
||
attr myTemperature format °C;
|
||
</pre>
|
||
</ul>
|
||
|
||
<p><a name="KNXstateRegex"></a> <b>stateRegex</b></p>
|
||
<ul>
|
||
Es kann eine Reihe an Search/Replace Patterns übergeben werden (getrennt durch einen Slash). Intern wird der neue Wert von state immer im Format getG<group>:<state-value>. abgebildet.
|
||
Die Ersetzungen werden bei bei jedem neuen Telegramm vorgenommen. Ihr könnt die Funktion für Konvertierungen nutzen, Einheiten hinzufügen, Spa<70> mit Icons haben, ...
|
||
Diese Funktion wirkt nur auf den Inhalt von State - sonst wird nichts beeinflusst.
|
||
Die Funktion wird direkt nach dem Ersetzen der Readings-Namen und dem erg<72>nzen der Formate ausgef<65>hrt.
|
||
<p>Example:</p>
|
||
<pre>
|
||
define myLamp KNX 0/1/1:dpt1 0/1/2:dpt1 0/1/2:dpt1
|
||
attr myLamp stateRegex /getG1:/steuern:/ /getG2:/status:/ /getG3:/sperre:/ /setG[13]:/steuern:/ /setG[3]://
|
||
attr myLamp devStateIcon status.on:general_an status.off:general_aus sperre.on:lock steuern.*:hourglass
|
||
</pre>
|
||
</ul>
|
||
|
||
<p><a name="KNXstateCmd"></a> <b>stateCmd</b></p>
|
||
<ul>
|
||
Hier k<>nnt Ihr ein perl-Kommando angeben, welches state beeinflusst. Die Funktion wird unmittelbar vor dem Update des Readings aufgerufen - also nach dem Umbennenen der Readings, format und regex.
|
||
Es ist ein g<>ltiges Perl-Kommando anzugeben (vgl. stateFormat). Im Gegensatz zu StateFormat wirkt sich dieses Attribut inhaltlich auf das Reading aus, und nicht "nur" auf die Anzeige im FHEMWEB.
|
||
<p>Beispiel:</p>
|
||
<pre>
|
||
define myLamp KNX 0/1/1:dpt1 0/1/2:dpt1 0/1/2:dpt1
|
||
attr myLamp stateCmd {$state = sprintf("%s", ReadingsVal($name,"getG2","undef"))}
|
||
</pre>
|
||
</ul>
|
||
|
||
<p><a name="KNXanswerReading"></a> <b>answerReading</b></p>
|
||
<ul>
|
||
Wenn aktiviert, antwortet FHEM auf Leseanfragen. Der Inhalt von state wird auf den Bus gelegt.
|
||
<p>Leseanfragen werden beantwortet, wenn der Wert auf 1 gesetzt ist.</p>
|
||
</ul>
|
||
|
||
<p><a name="KNXlistenonly"></a> <b>listenonly</b></p>
|
||
<ul>
|
||
Wenn auf 1 gesetzt, kann das Gerät keine Nachrichten senden. Sowohl Leseanfragen als auch get sind verboten.
|
||
</ul>
|
||
|
||
<p><a name="KNXreadonly"></a> <b>readonly</b></p>
|
||
<ul>
|
||
Wenn auf 1 gesetzt, kann das Gerät keine Nachrichten senden. Leseanfragen sind verboten. Get ist erlaubt.
|
||
</ul>
|
||
|
||
<p><a name="KNXslider"></a> <b>slider</b></p>
|
||
<ul>
|
||
slider <min>,<step>,<max><br>
|
||
Mit diesem Attribut könnt Ihr jedem Gerät einen Slider verpassen.
|
||
<p>Example:</p>
|
||
<pre>
|
||
define myDimmer KNX 0/1/1:dpt5
|
||
attr myDimmer slider 0,1,100
|
||
attr myDimmer webCmd value
|
||
</pre>
|
||
</ul>
|
||
|
||
<p><a name="KNXdpt"></a> <b>DPT - datapoint-types</b></p>
|
||
<ul>
|
||
<p>Die folgenden DPT sind implementiert und müssen in der Gruppendefinition angegeben werden.</p>
|
||
dpt1 on, off<br>
|
||
dpt1.001 on, off<br>
|
||
dpt1.002 true, false<br>
|
||
dpt1.003 enable, disable<br>
|
||
dpt1.008 up, down<br>
|
||
dpt1.009 open, closed<br>
|
||
dpt1.019 closed, open<br>
|
||
dpt3 -100..+100<br>
|
||
dpt5 0..255<br>
|
||
dpt5.001 0..100 %<br>
|
||
dpt5.003 0..360 °<br>
|
||
dpt5.004 0..255 %<br>
|
||
dpt6 -127..+127<br>
|
||
dpt6.001 0..100 %<br>
|
||
dpt7 0..65535<br>
|
||
dpt7.005 0..65535 s<br>
|
||
dpt7.006 0..65535 m<br>
|
||
dpt7.013 0..65535 lux<br>
|
||
dpt8 -32768..32768<br>
|
||
dpt8.005 -32768..32768 s<br>
|
||
dpt8.010 -32768..32768 %<br>
|
||
dpt8.011 -32768..32768 °<br>
|
||
dpt9 -670760.0..+670760.0<br>
|
||
dpt9.001 -670760.0..+670760.0 °<br>
|
||
dpt9.004 -670760.0..+670760.0 lux<br>
|
||
dpt9.006 -670760.0..+670760.0 Pa<br>
|
||
dpt9.005 -670760.0..+670760.0 m/s<br>
|
||
dpt9.009 -670760.0..+670760.0 m<>/h<br>
|
||
dpt9.010 -670760.0..+670760.0 s<br>
|
||
dpt9.021 -670760.0..+670760.0 mA<br>
|
||
dpt9.024 -670760.0..+670760.0 kW<br>
|
||
dpt9.025 -670760.0..+670760.0 l/h<br>
|
||
dpt9.026 -670760.0..+670760.0 l/h<br>
|
||
dpt9.028 -670760.0..+670760.0 km/h<br>
|
||
dpt10 01:00:00<br>
|
||
dpt11 01.01.2000<br>
|
||
dpt12 0..+Inf<br>
|
||
dpt13 -Inf..+Inf<br>
|
||
dpt13.010 -Inf..+Inf Wh<br>
|
||
dpt13.013 -Inf..+Inf kWh<br>
|
||
dpt14 -Inf.0..+Inf.0<br>
|
||
dpt14.019 -Inf.0..+Inf.0 A<br>
|
||
dpt14.027 -Inf.0..+Inf.0 V<br>
|
||
dpt14.056 -Inf.0..+Inf.0 W<br>
|
||
dpt14.068 -Inf.0..+Inf.0 °C;<br>
|
||
dpt14.076 -Inf.0..+Inf.0 m³<br>
|
||
dpt16 String<br>
|
||
</ul>
|
||
</ul>
|
||
=end html_DE
|
||
|
||
=cut
|