mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-05-04 22:19:38 +00:00

git-svn-id: https://svn.fhem.de/fhem/trunk/fhem@24888 2b470e98-0d58-463d-a4d8-8e2adae1ed80
2239 lines
94 KiB
Perl
2239 lines
94 KiB
Perl
##############################################
|
|
# $Id$
|
|
# ABU 20180218 restructuring, removed older documentation
|
|
# ABU 20180317 setExtensions reingebaut, set funktion
|
|
# ABU 20180319 repaired "reply"-function
|
|
# ABU 20180319 tuned "reply"-function
|
|
# ABU 20180322 switch context for put-cmd, minor fixes
|
|
# ABU 20180328 fixed get-name containing "-"
|
|
# ABU 20180408 Added attriut screening, implemented set/get/listenonly, prevent to identical GADS in one device
|
|
# ABU 20180411 Added timer functions, prevented two identical GAD in one device
|
|
# ABU 20180413 Fixed some naming issues in defined, made en-doku, removed DE-Doku
|
|
# ABU 20180416 corrected timedev in doku
|
|
# ABU 20180418 removed spam-log in "get"; replaced "$value" by "undef" in encode and decode function if model not defined
|
|
# ABU 20180419 fixed Doku, added nosuffix, added dpt1.000
|
|
# ABU 20180426 minor fixes in answering bus-requests
|
|
# ABU 20180509 Added dpt14.033
|
|
# ABU 20180519 Added dpt17.001, adjustet $PAT_GAD_OPTIONS with boundaries and whitespace
|
|
# ABU 20180523 Added dpt7.007
|
|
# ABU 20180528 Patched dpt1 in dpt-list and encodyByDpt for being backward-compatible
|
|
# ABU 20180604 Set dpt17-offset to "0", added examples
|
|
# ABU 20180605 Added dpt18, tuned doku
|
|
# ABU 20180605 Corrected dpt18
|
|
# ABU 20180605 Added example for autogenerated devices
|
|
# ABU 20180605 Added workaround for STATE
|
|
# ABU 20180606 Fixed dpt18, fixed offset-addition in decode (was "-" instead of "+"), fixed issue with slider
|
|
# ABU 20180607 seperated limit and scale from encode/decode in order to avoid warnings and clean up
|
|
# ABU 20180613 fixed scaling algo
|
|
# ABU 20180613 fixed scaling algo part 2
|
|
# ABU 20180624 no set-option for listenoly- or get-devices, warning for illegal EVAL
|
|
# ABU 20180626 fixed last changes
|
|
# ABU 20180706 changed eval, removed stateCopy
|
|
# ABU 20180706 fixed doku: changed readonly in listenonly
|
|
# ABU 20180815 updated link in doku, changed (dpt16$) to dpt16 in set, tried to fix last-sender (replaced bulk by single in decoding loop)
|
|
# ABU 20180829 added dpt9.0020, tried workaround in putCmd, remove non printable chars
|
|
# ABU 20180925 added dpt3.007, added last-sender "fhem"
|
|
# ABU 20180926 fixed KNX_Eval in line 1291 (replaced hash by deviceHash), fixed decoding dpt3
|
|
# ABU 20181007 fixed dpt19
|
|
# HAUSWART 20201112 implemented DPT20.102 #91462, KNX_parse set & get #115122, corrected dpt1 / dpt1.001 #112538
|
|
# HAUSWART 20201113 fixed dpt19 #91650, KNX_hexToName2
|
|
# MH 20201122 reworked most of dpt1, added dpt6.010, reworked dpt19, fixed (hopefully) putCmd, corrcetions to docu
|
|
# MH 20201202 dpt10 compatibility with widgetoverride :time, docu formatting
|
|
# MH 20201207 improve code (PerlBestPractices) changes marked with #PBP, added x-flag to most of regex, fixed dpt16
|
|
# MH 20201210 add docu example for dpt16, fix docu indent.
|
|
# MH 20201223 add Evolution-version string, add dpt2.000 (JoeALLb), correction to "unknow argument..."
|
|
# new attr disable, simplify set-cmd logic, removed 'use SetExtensions', rework DbLogsplit logic
|
|
# MH 20210110 E04.20 rework / simplify set and define subs. No functional changes, i hope...
|
|
# PBP /perlcritic: now down to 12 Lines (from original 425) on package main Level 3,
|
|
# most of them 'cascading if-elsif chain' or 'high complexity score's.
|
|
# Still one severity 5, don't know how to fix that one.
|
|
# MH 20210210 E04.40 reworked dpt3 en- de-code, added disable also for KNX_parse,
|
|
# reworked set & parse -> new sub KNX_SetReading
|
|
# fix dpt16 empty string / full string length
|
|
# autocreate: new devices will be default disabled during autocreate! - see cmdref
|
|
# the Log msg "Unknown code xxxxx please help me" cannot be suppressed, would require a change in TUL/KNXTUL Module
|
|
# "set xxx (on|off)-until hh:mm" now works like "at xxx on-till-overnight hh:mm"
|
|
# fixed toggle (current value)
|
|
# additional PBP/perlcritic fixes
|
|
# fixed ugly bug when doing defmod or copy (defptr not cleared!)
|
|
# MH 20210211 E04.41 quick fix readingnames (gammatwin)
|
|
# MH 20210218 E04.42 cleanup, change dpts: 6,8,13 en-/de-code, fixed $PAT_DATE,
|
|
# readings: a write from bus updates always the "get" reading, indepedend of option set !!!
|
|
# add KNX_toggle Attr & docu
|
|
# MH 20210225 E04.43 fix autocreate- unknown code..., defptr
|
|
# cmdref: changed <a name= to <a id=
|
|
# MH 20210515 E04.60 fix IsDisabled when state = inactive
|
|
# cleanup, replaced ok-dialog on get-cmd by "err-msg"
|
|
# docu correction
|
|
# fixed KNX_replaceByRegex
|
|
# replace eval by AnalyzePerlCommand
|
|
# added FingerPrintFn, fix DbLog_split
|
|
# MH 20210521 E04.62 DbLog_split replace regex by looks_like_number
|
|
# fix readingsnames "gx:...:nosuffix"
|
|
# MH 202105xx E04.65 own Package FHEM:KNX
|
|
# MH 20210803 E04.66 remove FingerPrintFn
|
|
# new dpt7.600, dpt9.029,dpt9.030
|
|
# MH 20210818 E04.67 1st checkin SVN version
|
|
# docu correction
|
|
# MH 20210829 fix crash when using Attr KNX_toogle (related to package)
|
|
|
|
|
|
#04.65 package main;
|
|
package FHEM::KNX; ## no critic 'package' #04.65
|
|
|
|
use strict;
|
|
use warnings;
|
|
use Encode;
|
|
use Time::HiRes qw(gettimeofday); #04.65 nxt 3 lines
|
|
use Scalar::Util qw(looks_like_number);
|
|
use GPUtils qw(GP_Import GP_Export); # Package Helper Fn
|
|
|
|
### perlcritic parameters
|
|
# these ones are NOT used! (constants,Policy::Modules::RequireFilenameMatchesPackage,Modules::RequireVersionVar,NamingConventions::Capitalization)
|
|
### the following percritic items will be ignored global ###
|
|
## no critic (ValuesAndExpressions::RequireNumberSeparators,ValuesAndExpressions::ProhibitMagicNumbers)
|
|
## no critic (RegularExpressions::RequireDotMatchAnything,RegularExpressions::RequireLineBoundaryMatching)
|
|
## no critic (ControlStructures::ProhibitPostfixControls)
|
|
## no critic (ControlStructures::ProhibitCascadingIfElse)
|
|
## no critic (Documentation::RequirePodSections)
|
|
|
|
### import FHEM functions / global vars
|
|
### run before package compilation
|
|
BEGIN {
|
|
# Import from main context
|
|
GP_Import(
|
|
qw(readingsSingleUpdate readingsBulkUpdate readingsBulkUpdateIfChanged readingsBeginUpdate readingsEndUpdate
|
|
Log3
|
|
AttrVal ReadingsVal ReadingsNum
|
|
addToDevAttrList
|
|
AssignIoPort IOWrite
|
|
CommandDefine CommandDelete CommandModify
|
|
defs modules attr
|
|
FW_detail FW_wname FW_directNotify
|
|
readingFnAttributes
|
|
InternalTimer RemoveInternalTimer
|
|
init_done
|
|
IsDisabled IsDummy IsDevice
|
|
deviceEvents devspec2array
|
|
AnalyzePerlCommand EvalSpecials
|
|
fhemTimeLocal)
|
|
);
|
|
}
|
|
|
|
### export to main context (with different name)
|
|
GP_Export(qw(Initialize));
|
|
|
|
### MH Evolution Version string
|
|
my $Eversion = '04.67 18-08-2021';
|
|
|
|
#string constants
|
|
my $modelErr = "MODEL_NOT_DEFINED"; # for autocreate
|
|
|
|
my $ONFORTIMER = "on-for-timer";
|
|
my $OFFFORTIMER = "off-for-timer";
|
|
my $ONUNTIL = "on-until";
|
|
my $OFFUNTIL = "off-until";
|
|
my $TOGGLE = "toggle";
|
|
my $RAW = "raw";
|
|
my $RGB = "rgb";
|
|
my $STRING = "string";
|
|
my $VALUE = "value";
|
|
|
|
my $TULid = 'C'; #identifier for TUL - extended adressing
|
|
|
|
#regex patterns
|
|
#pattern for group-adress
|
|
my $PAT_GAD = '(?:3[01]|([012])?[0-9])\/(?:1[0-5]|[0-9])\/(?:2[0-4][0-9]|25[0-5]|([01])?[0-9]{1,2})'; # 0-31/0-15/0-255
|
|
#pattern for group-adress in hex-format
|
|
my $PAT_GAD_HEX = '[01][0-9a-f]{4}'; # max is 1FFFF -> 31/15/255
|
|
#pattern for group-no
|
|
my $PAT_GNO = '[gG][1-9][0-9]?';
|
|
#pattern for GAD-Options
|
|
my $PAT_GAD_OPTIONS = '(get|set|listenonly)';
|
|
#pattern for GAD-suffixes
|
|
my $PAT_GAD_SUFFIX = 'nosuffix';
|
|
#pattern for forbidden GAD-Names
|
|
my $PAT_GAD_NONAME = '^(on|off|value|raw|' . $PAT_GAD_OPTIONS . q{|} . $PAT_GAD_SUFFIX . ')';
|
|
#pattern for DPT
|
|
my $PAT_GAD_DPT = 'dpt\d*\.?\d*';
|
|
#pattern for dpt1 (standard)
|
|
my $PAT_DPT1_PAT = '(on)|(off)|(0?1)|(0?0)';
|
|
#pattern for date
|
|
my $PAT_DTSEP = qr/(?:_)/ix; # date/time separator
|
|
my $PAT_DATE = qr/(3[01]|[0-2]?[0-9])\.(1[0-2]|0?[0-9])\.((?:19|20)[0-9][0-9])/ix;
|
|
#pattern for time
|
|
my $PAT_TIME = qr/(2[0-4]|[0?1][0-9]):([0?1-5][0-9]):([0?1-5][0-9])/ix;
|
|
my $PAT_DPT16_CLR = qr/>CLR</ix;
|
|
|
|
my $UNDEF = undef; #PBP avoid Level5 warnings
|
|
|
|
#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
|
|
#if supplied, setlist is passed directly to fhemweb in order to show comand-buttons in the details-view (e.g. "colorpicker" or "item1,item2,item3")
|
|
#if setlist is not supplied and min/max are given, a slider is shown for numeric values. Otherwise min/max value are shown in a list
|
|
my %dpttypes = (
|
|
#Binary value
|
|
"dpt1" => {CODE=>"dpt1", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/($PAT_DPT1_PAT)/ix, MIN=>"off", MAX=>"on"},
|
|
"dpt1.000" => {CODE=>"dpt1", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/($PAT_DPT1_PAT)/ix, MIN=>"0", MAX=>"1"},
|
|
"dpt1.001" => {CODE=>"dpt1", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/($PAT_DPT1_PAT)/ix, MIN=>"off", MAX=>"on"},
|
|
"dpt1.002" => {CODE=>"dpt1", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/($PAT_DPT1_PAT|(true)|(false))/ix, MIN=>"false", MAX=>"true"},
|
|
"dpt1.003" => {CODE=>"dpt1", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/($PAT_DPT1_PAT|(enable)|(disable))/ix, MIN=>"disable", MAX=>"enable"},
|
|
"dpt1.004" => {CODE=>"dpt1", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/($PAT_DPT1_PAT|(no_ramp)|(ramp))/ix, MIN=>"no_ramp", MAX=>"ramp"},
|
|
"dpt1.005" => {CODE=>"dpt1", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/($PAT_DPT1_PAT|(no_alarm)|(alarm))/ix, MIN=>"no_alarm", MAX=>"alarm"},
|
|
"dpt1.006" => {CODE=>"dpt1", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/($PAT_DPT1_PAT|(low)|(high))/ix, MIN=>"low", MAX=>"high"},
|
|
"dpt1.007" => {CODE=>"dpt1", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/($PAT_DPT1_PAT|(decrease)|(increase))/ix, MIN=>"decrease", MAX=>"increase"},
|
|
"dpt1.008" => {CODE=>"dpt1", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/($PAT_DPT1_PAT|(up)|(down))/ix, MIN=>"up", MAX=>"down"},
|
|
"dpt1.009" => {CODE=>"dpt1", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/($PAT_DPT1_PAT|(closed)|(open))/ix, MIN=>"open", MAX=>"closed"},
|
|
"dpt1.010" => {CODE=>"dpt1", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/($PAT_DPT1_PAT|(start)|(stop))/ix, MIN=>"stop", MAX=>"start"},
|
|
"dpt1.011" => {CODE=>"dpt1", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/($PAT_DPT1_PAT|(inactive)|(active))/ix, MIN=>"inactive", MAX=>"active"},
|
|
"dpt1.012" => {CODE=>"dpt1", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/($PAT_DPT1_PAT|(not_inverted)|(inverted))/ix, MIN=>"not_inverted", MAX=>"inverted"},
|
|
"dpt1.013" => {CODE=>"dpt1", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/($PAT_DPT1_PAT|(start_stop)|(cyclically))/ix, MIN=>"start_stop", MAX=>"cyclically"},
|
|
"dpt1.014" => {CODE=>"dpt1", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/($PAT_DPT1_PAT|(fixed)|(calculated))/ix, MIN=>"fixed", MAX=>"calculated"},
|
|
"dpt1.015" => {CODE=>"dpt1", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/($PAT_DPT1_PAT|(no_action)|(reset))/ix, MIN=>"no_action", MAX=>"reset"},
|
|
"dpt1.016" => {CODE=>"dpt1", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/($PAT_DPT1_PAT|(no_action)|(acknowledge))/ix, MIN=>"no_action", MAX=>"acknowledge"},
|
|
"dpt1.017" => {CODE=>"dpt1", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/($PAT_DPT1_PAT|(trigger)|(trigger))/ix, MIN=>"trigger", MAX=>"trigger"},
|
|
"dpt1.018" => {CODE=>"dpt1", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/($PAT_DPT1_PAT|(not_occupied)|(occupied))/ix, MIN=>"not_occupied", MAX=>"occupied"},
|
|
"dpt1.019" => {CODE=>"dpt1", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/($PAT_DPT1_PAT|(closed)|(open))/ix, MIN=>"closed", MAX=>"open"},
|
|
"dpt1.021" => {CODE=>"dpt1", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/($PAT_DPT1_PAT|(logical_or)|(logical_and))/ix, MIN=>"logical_or", MAX=>"logical_and"},
|
|
"dpt1.022" => {CODE=>"dpt1", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/($PAT_DPT1_PAT|(scene_A)|(scene_B))/ix, MIN=>"scene_A", MAX=>"scene_B"},
|
|
"dpt1.023" => {CODE=>"dpt1", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/($PAT_DPT1_PAT|(move_(up_down|and_step_mode)))/ix, MIN=>"move_up_down", MAX=>"move_and_step_mode"},
|
|
|
|
#Step value (two-bit)
|
|
"dpt2" => {CODE=>"dpt2", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/((on)|(off)|(forceon)|(forceoff))/ix, MIN=>undef, MAX=>undef, SETLIST=>"on,off,forceon,forceoff"},
|
|
"dpt2.000" => {CODE=>"dpt2", UNIT=>q{}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/(0?[0-3])/ix, MIN=>0, MAX=>3},
|
|
|
|
#Step value (four-bit)
|
|
"dpt3" => {CODE=>"dpt3", UNIT=>q{}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,3}/ix, MIN=>-100, MAX=>100},
|
|
"dpt3.007" => {CODE=>"dpt3", UNIT=>q{%}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,3}/ix, MIN=>-100, MAX=>100},
|
|
|
|
# 1-Octet unsigned value
|
|
"dpt5" => {CODE=>"dpt5", UNIT=>q{}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,3}/ix, MIN=>0, MAX=>255},
|
|
"dpt5.001" => {CODE=>"dpt5", UNIT=>q{%}, FACTOR=>100/255, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,3}/ix, MIN=>0, MAX=>100},
|
|
"dpt5.003" => {CODE=>"dpt5", UNIT=>q{°}, FACTOR=>360/255, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,3}/ix, MIN=>0, MAX=>360},
|
|
"dpt5.004" => {CODE=>"dpt5", UNIT=>q{%}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,3}/ix, MIN=>0, MAX=>255},
|
|
|
|
# 1-Octet signed value
|
|
"dpt6" => {CODE=>"dpt6", UNIT=>q{}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,3}/ix, MIN=>-127, MAX=>127},
|
|
"dpt6.001" => {CODE=>"dpt6", UNIT=>q{%}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,3}/ix, MIN=>0, MAX=>100},
|
|
"dpt6.010" => {CODE=>"dpt6", UNIT=>q{}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,3}/ix, MIN=>-127, MAX=>127},
|
|
|
|
# 2-Octet unsigned Value
|
|
"dpt7" => {CODE=>"dpt7", UNIT=>q{}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,5}/ix, MIN=>0, MAX=>65535},
|
|
"dpt7.001" => {CODE=>"dpt7", UNIT=>q{}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,5}/ix, MIN=>0, MAX=>65535},
|
|
"dpt7.005" => {CODE=>"dpt7", UNIT=>q{s}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,5}/ix, MIN=>0, MAX=>65535},
|
|
"dpt7.006" => {CODE=>"dpt7", UNIT=>q{m}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,5}/ix, MIN=>0, MAX=>65535},
|
|
"dpt7.007" => {CODE=>"dpt7", UNIT=>q{h}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,5}/ix, MIN=>0, MAX=>65535},
|
|
"dpt7.012" => {CODE=>"dpt7", UNIT=>q{mA}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,5}/ix, MIN=>0, MAX=>65535},
|
|
"dpt7.013" => {CODE=>"dpt7", UNIT=>q{lux}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,5}/ix, MIN=>0, MAX=>65535},
|
|
"dpt7.600" => {CODE=>"dpt7", UNIT=>q{K}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+]?\d{1,5}/ix, MIN=>0, MAX=>12000}, # 04.66 Farbtemperatur
|
|
|
|
# 2-Octet signed Value
|
|
"dpt8" => {CODE=>"dpt8", UNIT=>q{}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,5}/ix, MIN=>-32768, MAX=>32767},
|
|
"dpt8.005" => {CODE=>"dpt8", UNIT=>q{s}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,5}/ix, MIN=>-32768, MAX=>32767},
|
|
"dpt8.010" => {CODE=>"dpt8", UNIT=>q{%}, FACTOR=>0.01, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,5}/ix, MIN=>-327.68, MAX=>327.67}, #04.66 min/max
|
|
"dpt8.011" => {CODE=>"dpt8", UNIT=>q{°}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,5}/ix, MIN=>-32768, MAX=>32767},
|
|
|
|
# 2-Octet Float value
|
|
"dpt9" => {CODE=>"dpt9", UNIT=>q{}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[-+]?(?:\d*[\.\,])?\d+/ix, MIN=>-670760, MAX=>670760},
|
|
"dpt9.001" => {CODE=>"dpt9", UNIT=>q{°C}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[-+]?(?:\d*[\.\,])?\d+/ix, MIN=>-274, MAX=>670760},
|
|
"dpt9.002" => {CODE=>"dpt9", UNIT=>q{K}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[-+]?(?:\d*[\.\,])?\d+/ix, MIN=>-670760, MAX=>670760},
|
|
"dpt9.003" => {CODE=>"dpt9", UNIT=>q{K/h}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[-+]?(?:\d*[\.\,])?\d+/ix, MIN=>-670760, MAX=>670760},
|
|
"dpt9.004" => {CODE=>"dpt9", UNIT=>q{lux}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[-+]?(?:\d*[\.\,])?\d+/ix, MIN=>-670760, MAX=>670760},
|
|
"dpt9.005" => {CODE=>"dpt9", UNIT=>q{m/s}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[-+]?(?:\d*[\.\,])?\d+/ix, MIN=>-670760, MAX=>670760},
|
|
"dpt9.006" => {CODE=>"dpt9", UNIT=>q{Pa}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[-+]?(?:\d*[\.\,])?\d+/ix, MIN=>-670760, MAX=>670760},
|
|
"dpt9.007" => {CODE=>"dpt9", UNIT=>q{%}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[-+]?(?:\d*[\.\,])?\d+/ix, MIN=>-670760, MAX=>670760},
|
|
"dpt9.008" => {CODE=>"dpt9", UNIT=>q{ppm}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[-+]?(?:\d*[\.\,])?\d+/ix, MIN=>-670760, MAX=>670760},
|
|
"dpt9.009" => {CODE=>"dpt9", UNIT=>q{m³/h}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[-+]?(?:\d*[\.\,])?\d+/ix, MIN=>-670760, MAX=>670760},
|
|
"dpt9.010" => {CODE=>"dpt9", UNIT=>q{s}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[-+]?(?:\d*[\.\,])?\d+/ix, MIN=>-670760, MAX=>670760},
|
|
"dpt9.011" => {CODE=>"dpt9", UNIT=>q{ms}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[-+]?(?:\d*[\.\,])?\d+/ix, MIN=>-670760, MAX=>670760},
|
|
"dpt9.020" => {CODE=>"dpt9", UNIT=>q{mV}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[-+]?(?:\d*[\.\,])?\d+/ix, MIN=>-670760, MAX=>670760},
|
|
"dpt9.021" => {CODE=>"dpt9", UNIT=>q{mA}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[-+]?(?:\d*[\.\,])?\d+/ix, MIN=>-670760, MAX=>670760},
|
|
"dpt9.022" => {CODE=>"dpt9", UNIT=>q{W/m²}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[-+]?(?:\d*[\.\,])?\d+/ix, MIN=>-670760, MAX=>670760},
|
|
"dpt9.023" => {CODE=>"dpt9", UNIT=>q{K/%}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[-+]?(?:\d*[\.\,])?\d+/ix, MIN=>-670760, MAX=>670760},
|
|
"dpt9.024" => {CODE=>"dpt9", UNIT=>q{kW}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[-+]?(?:\d*[\.\,])?\d+/ix, MIN=>-670760, MAX=>670760},
|
|
"dpt9.025" => {CODE=>"dpt9", UNIT=>q{l/h}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[-+]?(?:\d*[\.\,])?\d+/ix, MIN=>-670760, MAX=>670760},
|
|
"dpt9.026" => {CODE=>"dpt9", UNIT=>q{l/h}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[-+]?(?:\d*[\.\,])?\d+/ix, MIN=>-670760, MAX=>670760},
|
|
"dpt9.028" => {CODE=>"dpt9", UNIT=>q{km/h}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[-+]?(?:\d*[\.\,])?\d+/ix, MIN=>-670760, MAX=>670760},
|
|
"dpt9.029" => {CODE=>"dpt9", UNIT=>q{g/m³}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[-+]?(?:\d*[\.\,])?\d+/ix, MIN=>-670760, MAX=>670760}, #04.66 Abs. Luftfeuchte
|
|
"dpt9.030" => {CODE=>"dpt9", UNIT=>q{μg/m³}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[-+]?(?:\d*[\.\,])?\d+/ix, MIN=>-670760, MAX=>670760}, #04.66 Dichte
|
|
|
|
# Time of Day
|
|
"dpt10" => {CODE=>"dpt10", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/($PAT_TIME|now)/ix, MIN=>undef, MAX=>undef},
|
|
|
|
# Date
|
|
"dpt11" => {CODE=>"dpt11", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/($PAT_DATE|now)/ix, MIN=>undef, MAX=>undef},
|
|
|
|
# 4-Octet unsigned value (handled as dpt7)
|
|
"dpt12" => {CODE=>"dpt12", UNIT=>q{}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,10}/ix, MIN=>0, MAX=>4294967295},
|
|
|
|
# 4-Octet Signed Value
|
|
"dpt13" => {CODE=>"dpt13", UNIT=>q{}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,10}/ix, MIN=>-2147483648, MAX=>2147483647},
|
|
"dpt13.010" => {CODE=>"dpt13", UNIT=>q{Wh}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,10}/ix, MIN=>-2147483648, MAX=>2147483647},
|
|
"dpt13.013" => {CODE=>"dpt13", UNIT=>q{kWh}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,10}/ix, MIN=>-2147483648, MAX=>2147483647},
|
|
|
|
# 4-Octet single precision float
|
|
"dpt14" => {CODE=>"dpt14", UNIT=>q{}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,40}[.,]?\d{1,4}/ix, MIN=>undef, MAX=>undef},
|
|
"dpt14.019" => {CODE=>"dpt14", UNIT=>q{A}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,40}[.,]?\d{1,4}/ix, MIN=>undef, MAX=>undef},
|
|
"dpt14.027" => {CODE=>"dpt14", UNIT=>q{V}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,40}[.,]?\d{1,4}/ix, MIN=>undef, MAX=>undef},
|
|
"dpt14.033" => {CODE=>"dpt14", UNIT=>q{Hz}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,40}[.,]?\d{1,4}/ix, MIN=>undef, MAX=>undef},
|
|
"dpt14.056" => {CODE=>"dpt14", UNIT=>q{W}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,40}[.,]?\d{1,4}/ix, MIN=>undef, MAX=>undef},
|
|
"dpt14.068" => {CODE=>"dpt14", UNIT=>q{°C}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,40}[.,]?\d{1,4}/ix, MIN=>undef, MAX=>undef},
|
|
"dpt14.076" => {CODE=>"dpt14", UNIT=>q{m³}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,40}[.,]?\d{1,4}/ix, MIN=>undef, MAX=>undef},
|
|
"dpt14.057" => {CODE=>"dpt14", UNIT=>q{cos Φ}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,40}[.,]?\d{1,4}/ix, MIN=>undef, MAX=>undef},
|
|
|
|
# 14-Octet String
|
|
"dpt16" => {CODE=>"dpt16", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/.{1,14}/ix, MIN=>undef, MAX=>undef, SETLIST=>"multiple,>CLR<"},
|
|
"dpt16.000" => {CODE=>"dpt16", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/.{1,14}/ix, MIN=>undef, MAX=>undef, SETLIST=>"multiple,>CLR<"},
|
|
"dpt16.001" => {CODE=>"dpt16", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/.{1,14}/ix, MIN=>undef, MAX=>undef, SETLIST=>"multiple,>CLR<"},
|
|
|
|
# Scene, 0-63
|
|
"dpt17.001" => {CODE=>"dpt5", UNIT=>q{}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,3}/ix, MIN=>0, MAX=>63},
|
|
|
|
# Scene, 1-64
|
|
"dpt18.001" => {CODE=>"dpt5", UNIT=>q{}, FACTOR=>1, OFFSET=>1, PATTERN=>qr/[+-]?\d{1,3}/ix, MIN=>1, MAX=>64},
|
|
|
|
#date and time
|
|
"dpt19" => {CODE=>"dpt19", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/($PAT_DATE$PAT_DTSEP$PAT_TIME|now)/ix, MIN=>undef, MAX=>undef},
|
|
"dpt19.001" => {CODE=>"dpt19", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/($PAT_DATE$PAT_DTSEP$PAT_TIME|now)/ix, MIN=>undef, MAX=>undef},
|
|
|
|
# HVAC mode, 1Byte
|
|
"dpt20.102" => {CODE=>"dpt20", UNIT=>q{}, FACTOR=>1, OFFSET=>0, PATTERN=>qr/((auto)|(comfort)|(standby)|(economy|night)|(protection|frost|heat))/ix, MIN=>undef, MAX=>undef, SETLIST=>"Auto,Comfort,Standby,Economy,Protection"}, ## no critic (RegularExpressions::ProhibitComplexRegexes)
|
|
|
|
# Color-Code
|
|
"dpt232" => {CODE=>"dpt232", UNIT=>q{}, FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/[0-9a-f]{6}/ix, MIN=>undef, MAX=>undef, SETLIST=>"colorpicker"}
|
|
);
|
|
|
|
#Init this device
|
|
#This declares the interface to fhem
|
|
#############################
|
|
#04.65 sub KNX_Initialize {
|
|
sub Initialize {
|
|
my $hash = shift // return;
|
|
|
|
$hash->{Match} = "^$TULid.*";
|
|
$hash->{DefFn} = \&KNX_Define;
|
|
$hash->{UndefFn} = \&KNX_Undef;
|
|
$hash->{SetFn} = \&KNX_Set;
|
|
$hash->{GetFn} = \&KNX_Get;
|
|
$hash->{StateFn} = \&KNX_State;
|
|
$hash->{ParseFn} = \&KNX_Parse;
|
|
$hash->{NotifyFn} = \&KNX_Notify;
|
|
$hash->{AttrFn} = \&KNX_Attr;
|
|
$hash->{DbLog_splitFn} = \&KNX_DbLog_split;
|
|
#04.66 $hash->{FingerprintFn} = \&KNX_FingerPrint;
|
|
|
|
$hash->{AttrList} = "IODev " . #tells the module the IO-Device to communicate with. Optionally set within definition.
|
|
"disable:1 " . #device disabled
|
|
"showtime:1,0 " . #shows time instead of received value in state
|
|
"answerReading:1,0 " . #allows FHEM to answer a read telegram
|
|
"stateRegex:textField-long " . #modifies state value
|
|
"stateCmd:textField-long " . #modify state value
|
|
"putCmd:textField-long " . #called when the KNX bus asks for a -put reading
|
|
"format " . #supplies post-string
|
|
"KNX_toggle:textField " . #toggle source <device>:<reading>
|
|
"listenonly:1,0 " . #DEPRECATED
|
|
"readonly:1,0 " . #DEPRECATED
|
|
"slider " . #DEPRECATED
|
|
"$readingFnAttributes "; #standard attributes
|
|
$hash->{noAutocreatedFilelog} = 1; # autocreate devices create no FileLog
|
|
$hash->{AutoCreate} = {"KNX_.*" => { ATTR => "disable:1"} }; # autocreate devices are disabled by default
|
|
return $UNDEF;
|
|
}
|
|
|
|
#Define this device
|
|
#Is called at every define
|
|
#############################
|
|
sub KNX_Define {
|
|
my $hash = shift // return;
|
|
my $def = shift;
|
|
#enable newline within define with \
|
|
my @a = split(/[ \t\n]+/x, $def);
|
|
#device name
|
|
my $name = $a[0];
|
|
|
|
$hash->{NAME} = $name;
|
|
$hash->{FVERSIONE} = $Eversion; ###MH Evolution version
|
|
$hash->{NOTIFYDEV} = "global,$name"; # limit notifies
|
|
|
|
Log3 ($name, 5, "KNX_define -enter: $name, attributes: " . join (", ", @a));
|
|
|
|
#too less arguments
|
|
return 'KNX_define: $name -wrong syntax "define <name> KNX <group:model[:GAD-name][:set|get|listenonly]> [<group:model[:GAD-name][:set|get|listenonly]>*] [<IODev>]" ' if (int(@a) < 3);
|
|
|
|
# check if the last arg matches any IO-Device - and assign it - else use the automatic mechanism
|
|
my @tulList = devspec2array('TYPE=(TUL|KNXTUL|KNXIO)',$hash);
|
|
foreach my $tuls (@tulList) {
|
|
if ($tuls eq $a[int(@a) - 1]) {
|
|
$attr{$name}{IODev} = pop(@a);
|
|
last;
|
|
}
|
|
}
|
|
AssignIoPort($hash); # AssignIoPort will take device from $attr{$name}{IODev} if defined
|
|
|
|
#reset
|
|
$hash->{GADDETAILS} = {};
|
|
$hash->{GADTABLE} = {};
|
|
|
|
#delete all defptr entries for this device (defmod & copy problem) bug is still in SVN version! 09-02-2021
|
|
KNX_delete_defptr($hash) if ($init_done); # verify with: {PrintHash($modules{KNX}{defptr},3) } on FHEM-cmdline
|
|
|
|
#create groups and models, iterate through all possible args
|
|
foreach my $i (2 .. $#a) {
|
|
my $gadCode = undef;
|
|
my $gadOption = undef;
|
|
my $gadNoSuffix = undef;
|
|
|
|
Log3 ($name, 5, "KNX_define: $name, argCtr $i, string: $a[$i]");
|
|
|
|
#G-nr
|
|
my $gadNo = $i - 1;
|
|
my $gadName = 'g' . $gadNo; # old syntax
|
|
|
|
my ($gad, $gadModel, @gadArgs) = split(/:/x, $a[$i]);
|
|
$gadCode = $gad // return "GAD not defined for group-number $gadNo";
|
|
return "KNX_define: $name -wrong GA format in group-number $gadNo: specify as 0-31/0-15/0-255 or as hex \nor invalid IO-Device specified" if (($gad !~ m/^$PAT_GAD$/ix) and ($gad !~ m/^$PAT_GAD_HEX$/ix));
|
|
|
|
#new syntax for extended adressing
|
|
$gad = KNX_hexToName ($gad) if ($gad =~ m/^$PAT_GAD_HEX$/ix);
|
|
|
|
#convert it vice-versa, just to be sure
|
|
$gadCode = KNX_nameToHex ($gad);
|
|
|
|
if(! defined($gadModel)) {
|
|
return "KNX_define: $name -no model defined for group-number $gadNo";
|
|
}
|
|
else {
|
|
#within autocreate no model is supplied - throw warning
|
|
if ($gadModel eq $modelErr) {
|
|
Log3 ($name, 3, "KNX_define: $name -autocreate device will be disabled, correct def with valid dpt and enable device") if ($init_done);
|
|
}
|
|
elsif (!defined($dpttypes{$gadModel})) { #check model-type
|
|
return "KNX_define: $name -invalid model: $gadModel for group-number $gadNo. Please consult commanref - available DPT for correct model definition.";
|
|
}
|
|
}
|
|
|
|
if (@gadArgs) {
|
|
if ($gadArgs[0] =~ m/^$PAT_GAD_OPTIONS$/ix) { # no gadname given
|
|
unshift ( @gadArgs , 'dummy' ); # shift option up in array
|
|
}
|
|
elsif ($gadArgs[0] =~ m/^$PAT_GAD_NONAME.*/ix) { # check for forbidden names
|
|
return "KNX_define: $name -forbidden gad-name: $gadArgs[0]";
|
|
}
|
|
else {
|
|
$gadName = $gadArgs[0]; # new syntax
|
|
}
|
|
|
|
$gadOption = $gadArgs[1] if(defined($gadArgs[1]) && $gadArgs[1] =~ m/$PAT_GAD_OPTIONS/ix);
|
|
$gadNoSuffix = 'noSuffix' if (join(q{ },@gadArgs) =~ m/nosuffix/ix);
|
|
|
|
return "KNX_define: $name -invalid option for group-number $gadNo. Use $PAT_GAD_OPTIONS" if (defined($gadOption) && ($gadOption !~ m/$PAT_GAD_OPTIONS/ix));
|
|
return "KNX_define: $name -invalid suffix for group-number $gadNo. Use $PAT_GAD_SUFFIX" if (defined($gadNoSuffix) && ($gadNoSuffix !~ m/$PAT_GAD_SUFFIX/ix));
|
|
}
|
|
|
|
#save 1st gadName for later backwardCompatibility
|
|
$hash->{FIRSTGADNAME} = $gadName if ($gadNo == 1);
|
|
|
|
###GADTABLE
|
|
#create a hash with gadCode and gadName for later mapping
|
|
my $tableHashRef = $hash->{GADTABLE};
|
|
#if not defined yet, define a new hash
|
|
if (not(defined($tableHashRef))) {
|
|
$tableHashRef={};
|
|
$hash->{GADTABLE}=$tableHashRef;
|
|
}
|
|
###GADTABLE
|
|
|
|
return "KNX_define: $name -GAD $gad may be supplied only once per device." if (defined ($tableHashRef->{$gadCode}));
|
|
|
|
#cache suffixes
|
|
my $suffixGet = q{-get};
|
|
my $suffixSet = q{-set};
|
|
my $suffixPut = q{-put};
|
|
if (defined ($gadNoSuffix)) {
|
|
$suffixGet = q{};
|
|
$suffixSet = q{};
|
|
$suffixPut = q{};
|
|
}
|
|
# new syntax readingNames
|
|
my $rdNameGet = $gadName . $suffixGet;
|
|
my $rdNameSet = $gadName . $suffixSet;
|
|
my $rdNamePut = $gadName . $suffixPut;
|
|
|
|
if (($gadName =~ /^g$gadNo/ix) && (! defined($gadNoSuffix))) { # old syntax
|
|
$rdNameGet = "getG" . $gadNo;
|
|
$rdNameSet = "setG" . $gadNo;
|
|
$rdNamePut = "putG" . $gadNo;
|
|
}
|
|
|
|
my $log = "KNX_define: $name -found GAD: $gad, NAME: $gadName NO: $gadNo, HEX: $gadCode, DPT: $gadModel";
|
|
$log .= ", OPTION: $gadOption" if (defined ($gadOption));
|
|
Log3 ($name, 5, "$log");
|
|
|
|
#determint dpt-details
|
|
my $dptDetails = $dpttypes{$gadModel};
|
|
my $setlist;
|
|
#case list is given, pass it through
|
|
if (defined ($dptDetails->{SETLIST})) {
|
|
$setlist = q{:} . $dptDetails->{SETLIST};
|
|
}
|
|
#case number - place slider
|
|
elsif (defined ($dptDetails->{MIN}) and ($dptDetails->{MIN} =~ m/0|[+-]?\d*[(.|,)\d*]/x)) {
|
|
my $min = $dptDetails->{MIN};
|
|
my $max = $dptDetails->{MAX};
|
|
my $interval = int(($max-$min)/100);
|
|
$interval = 1 if ($interval == 0);
|
|
$setlist = ':slider,' . $min . q{,} . $interval . q{,} . $max;
|
|
}
|
|
#on/off/...
|
|
elsif (defined ($dptDetails->{MIN})) {
|
|
my $min = $dptDetails->{MIN};
|
|
my $max = $dptDetails->{MAX};
|
|
$setlist = q{:} . $min . q{,} . $max;
|
|
}
|
|
#plain input field
|
|
else {
|
|
$setlist = q{};
|
|
}
|
|
|
|
Log3 ($name, 5, "define $name, Estimated reading-names: $rdNameGet, $rdNameSet, $rdNamePut");
|
|
Log3 ($name, 5, "define $name, SetList: $setlist") if (defined ($setlist));
|
|
|
|
#add details to hash
|
|
$hash->{GADDETAILS}{$gadName} = {GROUP => $gad, CODE => $gadCode, MODEL => $gadModel, NO => $gadNo, OPTION => $gadOption, RDNAMEGET => $rdNameGet, RDNAMESET => $rdNameSet, RDNAMEPUT => $rdNamePut, SETLIST => $setlist};
|
|
|
|
#add key and value to GADTABLE
|
|
$tableHashRef->{$gadCode} = $gadName;
|
|
|
|
###DEFPTR
|
|
my @devList = ();
|
|
#get list, if at least one GAD is installed
|
|
@devList = @{$modules{KNX}{defptr}{$gadCode}} if (defined ($modules{KNX}{defptr}{$gadCode}));
|
|
#push actual hash to list
|
|
push (@devList, $hash);
|
|
#restore list
|
|
@{$modules{KNX}{defptr}{$gadCode}} = @devList;
|
|
###DEFPTR
|
|
|
|
#create setlist/getlist for setFn / getFn
|
|
my $setString = q{};
|
|
my $getString = q{};
|
|
foreach my $key (keys %{$hash->{GADDETAILS}}) {
|
|
#no set-command for listenonly or get / no get cmds for set
|
|
my $option = $hash->{GADDETAILS}{$key}{OPTION};
|
|
if (defined ($option)) {
|
|
if ($option eq 'get') {
|
|
$getString .= q{ } . $key . ':noArg';
|
|
}
|
|
elsif ($option eq 'set') {
|
|
$setString .= ' on:noArg off:noArg' if (($hash->{GADDETAILS}{$key}{NO} == 1) && ($hash->{GADDETAILS}{$key}{MODEL} =~ /^(dpt1|dpt1.001)$/x));
|
|
$setString .= q{ } . $key . $hash->{GADDETAILS}{$key}{SETLIST};
|
|
}
|
|
# must be listenonly, do nothing
|
|
}
|
|
else { # no option def, select all
|
|
$getString .= q{ } . $key . ':noArg';
|
|
$setString .= ' on:noArg off:noArg' if (($hash->{GADDETAILS}{$key}{NO} == 1) && ($hash->{GADDETAILS}{$key}{MODEL} =~ /^(dpt1|dpt1.001)$/x));
|
|
$setString .= q{ } . $key . $hash->{GADDETAILS}{$key}{SETLIST};
|
|
}
|
|
}
|
|
$setString =~ s/^[\s?](.*)/$1/ix; # trim leading blank
|
|
$getString =~ s/^[\s?](.*)/$1/ix;
|
|
$hash->{SETSTRING} = $setString;
|
|
$hash->{GETSTRING} = $getString;
|
|
|
|
Log3 ($name, 5, "KNX_define: $name -GETSTR: " . $hash->{GETSTRING} . ", SETSTR: " . $hash->{SETSTRING});
|
|
}
|
|
|
|
#backup name for a later rename
|
|
$hash->{DEVNAME} = $name; # wer braucht das?
|
|
|
|
Log3 ($name, 5, "KNX_define: $name -exit");
|
|
|
|
return $UNDEF;
|
|
}
|
|
|
|
#Release this device
|
|
#Is called at every delete / shutdown
|
|
#############################
|
|
sub KNX_Undef {
|
|
my $hash = shift;
|
|
my $name = shift;
|
|
|
|
Log3 ($name, 5, "KNX_undef -enter: $name");
|
|
|
|
#delete all defptr entries for this device. this bug is still in SVN version! 09-02-2021
|
|
KNX_delete_defptr($hash); # verify with: {PrintHash($modules{KNX}{defptr},3) } on FHEM-cmdline
|
|
|
|
Log3 ($name, 5, "KNX_undef -exit");
|
|
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};
|
|
|
|
#FHEM asks with a ? at startup - no action, no log
|
|
return "unknown argument $a[1] choose one of " . $hash->{GETSTRING} if(defined($a[1]) && ($a[1] =~ m/\?/x));
|
|
return "KNX_Get device: $name is disabled" if (IsDisabled($name) == 1);
|
|
|
|
Log3 ($name, 5, "KNX_Get -enter: $name, " . join(", ", @a));
|
|
|
|
#no more than 1 argument allowed
|
|
Log3 ($name, 2, "KNX_Get: too much arguments. Only one argument allowed (group-address). Other Arguments are discarded.") if (int(@a) > 2);
|
|
|
|
#determine gadName to read - use first defined GAD if no argument is supplied
|
|
my $gadName;
|
|
if (defined ($a[1])) {
|
|
$gadName = $a[1];
|
|
}
|
|
else {
|
|
$gadName = $hash->{FIRSTGADNAME};
|
|
}
|
|
|
|
#get groupCode
|
|
my $groupc = $hash->{GADDETAILS}{$gadName}{CODE};
|
|
#get groupAddress
|
|
my $group = $hash->{GADDETAILS}{$gadName}{GROUP};
|
|
#get option
|
|
my $option = $hash->{GADDETAILS}{$gadName}{OPTION};
|
|
|
|
#return, if unknown group
|
|
return "KNX_Get: no valid address stored for gad: $gadName" if(!$groupc);
|
|
|
|
#exit if get is prohibited
|
|
return 'KNX_Get: did not request a value - "set" or "listenonly" option is defined.' if (defined ($option) and ($option =~ m/(set|listenonly)/ix));
|
|
|
|
#send read-request to the bus
|
|
Log3 ($name, 5, "KNX_Get-exit: $name request value for GAD: $group, GAD-NAME: $gadName");
|
|
|
|
IOWrite($hash, $TULid, 'r' . $groupc);
|
|
|
|
FW_directNotify("FILTER=" . $FW_detail, '#FHEMWEB:' . $FW_wname, 'FW_errmsg(" current value for ' . $name . ' - ' . $group . ' requested",5000)', qq{});
|
|
|
|
return;
|
|
}
|
|
|
|
#Does something according the given cmd...
|
|
#############################
|
|
sub KNX_Set {
|
|
my ($hash, @a) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $ret = q{};
|
|
my $na = scalar(@a);
|
|
|
|
#identify this sub
|
|
my @ca = caller(0);
|
|
(my $thisSub = $ca[3]) =~ s/.+[:]+//gx;
|
|
|
|
#FHEM asks with a "?" at startup or any reload of the device-detail-view
|
|
#return string for enabling webfrontend to show boxes, ...
|
|
return "unknown argument $a[1] choose one of " . $hash->{SETSTRING} if(defined($a[1]) && ($a[1] =~ m/\?/x));
|
|
return "$thisSub: device $name is disabled" if (IsDisabled($name) == 1);
|
|
|
|
Log3 ($name, 5, "$thisSub -enter: $name, " . join(", ", @a[1 .. $na-1])) if (defined ($a[1]));
|
|
|
|
#return, if no cmd specified
|
|
return "$thisSub: no gadname specified for set cmd" if((!defined($a[1])) || ($a[1] eq q{}));
|
|
#return, if no set value specified
|
|
return "$thisSub: no value specified for set cmd" if($na < 2);
|
|
|
|
#remove whitespaces
|
|
(my $targetGadName = $a[1]) =~ s/^\s+|\s+$//gix; # gad-name or cmd (in old syntax)
|
|
my @arg = @a[2 .. $na-1]; # copy cmd & arguments
|
|
|
|
#contains gadNames to be executed
|
|
my $cmd = undef;
|
|
|
|
#check, if old or new syntax
|
|
if (defined ($hash->{GADDETAILS}{$targetGadName})) { # #new syntax, if first arg is a valid gadName
|
|
#shift backup args as with newsyntax $a[2] is cmd
|
|
$cmd = shift(@arg);
|
|
}
|
|
else { #oldsyntax
|
|
(my $err, $targetGadName, $cmd) = KNX_Set_oldsyntax($hash,$targetGadName,@arg); ## process old syntax
|
|
return $err if defined($err);
|
|
}
|
|
|
|
return "$thisSub: no target and cmd found" if((!defined($targetGadName)) && (!defined($cmd)));
|
|
return "$thisSub: no cmd found" if(!defined($cmd));
|
|
return "$thisSub: no target found" if(!defined($targetGadName));
|
|
|
|
Log3 ($name, 5, "$thisSub: set $name: desired target is gad: $targetGadName, command: $cmd, args: " . join (q{ }, @arg));
|
|
|
|
#get details
|
|
my $groupCode = $hash->{GADDETAILS}{$targetGadName}{CODE};
|
|
my $option = $hash->{GADDETAILS}{$targetGadName}{OPTION};
|
|
my $rdName = $hash->{GADDETAILS}{$targetGadName}{RDNAMESET};
|
|
my $model = $hash->{GADDETAILS}{$targetGadName}{MODEL};
|
|
|
|
return $thisSub . ': did not set a value - "get" or "listenonly" option is defined.' if (defined ($option) and ($option =~ m/(get|listenonly)/ix));
|
|
|
|
##############################
|
|
#process set command with $value as output
|
|
my $value = $cmd;
|
|
#Text neads special treatment - additional args may be blanked words
|
|
$value .= q{ } . join (q{ }, @arg) if (($model =~ m/^dpt16/ix) and (scalar (@arg) > 0));
|
|
|
|
#Special commands for dpt1 and dpt1.001
|
|
if ($model =~ m/((dpt1)|(dpt1.001))$/ix) {
|
|
(my $err, $value) = KNX_Set_dpt1($hash, $targetGadName, $cmd, @arg);
|
|
return $err if defined($err);
|
|
}
|
|
|
|
##############################
|
|
#check and cast value
|
|
my $transval = KNX_checkAndClean($hash, $value, $targetGadName);
|
|
|
|
#if cast not successful
|
|
return "$thisSub: invalid value= $value" if (!defined($transval));
|
|
|
|
#process set command
|
|
my $transvale = KNX_encodeByDpt($hash, $transval, $targetGadName);
|
|
IOWrite($hash, $TULid, 'w' . $groupCode . $transvale);
|
|
|
|
Log3 ($name, 4, "$thisSub: $name, cmd= $cmd, value= $value, translated= $transvale");
|
|
|
|
# decode again for values that have been changed in encode process
|
|
if ($model =~ m/^(dpt3|dpt10|dpt11|dpt19)/ix) {
|
|
$transval = KNX_decodeByDpt($hash, $transvale, $targetGadName);
|
|
}
|
|
else {
|
|
my $unit = $dpttypes{$model}{UNIT};
|
|
$transval .= q{ } . $unit if (defined($unit) && ($unit ne q{})); # append units during set cmd
|
|
}
|
|
#apply post processing for state and set all readings
|
|
KNX_SetReadings($hash, $targetGadName, $transval, $rdName, undef);
|
|
|
|
Log3 ($name, 5, "$thisSub: -exit");
|
|
return $UNDEF;
|
|
}
|
|
|
|
# Process set command for old syntax
|
|
# calling param: $hash, $cmd, arg array
|
|
# returns ($err, targetgadname, $cmd)
|
|
sub KNX_Set_oldsyntax {
|
|
my ($hash, $cmd, @a) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $na = scalar(@a);
|
|
|
|
#contains gadNames to be executed
|
|
my $targetGadName = undef;
|
|
|
|
#default
|
|
my $groupnr = 1;
|
|
#select another group, if the last arg starts with a g
|
|
if($na >= 1 && $a[$na - 1] =~ m/$PAT_GNO/ix) {
|
|
$groupnr = pop (@a);
|
|
Log3 $name, 3, q{KNX_Set_oldsyntax: you are still using "old syntax", pls. change to "set } . "$name $groupnr $cmd " . join(q{ },@a) . q{"};
|
|
$groupnr =~ s/^g//gix; #remove "g"
|
|
}
|
|
|
|
#unknown groupnr
|
|
return "KNX_Set_oldsyntax: group-no. not found" if((!defined($groupnr)) || ($groupnr eq q{}));
|
|
|
|
foreach my $key (keys %{$hash->{GADDETAILS}}) {
|
|
$targetGadName = $key if (int ($hash->{GADDETAILS}{$key}{NO}) == int ($groupnr));
|
|
}
|
|
return "KNX_Set_oldsyntax: gadName not found for $groupnr" if(!defined($targetGadName));
|
|
|
|
# all of the following cmd's need at least 1 Argument (or more)
|
|
return ($UNDEF, $targetGadName, $cmd) if (scalar(@a) <= 0);
|
|
|
|
my $value = "$cmd " . join(q{ },@a); # default
|
|
if ($cmd =~ m/$RAW/ix) { # perlcritic (ControlStructures::ProhibitCascadingIfElse) ?
|
|
|
|
#check for 1-16 hex-digits
|
|
return "KNX_Set_oldsyntax: $cmd $a[0] has wrong syntax. Use hex-format only." if ($a[0] !~ m/[0-9A-F]{1,16}/ix);
|
|
$value = $a[0];
|
|
}
|
|
elsif ($cmd =~ m/$VALUE/ix) {
|
|
my $code = $hash->{GADDETAILS}{$targetGadName}{MODEL};
|
|
return 'KNX_Set_oldsyntax: "value" not allowed for dpt1, dpt16 and dpt232' if ($code =~ m/(dpt1$)|(dpt16$)|(dpt232$)/ix);
|
|
|
|
$value = $a[0];
|
|
$value =~ s/,/\./gx;
|
|
}
|
|
#set string <val1 val2 valn>
|
|
elsif ($cmd =~ m/$STRING/ix) {
|
|
my $code = $hash->{GADDETAILS}{$targetGadName}{MODEL};
|
|
return 'KNX_Set_oldsyntax: "string" only allowed for dpt16' if ($code !~ m/dpt16/ix);
|
|
|
|
$value = q{}; # will be joined in KNX_Set
|
|
}
|
|
#set RGB <RRGGBB>
|
|
elsif ($cmd =~ m/$RGB/ix) {
|
|
my $code = $hash->{GADDETAILS}{$targetGadName}{MODEL};
|
|
return 'KNX_Set_oldsyntax: "RGB" only allowed for dpt232' if ($code !~ m/dpt232$/ix);
|
|
|
|
#check for 6 hex-digits
|
|
return "KNX_Set_oldsyntax: $cmd $a[0] has wrong syntax. Use 6 hex-digits only." if ($a[0] !~ m/[0-9A-F]{6}/ix);
|
|
$value = lc($a[0]);
|
|
}
|
|
return ($UNDEF, $targetGadName, $value);
|
|
}
|
|
|
|
# process special dpt1, dpt1.001 set
|
|
# calling: $hash, $targetGadName, $cmd, @arg
|
|
# return: $err, $value
|
|
sub KNX_Set_dpt1 {
|
|
my ($hash, $targetGadName, $cmd, @arg) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
my $groupCode = $hash->{GADDETAILS}{$targetGadName}{CODE};
|
|
|
|
#delete any running timers
|
|
if ($hash->{"TIMER_$groupCode"}) {
|
|
CommandDelete(undef, $name . "_TIMER_$groupCode");
|
|
delete $hash->{"TIMER_$groupCode"};
|
|
}
|
|
|
|
# the defaults
|
|
my $value = 'off'; # default
|
|
if (($cmd eq 'on') || ($cmd eq '1')) {$value = 'on';}
|
|
# if (($cmd eq 'on') || ($cmd eq 'off')) {$value = $cmd;}
|
|
# elsif ($cmd eq '0') {$value = 'off';}
|
|
# elsif ($cmd eq '1') {$value = 'on';}
|
|
|
|
#set on-for-timer / off-for-timer
|
|
elsif ($cmd =~ m/($ONFORTIMER)|($OFFFORTIMER)/ix) {
|
|
#get duration
|
|
my $duration = sprintf("%02d:%02d:%02d", $arg[0]/3600, ($arg[0]%3600)/60, $arg[0]%60);
|
|
Log3 ($name, 5, "KNX_Set_dpt1 $name: \"on-for-timer\" for $duration");
|
|
#create local marker
|
|
$hash->{"TIMER_$groupCode"} = $duration;
|
|
#place at-command for switching off
|
|
#switch on or off...
|
|
if ($cmd =~ m/on/ix) {
|
|
$value = "on";
|
|
CommandDefine(undef, $name . "_TIMER_$groupCode at +$duration set $name $targetGadName off");
|
|
} else {
|
|
$value = "off";
|
|
CommandDefine(undef, $name . "_TIMER_$groupCode at +$duration set $name $targetGadName on");
|
|
}
|
|
}
|
|
#set on-until / off-until
|
|
elsif ($cmd =~ m/($ONUNTIL)|($OFFUNTIL)/ix) {
|
|
#get off-time
|
|
my ($err, $hr, $min, $sec, $fn) = GetTimeSpec($arg[0]); ## fhem.pl
|
|
return "KNX_Set_dpt1: Error trying to parse timespec for $arg[0]: $err" if (defined($err));
|
|
|
|
#do like (on|off)-until-overnight in at cmd !
|
|
my $hms_til = sprintf("%02d:%02d:%02d", $hr, $min, $sec);
|
|
|
|
Log3 ($name, 5, "KNX_Set_dpt1 $name: \"$cmd $hms_til\" ");
|
|
#create local marker
|
|
$hash->{"TIMER_$groupCode"} = $hms_til;
|
|
#place at-command for switching off
|
|
#switch on or off...
|
|
if ($cmd =~ m/on/ix) {
|
|
$value = "on";
|
|
CommandDefine(undef, $name . "_TIMER_$groupCode at $hms_til set $name $targetGadName off");
|
|
} else {
|
|
$value = "off";
|
|
CommandDefine(undef, $name . "_TIMER_$groupCode at $hms_til set $name $targetGadName on");
|
|
}
|
|
}
|
|
#toggle
|
|
elsif ($cmd =~ m/$TOGGLE/ix) {
|
|
my $togglereading = 'dummy';
|
|
my $toggleOldVal = 'dontknow';
|
|
my $tDev = $name; # default
|
|
|
|
if (defined($hash->{'.TOGGLESRC'})) { # prio1: use Attr. KNX_toggle: format: <device>:<reading>
|
|
($tDev, $togglereading) = split(qr/:/x,$hash->{'.TOGGLESRC'});
|
|
$toggleOldVal = ReadingsVal($tDev, $togglereading, 'dontknow');
|
|
}
|
|
else {
|
|
$togglereading = $hash->{GADDETAILS}{$targetGadName}{RDNAMEGET};
|
|
$toggleOldVal = ReadingsVal($tDev, $togglereading, undef); #prio2: use get-reading
|
|
if (! defined($toggleOldVal)) {
|
|
$togglereading = $hash->{GADDETAILS}{$targetGadName}{RDNAMESET}; #prio3: use set-reading
|
|
$toggleOldVal = ReadingsVal($tDev, $togglereading, 'dontknow');
|
|
}
|
|
}
|
|
# if (ReadingsVal($name, $hash->{GADDETAILS}{$targetGadName}{RDNAMEGET}, 'on') =~ m/off/ix) { # SVN-Version
|
|
|
|
Log3 ($name, 3, 'KNX_Set_dpt1: initial value for "set ' . "$name $targetGadName" . ' TOGGLE is not "on" or "off" - ' . "$targetGadName will be switched off") if ($toggleOldVal !~ /^(?:on|off)/ix);
|
|
if ($toggleOldVal =~ m/^off/ix) {
|
|
$value = "on";
|
|
}
|
|
else {
|
|
$value = "off"; # this is the default
|
|
}
|
|
}
|
|
|
|
return ($UNDEF,$value);
|
|
}
|
|
|
|
#In case setstate is executed, a readingsupdate is initiated
|
|
#############################
|
|
sub KNX_State {
|
|
my ($hash, $time, $reading, $value) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
return if (not (defined($value)));
|
|
return if (not (defined($reading)));
|
|
|
|
#remove whitespaces
|
|
$value =~ s/^\s+|\s+$//gix;
|
|
$reading =~ s/^\s+|\s+$//gix;
|
|
|
|
#workaround for STATE in capitol letters (caused by unknown external function)
|
|
$reading = "state" if ($reading eq 'STATE');
|
|
|
|
Log3 ($name, 5, "KNX_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) = @_;
|
|
my $hash = $defs{$name};
|
|
|
|
my $value = undef;
|
|
if ($cmd eq 'set') {
|
|
Log3 ($name, 2, 'Attribut "listenonly" is deprecated. Please supply in definition - see commandref for details.') if ($aName =~ m/listenonly/ix);
|
|
Log3 ($name, 2, 'Attribut "readonly" is deprecated. Please supply "get" in definition - see commandref for details.') if ($aName =~ m/readonly/ix);
|
|
Log3 ($name, 2, 'Attribut "slider" is deprecated. Please use widgetOverride in Combination with WebCmd instead. See commandref for details.') if ($aName =~ m/slider/ix);
|
|
Log3 ($name, 2, 'Attribut "useSetExtensions" is deprecated.') if ($aName =~ m/useSetExtensions/ix);
|
|
|
|
if ($aName eq 'KNX_toggle') { # validate device/reading
|
|
my ($srcDev,$srcReading) = split(qr/:/x,$aVal); # format: <device>:<reading>
|
|
$srcDev = $name if ($srcDev eq '$self');
|
|
return 'no valid device for attr: KNX_toggle' if (!IsDevice($srcDev));
|
|
$value = ReadingsVal($srcDev,$srcReading,undef) if (defined($srcReading)); #test for value
|
|
return 'no valid device/reading value for attr: KNX_toggle' if (!defined($value) && $init_done); # maybe device/reading not defined during starrtup
|
|
$hash->{'.TOGGLESRC'} = $srcDev . q{:} . $srcReading; # save for later processing
|
|
}
|
|
if (($aName eq 'disable') && (defined($aVal)) && ($aVal == 1)) {
|
|
$hash->{"SETSTRING"} = q{}; # remove set & get options from UI
|
|
$hash->{"GETSTRING"} = q{};
|
|
}
|
|
}
|
|
|
|
if ($cmd eq 'del') {
|
|
delete $hash->{'.TOGGLESRC'} if ($aName eq 'KNX_toggle');
|
|
CommandModify(undef, "$name $hash->{'DEF'}") if ($aName eq 'disable'); # do a defmod ...
|
|
}
|
|
|
|
return $UNDEF;
|
|
}
|
|
|
|
#Split reading for DBLOG
|
|
#############################
|
|
sub KNX_DbLog_split {
|
|
my ($event, $device) = @_;
|
|
my ($reading, $value, $unit);
|
|
|
|
Log3 $device, 5, "KNX_DbLog_split -enter: device= $device event= $event";
|
|
|
|
#split input-string
|
|
my @strings = split (q{ }, $event);
|
|
return $UNDEF if (not defined ($strings[0]));
|
|
|
|
#detect reading - real reading or state?
|
|
if ($strings[0] =~ m/.*:$/x) { # real reading
|
|
$reading = shift(@strings);
|
|
$reading =~ s/:$//x;
|
|
}
|
|
else {
|
|
$reading = 'state';
|
|
}
|
|
|
|
#per default join all single pieces
|
|
$value = join(q{ }, @strings);
|
|
|
|
#numeric value? and last value non numeric? - assume unit
|
|
if (looks_like_number($strings[0]) && (! looks_like_number($strings[scalar(@strings)-1]))) {
|
|
$value = join(q{ },@strings[0 .. (scalar(@strings)-2)]);
|
|
$unit = $strings[scalar(@strings)-1];
|
|
}
|
|
$unit = q{} if (!defined($unit));
|
|
|
|
Log3 $device, 5, "KNX_DbLog_split -exit: device= $device, reading= $reading, value= $value, unit= $unit";
|
|
return ($reading, $value, $unit);
|
|
}
|
|
|
|
#Handle incoming messages
|
|
#############################
|
|
sub KNX_Parse {
|
|
my $iohash = shift; # this is IO-Device hash !
|
|
my $msg = shift;
|
|
my $ioName = $iohash->{NAME};
|
|
|
|
return q{} if ((IsDisabled($ioName) == 1) || IsDummy($ioName)); # IO - device is disabled or dummy
|
|
|
|
#Msg format:
|
|
#C<src>[wrp]<group><value> i.e. Cw00000101
|
|
#we will also take reply telegrams into account,
|
|
#as they will be sent if the status is asked from bus
|
|
|
|
#new syntax for extended adressing
|
|
my ($src,$cmd,$gadCode,$val) = $msg =~ m/^$TULid([0-9a-f]{5})([prw])([0-9a-f]{5})(.*)$/ix;
|
|
|
|
my @foundMsgs;
|
|
|
|
Log3 ($ioName, 5, "KNX_Parse -enter: IO-name: $ioName, dest: $gadCode, msg: $msg");
|
|
|
|
#gad not defined yet, give feedback for autocreate
|
|
if (not (exists $modules{KNX}{defptr}{$gadCode})) {
|
|
#format gad
|
|
my $gad = KNX_hexToName($gadCode);
|
|
#create name
|
|
my $newDevName = sprintf("KNX_%.2d%.2d%.3d",split (/\//x, $gad));
|
|
return "UNDEFINED $newDevName KNX $gad:$modelErr";
|
|
}
|
|
|
|
#get list from device-hashes using given gadCode (==destination)
|
|
my @deviceList = @{$modules{KNX}{defptr}{$gadCode}};
|
|
|
|
#process message for all affected devices and gad's
|
|
foreach my $deviceHash (@deviceList) {
|
|
#get details
|
|
my $deviceName = $deviceHash->{NAME};
|
|
my $gadName = $deviceHash->{GADTABLE}{$gadCode};
|
|
|
|
push(@foundMsgs, $deviceName); # save to list even if dev is disabled
|
|
|
|
next if (IsDisabled($deviceName) == 1); # device is disabled
|
|
|
|
Log3 ($deviceName, 4, "KNX_Parse -process: IO-name: $ioName, device-name: $deviceName, rd-name: $gadName, gadCode: $gadCode, cmd: $cmd");
|
|
|
|
#########################
|
|
#process message
|
|
#handle write and reply messages
|
|
if ($cmd =~ /[w|p]/ix) {
|
|
#decode message
|
|
my $getName = $deviceHash->{GADDETAILS}{$gadName}{RDNAMEGET};
|
|
my $transval = KNX_decodeByDpt ($deviceHash, $val, $gadName);
|
|
#message invalid
|
|
if (not defined($transval) or ($transval eq q{})) {
|
|
Log3 ($deviceName, 2, "KNX_Parse (wp): $deviceName, READINGNAME: $getName, message $msg could not be decoded");
|
|
next;
|
|
}
|
|
Log3 ($deviceName, 4, "KNX_Parse (wp): $deviceName, READINGNAME: $getName, VALUE: $transval, SENDER: $src");
|
|
|
|
#apply post processing for state and set all readings
|
|
KNX_SetReadings($deviceHash, $gadName, $transval, $getName, $src);
|
|
}
|
|
|
|
#handle read messages
|
|
elsif ($cmd =~ /[r]/x) {
|
|
my $putName = $deviceHash->{GADDETAILS}{$gadName}{RDNAMEPUT};
|
|
Log3 ($deviceName, 5, "KNX_Parse (r): $deviceName, GET");
|
|
my $transval = undef;
|
|
|
|
#answer "old school"
|
|
my $value = undef;
|
|
if (AttrVal($deviceName, 'answerReading', 0) =~ m/1/x) {
|
|
my $putVal = ReadingsVal($deviceName, $putName, undef);
|
|
|
|
if ((defined($putVal)) && ($putVal ne q{})) {
|
|
$value = $putVal; #medium priority, overwrite $value
|
|
}
|
|
else {
|
|
$value = ReadingsVal($deviceName, 'state', undef); #lowest priority - use state
|
|
}
|
|
}
|
|
|
|
#high priority - eval
|
|
my $cmdAttr = AttrVal($deviceName, "putCmd", undef);
|
|
if ((defined($cmdAttr)) && ($cmdAttr ne q{})) {
|
|
$value = ReadingsVal($deviceName, 'state', undef); # get default value from state
|
|
$value = KNX_eval ($deviceHash, $gadName, $value, $cmdAttr);
|
|
if (defined($value) && ($value ne q{}) && ($value ne 'ERROR')) { # answer only, if eval was successful
|
|
Log3 ($deviceName, 5, "KNX_Parse (r): $deviceName - put replaced via command $cmdAttr - value: $value");
|
|
readingsSingleUpdate($deviceHash, $putName, $value,1);
|
|
}
|
|
else {
|
|
Log3 ($deviceName, 2, "KNX_parse error (r): $deviceName - no reply sent!");
|
|
$value = undef; # dont send !
|
|
}
|
|
}
|
|
|
|
#send transval
|
|
if (defined($value)) {
|
|
$transval = KNX_encodeByDpt($deviceHash, $value, $gadName);
|
|
Log3 ($deviceName, 4, "KNX_Parse send answer (r): $deviceName, GET: $transval, READING: $gadName");
|
|
IOWrite ($deviceHash, $TULid, "p" . $gadCode . $transval);
|
|
}
|
|
}
|
|
#/process message
|
|
}
|
|
|
|
Log3 ($ioName, 5, "KNX_parse -exit");
|
|
|
|
return @foundMsgs;
|
|
}
|
|
|
|
#Function is called at every notify
|
|
#############################
|
|
sub KNX_Notify {
|
|
my $ownHash = shift;
|
|
my $callHash = shift;
|
|
#own name / hash
|
|
my $ownName = $ownHash->{NAME};
|
|
return if(IsDisabled($ownName) == 1); # Return without any further action if the module is disabled
|
|
|
|
#Device that created the events
|
|
my $callName = $callHash->{NAME};
|
|
|
|
my $events = deviceEvents($callHash, 1);
|
|
if($callName eq "global") {
|
|
foreach my $ev (@{$events}) {
|
|
if ($ev =~ /^INITIALIZED|REREADCFG$/x) {
|
|
# X_FunctionWhoNeedsAttr($hash);
|
|
}
|
|
}
|
|
}
|
|
return $UNDEF;
|
|
}
|
|
|
|
# ignore duplicate messages (runs in TUL /KNXTUL context!)
|
|
#############################
|
|
sub KNX_FingerPrint {
|
|
my $ioname = shift;
|
|
my $buf = shift;
|
|
substr( $buf, 1, 5, '.....' ); # ignore src addr
|
|
Log3 $ioname, 5, 'KNX_FingerPrint: ' . $buf;
|
|
# return ( $ioname, $buf ); # ignore src addr only
|
|
return ( q{}, $buf ); # ignore ioname & src addr
|
|
}
|
|
|
|
########## begin of private functions ##########
|
|
|
|
# KNX_SetReadings is called from KNX_Set and KNX_Parse
|
|
# calling param: $hash, $gadName, $transval, $rdName, caller (set/parse)
|
|
sub KNX_SetReadings {
|
|
my ($hash, $gadName, $transval, $rdName, $src) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
#append post-string, if supplied
|
|
my $suffix = AttrVal($name, "format",undef);
|
|
$transval .= q{ } . $suffix if (defined($suffix));
|
|
#execute regex, if defined
|
|
my $regAttr = AttrVal($name, "stateRegex", undef);
|
|
my $state = KNX_replaceByRegex ($regAttr, $rdName, $transval);
|
|
|
|
my $logstr = (defined($state))?$state:'UNDEFINED';
|
|
Log3 ($name, 5, "KNX_SetReadings: $name - replaced $rdName value from: $transval to $logstr") if ($transval ne $logstr);
|
|
|
|
my $lsvalue = 'fhem'; # called from set
|
|
$lsvalue = KNX_hexToName2($src) if (defined($src) && ($src ne q{})); # called from parse
|
|
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate($hash, 'last-sender', $lsvalue);
|
|
readingsBulkUpdate($hash, $rdName, $transval);
|
|
|
|
if (defined($state)) {
|
|
#execute state-command if defined
|
|
#must be placed after first reading, because it may have a reference
|
|
my $deviceName = $name; #hack for being backward compatible - serve $name and $devname
|
|
my $cmdAttr = AttrVal($name, "stateCmd", undef);
|
|
|
|
if ((defined($cmdAttr)) && ($cmdAttr ne q{})) {
|
|
my $newstate = KNX_eval ($hash, $gadName, $state, $cmdAttr);
|
|
if (defined($newstate) && ($newstate ne q{}) && ($newstate !~ m/ERROR/ix)) {
|
|
$state = $newstate;
|
|
Log3 ($name, 5, "KNX_SetReadings: $name - state replaced via stateCmd $cmdAttr - state: $state");
|
|
}
|
|
else {
|
|
Log3 ($name, 3, "KNX_SetReadings: $name, gad: $gadName, error during stateCmd processing");
|
|
}
|
|
}
|
|
|
|
readingsBulkUpdate($hash, "state", $state);
|
|
}
|
|
readingsEndUpdate($hash, 1);
|
|
return;
|
|
}
|
|
|
|
# delete all defptr entries for this device
|
|
# used in undefine & define (avoid defmod problem) 09-02-2021
|
|
# calling param: $hash
|
|
# return param: none
|
|
sub KNX_delete_defptr {
|
|
my $hash = shift;
|
|
my $name = $hash->{NAME};
|
|
|
|
for my $gad (sort keys %{$modules{KNX}{defptr}}) { # get all gad for all KNX devices
|
|
my @olddefList = ();
|
|
@olddefList = @{$modules{KNX}{defptr}{$gad}} if (defined ($modules{KNX}{defptr}{$gad})); # get list of devices with this gad
|
|
my @newdefList = ();
|
|
foreach my $devHash (@olddefList) {
|
|
push (@newdefList, $devHash) if ($devHash != $hash); # remove previous definition for this device, but keep them for other devices!
|
|
}
|
|
#restore list if we have at least one entry left, or delete key!
|
|
if (scalar(@newdefList) == 0) {
|
|
delete $modules{KNX}{defptr}{$gad};
|
|
}
|
|
else {
|
|
@{$modules{KNX}{defptr}{$gad}} = @newdefList;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
# convert GAD from hex to readable version
|
|
sub KNX_hexToName {
|
|
my $v = shift;
|
|
|
|
#new syntax - extended adressing
|
|
my $p1 = hex(substr($v,0,2));
|
|
my $p2 = hex(substr($v,2,1));
|
|
my $p3 = hex(substr($v,3,2));
|
|
|
|
my $r = sprintf("%d/%d/%d", $p1,$p2,$p3);
|
|
|
|
return $r;
|
|
}
|
|
|
|
# convert PHY from hex to readable version
|
|
sub KNX_hexToName2 {
|
|
my $v = KNX_hexToName(shift);
|
|
$v =~ s/\//\./gx;
|
|
return $v;
|
|
}
|
|
|
|
# convert GAD from readable version to hex
|
|
sub KNX_nameToHex {
|
|
my $v = shift;
|
|
my $r = $v;
|
|
|
|
if($v =~ /^([0-9]{1,2})\/([0-9]{1,2})\/([0-9]{1,3})$/x) {
|
|
#new syntax - extended adressing
|
|
$r = sprintf("%02x%01x%02x",$1,$2,$3);
|
|
}
|
|
return $r;
|
|
}
|
|
|
|
# clean input string according DPT
|
|
sub KNX_checkAndClean {
|
|
my ($hash, $value, $gadName) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $orgValue = $value;
|
|
|
|
Log3 ($name, 5, "KNX_checkAndClean -enter: value= $value, gadName= $gadName");
|
|
|
|
my $model = $hash->{GADDETAILS}{$gadName}{MODEL};
|
|
|
|
#return unchecked, if this is a autocreate-device
|
|
return $value if ($model eq $modelErr);
|
|
|
|
my $pattern = $dpttypes{$model}{PATTERN};
|
|
|
|
#trim whitespaces at the end
|
|
$value =~ s/^\s+|\s+$//gix;
|
|
$value .= ':00' if ($model eq 'dpt10' && $value =~ /^[\d]{2}:[\d]{2}$/gix); # compatibility with widgetoverride :time
|
|
|
|
#match against model pattern
|
|
# my $pattern = qr/^($dpttypes{$model}{PATTERN})$/;
|
|
# ($value) = ($value =~ m/$pattern/ix);
|
|
# return $UNDEF if (!defined($value));
|
|
|
|
my @tmp = ($value =~ m/$pattern/gix);
|
|
#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);
|
|
|
|
$value = KNX_limit ($hash, $value, $gadName, undef);
|
|
|
|
Log3 ($name, 3, "KNX_checkAndClean: value= $orgValue was casted to $value") if ($orgValue ne $value);
|
|
Log3 ($name, 5, "KNX_checkAndClean -exit: value= $value, gadName= $gadName, model= $model, pattern= $pattern");
|
|
|
|
return $value;
|
|
}
|
|
|
|
# replace state-values Attribute: stateRegex
|
|
sub KNX_replaceByRegex {
|
|
my ($regAttr, $prefix, $input) = @_;
|
|
|
|
return $input if (! defined($regAttr));
|
|
|
|
my $retVal = $input;
|
|
|
|
#execute regex, if defined
|
|
#get array of given attributes
|
|
my @reg = split(/\s\//x, $regAttr);
|
|
|
|
$prefix .= q{:};
|
|
my $tempVal = $prefix . $input;
|
|
|
|
#loop over all regex
|
|
foreach my $regex (@reg) {
|
|
#trim leading and trailing slashes
|
|
$regex =~ s/^\/|\/$//gix;
|
|
#get pairs
|
|
my @regPair = split(/\//x, $regex);
|
|
|
|
#skip if first part of regex not match readingName
|
|
next if ((not defined($regPair[0])) || ($regPair[0] eq q{}) || ($regPair[0] !~ /$prefix/ix));
|
|
|
|
if (not defined ($regPair[1])) {
|
|
#cut value
|
|
$tempVal = $UNDEF;
|
|
}
|
|
else {
|
|
#replace value
|
|
$tempVal =~ s/$regPair[0]/$regPair[1]/gix;
|
|
}
|
|
|
|
#restore value
|
|
$retVal = $tempVal;
|
|
last;
|
|
}
|
|
return $retVal;
|
|
}
|
|
|
|
# limit numeric values. Valid directions: encode, decode
|
|
sub KNX_limit {
|
|
my ($hash, $value, $gadName, $direction) = @_;
|
|
|
|
#continue only if numeric value
|
|
return $value if (! looks_like_number ($value));
|
|
return $value if (! defined($direction));
|
|
|
|
my $name = $hash->{NAME};
|
|
my $model = $hash->{GADDETAILS}{$gadName}{MODEL};
|
|
my $retVal = $value;
|
|
|
|
#get correction details
|
|
my $factor = $dpttypes{$model}{FACTOR};
|
|
my $offset = $dpttypes{$model}{OFFSET};
|
|
#get limits
|
|
my $min = $dpttypes{$model}{MIN};
|
|
my $max = $dpttypes{$model}{MAX};
|
|
|
|
#determine direction of scaling, do only if defined
|
|
if ($direction =~ m/^encode/ix) {
|
|
#limitValue
|
|
$retVal = $min if (defined ($min) and ($retVal < $min));
|
|
$retVal = $max if (defined ($max) and ($retVal > $max));
|
|
|
|
#correct value
|
|
$retVal /= $factor if (defined ($factor));
|
|
$retVal -= $offset if (defined ($offset));
|
|
}
|
|
elsif ($direction =~ m/^decode/ix) {
|
|
#correct value
|
|
$retVal += $offset if (defined ($offset));
|
|
$retVal *= $factor if (defined ($factor));
|
|
|
|
#limitValue
|
|
$retVal = $min if (defined ($min) and ($retVal < $min));
|
|
$retVal = $max if (defined ($max) and ($retVal > $max));
|
|
}
|
|
|
|
my $logString = "DIR: $direction";
|
|
$logString .= " FACTOR: $factor" if (defined ($factor));
|
|
$logString .= " OFFSET: $offset" if (defined ($offset));
|
|
$logString .= " MIN: $min" if (defined ($min));
|
|
$logString .= " MAX: $max" if (defined ($max));
|
|
Log3 ($name, 5, "KNX_limit: $gadName $logString");
|
|
Log3 ($name, 4, "KNX_limit: $gadName modified... Output: $retVal, Input: $value, Model: $model") if ($retVal != $value);
|
|
|
|
return $retVal;
|
|
}
|
|
|
|
# process attributes stateCmd & putCmd
|
|
sub KNX_eval {
|
|
my ($hash, $gadName, $state, $evalString) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $retVal = undef;
|
|
|
|
#04.65 Log3 ($name, 5, "KNX_Eval-enter: $name, gadName: $gadName, evalString: $evalString");
|
|
|
|
my $code = EvalSpecials($evalString,("%hash" => $hash, '%name' => $name, '%gadName' => $gadName, '%state' => $state)); # prepare vars for AnalyzePerlCommand
|
|
$retVal = AnalyzePerlCommand(undef, $code);
|
|
|
|
$retVal = "ERROR" if (not defined ($retVal));
|
|
|
|
if ($retVal =~ /(^Forbidden|error)/ix) { # eval error or forbidden by Authorize
|
|
Log3 ($name, 2, "KNX_Eval-error: device= $name, gadName= $gadName, evalString= $evalString, result= $retVal"); #04.65
|
|
#04.65 Log3 ($name, 2, "KNX_Eval-error: $retVal");
|
|
$retVal = 'ERROR';
|
|
}
|
|
#04.65 Log3 ($name, 5, "KNX_Eval-exit: result: $retVal");
|
|
return $retVal;
|
|
}
|
|
|
|
# encode KNX-Message according DPT
|
|
sub KNX_encodeByDpt {
|
|
my ($hash, $value, $gadName) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
my $model = $hash->{GADDETAILS}{$gadName}{MODEL};
|
|
my $code = $dpttypes{$model}{CODE};
|
|
|
|
#return unchecked, if this is a autocreate-device
|
|
return 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, "KNX_encodeByDpt: $gadName model: $model, code: $code, value: $value");
|
|
|
|
$value = KNX_limit ($hash, $value, $gadName, "ENCODE");
|
|
|
|
#Binary value
|
|
if ($code eq "dpt1") {
|
|
$numval = "00" if ($value eq q{0});
|
|
$numval = "01" if ($value eq q{1});
|
|
$numval = "00" if ($value =~ m/off$/ix);
|
|
$numval = "01" if ($value =~ m/on$/ix);
|
|
$numval = "00" if ($value eq $dpttypes{$model}{MIN});
|
|
$numval = "01" if ($value eq $dpttypes{$model}{MAX});
|
|
|
|
$hexval = $numval;
|
|
}
|
|
#Step value (two-bit)
|
|
elsif ($code eq "dpt2") {
|
|
$numval = 0; # default
|
|
$numval = $value if ($value =~ m/^0?[0-3]$/ix); ## JoeALLb request
|
|
$numval = 0 if ($value =~ m/off/ix);
|
|
$numval = 1 if ($value =~ m/on/ix);
|
|
$numval = 2 if ($value =~ m/forceoff/i);
|
|
$numval = 3 if ($value =~ m/forceon/i);
|
|
|
|
$hexval = sprintf("%.2x",$numval);
|
|
}
|
|
#Step value (four-bit)
|
|
elsif ($code eq "dpt3") {
|
|
$numval = 0;
|
|
my $sign = ($value >=0 )?1:0;
|
|
$value = abs($value);
|
|
|
|
my @values = qw( 75 50 25 12 6 3 1 );
|
|
my $i = 0;
|
|
foreach my $key (@values) {
|
|
$i++;
|
|
if ($value >= $key) {
|
|
$numval = $i;
|
|
last;
|
|
}
|
|
}
|
|
$numval += 8 if ($sign == 1);
|
|
$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 = unpack("C", pack("c", $value));
|
|
$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 = unpack("S", pack("s", $value));
|
|
$hexval = sprintf("00%.4x",$numval);
|
|
}
|
|
#2-Octet Float value
|
|
elsif ($code eq "dpt9") {
|
|
my $sign = ($value <0 ? 0x8000 : 0);
|
|
my $exp = 0;
|
|
|
|
my $mant = $value * 100;
|
|
while (abs($mant) > 0x07FF) {
|
|
$mant /= 2;
|
|
$exp++;
|
|
}
|
|
$numval = $sign | ($exp << 11) | ($mant & 0x07FF);
|
|
$hexval = sprintf("00%.4x",$numval);
|
|
}
|
|
#Time of Day
|
|
elsif ($code eq "dpt10") {
|
|
if ($value =~ m/now/ix) {
|
|
#get actual time
|
|
my ($secs,$mins,$hours,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
|
|
|
|
#add offsets
|
|
$year+=1900;
|
|
$mon++;
|
|
# calculate offset for weekday
|
|
$wday = 7 if ($wday == 0);
|
|
$hours += 32 * $wday;
|
|
|
|
$value = "$hours:$mins:$secs";
|
|
$numval = $secs + ($mins << 8) + ($hours << 16);
|
|
}
|
|
else {
|
|
my ($hh, $mm, $ss) = split(/:/x, $value);
|
|
$numval = $ss + ($mm << 8) + ($hh << 16);
|
|
}
|
|
$hexval = sprintf("00%.6x",$numval);
|
|
}
|
|
#Date
|
|
elsif ($code eq "dpt11") {
|
|
if ($value =~ m/now/ix) {
|
|
#get actual time
|
|
my ($secs,$mins,$hours,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
|
|
|
|
#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 (/\./x, $value);
|
|
if ($yyyy >= 2000) {
|
|
$yyyy -= 2000;
|
|
}
|
|
else {
|
|
$yyyy -= 1900;
|
|
}
|
|
$numval = ($yyyy) + ($mm << 8) + ($dd << 16);
|
|
}
|
|
$hexval = sprintf("00%.6x",$numval);
|
|
}
|
|
#4-Octet unsigned value
|
|
elsif ($code eq "dpt12") {
|
|
$numval = $value;
|
|
$hexval = sprintf("00%.8x",($numval));
|
|
}
|
|
#4-Octet Signed Value
|
|
elsif ($code eq "dpt13") {
|
|
#build 2-complement
|
|
$numval = unpack("L", pack("l", $value));
|
|
$hexval = sprintf("00%.8x",$numval);
|
|
}
|
|
#4-Octet single precision float
|
|
elsif ($code eq "dpt14") {
|
|
$numval = unpack("L", pack("f", $value));
|
|
$hexval = sprintf("00%.8x",$numval);
|
|
}
|
|
#14-Octet String
|
|
elsif ($code eq "dpt16") {
|
|
#convert to latin-1
|
|
$numval = encode("iso-8859-1", decode("utf8", $value));
|
|
|
|
#convert to hex-string
|
|
my $dat = unpack "H*", $numval;
|
|
|
|
$dat = '00' if ($value =~ /^$PAT_DPT16_CLR/ix); # send all zero string if "clear line string"
|
|
|
|
#format for 14-byte-length and replace trailing blanks with zeros
|
|
$hexval = sprintf("00%-28s",$dat);
|
|
$hexval =~ s/\s/0/gx;
|
|
}
|
|
#DateTime
|
|
elsif ($code eq "dpt19") {
|
|
my $ts = time; # default or when "now" is given
|
|
# if no match we assume now and use current date/time
|
|
$ts = fhemTimeLocal($6, $5, $4, $1, $2-1, $3 - 1900) if ($value =~ m/^$PAT_DATE$PAT_DTSEP$PAT_TIME/x);
|
|
my ($secs,$mins,$hours,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($ts);
|
|
$wday = 7 if ($wday eq "0"); # calculate offset for weekday
|
|
$hours += ($wday << 5); # add day of week
|
|
my $status1 = 0x20; # Fault=0, WD = 0, NWD = 1 (WD Field valid), NY = 0, ND = 0, NDOW= 0,NT=0, SUTI = 0
|
|
$status1 += 1 if ($isdst == 1);
|
|
my $status0 = 0x00; # CLQ=0
|
|
$mon++;
|
|
$numval = 0;
|
|
$hexval = sprintf("00%02x%02x%02x%02x%02x%02x%02x%02x",$year,$mon,$mday,$hours,$mins,$secs,$status1,$status0);
|
|
}
|
|
# HVAC 1Byte
|
|
elsif ($code eq "dpt20") {
|
|
$numval = "00" if ($value =~ m/Auto/ix);
|
|
$numval = "01" if ($value =~ m/Comfort/ix);
|
|
$numval = "02" if ($value =~ m/Standby/ix);
|
|
$numval = "03" if ($value =~ m/Economy/ix);
|
|
$numval = "04" if ($value =~ m/Protection/ix);
|
|
$hexval = sprintf("00%.2x",($numval));
|
|
}
|
|
#RGB-Code
|
|
elsif ($code eq "dpt232") {
|
|
$hexval = "00" . $value;
|
|
$numval = $value;
|
|
}
|
|
else {
|
|
Log3 ($name, 2, "KNX_encodeByDpt: $gadName, model: $model not valid");
|
|
return $UNDEF;
|
|
}
|
|
|
|
Log3 ($name, 5, "KNX_encodeByDpt -exit: model: $model, code: $code, value: $value, numval: $numval, hexval: $hexval");
|
|
return $hexval;
|
|
}
|
|
|
|
# decode KNX-Message according DPT
|
|
sub KNX_decodeByDpt {
|
|
my ($hash, $value, $gadName) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
#get model
|
|
my $model = $hash->{GADDETAILS}{$gadName}{MODEL};
|
|
my $code = $dpttypes{$model}{CODE};
|
|
|
|
#return unchecked, if this is a autocreate-device
|
|
return 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, "KNX_decodeByDpt -enter: model: $model, code: $code, value: $value");
|
|
|
|
my $min = $dpttypes{"$model"}{MIN};
|
|
my $max = $dpttypes{"$model"}{MAX};
|
|
|
|
#Binary value
|
|
if ($code eq "dpt1") {
|
|
$numval = $min;
|
|
$numval = $max if ($value =~ m/01/ix);
|
|
$state = $numval;
|
|
}
|
|
#Step value (two-bit)
|
|
elsif ($code eq "dpt2") {
|
|
$numval = hex ($value);
|
|
|
|
if ($model eq 'dpt2.000') { ## JoeALLb request
|
|
$state = $numval;
|
|
}
|
|
else {
|
|
$state = "off" if ($numval == 0);
|
|
$state = "on" if ($numval == 1);
|
|
$state = "forceOff" if ($numval == 2);
|
|
$state = "forceOn" if ($numval == 3);
|
|
}
|
|
}
|
|
#Step value (four-bit)
|
|
elsif ($code eq "dpt3") {
|
|
$numval = hex ($value);
|
|
|
|
my $dir = ($numval & 0x08) >> 3;
|
|
my $step = ($numval & 0x07);
|
|
my $stepcode = 0;
|
|
if ($step > 0) {
|
|
$stepcode = int(100 / (2**($step-1)));
|
|
$stepcode *= -1 if ($dir == 0);
|
|
}
|
|
$state = sprintf ("%.0f", $stepcode);
|
|
}
|
|
#1-Octet unsigned value
|
|
elsif ($code eq "dpt5") {
|
|
$numval = hex ($value);
|
|
$state = KNX_limit ($hash, $numval, $gadName, "DECODE");
|
|
|
|
$state = sprintf ("%.0f", $state);
|
|
}
|
|
#1-Octet signed value
|
|
elsif ($code eq "dpt6") {
|
|
$numval = hex ($value);
|
|
$numval = unpack("c",pack("C",$numval));
|
|
$state = KNX_limit ($hash, $numval, $gadName, "DECODE");
|
|
|
|
$state = sprintf ("%d", $state);
|
|
}
|
|
#2-Octet unsigned Value
|
|
elsif ($code eq "dpt7") {
|
|
$numval = hex ($value);
|
|
$state = KNX_limit ($hash, $numval, $gadName, "DECODE");
|
|
|
|
$state = sprintf ("%.0f", $state);
|
|
}
|
|
#2-Octet signed Value
|
|
elsif ($code eq "dpt8") {
|
|
$numval = hex ($value);
|
|
$numval = unpack("s",pack("S",$numval));
|
|
$state = KNX_limit ($hash, $numval, $gadName, "DECODE");
|
|
|
|
$state = sprintf ("%d", $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;
|
|
|
|
$numval = KNX_limit ($hash, $numval, $gadName, "DECODE");
|
|
|
|
$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);
|
|
my $wday = ($numval & 0xE00000) >> 21;
|
|
my @wdays = (q{},'Monday, ','Tuesday, ','Wednesday, ','Thursday, ','Friday, ','Saturday, ','Sunday, ');
|
|
|
|
$state = sprintf("%02d:%02d:%02d",$hours,$mins,$secs);
|
|
# $state = sprintf("%s%02d:%02d:%02d",$wdays[$wday],$hours,$mins,$secs); # new option ?
|
|
}
|
|
#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 = KNX_limit ($hash, $numval, $gadName, "DECODE");
|
|
|
|
$state = sprintf ("%.0f", $state);
|
|
}
|
|
#4-Octet Signed Value
|
|
elsif ($code eq "dpt13") {
|
|
$numval = hex ($value);
|
|
$numval = unpack("l",pack("L",$numval));
|
|
$state = KNX_limit ($hash, $numval, $gadName, "DECODE");
|
|
|
|
$state = sprintf ("%d", $state);
|
|
}
|
|
#4-Octet single precision float
|
|
elsif ($code eq "dpt14") {
|
|
$numval = unpack "f", pack "L", hex ($value);
|
|
$numval = KNX_limit ($hash, $numval, $gadName, "DECODE");
|
|
|
|
$state = sprintf ("%.3f","$numval");
|
|
}
|
|
#14-Octet String
|
|
elsif ($code eq "dpt16") {
|
|
$numval = 0;
|
|
$state = q{};
|
|
$value =~ s/\s*$//gx; # strip trailing blanks
|
|
|
|
$state = pack("H*",$value);
|
|
|
|
#convert to latin-1
|
|
$state = encode ("utf8", $state) if ($model =~ m/16.001/x);
|
|
|
|
$state = q{} if ($state =~ m/^[\x00]/ix); # case all zeros received
|
|
|
|
#remove non printable chars
|
|
$state =~ s/[\x00-\x1F]+//gx;
|
|
}
|
|
#DateTime
|
|
elsif ($code eq "dpt19") {
|
|
$numval = substr($value,-16); # strip off 1st byte
|
|
my $time = hex (substr ($numval, 6, 6));
|
|
my $date = hex (substr ($numval, 0, 6));
|
|
my $secs = ($time & 0x3F) >> 0;
|
|
my $mins = ($time & 0x3F00) >> 8;
|
|
my $hours = ($time & 0x1F0000) >> 16;
|
|
my $day = ($date & 0x1F) >> 0;
|
|
my $month = ($date & 0x0F00) >> 8;
|
|
my $year = ($date & 0xFF0000) >> 16;
|
|
#extras
|
|
my $wday = ($time & 0xE00000) >> 21; # 0 = anyday/not valid, 1= Monday,...
|
|
|
|
$year += 1900;
|
|
$state = sprintf("%02d.%02d.%04d_%02d:%02d:%02d", $day, $month, $year, $hours, $mins, $secs);
|
|
}
|
|
elsif ($code eq "dpt20") {
|
|
$numval = hex ($value);
|
|
$state = "Auto" if ($numval >=0);
|
|
$state = "Comfort" if ($numval >=1);
|
|
$state = "Standby" if ($numval >=2);
|
|
$state = "Economy/Night" if ($numval >=3);
|
|
$state = "Protection/Frost/Heat" if ($numval >=4);
|
|
}
|
|
#RGB-Code
|
|
elsif ($code eq "dpt232") {
|
|
$numval = hex ($value);
|
|
$state = sprintf ("%.6x",$numval);
|
|
}
|
|
else {
|
|
Log3 ($name, 2, "KNX_decodeByDpt: $model, no valid model defined");
|
|
return;
|
|
}
|
|
|
|
#append unit, if supplied
|
|
my $unit = $dpttypes{$model}{UNIT};
|
|
$state = $state . q{ } . $unit if (defined ($unit) && ($unit ne q{}));
|
|
|
|
Log3 ($name, 5, "KNX_decodeByDpt -exit: model: $model, code: $code, value: $value, numval: $numval, state: $state");
|
|
return $state;
|
|
}
|
|
|
|
1;
|
|
|
|
=pod
|
|
|
|
=encoding utf8
|
|
|
|
=item [device]
|
|
=item summary Devices communicate via the IO-Device TUL or KNXTUL with KNX-bus
|
|
=item summary_DE Geräte kommunizieren über IO-Gerät TUL / mit dem KNX-Bus
|
|
|
|
=begin html
|
|
|
|
<style>
|
|
.wrap {
|
|
list-style-type: none;
|
|
padding-left: 30px;
|
|
width:100%;
|
|
column-count:2;
|
|
column-gap:20px;
|
|
-moz-column-count:2;
|
|
-moz-column-gap:20px;
|
|
-webkit-column-count:2;
|
|
-webkit-column-gap:20px;}
|
|
.wrap.ul {
|
|
float: left;
|
|
margin: 3px;
|
|
padding: 3px;
|
|
width: 380px;
|
|
border: 3px solid gray; }
|
|
.wrap li {
|
|
white-space: pre; }
|
|
.wrap.a {
|
|
float: left;
|
|
margin: 3px;
|
|
padding: 3px;
|
|
width: 380px;
|
|
border: 3px solid gray; }
|
|
.pad20l {padding-left: 20px;}
|
|
.pad30l {padding-left: 30px;}
|
|
.pad40l {padding-left: 40px;}
|
|
</style>
|
|
<a id="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>
|
|
<p>For getting started, please refer to this document: <a href="https://www2.knx.org/media/docs/downloads/Marketing/Flyers/KNX-Basics/KNX-Basics_en.pdf">KNX-Basics</a></p>
|
|
<p>While the <a href="#TUL">TUL-module</a> or the <a href="#KNXTUL">KNXTUL-module</a> represent the connection to the KNX network, the KNX modules represent individual KNX devices. <br />
|
|
This module provides a basic set of operations (on, off, toggle, on-until, on-for-timer) to switch on/off KNX devices and to send values to the bus. </p>
|
|
<p>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.</p>
|
|
<p>For each received telegram there will be a reading with containing the received value and the sender address.<br />
|
|
For every set, there will be a reading containing the sent value.<br />
|
|
The reading <state> will be updated with the last sent or received value. </p>
|
|
|
|
<a id="KNX-define"></a>
|
|
<p><strong>Define</strong></p>
|
|
<p><code>define <name> KNX <group>:<DPT>[:[gadName]:[set|get|listenonly]:[nosuffix]] [<group>:<DPT> ..] [IODev]</code></p>
|
|
<p><strong>Important: a KNX device needs at least one concrete DPT.</strong> Please refer to <a href="#KNX-dpt">available DPT</a>. Otherwise the system cannot en- or decode the messages.<br />
|
|
<strong>Devices defined by autocreate have to be reworked with the suitable dpt and the disable attribute cleared. Otherwise they won't do anything.</strong></p>
|
|
|
|
<p>The <group> parameters are either a group name notation (0-31/0-15/0-255) or the hex representation of the value ([00-1f][0-f][00-ff]) (5 digits).
|
|
All of the defined groups can be used for bus-communication.
|
|
It is not allowed to have the same group more then once in one device. You can have multiple devices containing the same adresses.<br />
|
|
As described above the parameter <DPT> must contain the corresponding DPT.<br />
|
|
The optional parameteter [gadName] may contain an alias for the GAD. The gadName <b>must not</b> begin with one of the following strings: on, off, on-for-timer,
|
|
on-until, off-for-timer, off-until, toggle, raw, rgb, string, value, set, get, listenonly, nosuffix.<br />
|
|
Especially if attribute <code>answerReading</code> is set to 1, it might be useful to modifiy the behaviour of single GADs. If you want to restrict the GAD, you can raise the flags "get", "set", or "listenonly".
|
|
The usage should be self-explanatory. It is not possible to combine the flags.<br />
|
|
Furthermore you can supply a IO-Device directly at startup. This is only required in special cases. For details see: <a href="#KNX-attr-KNX_IODev">IODev Attribute</a>.</p>
|
|
<p>The GAD's are per default named with "g<number>". The corresponding reading-names are getG<number>, setG<number> and putG<number>.<br />
|
|
If you supply <gadName> this name is used instead. The readings are <gadName>-get, <gadName>-set and <gadName>-put.
|
|
We will use the synonyms <getName>, <setName> and <putName> in this documentation.
|
|
If you add the option "nosuffix", <getName>, <setName> and <putName> have the identical name - only <gadName>.</p>
|
|
<p>Per default, the first group is used for sending. If you want to send via a different group, you have to address it. E.g: <code>set <name> <gadName> <value> </code></p>
|
|
<p>Without further attributes, all incoming and outgoing messages are translated into reading <state>.</p>
|
|
<p>If enabled, the module <a href="#autocreate">autocreate</a> is creating a new definition for any unknown sender. The device itself will be disabled
|
|
until you added a DPT to the definition and clear the disabled attribute. The name will be KNX_nnmmooo where nn is the line adress, mm the area and ooo the device.
|
|
No FileLog or SVG definition is created for KNX-devices by autocreate. Use for example <code>define <name> FileLog <filename> KNX_.*</code>
|
|
to create a single FileLog-definition for all KNX-devices created by autocreate.<br />
|
|
Another option is to disable autocreate for KNX-devices in production environments (when no changes / additions are expected) by using: <code>attr <autocreate> ignoreTypes KNX_.*</code></p>
|
|
|
|
<p>Examples:</p>
|
|
<blockquote>
|
|
<code>define lamp1 KNX 0/10/11:dpt1:listenonly</code><br/>
|
|
<code>attr lamp1 webCmd on:off</code><br/>
|
|
<code>attr lamp1 devStateIcon on::off off::on</code><br/>
|
|
<br/>
|
|
<code>define lamp2 KNX 0/10/12:dpt1:steuern 0/10/13:dpt1.001:status</code><br/>
|
|
<br/>
|
|
<code>define lamp3 KNX 00A0D:dpt1.003 myTul</code><br/>
|
|
</blockquote>
|
|
|
|
<a id="KNX-set"></a>
|
|
<p><strong>Set</strong></p>
|
|
<p><code>set <deviceName> [gadName] <on|off|toggle><br />
|
|
set <deviceName> [gadName] <on-for-timer|on-until|off-for-timer|off-until> <timespec><br />
|
|
set <deviceName> [gadName] <value><br /></code></p>
|
|
<p>Set sends the given value to the bus.<br /> If <gadName> is omitted, the first listed GAD of the device is used.
|
|
If the GAD is restricted in the definition with "get" or "listenonly", the set-command will be refused.<br />
|
|
For dpt1 and dpt1.001 valid values are on, off and toggle. Also the timer-functions can be used.
|
|
For all other binary DPT (dpt1.xxx) the min- and max-values can be used for en- and decoding alternatively to on/off.<br/>
|
|
After successful sending the value, it is stored in the readings <setName>.</p>
|
|
<p>Examples:</p>
|
|
<blockquote>
|
|
<code>set lamp2 on</code><br/>
|
|
<code>set lamp2 off</code><br/>
|
|
<code>set lamp2 on-for-timer 10</code><br/>
|
|
<code>set lamp2 on-until 13:15:00</code><br/>
|
|
<code>set lamp2 steuern on-until 13:15:00</code><br/>
|
|
<br/>
|
|
<code>set myThermoDev 23.44</code><br/>
|
|
<br/>
|
|
<code>set my MessageDev Hallo Welt</code><br/>
|
|
</blockquote>
|
|
|
|
<a id="KNX-get"></a>
|
|
<p><strong>Get</strong></p>
|
|
<p>If you execute "get" for a KNX-Element the status will be requested from the device. The device has to be able to respond to a read - this is not given for all devices.<br />
|
|
If the GAD is restricted in the definition with "set", the execution will be refused.<br />
|
|
The answer from the bus-device updates reading and state.</p>
|
|
|
|
<a id="KNX-attr"></a>
|
|
<p><strong>Common attributes</strong></p>
|
|
<div>
|
|
<a href="#DbLogInclude">DbLogInclude</a><br />
|
|
<a href="#DbLogExclude">DbLogExclude</a><br />
|
|
<a href="#DbLogValueFn">DbLogValueFn</a><br />
|
|
<a href="#alias">alias</a><br />
|
|
<a href="#cmdIcon">cmdIcon</a><br />
|
|
<a href="#comment">comment</a><br />
|
|
<a href="#devStateIcon">devStateIcon</a><br />
|
|
<a href="#devStateStyle">devStateStyle</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="#oldreadings">oldreadings</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="#timestamp-on-change-reading">timestamp-on-change-reading</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="#webCmdLabel">webCmdLabel</a><br />
|
|
<a href="#widgetOverride">widgetOverride</a>
|
|
</div>
|
|
|
|
<p><strong>Special attributes</strong></p>
|
|
<ul>
|
|
<a id="KNX-attr-answerReading"></a><li>answerReading<br/>
|
|
If enabled, FHEM answers on read requests. The content of reading <state> is sent to the bus as answer.
|
|
If defined, the content of the reading <putName> is used as value for the answer.</li>
|
|
<br/>
|
|
<a id="KNX-attr-stateRegex"></a><li>stateRegex<br/>
|
|
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 <getName>:<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, ...<br/>
|
|
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.</li>
|
|
<br/>
|
|
<a id="KNX-attr-stateCmd"></a><li>stateCmd<br/>
|
|
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.<br/>
|
|
Unlike stateFormat the stateCmd modifies also the content of the reading, not only the hash-content for visualization.<br/>
|
|
You can access the device-hash ("$hash") in the perl string (e.g: $hash{IODev} )in yr. perl-cmd. In addition the variables "$name", "$gadName" and "$state" are avavailable.
|
|
The return-value overrides "state".</li>
|
|
<br/>
|
|
<a id="KNX-attr-putCmd"></a> <li>putCmd<br/>
|
|
Every time a KNX-value is requested from the bus to FHEM, the content of putCmd is evaluated before the answer is sent. You can supply a perl-command for modifying content.
|
|
If putCmd is defined, the attr answerReading has no effect.
|
|
This command is executed directly before sending the data. A copy is stored in the reading <putName>.<br/>
|
|
Each device only knows one putCmd, so you have to take care about the different GAD's in the perl string.<br/>
|
|
Like in stateCmd you can access the device hash ("$hash") in yr. perl-cmd. In addition the variables "$name", "$gadName" and "$state" are avavailable.
|
|
"$state" contains the prefilled return-value. The return-value overrides "state".</li>
|
|
<br/>
|
|
<a id="KNX-attr-format"></a><li>format<br/>
|
|
The content of this attribute is appended to every received value, before copied to state.</li>
|
|
<br/>
|
|
<a id="KNX-attr-disable"></a><li>disable<br/>
|
|
Disable the device if set to <b>1</b>. No send/receive from bus and no set/get possible. Delete this attr to enable device again.</li>
|
|
<br/>
|
|
<a id="KNX-attr-KNX_toggle"></a><li>KNX_toggle<br/>
|
|
Lookup current value before issuing "set device <gadName> toggle" cmd.<br/>
|
|
FHEM has to retrieve a current value to make the toggle-cmd acting correctly. This attribute can be used to define the source of the current value.<br/>
|
|
Format is: <b><devicename>:<readingname></b>. If you want to use a reading from own device, you can use "$self" as devicename. Be aware that only <b>on</b> and <b>off</b>
|
|
are supported as valid values when defining device:readingname.<br/>
|
|
If this attribute is not defined, the current value will be taken from owndevice:readingName-get or, if readingName-get is not defined, the value will be taken from readingName-set.</li>
|
|
<br/>
|
|
<a id="KNX-attr-IODev"></a><li>IODev<br/>
|
|
This attr will be set to the default (or only) TUL/KNXTUL device during device definition. Manually setting this attr is not required, except in cases where multiple IO-devices
|
|
(of type TUL/KNXTUL) are defined. Defining more than one IO-device is NOT recommended unless you take special care with yr. knxd or KNX-router definitions -
|
|
to prevent multiple path resulting in message loops.</li>
|
|
<br/>
|
|
<!--<a id="KNX-attr-KNX_FIFO"></a><li>KNX_FIFO<br/>
|
|
Set this attr to 1 <b> in the IO-device ! </b>to enable a receive buffer for incoming messages. The KNX-messages will not processed faster,
|
|
but the overall responsiveness and latency of FHEM benefit from this setting.</li>
|
|
<br/>
|
|
-->
|
|
<a id="KNX-attr-listenonly"></a><li>listenonly - This attr is deprecated - do not use - see cmdref device definition for alternatives</li>
|
|
<a id="KNX-attr-readonly"></a><li>readonly - This attr is deprecated - do not use - see cmdref device definition for alternatives</li>
|
|
<a id="KNX-attr-slider"></a><li>slider - This attr is deprecated - do not use - see slider example in cmdref for alternatives</li>
|
|
<a id="KNX-attr-useSetExtensions"></a><li>useSetExtensions - This attr is deprecated - do not use</li>
|
|
</ul>
|
|
|
|
<a id="KNX-dpt"></a>
|
|
<p><strong>DPT - datapoint-types</strong></p>
|
|
<p>The following dpt are implemented and have to be assigned within the device definition.</p>
|
|
<ul>
|
|
<li><b>dpt1 </b> on, off</li>
|
|
<li><b>dpt1.000 </b> 1, 0</li>
|
|
<li><b>dpt1.001 </b> on, off</li>
|
|
<li><b>dpt1.002 </b> true, false</li>
|
|
<li><b>dpt1.003 </b> enable, disable</li>
|
|
<li><b>dpt1.004 </b> no ramp, ramp</li>
|
|
<li><b>dpt1.005 </b> no alarm, alarm</li>
|
|
<li><b>dpt1.006 </b> low, high</li>
|
|
<li><b>dpt1.007 </b> decrease, increase</li>
|
|
<li><b>dpt1.008 </b> up, down</li>
|
|
<li><b>dpt1.009 </b> open, closed</li>
|
|
<li><b>dpt1.010 </b> start, stop</li>
|
|
<li><b>dpt1.011 </b> inactive, active</li>
|
|
<li><b>dpt1.012 </b> not inverted, inverted</li>
|
|
<li><b>dpt1.013 </b> start/stop, ciclically</li>
|
|
<li><b>dpt1.014 </b> fixed, calculated</li>
|
|
<li><b>dpt1.015 </b> no action, reset</li>
|
|
<li><b>dpt1.016 </b> no action, acknowledge</li>
|
|
<li><b>dpt1.017 </b> trigger, trigger</li>
|
|
<li><b>dpt1.018 </b> not occupied, occupied</li>
|
|
<li><b>dpt1.019 </b> closed, open</li>
|
|
<li><b>dpt1.021 </b> logical or, logical and</li>
|
|
<li><b>dpt1.022 </b> scene A, scene B</li>
|
|
<li><b>dpt1.023 </b> move up/down, move and step mode</li>
|
|
<li><b>dpt2 </b> off, on, forceOff, forceOn</li>
|
|
<li><b>dpt2.000 </b> 0,1,2,3</li>
|
|
<li><b>dpt3 </b> -100..+100</li>
|
|
<li><b>dpt3.007 </b> -100..+100 %</li>
|
|
<li><b>dpt5 </b> 0..255</li>
|
|
<li><b>dpt5.001 </b> 0..100 %</li>
|
|
<li><b>dpt5.003 </b> 0..360 °</li>
|
|
<li><b>dpt5.004 </b> 0..255 %</li>
|
|
<li><b>dpt6 </b> -128..+127</li>
|
|
<li><b>dpt6.001 </b> -128 %..+127 %</li>
|
|
<li><b>dpt6.010 </b> -128..+127</li>
|
|
<li><b>dpt7 </b> 0..65535</li>
|
|
<li><b>dpt7.001 </b> 0..65535 s</li>
|
|
<li><b>dpt7.005 </b> 0..65535 s</li>
|
|
<li><b>dpt7.006 </b> 0..65535 m</li>
|
|
<li><b>dpt7.007 </b> 0..65535 h</li>
|
|
<li><b>dpt7.012 </b> 0..65535 mA</li>
|
|
<li><b>dpt7.013 </b> 0..65535 lux</li>
|
|
<li><b>dpt7.600 </b> 0..12000 K</li>
|
|
<li><b>dpt8 </b> -32768..32768</li>
|
|
<li><b>dpt8.005 </b> -32768..32768 s</li>
|
|
<li><b>dpt8.010 </b> -32768..32768 %</li>
|
|
<li><b>dpt8.011 </b> -32768..32768 °</li>
|
|
<li><b>dpt9 </b> -670760.0..+670760.0</li>
|
|
<li><b>dpt9.001 </b> -274.0..+670760.0 °C</li>
|
|
<li><b>dpt9.002 </b> -670760.0..+670760.0 K</li>
|
|
<li><b>dpt9.003 </b> -670760.0..+670760.0 K/h</li>
|
|
<li><b>dpt9.004 </b> -670760.0..+670760.0 lux</li>
|
|
<li><b>dpt9.005 </b> -670760.0..+670760.0 m/s</li>
|
|
<li><b>dpt9.006 </b> -670760.0..+670760.0 Pa</li>
|
|
<li><b>dpt9.007 </b> -670760.0..+670760.0 %</li>
|
|
<li><b>dpt9.008 </b> -670760.0..+670760.0 ppm</li>
|
|
<li><b>dpt9.009 </b> -670760.0..+670760.0 m³/h</li>
|
|
<li><b>dpt9.010 </b> -670760.0..+670760.0 s</li>
|
|
<li><b>dpt9.011 </b> -670760.0..+670760.0 ms</li>
|
|
<li><b>dpt9.020 </b> -670760.0..+670760.0 mV</li>
|
|
<li><b>dpt9.021 </b> -670760.0..+670760.0 mA</li>
|
|
<li><b>dpt9.022 </b> -670760.0..+670760.0 W/m²</li>
|
|
<li><b>dpt9.023 </b> -670760.0..+670760.0 K/%</li>
|
|
<li><b>dpt9.024 </b> -670760.0..+670760.0 kW</li>
|
|
<li><b>dpt9.025 </b> -670760.0..+670760.0 l/h</li>
|
|
<li><b>dpt9.026 </b> -670760.0..+670760.0 l/h</li>
|
|
<li><b>dpt9.028 </b> -670760.0..+670760.0 km/h</li>
|
|
<li><b>dpt9.029 </b> -670760.0..+670760.0 g/m³</li>
|
|
<li><b>dpt9.030 </b> -670760.0..+670760.0 μg/m³</li>
|
|
<li><b>dpt10 </b> 01:00:00 (Time: HH:MM:SS)</li>
|
|
<li><b>dpt11 </b> 01.01.2000 (Date: DD.MM.YYYY)</li>
|
|
<li><b>dpt12 </b> 0..+Inf</li>
|
|
<li><b>dpt13 </b> -Inf..+Inf</li>
|
|
<li><b>dpt13.010</b> -Inf..+Inf Wh</li>
|
|
<li><b>dpt13.013</b> -Inf..+Inf kWh</li>
|
|
<li><b>dpt14 </b> -Inf.0..+Inf.0</li>
|
|
<li><b>dpt14.019</b> -Inf.0..+Inf.0 A</li>
|
|
<li><b>dpt14.027</b> -Inf.0..+Inf.0 V</li>
|
|
<li><b>dpt14.033</b> -Inf.0..+Inf.0 Hz</li>
|
|
<li><b>dpt14.056</b> -Inf.0..+Inf.0 W</li>
|
|
<li><b>dpt14.057</b> -Inf.0..+Inf.0 cosΦ</li>
|
|
<li><b>dpt14.068</b> -Inf.0..+Inf.0 °C</li>
|
|
<li><b>dpt14.076</b> -Inf.0..+Inf.0 m³</li>
|
|
<li><b>dpt16 </b> String</li>
|
|
<li><b>dpt16.000</b> ASCII-String</li>
|
|
<li><b>dpt16.001</b> ISO-8859-1-String (Latin1)</li>
|
|
<li><b>dpt17.001</b> Scene number: 0..63</li>
|
|
<li><b>dpt18.001</b> Scene number: 1..64. <br/> Watch out - only "activation" works. <br/> "Learning" will be limited to 64...</li>
|
|
<li><b>dpt19 </b> 01.12.2020_01:02:03 (Date & Time combined)</li>
|
|
<li><b>dpt19.001</b> 01.12.2020_01:02:03</li>
|
|
<li><b>dpt20.102</b> HVAC mode</li>
|
|
<li><b>dpt22.101</b> not yet implemented</li>
|
|
<li><b>dpt232 </b> RGB-Value RRGGBB</li>
|
|
</ul>
|
|
|
|
<p> </p>
|
|
<p><strong>More complex examples:</strong></p>
|
|
<p><em>Rollo:</em></p>
|
|
<code>define rollo KNX 0/10/12:dpt1.008:wdw1 0/10/13:dpt1</code><br/>
|
|
<code>set rollo wdw1 down </code>moves down rollo at window 1<br/>
|
|
<code>set rollo g2 on </code>moves up rollo at window 2</br>
|
|
<code>set rollo g2 off-for-timer 5 </code>moves down rollo at window 2 for 5 sec<br/>
|
|
|
|
<p><em>Object with feedback, icon showing transistions:</em><br/></p>
|
|
<code>define sps KNX 0/4/0:dpt1:steuern 0/4/1:dpt1:status</code><br/>
|
|
<code>attr sps devStateIcon status-on:general_an:Aus status-off:general_aus:Ein steuern.*:hourglass:Aus</code><br/>
|
|
<code>attr sps eventMap /steuern on:Ein/steuern off:Aus/</code><br/>
|
|
<code>attr sps stateRegex /steuern-set:/steuern-/ /steuern-get:// /status-get:/status-/</code><br/>
|
|
<code>attr sps webCmd Ein:Aus</code><br/>
|
|
|
|
<p><em>Object with feedback, state is always showing status:</em></p>
|
|
<code>define wasser_status KNX 11/3/0:dpt1.001:status:listenonly 11/3/1:dpt1.001:steuern-auf 11/3/2:dpt1.001:steuern-zu</code><br/>
|
|
<code>attr wasser_status devStateIcon on:general_an off:general_aus</code><br/>
|
|
<code>attr wasser_status stateCmd {sprintf("%s", ReadingsVal($name,"status-get",""))}</code><br/>
|
|
<code>attr wasser_status webCmd :</code><br/>
|
|
|
|
<p><em>If requested, fhem answers content of GAD refVal to GAD temp, answer nothing to GAD humidity. Both refVal and temp <b>must</b>
|
|
have the same dpt-code (e.g dpt9 in this example), else garbage will be returned!:</em></p>
|
|
<code>define demo KNX 1/0/30:dpt9.001:temp 1/0/31:dpt9.001:humidity 1/0/32:dpt9:refVal</code><br/>
|
|
<code>attr demo putCmd {ReadingsNum("demo","refVal-get",0) if ($gadName =~ /temp/);}</code><br/>
|
|
|
|
<p><em>Time master:</em></p>
|
|
<code>define timedev KNX 0/0/7:dpt10:time 0/0/8:dpt11:date 0/0/9:dpt19</code><br/>
|
|
<code>set timedev date now or set timedev date 01.11.2020 </code>Send date to the bus<br/>
|
|
<code>set timedev [time] now or set timedev time 18:33:44 </code>Send time to the bus<br/>
|
|
<code>set timedev g3 now or set timedev g3 01.11.2020_18:33:44 </code>Send date <b>and</b> time to the bus (combined)<br/>
|
|
|
|
<p><em>Send text to the bus:</em></p>
|
|
<code>define textdev KNX 0/0/9:dpt16</code><br/>
|
|
<code>set textdev g1 AbCdEfGhIjkLmN </code>send text to Bus (max 14 Char.)<br/>
|
|
<code>set textdev g1 >CLR< </code>delete text on the KNX display<br/>
|
|
|
|
<p><em>Slider:</em></p>
|
|
<code>define newTest KNX 15/2/2:dpt5</code><br/>
|
|
<code>attr newTest webCmd g1</code><br/>
|
|
<code>attr newTest widgetOverride setG1:slider,0,5,100</code><br/>
|
|
|
|
<p><em>Two independent slider and on/off buttons:</em></p>
|
|
<code>define newTest KNX 15/2/9:dpt5 15/2/3:dpt5 15/2/2:dpt1.001:power</code><br/>
|
|
<code>attr newTest IODev knxd</code><br/>
|
|
<code>attr newTest eventMap { usr=>{'^getG1 (\d+)'=>'g1 $1','^getG2 (\d+)'=>'g2 $1','^An'=>'power on','^Aus'=>'power off'}, \</code>
|
|
<blockquote>
|
|
<code>fw=>{'^getG1 (\d+)'=>'getG1','^getG2 (\d+)'=>'getG2','^power-get'=>'state'} \</code>
|
|
</blockquote>
|
|
<code>}</code><br/>
|
|
<code>attr newTest webCmd An:Aus::Label1:getG1::Label2:getG2</code><br/>
|
|
<code>attr newTest widgetOverride getG1:slider,0,5,100 getG2:slider,0,5,100</code><br/>
|
|
|
|
<p><em>Synchronized slider for send and reveive-values, on/off-buttons and a state-icon:</em></p>
|
|
<code>define testDev11 KNX 15/1/19:dpt1:steuern 15/1/20:dpt1:status 15/1/21:dpt5.001:dimmwert:nosuffix\</code><br/>
|
|
<code>attr testDev11 IODev knxd</code><br/>
|
|
<code>attr testDev11 eventMap {\</code>
|
|
<blockquote>
|
|
<code>#Von Device nach Frontend sollte eigentlich bei einem Dezimalwert \</code><br/>
|
|
<code>#das Reading status-get angezeigt werden. Geht aber nicht. Deshalb: stateCmd... \</code><br/>
|
|
<code>dev=>{# '^(\d+)?.%$'=>ReadingsVal($dev,'status-get',"Zefix...")} \</code><br/>
|
|
<code>#Frontend nach Device: Ersetze "An"/"Aus" durch "Steuern on/off". \</code><br/>
|
|
<code>#Alle numerischen Werte vom Slider landen in "dimmwert" \</code><br/>
|
|
<code>usr=>{'^An'=>'steuern on', '^Aus'=>'steuern off'}, \</code><br/>
|
|
<code>#Tuning f$uuml;r die Detailseite...Zeige An/Aus richtig an \</code><br/>
|
|
<code>fw=>{'^An'=>'An', '^Aus'=>'Aus'} \</code>
|
|
</blockquote>
|
|
<code>}</code><br/>
|
|
<code>attr testDev11 stateRegex /steuern-set:/steuern-/ /steuern-get:// /status-get:/status-/</code><br/>
|
|
<code>attr testDev11 stateCmd {\</code>
|
|
<blockquote>
|
|
<code>if ($state =~ m/dimmwert:/i) {'status-' . ReadingsVal($name,"status-get","")}\</code><br/>
|
|
<code>else {return $state}\</code>
|
|
</blockquote>
|
|
<code>}</code><br/>
|
|
<code>attr testDev11 devStateIcon status-on:general_an:Aus status-off:general_aus:Ein steuern.*:hourglass:Aus</code><br/>
|
|
<code>#Dimmwert muss ein reales reading sein. Kann auch ...-get sein, wenn</code><br/>
|
|
<code>#nosuffix nicht angegeben ist.</code><br/>
|
|
<code>#Achtung: bei einem Userreading sind die Werte nicht persistent, also nicht machen!!!</code><br/>
|
|
<code>attr testDev11 webCmd An:Aus:dimmwert</code><br/>
|
|
<code>attr testDev11 widgetOverride dimmwert:slider,0,5,100</code><br/>
|
|
|
|
<p><em>Remap values to/from Bus:</em><br />
|
|
Problem: the Bus-device send's and expects values 0,31,63,95,127,159,191,223,255 but you want 0-8 in your UI...</p>
|
|
<code>
|
|
define testStufen KNX 0/4/2:dpt5:StufeIst:get 0/4/7:dpt5:StufeSoll:set<br />
|
|
attr testStufen eventMap { usr => {'^Stufe.(\d)$' => '".sprintf("StufeSoll %d",$1*32 -1)."' }, fw => {'^Stufe.(\d)$' => 'Stufe'} }<br />
|
|
attr testStufen stateCmd {if ($gadName =~ 'Stufe(Ist|Soll)') { \</code>
|
|
<blockquote><blockquote>
|
|
<code>my $newval = int(($state +1) / 32); \</code><br />
|
|
<code>#Log3 $name,1, "StateCmd2: $name $gadName Stufe$1 $state -> $newval"; \</code><br />
|
|
<code>fhem "sleep 0.1;setreading $name Stufe $newval"; \</code><br />
|
|
<code>OldValue($name); \</code>
|
|
</blockquote>
|
|
<code>} else { \</code>
|
|
<blockquote>
|
|
<code>$state; \</code>
|
|
</blockquote>
|
|
<code>} \</code>
|
|
</blockquote>
|
|
<code>}<br />
|
|
attr testStufen widgetOverride Stufe:slider,0,1,8
|
|
</code><br />
|
|
|
|
<p><em>How to handle dpt's not available in KNX-Module:</em><br />
|
|
Problem: You know you receive (from a KNX-Sensor/Device) a 2 Byte Floating Point Value, but the sub-Datapoint is not defined.<br />
|
|
Solution: define a base DPT, in this example dpt9 and add a unit-value with the format attribute.</p>
|
|
<code>
|
|
define dpt9test KNX 0/4/6:dpt9 <br />
|
|
attr dpt9test format g/m&sup3; # gramm / m³ <br />
|
|
</code>
|
|
With attr stateCmd you can do almost any conversion to the received value. Example: convert from Watt to kW:<br/>
|
|
<code>
|
|
attr dpt9test stateCmd { $state = $state / 1000; }<br/>
|
|
</code><br />
|
|
|
|
</ul>
|
|
<br />
|
|
|
|
=end html
|
|
|
|
=begin html_DE
|
|
|
|
<a id="KNX"></a>
|
|
<h3>KNX</h3>
|
|
<ul>
|
|
Eine deutsche Version der Dokumentation ist derzeit nicht vorhanden. Die englische Version ist hier zu finden:
|
|
<a href='./commandref.html#KNX'>KNX</a>
|
|
</ul>
|
|
|
|
=end html_DE
|
|
|
|
=cut
|