## no critic (Modules::RequireVersionVar,Policy::CodeLayout::RequireTidyCode) ##
# $Id$
################################################################################
### changelog:
# ABU 20180218 restructuring, removed older documentation
# MH 20210908 deleted part of change history
# 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 wiki
# MH 20211118 E04.90 fix dpt10 now, fix dpt19 workingdays
# fix dpt3 encode
# MH 20220107 E05.00 feature: add support for FHEM2FHEM as IO-Device
# E05.01 feature: utitity KNX_scan
# corrections cmd-ref
# optimize replaceByRegex
# MH 20220108 fix KNX_scan sub (export-problem)
# MH 202201xx E05.02 fix dpt14 "0"
# avoid undefined event when autocreate ignoreTypes is set
# MH 20220313 fix dpt20_decode
# new dpt22.101 receive only
# changed unit encoding to UTF8 eg: dpt9.030 from: μg/m³ to: µg/m³
# MH 20220403 allow 'nosuffix' as only option in define
# minor corrections to cmdref
# MH 20220429 minor additions to cmdref & some cleanup
# MH 20221019 cleanup, replace doubleqoutes in Log3, sprintf,pack,....
# add devicename to every Log msg
# rework doKNXscan, stateregex
# add dpt4, dpt15, added sub-dpts for dpts 3,5,8,9,14; corrected min/max/pattern values in dpts
# fix dpt19, dpt11 parsing
# unify Log-Msg's
# prevent setting deprecated (since 2018!) Attr: readonly,listenonly,slider - with errormsg
# prevent setting IODev in define...
# .... both will be completly removed with next version
# new: Internal "RAWMSG" shows msg from Bus while device is disabled (debugging)
# bugfix: allowed group-format corrected: was 0-31/0-15/0-255 -> now: 0-31/0-7/0-255 lt.KNX-spec
# MH 202210xx changed package name FHEM::KNX -> KNX
# changed svnid format
# fix dpt4,dpt16 encode/decode (ascii vs. ISO-8859-1)
# fix dpt14.057 unit 'dpt14.057' { cosφ vs. cosφ ) =>need UTF8 in DbLog spec !
# new dpt217 - for EBUSD KNX implementation
# no default slider in FHEMWEB-set/get for dpt7,8,9,12,13 - use widgetoverride slider !
# MH 20221113 cleanup, cmdref formatting
# MH 202212xx fix dpt217 range/fomatting, cmd-ref links,
# remove support for IODev in define
# modify disabled logic
# MH 20221226 device define after init_complete
# remove $hash->{DEVNAME}
# modify autocreate, get/set logic
# changed not user relevant internals to {.XXXX}
# changed DbLog_split function
# disabled StateFn
# MH 20230104 change pattern matching for dpt1 and dptxxx
# fix DbLogSplitFn
# MH 20230124 simplify DbLogSplitFn
# modify parsing of gadargs in define
# modify KNX_parse - reply msg code
# modify KNX_set
# add pulldown menu for attr IODev with vaild IO-devs
# KNX_scan now avail also from cmd-line
# MH 20230129 PBP changes /ms flag
# syntax check on attr stateCmd & putCmd
# fix define parsing
# MH 20230328 syntax check on attr stateregex
# reminder to migrate to KNXIO
# announce answerreading as deprecated
# MH 20230407 implement KNX_Log
# simplyfy checkAndClean-sub
# MH 20230415 fixed unint in define2 [line 543]
# fixed allowed range for dpt6.001
# add dpt7.002 - 7.004
# fixed scaling dpt8.003 .004 .010
# MH 20230525 rework define parsing
# removed deprecated (since 2018!) Attr's: readonly,listenonly,slider
# removed deprecated IODEV from define - errormsg changed!
# remove FACTOR & OFFSET keys from %dpttypes where FACTOR = undef/1 or OFFSET= undef/0
# correct rounding on dpt5.003 encode
# remove trailing zero(s) on reading values
# integrate sub checkandclean into encodebydpt
# cmd-ref: correct wiki links, W3C conformance...
# error-msg on invalid set dpt1 cmd (on-till...)
# Attr anwerReading now deprecated - converted to putCmd - autosave will be deactivated during fhem-restart if a answerReading is converted - use save!
# MH 20230615 move KNX_scan Function to KNXIO-Module - update both KNXIO and KNX-Module !!!
# update cmd-ref
# MH 20230713 cleanup, add events chapter to cmd-ref
# moved KNX_scan function to KNX-Module, KNX_scan cmdline cmd into new Module 98_KNX_scan.pm
# MH 20230828 verify allowed oldsyntax cmd's,
# deprecate announcement for cmd's: raw,value,string,rgb
# improve regex for dpt4, dpt16
# add sub-dpts to dpt1,7,8,12,13,14 (new KNX-spec)
# change max-limit for dpt9 acc. to new KNX-spec to 670433.28
# implementation beta-test dpt251.600, dpt221
# new dpt: dptRAW -allow unlimited hex char. w.o. any checking !
# do NOT remove leading & trailing zeros on reading values (hex values would be destroyed)
# allow more than 14 char on "set dpt16" - truncate during encoding to 1st 14 char
# hide set-pulldown for readonly dpts (dpt15,22,217,221)
# MH 20231002 allow exp numbers in set cmd for dpt14, formatting of (dpt14) reading values
# corr dpt9 min-limit to -671088.64
# dpt9, dpt14: deny use of comma as dec-separator...
# fix replacebyregex
# rework KNX_scan
# MH 20231125 replace GP_export function
# PBP cleanup -1
# correct cmdref links for DbLog attr Fn's
# modified limit Log msg
# dpttypes optimisation (no variable case regex for numbers)
# MH 20231226 code optimisation KNX_scan, doKNX_scan
# adapt cmds ref set/get cmd's (new KNXIO feature - send queing)
# additional dpts 14.xxx - see cmdref
# MH 20240114 correct unit for dpt9.026
# additional dpts 14.xxx - see cmdref
# fix dpt14 min/max values
# performance tuning in define
# MH 20240305 change dpt1.009 max-value from closed->close
# prevent gadName 'state' in define when nosuffix specified
# add on-for...|off-for... to forbidden gadNames
# MH 20240425 remove Attr answerreading & conversion to putcmd (announced 5/2023)
# modify set cmd
# modified address converssion hex2Name()
# MH 20240819 add sub-dpts for dpt14, cmdref
# enforce gadName rules
# prevent set- and get-cmd during fhem-start (e.g. in fhem.cfg)
# change dpt16 encoding - fix dblogsplit for dpt16
# MH 20241020 replace gettimeofday w. Time::HiRes::time
# replace encode & decode w. Encode::encode & Encode::decode
# replace looks_like_number w. Scalar::Util::looks_like_number
# prevent subsecond parameter in (on|off)-for-timer
# rework KNX_Set_dpt1 logic
# correct dpt1.009, dpt1.024, dpt1.100
# add dpt18 "learn" - still experimental
# cmd-ref update
# MH 20241114 dpt5, dpt7 change to float (rounding) again...
# (on|off)-for-timer now supports > 86400 seconds and fractions of a sec (up to 9.9 secs)
# new cmd "on-till" - works like set extensions cmd - end time must be gt current time (same day)!
# dpt18 "learn" now implemented for dpt18.001, experimental dpt18.099 removed
# PBP: replace unless w. if not..., fix postfix if
# cmd-ref update
# MH 20250201 fix dpt16 dblog-split fn
# feature: new attr readingNmap - modify readingnames on the fly
# internal code change: _define2, _attr, _set, _encodeByDpt
# MH 20250727 modify blink cmd logic - support widgetList
# MH 20251025 modify dpt10, dpt11 encode
# test for duplicate gadNames - modify on the fly
# fix dpt19 regex $PAT_DATE
# add dpt21, add sub-dpts for dpt20, cmd-ref
# modify DBLog_split unit logic
# modified dpt18 encode
# prepare for removal of $MODELERR
# MH 202511yy change autocreate (removal of $MODELERR)
# Attribute format is announced deprecated
# prepare removal of $dpttypes{$model}->{CODE};
#
# todo-4/2024 remove support for oldsyntax cmd's: raw,value,string,rgb
# todo-5/2026 remove support for attr format
package KNX; ## no critic 'package'
use strict;
use warnings;
use Encode qw(encode decode);
use Time::HiRes qw(time);
use Scalar::Util qw(looks_like_number);
use GPUtils qw(GP_Import); # Package Helper Fn
### perlcritic parameters
# these ones are NOT used! (constants,Policy::Modules::RequireFilenameMatchesPackage,NamingConventions::Capitalization)
# these ones are NOT used! (ControlStructures::ProhibitCascadingIfElse)
# these ones are NOT used! (RegularExpressions::RequireDotMatchAnything,RegularExpressions::RequireLineBoundaryMatching)
# these ones are NOT used! (ControlStructures::ProhibitPostfixControls)
### the following percritic items will be ignored global ###
## no critic (NamingConventions::Capitalization)
## no critic (Policy::CodeLayout::ProhibitParensWithBuiltins)
## no critic (ValuesAndExpressions::RequireNumberSeparators,ValuesAndExpressions::ProhibitMagicNumbers)
## 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 AttrNum InternalVal ReadingsVal ReadingsNum
AssignIoPort IOWrite
CommandDefMod CommandModify CommandDelete CommandAttr CommandDeleteAttr CommandDeleteReading
defs modules attr cmds
perlSyntaxCheck
FW_detail FW_wname FW_room FW_directNotify
readingFnAttributes
InternalTimer RemoveInternalTimer
GetTimeSpec
init_done
IsDisabled IsDummy IsDevice
deviceEvents devspec2array
AnalyzePerlCommand AnalyzeCommandChain EvalSpecials
fhemTimeLocal)
);
}
#string constants
my $KNXID = 'C'; #identifier for KNX - extended adressing
my $SVNID = '$Id$'; ## no critic (Policy::ValuesAndExpressions::RequireInterpolationOfMetachars)
my $BLINK = 'blink';
my $TOGGLE = 'toggle';
my $RAW = 'raw'; # announced deprecated
my $RGB = 'rgb'; # announced deprecated
my $STRING = 'string'; # announced deprecated
my $VALUE = 'value'; # announced deprecated
#regex patterns
#pattern for group-adress
my $PAT_GAD = '(3[01]|[012][\d]|[\d])\/([0-7])\/(2[0-4][\d]|25[0-5]|(?:[01])?[\d]{1,2})'; # 0-31/0-7/0-255
#pattern for group-adress in hex-format
my $PAT_GADHEX = '[01][0-9a-f][0-7][0-9a-f]{2}'; # max is 1F7FF -> 31/7/255 5 digits
#pattern for group-no
my $PAT_GNO = qr/^g[1-9][\d]?$/xms;
#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 = qr/^((?:on|off)(?:-for-timer|-until|-till)?|toggle|blink|raw|rgb|string|value|get|set|listenonly|nosuffix)$/xms; ## no critic (RegularExpressions::ProhibitComplexRegexes)
#pattern for DPT
my $PAT_DPT = qr/dpt(?:\d+(?:[.]\d{3,4})?|RAW)/mxs;
#my $PAT_DPT = qr/(dpt\d{1,3}(?:[.]\d{3,4})?|dptRAW))(?:[:].*)?$/xms; # $1 contains dptxxx.yyy
#pattern for dpt1 (standard)
my $PAT_DPT1_PAT = 'on|off|[01]';
#pattern for date
my $PAT_DATEdm = qr/^(3[01]|[1-2]\d|0?[1-9])[.](1[0-2]|0?[1-9])/ixms; # day/month
my $PAT_DATE19 = qr/$PAT_DATEdm[.]((?:19|20|)\d{2}|21[0-4]\d|215[0-5])/xms; # dpt19 year range: 1900-2155 !
my $PAT_DATE11 = qr/$PAT_DATEdm[.](199\d|20[0-8]\d)/ixms; # dpt11 year range: 1990-2089 !
#pattern for time
my $PAT_TIME = qr/(2[0-4]|[01]{0,1}\d):([0-5]{0,1}\d):([0-5]{0,1}\d)/ixms;
my $PAT_DPT16_CLR = qr/>CLR {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT)/ixms, MIN=>'off', MAX=>'on', SETLIST=>'on,off,toggle',
DEC=>\&dec_dpt1,ENC=>\&enc_dpt1,},
'dpt1.000' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT)/ixms, MIN=>0, MAX=>1, SETLIST=>'0,1'},
'dpt1.001' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT)/ixms, MIN=>'off', MAX=>'on', SETLIST=>'on,off,toggle'},
'dpt1.002' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|true|false)/ixms, MIN=>'false', MAX=>'true'},
'dpt1.003' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|enable|disable)/ixms, MIN=>'disable', MAX=>'enable'},
'dpt1.004' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|no_ramp|ramp)/ixms, MIN=>'no_ramp', MAX=>'ramp'},
'dpt1.005' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|no_alarm|alarm)/ixms, MIN=>'no_alarm', MAX=>'alarm'},
'dpt1.006' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|low|high)/ixms, MIN=>'low', MAX=>'high'},
'dpt1.007' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|decrease|increase)/ixms, MIN=>'decrease', MAX=>'increase'},
'dpt1.008' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|up|down)/ixms, MIN=>'up', MAX=>'down'},
'dpt1.009' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|close|open)/ixms, MIN=>'open', MAX=>'close'},
'dpt1.010' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|start|stop)/ixms, MIN=>'stop', MAX=>'start'},
'dpt1.011' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|inactive|active)/ixms, MIN=>'inactive', MAX=>'active'},
'dpt1.012' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|not_inverted|inverted)/ixms, MIN=>'not_inverted', MAX=>'inverted'},
'dpt1.013' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|start_stop|cyclically)/ixms, MIN=>'start_stop', MAX=>'cyclically'},
'dpt1.014' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|fixed|calculated)/ixms, MIN=>'fixed', MAX=>'calculated'},
'dpt1.015' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|no_action|reset)/ixms, MIN=>'no_action', MAX=>'reset'},
'dpt1.016' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|no_action|acknowledge)/ixms, MIN=>'no_action', MAX=>'acknowledge'},
'dpt1.017' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|trigger_0|trigger_1)/ixms, MIN=>'trigger_0', MAX=>'trigger_1', SETLIST=>'trigger_0,trigger_1',},
'dpt1.018' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|not_occupied|occupied)/ixms, MIN=>'not_occupied', MAX=>'occupied'},
'dpt1.019' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|closed|open)/ixms, MIN=>'closed', MAX=>'open'},
'dpt1.021' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|logical_or|logical_and)/ixms, MIN=>'logical_or', MAX=>'logical_and'},
'dpt1.022' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|scene_A|scene_B)/ixms, MIN=>'scene_A', MAX=>'scene_B'},
'dpt1.023' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|move_(up_down|and_step_mode))/ixms, MIN=>'move_up_down', MAX=>'move_and_step_mode'},
'dpt1.024' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|Day|Night)/ixms, MIN=>'day', MAX=>'night'},
'dpt1.100' => {CODE=>'dpt1', UNIT=>q{}, PATTERN=>qr/($PAT_DPT1_PAT|heating|cooling)/ixms, MIN=>'cooling', MAX=>'heating'},
#Step value (two-bit)
'dpt2' => {CODE=>'dpt2', UNIT=>q{}, PATTERN=>qr/(on|off|forceon|forceoff)/ixms, MIN=>undef, MAX=>undef, SETLIST=>'on,off,forceon,forceoff',
DEC=>\&dec_dpt2,ENC=>\&enc_dpt2,},
'dpt2.000' => {CODE=>'dpt2', UNIT=>q{}, PATTERN=>qr/(0?[0-3])/xms, MIN=>0, MAX=>3, SETLIST=>'0,1,2,3'},
#Step value (four-bit)
'dpt3' => {CODE=>'dpt3', UNIT=>q{}, PATTERN=>qr/[+-]?\d{1,3}/xms, MIN=>-100, MAX=>100,
DEC=>\&dec_dpt3,ENC=>\&enc_dpt3,},
'dpt3.007' => {CODE=>'dpt3', UNIT=>q{%}, PATTERN=>qr/[+-]?\d{1,3}/xms, MIN=>-100, MAX=>100},
'dpt3.008' => {CODE=>'dpt3', UNIT=>q{%}, PATTERN=>qr/[+-]?\d{1,3}/xms, MIN=>-100, MAX=>100},
#single ascii/iso-8859-1 char
'dpt4' => {CODE=>'dpt4', UNIT=>q{}, PATTERN=>qr/[[:ascii:]]{1}/ixms, MIN=>undef, MAX=>undef,
DEC=>\&dec_dpt16,ENC=>\&enc_dpt4,},
'dpt4.001' => {CODE=>'dpt4', UNIT=>q{}, PATTERN=>qr/[[:ascii:]]{1}/ixms, MIN=>undef, MAX=>undef}, # ascii
'dpt4.002' => {CODE=>'dpt4', UNIT=>q{}, PATTERN=>qr/(?:[[:ascii:]]{1}|[\xC2-\xF4][\x80-\xBF]{1,3})/ixms, MIN=>undef, MAX=>undef}, # iso-8859-1
# 1-Octet unsigned value
'dpt5' => {CODE=>'dpt5', UNIT=>q{}, PATTERN=>qr/[+]?\d{1,3}/xms, MIN=>0, MAX=>255,
DEC=>\&dec_dpt5,ENC=>\&enc_dpt5,},
'dpt5.001' => {CODE=>'dpt5', UNIT=>q{%}, PATTERN=>qr/[+]?\d{1,3}/xms, FACTOR=>100/255, MIN=>0, MAX=>100},
'dpt5.003' => {CODE=>'dpt5', UNIT=>q{°}, PATTERN=>qr/[+]?\d{1,3}/xms, FACTOR=>360/255, MIN=>0, MAX=>360},
'dpt5.004' => {CODE=>'dpt5', UNIT=>q{%}, PATTERN=>qr/[+]?\d{1,3}/xms, MIN=>0, MAX=>255},
'dpt5.005' => {CODE=>'dpt5', UNIT=>q{}, PATTERN=>qr/[+]?\d{1,3}/xms, MIN=>0, MAX=>255}, # Decimal Factor
'dpt5.006' => {CODE=>'dpt5', UNIT=>q{}, PATTERN=>qr/[+]?\d{1,3}/xms, MIN=>0, MAX=>255}, # Tariff
'dpt5.010' => {CODE=>'dpt5', UNIT=>q{p}, PATTERN=>qr/[+]?\d{1,3}/xms, MIN=>0, MAX=>255}, # counter pulses
# 1-Octet signed value
'dpt6' => {CODE=>'dpt6', UNIT=>q{}, PATTERN=>qr/[+-]?\d{1,3}/xms, MIN=>-128, MAX=>127,
DEC=>\&dec_dpt6,ENC=>\&enc_dpt6,},
'dpt6.001' => {CODE=>'dpt6', UNIT=>q{%}, PATTERN=>qr/[+-]?\d{1,3}/xms, MIN=>-128, MAX=>127},
'dpt6.010' => {CODE=>'dpt6', UNIT=>q{p}, PATTERN=>qr/[+-]?\d{1,3}/xms, MIN=>-128, MAX=>127},
# 2-Octet unsigned Value
'dpt7' => {CODE=>'dpt7', UNIT=>q{}, PATTERN=>qr/[+]?\d{1,5}/xms, MIN=>0, MAX=>65535,
DEC=>\&dec_dpt7,ENC=>\&enc_dpt7,},
'dpt7.001' => {CODE=>'dpt7', UNIT=>q{p}, PATTERN=>qr/[+]?\d{1,5}/xms, MIN=>0, MAX=>65535},
'dpt7.002' => {CODE=>'dpt7', UNIT=>q{ms}, PATTERN=>qr/[+]?\d{1,5}/xms, MIN=>0, MAX=>65535},
'dpt7.003' => {CODE=>'dpt7', UNIT=>q{s}, PATTERN=>qr/[+]?\d{1,3}([.]\d+)?/xms, FACTOR=>0.01, MIN=>0, MAX=>655.35},
'dpt7.004' => {CODE=>'dpt7', UNIT=>q{s}, PATTERN=>qr/[+]?\d{1,4}([.]\d+)?/xms, FACTOR=>0.1, MIN=>0, MAX=>6553.5},
'dpt7.005' => {CODE=>'dpt7', UNIT=>q{s}, PATTERN=>qr/[+]?\d{1,5}/xms, MIN=>0, MAX=>65535},
'dpt7.006' => {CODE=>'dpt7', UNIT=>q{min}, PATTERN=>qr/[+]?\d{1,5}/xms, MIN=>0, MAX=>65535},
'dpt7.007' => {CODE=>'dpt7', UNIT=>q{h}, PATTERN=>qr/[+]?\d{1,5}/xms, MIN=>0, MAX=>65535},
'dpt7.011' => {CODE=>'dpt7', UNIT=>q{mm}, PATTERN=>qr/[+]?\d{1,5}/xms, MIN=>0, MAX=>65535},
'dpt7.012' => {CODE=>'dpt7', UNIT=>q{mA}, PATTERN=>qr/[+]?\d{1,5}/xms, MIN=>0, MAX=>65535},
'dpt7.013' => {CODE=>'dpt7', UNIT=>q{lux}, PATTERN=>qr/[+]?\d{1,5}/xms, MIN=>0, MAX=>65535},
'dpt7.600' => {CODE=>'dpt7', UNIT=>q{K}, PATTERN=>qr/[+]?\d{1,5}/xms, MIN=>0, MAX=>12000}, # Farbtemperatur
# 2-Octet signed Value
'dpt8' => {CODE=>'dpt8', UNIT=>q{}, PATTERN=>qr/[+-]?\d{1,5}/xms, MIN=>-32768, MAX=>32767,
DEC=>\&dec_dpt8,ENC=>\&enc_dpt8,},
'dpt8.001' => {CODE=>'dpt8', UNIT=>q{p}, PATTERN=>qr/[+-]?\d{1,5}/xms, MIN=>-32768, MAX=>32767},
'dpt8.002' => {CODE=>'dpt8', UNIT=>q{ms}, PATTERN=>qr/[+-]?\d{1,5}/xms, MIN=>-32768, MAX=>32767},
'dpt8.003' => {CODE=>'dpt8', UNIT=>q{s}, PATTERN=>qr/[+-]?\d{1,3}([.]\d+)?/xms, FACTOR=>0.01, MIN=>-327.68, MAX=>327.67},
'dpt8.004' => {CODE=>'dpt8', UNIT=>q{s}, PATTERN=>qr/[+-]?\d{1,4}([.]\d+)?/xms, FACTOR=>0.1, MIN=>-3276.8, MAX=>3276.7},
'dpt8.005' => {CODE=>'dpt8', UNIT=>q{s}, PATTERN=>qr/[+-]?\d{1,5}/xms, MIN=>-32768, MAX=>32767},
'dpt8.006' => {CODE=>'dpt8', UNIT=>q{min}, PATTERN=>qr/[+-]?\d{1,5}/xms, MIN=>-32768, MAX=>32767},
'dpt8.007' => {CODE=>'dpt8', UNIT=>q{h}, PATTERN=>qr/[+-]?\d{1,5}/xms, MIN=>-32768, MAX=>32767},
'dpt8.010' => {CODE=>'dpt8', UNIT=>q{%}, PATTERN=>qr/[+-]?\d{1,3}([.]\d+)?/ixms, FACTOR=>0.01, MIN=>-327.68, MAX=>327.67}, # min/max
'dpt8.011' => {CODE=>'dpt8', UNIT=>q{°}, PATTERN=>qr/[+-]?\d{1,5}/xms, MIN=>-32768, MAX=>32767},
'dpt8.012' => {CODE=>'dpt8', UNIT=>q{m}, PATTERN=>qr/[+-]?\d{1,5}/xms, MIN=>-32768, MAX=>32767},
# 2-Octet Float value
'dpt9' => {CODE=>'dpt9', UNIT=>q{}, PATTERN=>qr/[-+]?(?:\d*[.])?\d+/xms, MIN=>-671088.64, MAX=>670433.28,
DEC=>\&dec_dpt9,ENC=>\&enc_dpt9,},
'dpt9.001' => {CODE=>'dpt9', UNIT=>q{°C}, PATTERN=>qr/[-+]?(?:\d*[.])?\d+/xms, MIN=>-273.15, MAX=>670433.28},
'dpt9.002' => {CODE=>'dpt9', UNIT=>q{K}, PATTERN=>qr/[-+]?(?:\d*[.])?\d+/xms, MIN=>-671088.64, MAX=>670433.28},
'dpt9.003' => {CODE=>'dpt9', UNIT=>q{K/h}, PATTERN=>qr/[-+]?(?:\d*[.])?\d+/xms, MIN=>-671088.64, MAX=>670433.28},
'dpt9.004' => {CODE=>'dpt9', UNIT=>q{lux}, PATTERN=>qr/[-+]?(?:\d*[.])?\d+/xms, MIN=>0, MAX=>670433.28},
'dpt9.005' => {CODE=>'dpt9', UNIT=>q{m/s}, PATTERN=>qr/[-+]?(?:\d*[.])?\d+/xms, MIN=>0, MAX=>670433.28},
'dpt9.006' => {CODE=>'dpt9', UNIT=>q{Pa}, PATTERN=>qr/[-+]?(?:\d*[.])?\d+/xms, MIN=>0, MAX=>670433.28},
'dpt9.007' => {CODE=>'dpt9', UNIT=>q{%}, PATTERN=>qr/[-+]?(?:\d*[.])?\d+/xms, MIN=>0, MAX=>670433.28},
'dpt9.008' => {CODE=>'dpt9', UNIT=>q{ppm}, PATTERN=>qr/[-+]?(?:\d*[.])?\d+/xms, MIN=>0, MAX=>670433.28},
'dpt9.009' => {CODE=>'dpt9', UNIT=>q{m³/h}, PATTERN=>qr/[-+]?(?:\d*[.])?\d+/xms, MIN=>-671088.64, MAX=>670433.28},
'dpt9.010' => {CODE=>'dpt9', UNIT=>q{s}, PATTERN=>qr/[-+]?(?:\d*[.])?\d+/xms, MIN=>-671088.64, MAX=>670433.28},
'dpt9.011' => {CODE=>'dpt9', UNIT=>q{ms}, PATTERN=>qr/[-+]?(?:\d*[.])?\d+/xms, MIN=>-671088.64, MAX=>670433.28},
'dpt9.020' => {CODE=>'dpt9', UNIT=>q{mV}, PATTERN=>qr/[-+]?(?:\d*[.])?\d+/xms, MIN=>-671088.64, MAX=>670433.28},
'dpt9.021' => {CODE=>'dpt9', UNIT=>q{mA}, PATTERN=>qr/[-+]?(?:\d*[.])?\d+/xms, MIN=>-671088.64, MAX=>670433.28},
'dpt9.022' => {CODE=>'dpt9', UNIT=>q{W/m²}, PATTERN=>qr/[-+]?(?:\d*[.])?\d+/xms, MIN=>-671088.64, MAX=>670433.28},
'dpt9.023' => {CODE=>'dpt9', UNIT=>q{K/%}, PATTERN=>qr/[-+]?(?:\d*[.])?\d+/xms, MIN=>-671088.64, MAX=>670433.28},
'dpt9.024' => {CODE=>'dpt9', UNIT=>q{kW}, PATTERN=>qr/[-+]?(?:\d*[.])?\d+/xms, MIN=>-671088.64, MAX=>670433.28},
'dpt9.025' => {CODE=>'dpt9', UNIT=>q{l/h}, PATTERN=>qr/[-+]?(?:\d*[.])?\d+/xms, MIN=>-671088.64, MAX=>670433.28},
'dpt9.026' => {CODE=>'dpt9', UNIT=>q{l/m²}, PATTERN=>qr/[-+]?(?:\d*[.])?\d+/xms, MIN=>-671088.64, MAX=>670433.28},
'dpt9.027' => {CODE=>'dpt9', UNIT=>q{°F}, PATTERN=>qr/[-+]?(?:\d*[.])?\d+/xms, MIN=>-459.6, MAX=>670433.28},
'dpt9.028' => {CODE=>'dpt9', UNIT=>q{km/h}, PATTERN=>qr/[-+]?(?:\d*[.])?\d+/xms, MIN=>0, MAX=>670433.28},
'dpt9.029' => {CODE=>'dpt9', UNIT=>q{g/m³}, PATTERN=>qr/[-+]?(?:\d*[.])?\d+/xms, MIN=>0, MAX=>670433.28}, # Abs. Luftfeuchte
'dpt9.030' => {CODE=>'dpt9', UNIT=>q{µg/m³}, PATTERN=>qr/[-+]?(?:\d*[.])?\d+/xms, MIN=>0, MAX=>670433.28}, # Dichte
# Time of Day
'dpt10' => {CODE=>'dpt10', UNIT=>q{}, PATTERN=>qr/($PAT_TIME|now)/ixms, MIN=>undef, MAX=>undef,
DEC=>\&dec_dpt10,ENC=>\&enc_dpt10,},
'dpt10.001' => {CODE=>'dpt10', UNIT=>q{}, PATTERN=>qr/($PAT_TIME|now)/ixms, MIN=>undef, MAX=>undef},
# Date
'dpt11' => {CODE=>'dpt11', UNIT=>q{}, PATTERN=>qr/($PAT_DATE11|now)/ixms, MIN=>undef, MAX=>undef,
DEC=>\&dec_dpt11,ENC=>\&enc_dpt11,}, # year range 1990-2089 !
'dpt11.001' => {CODE=>'dpt11', UNIT=>q{}, PATTERN=>qr/($PAT_DATE11|now)/ixms, MIN=>undef, MAX=>undef},
# 4-Octet unsigned value
'dpt12' => {CODE=>'dpt12', UNIT=>q{}, PATTERN=>qr/[+]?\d{1,10}/xms, MIN=>0, MAX=>4294967295,
DEC=>\&dec_dpt12,ENC=>\&enc_dpt12,},
'dpt12.001' => {CODE=>'dpt12', UNIT=>q{p}, PATTERN=>qr/[+]?\d{1,10}/xms, MIN=>0, MAX=>4294967295},
'dpt12.100' => {CODE=>'dpt12', UNIT=>q{s}, PATTERN=>qr/[+]?\d{1,10}/xms, MIN=>0, MAX=>4294967295},
'dpt12.101' => {CODE=>'dpt12', UNIT=>q{min}, PATTERN=>qr/[+]?\d{1,10}/xms, MIN=>0, MAX=>4294967295},
'dpt12.102' => {CODE=>'dpt12', UNIT=>q{h}, PATTERN=>qr/[+]?\d{1,10}/xms, MIN=>0, MAX=>4294967295},
# 4-Octet Signed Value
'dpt13' => {CODE=>'dpt13', UNIT=>q{}, PATTERN=>qr/[+-]?\d{1,10}/xms, MIN=>-2147483648, MAX=>2147483647,
DEC=>\&dec_dpt13,ENC=>\&enc_dpt13,},
'dpt13.001' => {CODE=>'dpt13', UNIT=>q{p}, PATTERN=>qr/[+-]?\d{1,10}/xms, MIN=>-2147483648, MAX=>2147483647},
'dpt13.002' => {CODE=>'dpt13', UNIT=>q{m³/h}, PATTERN=>qr/[+-]?\d{1,10}/xms, MIN=>-2147483648, MAX=>2147483647},
'dpt13.010' => {CODE=>'dpt13', UNIT=>q{Wh}, PATTERN=>qr/[+-]?\d{1,10}/xms, MIN=>-2147483648, MAX=>2147483647},
'dpt13.011' => {CODE=>'dpt13', UNIT=>q{VAh}, PATTERN=>qr/[+-]?\d{1,10}/xms, MIN=>-2147483648, MAX=>2147483647},
'dpt13.012' => {CODE=>'dpt13', UNIT=>q{VARh}, PATTERN=>qr/[+-]?\d{1,10}/xms, MIN=>-2147483648, MAX=>2147483647},
'dpt13.013' => {CODE=>'dpt13', UNIT=>q{kWh}, PATTERN=>qr/[+-]?\d{1,10}/xms, MIN=>-2147483648, MAX=>2147483647},
'dpt13.014' => {CODE=>'dpt13', UNIT=>q{kVAh}, PATTERN=>qr/[+-]?\d{1,10}/xms, MIN=>-2147483648, MAX=>2147483647},
'dpt13.015' => {CODE=>'dpt13', UNIT=>q{kVARh}, PATTERN=>qr/[+-]?\d{1,10}/xms, MIN=>-2147483648, MAX=>2147483647},
'dpt13.016' => {CODE=>'dpt13', UNIT=>q{MWh}, PATTERN=>qr/[+-]?\d{1,10}/xms, MIN=>-2147483648, MAX=>2147483647},
'dpt13.100' => {CODE=>'dpt13', UNIT=>q{s}, PATTERN=>qr/[+-]?\d{1,10}/xms, MIN=>-2147483648, MAX=>2147483647},
# 4-Octet single precision float
'dpt14' => {CODE=>'dpt14', UNIT=>q{}, PATTERN=>qr/[+-]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38,
DEC=>\&dec_dpt14,ENC=>\&enc_dpt14,},
'dpt14.000' => {CODE=>'dpt14', UNIT=>q{m/s²}, PATTERN=>qr/[+-]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # acceleration superscr - = alt8315
'dpt14.001' => {CODE=>'dpt14', UNIT=>q{rad/s²}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # acceleration angular
'dpt14.002' => {CODE=>'dpt14', UNIT=>q{J/mol}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # activation energy
'dpt14.003' => {CODE=>'dpt14', UNIT=>q{1/s}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # activity (radioactive)
'dpt14.004' => {CODE=>'dpt14', UNIT=>q{mol}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # amount of substance
'dpt14.005' => {CODE=>'dpt14', UNIT=>q{ }, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # Amplitude
'dpt14.006' => {CODE=>'dpt14', UNIT=>q{rad}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # angle, radiant
'dpt14.007' => {CODE=>'dpt14', UNIT=>q{°}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # angle, degree
'dpt14.008' => {CODE=>'dpt14', UNIT=>q{Js}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # Angular Momentum
'dpt14.009' => {CODE=>'dpt14', UNIT=>q{rad/s}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # Angular velocity
'dpt14.010' => {CODE=>'dpt14', UNIT=>q{m²}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # area
'dpt14.011' => {CODE=>'dpt14', UNIT=>q{F}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # capacitance
'dpt14.012' => {CODE=>'dpt14', UNIT=>q{C/m²}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # charge density surface
'dpt14.013' => {CODE=>'dpt14', UNIT=>q{C/m³}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # charge density volume
'dpt14.014' => {CODE=>'dpt14', UNIT=>q{m²/N}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # compressibility
'dpt14.015' => {CODE=>'dpt14', UNIT=>q{S}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # conductance 1/Ohm
'dpt14.016' => {CODE=>'dpt14', UNIT=>q{S/m}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # electrical conductivity
'dpt14.017' => {CODE=>'dpt14', UNIT=>q{kg/m²}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # density
'dpt14.018' => {CODE=>'dpt14', UNIT=>q{C}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # electric charge
'dpt14.019' => {CODE=>'dpt14', UNIT=>q{A}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # electric current
'dpt14.020' => {CODE=>'dpt14', UNIT=>q{A/m²}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # electric current density
'dpt14.021' => {CODE=>'dpt14', UNIT=>q{Cm}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # electric dipole moment
'dpt14.022' => {CODE=>'dpt14', UNIT=>q{C/m²}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # electric displacement
'dpt14.023' => {CODE=>'dpt14', UNIT=>q{V/m}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # electric field strength
'dpt14.024' => {CODE=>'dpt14', UNIT=>q{c}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # electric flux
'dpt14.025' => {CODE=>'dpt14', UNIT=>q{C/m²}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # electric flux density
'dpt14.016' => {CODE=>'dpt14', UNIT=>q{C/m²}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # electric polarization
'dpt14.027' => {CODE=>'dpt14', UNIT=>q{V}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # electric potential
'dpt14.028' => {CODE=>'dpt14', UNIT=>q{V}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # electric potential difference
'dpt14.029' => {CODE=>'dpt14', UNIT=>q{A/m²}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # electromagnetive moment
'dpt14.030' => {CODE=>'dpt14', UNIT=>q{V}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # electromotive force
'dpt14.031' => {CODE=>'dpt14', UNIT=>q{J}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # energy
'dpt14.032' => {CODE=>'dpt14', UNIT=>q{N}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # force
'dpt14.033' => {CODE=>'dpt14', UNIT=>q{Hz}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # frequency
'dpt14.034' => {CODE=>'dpt14', UNIT=>q{rad/s}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # frequency, angular
'dpt14.035' => {CODE=>'dpt14', UNIT=>q{J/K}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # heat capacity
'dpt14.036' => {CODE=>'dpt14', UNIT=>q{W}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # heat flow rate
'dpt14.037' => {CODE=>'dpt14', UNIT=>q{J}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # heat quantity
'dpt14.038' => {CODE=>'dpt14', UNIT=>qq{\xCE\xA9}, ## no critic (ValuesAndExpressions::ProhibitEscapedCharacters)
PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # Impedance OHM
'dpt14.039' => {CODE=>'dpt14', UNIT=>q{m}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # length
'dpt14.040' => {CODE=>'dpt14', UNIT=>q{J}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # light quantity
'dpt14.041' => {CODE=>'dpt14', UNIT=>q{cd/m²}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # luminance
'dpt14.042' => {CODE=>'dpt14', UNIT=>q{lm}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # luminous flux
'dpt14.043' => {CODE=>'dpt14', UNIT=>q{cd}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # luminous intensity
'dpt14.044' => {CODE=>'dpt14', UNIT=>q{A/m}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # magnetic field strenght
'dpt14.045' => {CODE=>'dpt14', UNIT=>q{Wb}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # magnetic flux
'dpt14.046' => {CODE=>'dpt14', UNIT=>q{T}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # magnetic flux density
'dpt14.047' => {CODE=>'dpt14', UNIT=>q{A/m²}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # magnetic moment
'dpt14.048' => {CODE=>'dpt14', UNIT=>q{T}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # magnetic polarisation
'dpt14.049' => {CODE=>'dpt14', UNIT=>q{A/m}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # magnetization
'dpt14.050' => {CODE=>'dpt14', UNIT=>q{A}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # magneto motive force
'dpt14.051' => {CODE=>'dpt14', UNIT=>q{kg}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # mass
'dpt14.052' => {CODE=>'dpt14', UNIT=>q{kg/s}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # mass flux
'dpt14.053' => {CODE=>'dpt14', UNIT=>q{N/s}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # momentum
'dpt14.054' => {CODE=>'dpt14', UNIT=>q{rad}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # phase angle, radiant
'dpt14.055' => {CODE=>'dpt14', UNIT=>q{°}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # phase angle, degrees
'dpt14.056' => {CODE=>'dpt14', UNIT=>q{W}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # power
'dpt14.057' => {CODE=>'dpt14', UNIT=>q{cosφ}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # power factor
'dpt14.058' => {CODE=>'dpt14', UNIT=>q{Pa}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # pressure
'dpt14.059' => {CODE=>'dpt14', UNIT=>qq{\xCE\xA9}, ## no critic (ValuesAndExpressions::ProhibitEscapedCharacters)
PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # Reactance OHM
'dpt14.060' => {CODE=>'dpt14', UNIT=>qq{\xCE\xA9}, ## no critic (ValuesAndExpressions::ProhibitEscapedCharacters)
PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # Resistance OHM
'dpt14.061' => {CODE=>'dpt14', UNIT=>q{kg}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # mass
'dpt14.062' => {CODE=>'dpt14', UNIT=>q{H}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # self inductance
'dpt14.063' => {CODE=>'dpt14', UNIT=>q{sr}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # solid angle
'dpt14.064' => {CODE=>'dpt14', UNIT=>q{W/m²}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # sound intensity
'dpt14.065' => {CODE=>'dpt14', UNIT=>q{m/s}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # speed
'dpt14.066' => {CODE=>'dpt14', UNIT=>q{N/m²}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # stress
'dpt14.067' => {CODE=>'dpt14', UNIT=>q{N/m}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # surface tension
'dpt14.068' => {CODE=>'dpt14', UNIT=>q{°C}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # temperature, common
'dpt14.069' => {CODE=>'dpt14', UNIT=>q{K}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # temperature (absolute)
'dpt14.070' => {CODE=>'dpt14', UNIT=>q{K}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # temperature difference
'dpt14.071' => {CODE=>'dpt14', UNIT=>q{J/K}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # thermal capacity
'dpt14.072' => {CODE=>'dpt14', UNIT=>q{1/K}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # thermal conductivity
'dpt14.073' => {CODE=>'dpt14', UNIT=>q{V/K}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # thermoelectric power
'dpt14.074' => {CODE=>'dpt14', UNIT=>q{s}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # time
'dpt14.075' => {CODE=>'dpt14', UNIT=>q{Nm}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # torque
'dpt14.076' => {CODE=>'dpt14', UNIT=>q{m³}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # volume
'dpt14.077' => {CODE=>'dpt14', UNIT=>q{m³/s}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # volume flow
'dpt14.078' => {CODE=>'dpt14', UNIT=>q{N}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # weight
'dpt14.079' => {CODE=>'dpt14', UNIT=>q{J}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # work
'dpt14.080' => {CODE=>'dpt14', UNIT=>q{VA}, PATTERN=>qr/[-+]?(?:\d+(?:[.]\d*)?(?:e[+-]?\d+)?)/xms, MIN=>-3.4e38, MAX=>3.4e38}, # apparent power
# Access data - receive only
'dpt15' => {CODE=>'dpt15', UNIT=>q{}, PATTERN=>qr/noset/ixms, MIN=>undef, MAX=>undef, SETLIST=>'noset',
DEC=>\&dec_dpt15,},
'dpt15.000' => {CODE=>'dpt15', UNIT=>q{}, PATTERN=>qr/noset/ixms, MIN=>undef, MAX=>undef, SETLIST=>'noset',},
# 14-Octet String
'dpt16' => {CODE=>'dpt16', UNIT=>q{}, PATTERN=>qr/[[:ascii:]]{1,}/ixms, MIN=>undef, MAX=>undef, SETLIST=>'multiple,>CLR<',
DEC=>\&dec_dpt16,ENC=>\&enc_dpt16,},
'dpt16.000' => {CODE=>'dpt16', UNIT=>q{}, PATTERN=>qr/[[:ascii:]]{1,}/ixms, MIN=>undef, MAX=>undef, SETLIST=>'multiple,>CLR<'},
'dpt16.001' => {CODE=>'dpt16', UNIT=>q{}, PATTERN=>qr/(?:[[:ascii:]]|[\xC2-\xF4][\x80-\xBF]{1,3}){1,}/ixms, MIN=>undef, MAX=>undef, SETLIST=>'multiple,>CLR<'},
# Scene, 0-63
'dpt17' => {CODE=>'dpt17', UNIT=>q{}, PATTERN=>qr/[+]?\d{1,2}/xms, MIN=>0, MAX=>63,
DEC=>\&dec_dpt18,ENC=>\&enc_dpt18,},
'dpt17.001' => {CODE=>'dpt17', UNIT=>q{}, PATTERN=>qr/[+]?\d{1,2}/xms, MIN=>0, MAX=>63},
# Scene, 1-64
'dpt18' => {CODE=>'dpt18', UNIT=>q{}, PATTERN=>qr/(activate[,\s]|learn[,\s])?[+]?\d{1,2}/xms, OFFSET=>1, MIN=>1, MAX=>64,
DEC=>\&dec_dpt18,ENC=>\&enc_dpt18,},
'dpt18.001' => {CODE=>'dpt18', UNIT=>q{}, PATTERN=>qr/(activate[,\s]|learn[,\s])?[+]?\d{1,2}/xms, OFFSET=>1, MIN=>1, MAX=>64, SETLIST=>'widgetList,3,select,activate,learn,1,textField'},
#date and time
'dpt19' => {CODE=>'dpt19', UNIT=>q{}, PATTERN=>qr/($PAT_DATE19[_]$PAT_TIME|now)/ixms, MIN=>undef, MAX=>undef,
DEC=>\&dec_dpt19,ENC=>\&enc_dpt19,},
'dpt19.001' => {CODE=>'dpt19', UNIT=>q{}, PATTERN=>qr/($PAT_DATE19[_]$PAT_TIME|now)/ixms, MIN=>undef, MAX=>undef},
# 1 Byte HVAC mode, Windspeed (Bf), Cloudstate
'dpt20' => {CODE=>'dpt20', UNIT=>q{}, PATTERN=>qr/(auto|comfort|standby|(economy|night)|(protection|frost|heat))/ixms, ## no critic (RegularExpressions::ProhibitComplexRegexes)
MIN=>undef, MAX=>undef, SETLIST=>'Auto,Comfort,Standby,Economy,Protection',
DEC=>\&dec_dpt20,ENC=>\&enc_dpt20,},
'dpt20.014' => {CODE=>'dpt20', UNIT=>q{}, PATTERN=>qr/(calm|light_air|(light|gentle|moderate|fresh|strong)_breeze|(near|fresh|strong)_gale|storm|violent_storm|hurricane)/ixms, ## no critic (RegularExpressions::ProhibitComplexRegexes)
MIN=>0, MAX=>12,
SETLIST=>'calm,light_air,light_breeze,gentle_breeze,moderate_breeze,fresh_breeze,strong_breeze,near_gale,fresh_gale,strong_gale,storm,violent_storm,hurricane'},
'dpt20.021' => {CODE=>'dpt20', UNIT=>q{}, PATTERN=>qr/(cloudless|sunny|sunshiny|lightly_cloudy|scattered_cloudy|cloudy|most_cloudy|most_overcast|overcast|sky_obstructed)/xms, ## no critic (RegularExpressions::ProhibitComplexRegexes)
MIN=>0, MAX=>9, SETLIST=>'cloudless,sunny,sunshiny,lightly_cloudy,scattered_cloudy,cloudy,most_cloudy,most_overcast,overcast,sky_obstructed'},
'dpt20.102' => {CODE=>'dpt20', UNIT=>q{}, PATTERN=>qr/(auto|comfort|standby|(economy|night)|(protection|frost|heat))/ixms, ## no critic (RegularExpressions::ProhibitComplexRegexes)
MIN=>undef, MAX=>undef, SETLIST=>'Auto,Comfort,Standby,Economy,Protection'},
# Z8 Status info - receive only
'dpt21' => {CODE=>'dpt21', UNIT=>q{}, PATTERN=>qr/noset/xms,MIN=>0, MAX=>31,SETLIST=>'noset',
DEC=>\&dec_dpt21,},
'dpt21.001' => {CODE=>'dpt21', UNIT=>q{}, PATTERN=>qr/noset/xms,MIN=>0, MAX=>31,SETLIST=>'noset',},
'dpt21.002' => {CODE=>'dpt21', UNIT=>q{}, PATTERN=>qr/noset/xms,MIN=>0, MAX=>7,SETLIST=>'noset',},
# HVAC mode RHCC Status, 2Byte - receive only!!!
'dpt22' => {CODE=>'dpt22', UNIT=>q{}, PATTERN=>qr/noset/ixms, MIN=>undef, MAX=>undef, SETLIST=>'noset',
DEC=>\&dec_dpt22,},
'dpt22.101' => {CODE=>'dpt22', UNIT=>q{}, PATTERN=>qr/noset/ixms, MIN=>undef, MAX=>undef, SETLIST=>'noset',},
# Version Info - receive only!!! for EBUSD KNX implementation
'dpt217' => {CODE=>'dpt217', UNIT=>q{}, PATTERN=>qr/\d+[.]\d+[.]\d+/xms, MIN=>undef, MAX=>undef, SETLIST=>'noset',
DEC=>\&dec_dpt217,},
'dpt217.001' => {CODE=>'dpt217', UNIT=>q{}, PATTERN=>qr/\d+[.]\d+[.]\d+/xms, MIN=>undef, MAX=>undef, SETLIST=>'noset',},
#Serial number (2byte mfg-code, 4 byte serial)
'dpt221' => {CODE=>'dpt221', UNIT=>q{}, PATTERN=>qr/noset/ixms, MIN=>undef, MAX=>undef, SETLIST=>'noset',
DEC=>\&dec_dpt221,},
'dpt221.001' => {CODE=>'dpt221', UNIT=>q{}, PATTERN=>qr/noset/ixms, MIN=>undef, MAX=>undef, SETLIST=>'noset',},
# Color-Code
'dpt232' => {CODE=>'dpt232', UNIT=>q{}, PATTERN=>qr/[\da-f]{6}/ixms, MIN=>undef, MAX=>undef, SETLIST=>'colorpicker,RGB',
DEC=>\&dec_dpt232,ENC=>\&enc_dpt232,},
# RGBW
'dpt251' => {CODE=>'dpt251', UNIT=>q{}, PATTERN=>qr/[\da-f]{8}/ixms, MIN=>undef, MAX=>undef,
DEC=>\&dec_dpt251,ENC=>\&enc_dpt251,},
'dpt251.600' => {CODE=>'dpt251', UNIT=>q{}, PATTERN=>qr/[\da-f]{8}/ixms, MIN=>undef, MAX=>undef},
# RAW special dptmodel for extreme situations: 2-nn hex digits only - 00 prefix required except for dpt1,2,3 !!!
'dptRAW' => {CODE=>'dptRAW', UNIT=>q{}, PATTERN=>qr/(?:[0-3][\da-f]|00([\da-f]{2}){1,})/ixms, MIN=>undef, MAX=>undef,
DEC=>\&dec_dptRAW,ENC=>\&enc_dptRAW,},
);
#Init this device
#This declares the interface to fhem
#############################
sub main::KNX_Initialize {
goto &Initialize;
}
sub Initialize {
my $hash = shift // return;
$hash->{Match} = "^$KNXID.*";
$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->{AttrFn} = \&KNX_Attr;
$hash->{DbLog_splitFn} = \&KNX_DbLog_split;
$hash->{AttrList} = 'IODev ' . #define IO-Device to communicate with. Deprecated at definition line.
'disable:1 ' . #device disabled
'showtime:0,1 ' . #show event-time instead of value in device overview
'stateRegex:textField-long ' . #modifies state value
'stateCmd:textField-long ' . #modify state value
'readingNmap:textField-long ' . #modify reading names
'putCmd:textField-long ' . #enable FHEM to answer KNX read telegrams
'format ' . #supplies post-string
'KNX_toggle:textField ' . #toggle source 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. For getting started, please refer to this document:
KNX for your home - knx.org web-site. While the TUL-module, KNXTUL-module,
or KNXIO-module represent the connection to the KNX network,
the KNX module represent a individual KNX device. Sophisticated setups can be achieved by combining multiple KNX-groupaddresses:datapoints (GAD's:dpt's)
in one KNX device instance. KNX defines a series of Data-Point-Types (dpt) as standard data types to allow
interpretation/encoding of messages from/to devices manufactured by different vendors.
These datatypes are used to interpret the status of a device, so the readings in FHEM will show the
correct value and optional unit. The KNX-Bus protocol defines three basic commands, any KNX-device, including FHEM, has to support:
',@logarr);
if (defined($FW_wname)) {FW_directNotify("FILTER=$name",'#FHEMWEB:' . $FW_wname, qq{FW_okDialog('$logtxt')}, q{});}
foreach (@logarr) {
KNX_Log ($name, 2, $_);
}
}
return;
}
#Release this device
#Is called at every delete / shutdown
#############################
sub KNX_Undef {
my $hash = shift;
my $name = shift;
#delete all defptr entries for this device
KNX_delete_defptr($hash); # verify with: {PrintHash($modules{KNX}->{defptr},3) } on FHEM-cmdline
return;
}
#send a "GroupValueRead" Message to the KNX-Bus
#The answer is treated as regular telegram
#############################
sub KNX_Get {
my $hash = shift;
my $name = shift;
my $gadName = shift // KNX_gadNameByNO($hash,1); # use first defined GAD if no argument is supplied
return qq{KNX_Get ($name): gadName not defined} if (! defined($gadName));
if (defined(shift)) {
KNX_Log ($name, 3, q{too much arguments. Only one argument allowed (gadName). Other Arguments are discarded.});
}
#FHEM asks with a ? at startup - no action, no log - if dev is disabled: no SET/GET pulldown !
if ($gadName =~ m/[?]/xms) {
my $getter = q{};
foreach my $key (keys %{$hash->{GADDETAILS}}) {
last if (! defined($key));
my $option = $hash->{GADDETAILS}->{$key}->{OPTION};
next if (defined($option) && $option =~ /(?:set|listenonly)/xms);
$getter .= $key . ':noArg ';
}
$getter =~ s/\s+$//xms; #trim trailing blank
if (IsDisabled($name) == 1) {$getter = q{};}
return qq{unknown argument $gadName choose one of $getter};
}
return qq{get cmd ($name) not allowed during fhem-start} if (! $init_done);
return qq{KNX_Get ($name): is disabled} if (IsDisabled($name) == 1);
#return, if unknown group
return qq{KNX_Get ($name): invalid gadName: $gadName} if(! exists($hash->{GADDETAILS}->{$gadName}));
#get groupCode, groupAddress, option
my $groupc = $hash->{GADDETAILS}->{$gadName}->{CODE};
my $group = KNX_hex2Name($groupc);
my $option = $hash->{GADDETAILS}->{$gadName}->{OPTION};
#exit if get is prohibited
if (defined ($option) && ($option =~ m/(?:set|listenonly)/xms)) {
return qq{KNX_Get ($name): did not request a value - "set" or "listenonly" option is defined.};
}
KNX_Log ($name, 5, qq{request value for GAD: $group GAD-NAME: $gadName});
delete $hash->{GADDETAILS}->{$gadName}->{noreplyflag}; # allow events for groupValueResponse when triggerd by fhem 9/2023
IOWrite($hash, $KNXID, 'r' . $groupc); #send read-request to the bus
if (defined($FW_wname)) {
FW_directNotify("FILTER=$name",'#FHEMWEB:' . $FW_wname, 'FW_errmsg(" value for ' . $name . ' - ' . $group . ' requested",5000)', q{});
}
return;
}
#send a "GroupValueWrite" to bus
#############################
sub KNX_Set {
my ($hash, $name, $targetGadName, @arg) = @_;
return qq{$name no parameter(s) specified for set cmd} if((!defined($targetGadName)) || ($targetGadName eq q{})); #return, if no cmd specified
#FHEM asks with a "?" at startup or any reload of the device-detail-view - if dev is disabled: no SET/GET pulldown !
if ($targetGadName eq q{?}) {
my $setter = exists($hash->{'.SETSTRING'})?$hash->{'.SETSTRING'}:q{};
if (IsDisabled($name) == 1) {$setter = q{};}
return qq{unknown argument $targetGadName choose one of $setter};
}
return qq{set $name - not allowed during fhem-start} if (! $init_done);
return qq{$name is disabled} if (IsDisabled($name) == 1);
$targetGadName =~ s/^\s+|\s+$//gxms; # gad-name or cmd (in old syntax)
my $cmd = undef;
if (exists ($hash->{GADDETAILS}->{$targetGadName})) { #new syntax, if first arg is a valid gadName
$cmd = shift(@arg); #shift args as with newsyntax $arg[0] is cmd
return qq{set $name $targetGadName - cmd missing} if(!defined($cmd));
}
else { # process old syntax targetGadName contains command!
(my $err, $targetGadName, $cmd, @arg) = KNX_Set_oldsyntax($hash,$targetGadName,@arg);
return qq{set $name - $err} if defined($err);
}
KNX_Log ($name, 4, qq{gadName: $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};
if (defined ($option) && ($option =~ m/(?:get|listenonly)/xms)) {
# return $name . q{ set cmd rejected - "get" or "listenonly" option is defined.};
return qq{set $name $targetGadName $cmd} . q{ - rejected - "get" or "listenonly" option is defined};
}
my $value = $cmd; #process set command with $value as output
#Special commands for dpt1 and dpt1.001
if ($model =~ m/^(?:dpt1|dpt1.001)$/xms) {
(my $err, $value) = KNX_Set_dpt1($hash, $targetGadName, $cmd, @arg);
return $err if defined($err);
}
#special treatment for dpt 10,16,18 done in KNX_encodeByDpt
my $transval = KNX_encodeByDpt($hash, $targetGadName, $value, @arg); #process set command
return qq{"set $name $targetGadName $value" failed, see Log-Messages} if (!defined($transval)); # encodeByDpt failed
IOWrite($hash, $KNXID, 'w' . $groupCode . $transval);
KNX_Log ($name, 5, qq{cmd= $cmd , value= $value , translated= $transval});
# decode again for values that have been changed in encode process
if ($model =~ m/^dpt(?:3|10|11|16|18|19)(?:[.][\d]{3})?$/xms) {
$value = KNX_decodeByDpt($hash, $targetGadName, $transval);
}
#apply post processing for state and set all readings
KNX_SetReadings($hash, $targetGadName, $value, undef, undef);
return;
}
# Process set command for old syntax
# calling param: $hash, $cmd, arg array
# returns ($err, targetgadname, $cmd, @arg - array might be modified)
sub KNX_Set_oldsyntax {
my ($hash, $cmd, @arg) = @_;
my $name = $hash->{NAME};
my $na = scalar(@arg);
my $targetGadName = undef; #contains gadNames to process
my $groupnr = 1; #default group
#select another group, if the last arg starts with a g
if($na >= 1 && $arg[$na - 1] =~ m/$PAT_GNO/xms) {
$groupnr = pop (@arg);
KNX_Log ($name, 3, q{you are still using old syntax, pls. change to "set } .
qq{$name $groupnr $cmd } . join(q{\s},@arg) . q{"});
$groupnr =~ s/^[g]//ixms; #remove "g"
$na--;
}
# if cmd contains g1: the check for valid gadnames failed !
# this is NOT oldsyntax, but a user-error!
if ($cmd =~ /$PAT_GNO/xms) {
KNX_Log ($name, 2, qq{an invalid gadName: $cmd or invalid dpt used in set-cmd});
return qq{an invalid gadName: $cmd or invalid dpt used in set-cmd};
}
$targetGadName = KNX_gadNameByNO($hash, $groupnr);
return qq{gadName not found or invalid dpt used for group $groupnr} if(!defined($targetGadName));
# all of the following cmd's need at least 1 Argument (or more)
return (undef, $targetGadName, $cmd) if ($na <= 0);
# pass thru -for-timer,-until,blink cmds...
return (undef, $targetGadName, $cmd, @arg) if ($cmd =~ m/(?:-till|-until|-till-overnight|-for-timer|$BLINK)$/ixms);
## prepare for deletion of deprecated cmds
my $code = $hash->{GADDETAILS}->{$targetGadName}->{MODEL};
my $value = shift(@arg);
my $rtxt = qq{"set $name $cmd $value }; # log text
if ($cmd =~ m/$STRING/ixms) { ## no critic (ControlStructures::ProhibitCascadingIfElse)
return $rtxt . q{ ..." only allowed for dpt16} if ($code !~ m/^dpt16/ixms);
}
elsif ($cmd =~ m/$RAW/ixms) {
#check for 1-16 hex-digits
return $rtxt . q{" has wrong syntax. Use hex-format only.} if ($value !~ m/[\da-f]{1,16}/ixms);
}
elsif ($cmd =~ m/$VALUE/ixms) {
return $rtxt . q{" not allowed for dpt1, dpt16 and dpt232} if ($code =~ m/(dpt1|dpt16|dpt232)(?:[.]\d+)?$/ixms);
$value =~ s/,/\./gxms;
}
elsif ($cmd =~ m/$RGB/ixms) {
return $rtxt . q{" only allowed for dpt232} if ($code !~ m/dpt232/ixms);
#check for 6 hex-digits
return $rtxt . q{" has wrong syntax. Use 6 hex-digits only.} if ($value !~ m/[\da-f]{6}/ixms);
}
else {
KNX_Log ($name, 2, q{invalid cmd: } . $rtxt . join(q{ },@arg) . q{" issued - ignored});
return q{invalid cmd: } . $rtxt . join(q{ },@arg) . q{" issued - ignored};
}
KNX_Log ($name, 2, q{This cmd is deprecated since 1/2024: } . $rtxt . join(q{ },@arg) .
qq{" - use: "set $name $targetGadName $value } . join(q{ },@arg) . q{"} );
return (undef, $targetGadName, $value, @arg);
## prepare for deletion of deprecated cmds
## my $rtxt = q{invalid cmd: "set } . qq{$name $cmd $value } . join(q{ },@arg) . q{" issued - ignored};
## KNX_Log ($name, 2, $rtxt);
## return $rtxt;
}
# 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"} || IsDevice($name . "_TIMER_$groupCode")) {
CommandDelete(undef, $name . "_TIMER_$groupCode");
delete $hash->{".TIMER_$groupCode"};
}
my $value = ($cmd =~ m/^(on|1)/ixms)?'on':'off';
my $tvalue = ($value eq 'on')?'off':'on';
$cmd =~ s/[,]*$//xms; # remove empty widget parameters
return (undef,$value) if ($cmd =~ m/(?:$PAT_DPT1_PAT)$/ixms); # shortcut
#set on-for-timer / off-for-timer
my $endTS = undef;
if ($cmd =~ m/(?:(on|off)-for-timer)$/ixms) {
#get duration
return qq{KNX_Set_dpt1 ($name): parameter must be numeric} if (! Scalar::Util::looks_like_number($arg[0]));
# subsecond timer duration - set called with only $hash as param
if ($arg[0] < 10) { # allow fraction secs when dur < 10sec
my $param = {hash=>$hash, gadName=>$targetGadName, cmd=>$tvalue};
InternalTimer(Time::HiRes::time() + $arg[0],\&doKNX_Set,$param);
return (undef,$value);
} else {
$endTS = Time::HiRes::time() + $arg[0]; # allow more than one day ( > 86400) !!!
}
}
#set (on|off)-until / -till
elsif ($cmd =~ m/(?:(on|off)-(until|till-overnight|till))$/ixms) {
#get end-time
my ($err, $hr, $min, $sec, $fn) = GetTimeSpec($arg[0]); # fhem.pl
return qq{KNX_Set_dpt1 ($name): $err} if (defined($err));
return qq{KNX_Set_dpt1 ($name): Error trying to parse timespec $arg[0] } if ($hr >= 24);
my ($secs,$mins,$hours,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time()); # default now
$endTS = fhemTimeLocal($sec, $min, $hr, $mday, $mon, $year); # today end-time spec
if (($cmd =~ /-till$/xms) && (time() >= $endTS)) { # prevent overnight times
return qq{KNX_Set_dpt1 ($name): will not execute as end-Time is earlier than now};
}
}
# process both timer variants
if (defined($endTS)) {
my $hms_til = ::FmtDateTime($endTS) =~ s/[\s]/T/rxms; # fhem.pl alternativ use full datespec
$hash->{".TIMER_$groupCode"} = $hms_til; #create local marker
#place at-command for switching on / off
CommandDefMod(undef, '-silent ' . $name . qq{_TIMER_$groupCode at $hms_til set $name $targetGadName $tvalue});
return (undef,$value);
}
return KNX_set_dpt1_sp($hash, $targetGadName, $cmd, @arg);
}
# process special dpt1, dpt1.001 set blink & toggle
# return: $err, $value
sub KNX_set_dpt1_sp {
my ($hash, $targetGadName, $cmd, @arg) = @_;
my $name = $hash->{NAME};
my $groupCode = $hash->{GADDETAILS}->{$targetGadName}->{CODE};
# get current value from reading(s)
my $toggleOldVal = undef;
my ($tDev, $togglereading) = split(qr/:/xms,AttrVal($name,'KNX_toggle',$name));
if (defined($togglereading)) { # prio1: use Attr. KNX_toggle: format: KNX
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.
For each received message or get-cmd there will be a reading containing the received value and the sender address.
For every set-cmd, there will be a reading containing the sent value.
The reading <state> will be updated with the last sent or received value.
A (german) wiki page is avaliable here: FHEM Wiki
define <name> KNX <group>:<dpt>[:[<gadName>]:[set|get|listenonly]:[nosuffix]]
[<group>:<dpt> ..] [IODev]
The <group> parameter is either a group name notation (0-31/0-7/0-255) or the hex representation of it ([00-1f][0-7][00-ff]) (5 digits). All of the defined groups can be used for bus-communication. It is not allowed to have the same group-address more then once in one device. You can have multiple FHEM-devices containing the same group-adresses.
Important: a KNX device needs at least one valid DPT matching the dpt-spec of the KNX-Hardware.
The system cannot en- or de-code messages without a valid dpt defined.
If <gadName> not specified, the default is "g<number>". The corresponding reading-names are getG<number>
and setG<number>.
The optional parameteter <gadName> may contain an alias for the GAD. The following gadNames are not allowed:
state, on, off, on-for-timer, on-until, off-for-timer, off-until, toggle, blink, raw, rgb, string, value, set, get, listenonly, nosuffix
- because of conflict with cmds & parameters.
If you supply <gadName> this name is used instead as cmd prefix. <gadName> has to be unique within a device definition.
The reading-names are <gadName>-get and <gadName>-set.
The synonyms <getName> and <setName> are used in this documentation.
Reading-names may be modified by attribute readingNmap, see examples.
If you add the option "nosuffix", <getName> and <setName> have the identical name - <gadName>.
Both sent and received bus messages will be stored in the same reading <gadName>.
If you want to restrict the GAD, use the options "get", "set", or "listenonly". The usage is described in Set/Get-cmd chapter.
It is not possible to combine the options.
Specifying an IO-Device in define is deprecated! Use attribute IODev instead, but only if absolutely required!
If enabled, the module autocreate is creating a new definition for each not already defined group-address.
However, the new device will be disabled until you added a DPT to the definition and delete the
disable attribute.
The device name will be KNX_<llaaddd> where <ll> is the line-, <aa> the area- and <ddd> the device-address.
As the correct dpt cannot be determined by autocreate, dptRAW is used as dpt. It is recommended to change the
devicename to a name indicating the usage or function of the device (e.g. KitchenLight) and change dptRAW to the correct
dpt used by the KNX hardware. Migration from "old style" autocreate handling (MODEL_NOT_DEFINED) to dptRAW
format will be done during FHEM-start or device definition.
No FileLog or SVG definition is created for KNX-devices by autocreate. Use for example:
define <name> FileLog <filename> KNX_.*
to create a single FileLog-definition for all KNX-devices created by autocreate.
Another option is to disable autocreate for KNX-devices in production environments (when no changes / additions are expected)
by using: attr <autocreate> ignoreTypes KNX_.*
Examples:
define lamp1 KNX 0/7/11:dpt1
attr lamp1 webCmd on:off
attr lamp1 devStateIcon on:li_wht_on:off off:li_wht_off:on
define lamp2 KNX 0/7/12:dpt1:switch:set 0/7/13:dpt1.001:status:listenonly
define lamp3 KNX 0070E:dpt1.001 # equivalent to 0/7/14:dpt1.001
set <deviceName> [<gadName>] on|off|toggle
set <deviceName> <gadName> blink <nr of blinks> <duration seconds>
set <deviceName> <gadName> on-for-timer|off-for-timer <duration seconds>
set <deviceName> <gadName> on-until|off-until <timespec (HH:MM[:SS])>
Set sends the given value to the bus.
If <gadName> is omitted, the first listed GAD of the device definition is used.
If you want to send to a different group, you have to address it (see Examples). Without additional attributes,
all incoming and outgoing messages are in addition copied into reading <state>.
If the GAD is restricted in the definition with "get" or "listenonly", the set-command will be refused.
For dpt1 and dpt1.001 valid values are on, off, toggle and blink. Also the timer-functions can be used.
A running timer-function will be cancelled if a new set cmd (on,off,on-for-,....) for this GAD is issued.
For all other dpt1.<xxx> the min- and max-values can be used for en- and de-coding alternatively to on/off.
All DPTs: allowed values or range of values are specified here: KNX-dpt
After successful sending, the value is stored in readings <setName> and state.
Do not use wildcards for <deviceName>, the KNX-GW/Bus might be not perfomant enough to handle that.
The last sentence is not true for definitions that use module KNXIO as IO-device. The KNXIO Module implements a queing
mechanism to prevent KNX-bus overload.
Examples:
set lamp2 on # gadName omitted
set lamp2 off # gadName omitted
set lamp2 switch on
set lamp2 switch off
set lamp2 switch on-for-timer 10 # seconds - seconds > 86400 are supported, fractions of seconds if seconds < 10
set lamp2 switch on-until 13:15:00 # if timestamp is earlier than "now", device will be switched off tomorrow !
set lamp2 switch on-till 13:15:00 # if timestamp is earlier than "now", cmd will be rejected (same function as in SetExtensions)
set lamp2 status on # will be refused - status has option "listenonly" set
set lamp3 g1 off-for-timer 86460 # switch off until tomorrow, same time + 60 seconds
set lamp3 g1 off-until 13:15:00
set lamp3 g1 off-till 13:15:00 # if timestamp is earlier than "now", cmd will be rejected (same function as in SetExtensions)
set lamp3 g1 toogle # lamp3 change state
set lamp3 g1 blink 2 4 # lamp3 on for 4 seconds, off for 4 seconds, 2 repeats
set myThermoDev g1 23.44
set myMessageDev g1 Hello World! # dpt16 def
More complex examples can be found on the (german) Wiki
If you execute "get" for a KNX-Element the status will be requested from the KNX-device. The device has to be able to respond to a read -
this might not be supported by the target KNX-device.
If the GAD is restricted in the definition with "set" or "listenonly", the execution will be refused.
The answer from the bus-device updates the readings <getName> and state.
Do not use wildcards for <deviceName>, the KNX-GW/Bus might be not perfomant enough to handle that.
The last sentence is not true for definitions that use module KNXIO as IO-device. The KNXIO Module implements a queing
mechanism to prevent KNX-bus overload, but you can also use KNX_scan cmd instead.
Examples:
attr <device> readingNmap desired-temp-get:WunschThemperatur # desired-temp-get -> WunschThemperatur
attr <device> readingNmap position:desired-pos # replace "position" by "desired-pos"
attr <device> readingNmap pct:Postion g1:Position # both gadNames store their values into reading "Position".
This is useful for e.g. setting the actual blind-Position (as received from actor) into a reading "desired-position"
or "desired-pct" to update a cmd-slider to the current position of the blind.
Examples:
attr <device> stateRegex /position:0/aus/ # on update of reading "position" and value=0, reading state value will be "aus"
attr <device> stateRegex /position:(.*)/$1/ # on update of reading "position" reading state is updated with reading position value".
attr <device> stateRegex /position/pos-/ # on update of reading "position" reading state value will be prepended with "pos-".
attr <device> stateRegex /desired-pos// # reading state will NOT get updated when reading "desired-pos" is updated.
Examples:
attr <device> stateCmd {return sprintf("%.1f",ReadingsNum($name,'g1',0)*3.6);} # multiply reading value 'g1' and set value into state-reading
attr <device> stateCmd {my99Utilssub($name,$gadName,$state);} # call subroutine (99_myUtils.pm ?) and use return value as state value
attr <device> stateCmd {return 123 if ($gadName eq 'position' && $state eq 'off'); return $state;} # on change of GadName position and value = off ..., else no change
Examples:
attr <device> putCmd {return $state if($gadName eq 'status'); return;} #returns value of reading state on request from bus for gadName "status".
attr <device> putCmd {return ReadingsVal('dummydev','state','error') if(...); return;} #returns value of device "dummydev" reading "state".
attr <device> putCmd {return (split(/[\s]/xms,TimeNow()))[1] if ($gadName eq 'time'); return;} #returns system timestamp (dpt10 format) ...
set <device> <gadName> toggle cmd.Events are generated for each message sent or received to/from KNX-Bus unless restricted by event-xxx attributes
or modified by readingNmap, eventMap, stateRegex attributes.
KNX-events have this format:
<device> <readingName>: <value> [<unit>] # reading event, unit optional
<device> <value> [<unit>] # state event
The following dpt are implemented and have to be assigned within the device definition. The values right to the dpt define the valid range or min/max Set-command values and Get-command return values and units.
syntax: set <device> <gadName> <hex-string>
Examples of valid / invalid hex-strings:
00..3f # valid, single byte range x00-x3f
40..ff # invalid, only values x00-x3f allowed as first byte
006b # valid, 1st byte > x3f and multiple bytes have to be prefixed with x00
001a2b3c4e5f.. # valid, any length as long as even number of hex-digits are used
00112233445 # invalid, odd number of digits
get <device> <gadName> cmd to each selected device/GAD.
The result of the "get" cmd will be stored in the respective readings.
The command is supported only if the IO-device is of TYPE KNXIO!
Examples:
syntax when used as perl-function (eg. in at, notify,...)
KNX_scan() - scan all possible devices
KNX_scan('dev-A') - scan device-A only
KNX_scan('dev-A,dev-B,dev-C') - scan device-A, device-B, device-C
KNX_scan('room=Kueche') - scan all KNX-devices in room Kueche
KNX_scan('EG_.*') - scan all KNX-devices where device-names begin with EG_
syntax when used from FHEM-cmdline
KNX_scan - scan all possible devices
KNX_scan dev-A - scan device-A only
KNX_scan dev-A,dev-B,dev-C - scan device-A, device-B, device-C
KNX_scan room=Kueche - scan all KNX-devices in room Kueche
KNX_scan EG_.* - scan all KNX-devices where device-names begin with EG_
Do not use KNX_scan or any 'set or get <KNX-device> ...' in a global:INITIALIZED notify, the IO-device will not be
ready for communication at that time!
defmod initializedKNXIO_nf notify <KNXIO-deviceName>:INITIALIZED set KNX_date now;; set KNX_time now;; KNX_scan;;