diff --git a/fhem/contrib/HMCCU/FHEM/88_HMCCUCHN.pm b/fhem/contrib/HMCCU/FHEM/88_HMCCUCHN.pm new file mode 100644 index 000000000..076d1a71b --- /dev/null +++ b/fhem/contrib/HMCCU/FHEM/88_HMCCUCHN.pm @@ -0,0 +1,564 @@ +################################################################ +# +# 88_HMCCUCHN.pm +# +# $Id:$ +# +# Version 2.7 +# +# (c) 2016 zap (zap01 t-online de) +# +################################################################ +# +# define HMCCUCHN [readonly] +# +# set control +# set datapoint [...] +# set devstate [...] +# set +# set toggle +# set config [] = [...] +# +# get devstate +# get datapoint +# get channel +# get config [] +# get configdesc [] +# get update +# +# attr ccureadings { 0 | 1 } +# attr ccureadingfilter +# attr ccureadingformat { name | address | datapoint } +# attr ccuverify { 0 | 1 } +# attr controldatapoint +# attr statevals :[,...] +# attr substitute [;...] +# +################################################################ +# Requires module 88_HMCCU.pm +################################################################ + +package main; + +use strict; +use warnings; +use SetExtensions; + +use Time::HiRes qw( gettimeofday usleep ); + +sub HMCCUCHN_Define ($@); +sub HMCCUCHN_Set ($@); +sub HMCCUCHN_Get ($@); +sub HMCCUCHN_Attr ($@); +sub HMCCUCHN_SetError ($$); + +##################################### +# Initialize module +##################################### + +sub HMCCUCHN_Initialize ($) +{ + my ($hash) = @_; + + $hash->{DefFn} = "HMCCUCHN_Define"; + $hash->{SetFn} = "HMCCUCHN_Set"; + $hash->{GetFn} = "HMCCUCHN_Get"; + $hash->{AttrFn} = "HMCCUCHN_Attr"; + + $hash->{AttrList} = "IODev ccureadingfilter ccureadingformat:name,address,datapoint ccureadings:0,1 ccuverify:0,1 ccustate ccuget:State,Value controldatapoint statedatapoint statevals substitute stripnumber:0,1,2 ". $readingFnAttributes; +} + +##################################### +# Define device +##################################### + +sub HMCCUCHN_Define ($@) +{ + my ($hash, $def) = @_; + my $name = $hash->{NAME}; + my @a = split("[ \t][ \t]*", $def); + + return "Specifiy the CCU device name or address as parameters" if (@a < 3); + + my $devname = shift @a; + my $devtype = shift @a; + my $devspec = shift @a; + + return "Invalid or unknown CCU channel name or address" if (! HMCCU_IsValidDevice ($devspec)); + + if ($devspec =~ /^(.+)\.([A-Z]{3,3}[0-9]{7,7}:[0-9]+)$/) { + # CCU Channel address with interface + $hash->{ccuif} = $1; + $hash->{ccuaddr} = $2; + $hash->{ccuname} = HMCCU_GetChannelName ($hash->{ccuaddr}, ''); + return "CCU device name not found for channel address $devspec" if ($hash->{ccuname} eq ''); + } + elsif ($devspec =~ /^[A-Z]{3,3}[0-9]{7,7}:[0-9]+$/) { + # CCU Channel address + $hash->{ccuaddr} = $devspec; + $hash->{ccuif} = HMCCU_GetDeviceInterface ($hash->{ccuaddr}, 'BidCos-RF'); + $hash->{ccuname} = HMCCU_GetChannelName ($devspec, ''); + return "CCU device name not found for channel address $devspec" if ($hash->{ccuname} eq ''); + } + else { + # CCU Device name + $hash->{ccuname} = $devspec; + my ($add, $chn) = HMCCU_GetAddress ($devspec, '', ''); + return "Channel address not found for channel name $devspec" if ($add eq '' || $chn eq ''); + $hash->{ccuaddr} = $add.':'.$chn; + $hash->{ccuif} = HMCCU_GetDeviceInterface ($hash->{ccuaddr}, 'BidCos-RF'); + } + + $hash->{ccutype} = HMCCU_GetDeviceType ($hash->{ccuaddr}, ''); + $hash->{channels} = 1; + $hash->{statevals} = 'devstate'; + + my $arg = shift @a; + if (defined ($arg) && $arg eq 'readonly') { + $hash->{statevals} = $arg; + } + + # Inform HMCCU device about client device + AssignIoPort ($hash); + + readingsSingleUpdate ($hash, "state", "Initialized", 1); + $hash->{ccudevstate} = 'Active'; + + return undef; +} + +##################################### +# Set attribute +##################################### + +sub HMCCUCHN_Attr ($@) +{ + my ($cmd, $name, $attrname, $attrval) = @_; + my $hash = $defs{$name}; + + if ($cmd eq "set") { + return "Missing attribute value" if (!defined ($attrval)); + if ($attrname eq 'IODev') { + $hash->{IODev} = $defs{$attrval}; + } + elsif ($attrname eq 'statevals') { + return "Device is read only" if ($hash->{statevals} eq 'readonly'); + $hash->{statevals} = "devstate"; + my @states = split /,/,$attrval; + foreach my $st (@states) { + my @statesubs = split /:/,$st; + return "value := text:substext[,...]" if (@statesubs != 2); + $hash->{statevals} .= '|'.$statesubs[0]; + } + } + } + elsif ($cmd eq "del") { + if ($attrname eq 'statevals') { + $hash->{statevals} = "devstate"; + } + } + + return undef; +} + +##################################### +# Set commands +##################################### + +sub HMCCUCHN_Set ($@) +{ + my ($hash, @a) = @_; + my $name = shift @a; + my $opt = shift @a; + + if (!defined ($hash->{IODev})) { + return HMCCUCHN_SetError ($hash, "No IO device defined"); + } + if ($hash->{statevals} eq 'readonly' && $opt ne 'config') { + return undef; + } + + my $hmccu_hash = $hash->{IODev}; + + if (HMCCU_IsRPCStateBlocking ($hmccu_hash)) { + return undef if ($opt eq '?'); + return "HMCCUCHN: CCU busy"; + } + + my $statevals = AttrVal ($name, "statevals", ''); + my $statedatapoint = AttrVal ($name, "statedatapoint", 'STATE'); + my $controldatapoint = AttrVal ($name, "controldatapoint", ''); + my $ccuverify = AttrVal ($name, "ccuverify", 0); + + my $result = ''; + my $rc; + + if ($opt eq 'datapoint') { + my $objname = shift @a; + my $objvalue = join ('%20', @a); + + if (!defined ($objname) || !defined ($objvalue)) { + return HMCCUCHN_SetError ($hash, "Usage: set datapoint [...]"); + } + $objvalue = HMCCU_Substitute ($objvalue, $statevals, 1, ''); + + # Build datapoint address + $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.'.'.$objname; + + $rc = HMCCU_SetDatapoint ($hash, $objname, $objvalue); + return HMCCUCHN_SetError ($hash, $rc) if ($rc < 0); + + if ($ccuverify) { + usleep (100000); + ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); + return HMCCUCHN_SetError ($hash, $rc) if ($rc < 0); + } + + return undef; + } + elsif ($opt eq 'control') { + return HMCCUCHN_SetError ($hash, "Attribute control datapoint not set") if ($controldatapoint eq ''); + my $objvalue = shift @a; + my $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.':'.$controldatapoint; + $rc = HMCCU_SetDatapoint ($hash, $objname, $objvalue); + return HMCCUDEV_SetError ($hash, $rc) if ($rc < 0); + + HMCCU_SetState ($hash, "OK"); + return undef; + } + elsif ($opt =~ /^($hash->{statevals})$/) { + my $cmd = $1; + my $objvalue = ($cmd ne 'devstate') ? $cmd : join ('%20', @a); + + if (!defined ($objvalue)) { + return HMCCUCHN_SetError ($hash, "Usage: set devstate "); + } + $objvalue = HMCCU_Substitute ($objvalue, $statevals, 1, ''); + + # Build datapoint address + my $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.'.'.$statedatapoint; + + $rc = HMCCU_SetDatapoint ($hash, $objname, $objvalue); + return HMCCUCHN_SetError ($hash, $rc) if ($rc < 0); + + if ($ccuverify) { + usleep (100000); + ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); + return HMCCUCHN_SetError ($hash, $rc) if ($rc < 0); + } + + return undef; + } + elsif ($opt eq 'toggle') { + return HMCCUCHN_SetError ($hash, "Attribute statevals not set") + if ($statevals eq '' || !exists($hash->{statevals})); + + my $tstates = $hash->{statevals}; + $tstates =~ s/devstate\|//; + my @states = split /\|/, $tstates; + my $sc = scalar (@states); + + my $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.'.'.$statedatapoint; + ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); + return HMCCUCHN_SetError ($hash, $rc) if ($rc < 0); + + my $objvalue = ''; + my $st = 0; + while ($st < $sc) { + if ($states[$st] eq $result) { + $objvalue = ($st == $sc-1) ? $states[0] : $states[$st+1]; + last; + } + else { + $st++; + } + } + + return HMCCUCHN_SetError ($hash, "Current device state doesn't match statevals") + if ($objvalue eq ''); + + $objvalue = HMCCU_Substitute ($objvalue, $statevals, 1, ''); + $rc = HMCCU_SetDatapoint ($hash, $objname, $objvalue); + return HMCCUCHN_SetError ($hash, $rc) if ($rc < 0); + + HMCCU_SetState ($hash, "OK"); + return undef; + } + elsif ($opt eq 'config') { + return HMCCUCHN_SetError ($hash, "Usage: set $name config {parameter}={value} [...]") if (@a < 1);; + + my $rc = HMCCU_RPCSetConfig ($hash, $hash->{ccuaddr}, \@a); + return HMCCUCHN_SetError ($hash, $rc) if ($rc < 0); + return undef; + } + else { + my $retmsg = "HMCCUCHN: Unknown argument $opt, choose one of config datapoint devstate"; + return undef if ($hash->{statevals} eq 'readonly'); + + if ($hash->{statevals} ne '') { + my @cmdlist = split /\|/,$hash->{statevals}; + shift @cmdlist; + $retmsg .= ':'.join(',',@cmdlist); + foreach my $sv (@cmdlist) { + $retmsg .= ' '.$sv.':noArg'; + } + $retmsg .= " toggle:noArg"; + } + + return $retmsg; + } +} + +##################################### +# Get commands +##################################### + +sub HMCCUCHN_Get ($@) +{ + my ($hash, @a) = @_; + my $name = shift @a; + my $opt = shift @a; + + if (!defined ($hash->{IODev})) { + return HMCCUCHN_SetError ($hash, "No IO device defined"); + } + + my $hmccu_hash = $hash->{IODev}; + + if (HMCCU_IsRPCStateBlocking ($hmccu_hash)) { + return undef if ($opt eq '?'); + return "HMCCUCHN: CCU busy"; + } + + my $statedatapoint = AttrVal ($name, "statedatapoint", 'STATE'); + my $ccureadings = AttrVal ($name, "ccureadings", 1); + + my $result = ''; + my $rc; + + if ($opt eq 'devstate') { + my $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.'.'.$statedatapoint; + ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); + return HMCCUCHN_SetError ($hash, $rc) if ($rc < 0); + return $ccureadings ? undef : $result; + } + elsif ($opt eq 'datapoint') { + my $objname = shift @a; + return HMCCUCHN_SetError ($hash, "Usage: get datapoint ") if (!defined ($objname)); + + $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.'.'.$objname; + ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); + return HMCCUCHN_SetError ($hash, $rc) if ($rc < 0); + return $ccureadings ? undef : $result; + } + elsif ($opt eq 'channel') { + my $dptexpr = shift @a; + my $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}; + $objname .= '.'.$dptexpr if (defined ($dptexpr)); + my @chnlist = ($objname); + ($rc, $result) = HMCCU_GetChannel ($hash, \@chnlist); + return HMCCUCHN_SetError ($hash, $rc) if ($rc < 0); + return $ccureadings ? undef : $result; + } + elsif ($opt eq 'update') { + my $ccuget = shift @a; + $ccuget = 'Attr' if (!defined ($ccuget)); + if ($ccuget !~ /^(Attr|State|Value)$/) { + return HMCCUCHN_SetError ($hash, "Usage: get $name update [{'State'|'Value'}]"); + } + $rc = HMCCU_GetUpdate ($hash, $hash->{ccuaddr}, $ccuget); + return HMCCUCHN_SetError ($hash, $rc) if ($rc < 0); + return undef; + } + elsif ($opt eq 'config') { + my $port = shift @a; + my $ccuobj = $hash->{ccuaddr}; + + $port = 2001 if (!defined ($port)); + my ($rc, $res) = HMCCU_RPCGetConfig ($hash, $ccuobj, "getParamset", $port); + return HMCCUCHN_SetError ($hash, $rc) if ($rc < 0); + return $ccureadings ? undef : $res; + } + elsif ($opt eq 'configdesc') { + my $port = shift @a; + my $ccuobj = $hash->{ccuaddr}; + + $port = 2001 if (!defined ($port)); + my ($rc, $res) = HMCCU_RPCGetConfig ($hash, $ccuobj, "getParamsetDescription", $port); + return HMCCUCHN_SetError ($hash, $rc) if ($rc < 0); + return $res; + } + else { + return "HMCCUCHN: Unknown argument $opt, choose one of devstate:noArg datapoint channel update:noArg config:noArg configdesc:noArg"; + } +} + +##################################### +# Set error status +##################################### + +sub HMCCUCHN_SetError ($$) +{ + my ($hash, $text) = @_; + my $name = $hash->{NAME}; + my $msg; + my %errlist = ( + -1 => 'Channel name or address invalid', + -2 => 'Execution of CCU script failed', + -3 => 'Cannot detect IO device', + -4 => 'Device deleted in CCU', + -5 => 'No response from CCU', + -6 => 'Update of readings disabled. Set attribute ccureadings first' + ); + + if (exists ($errlist{$text})) { + $msg = $errlist{$text}; + } + else { + $msg = $text; + } + + $msg = "HMCCUCHN: ".$name." ". $msg; + readingsSingleUpdate ($hash, "state", "Error", 1); + Log3 $name, 1, $msg; + return $msg; +} + +1; + +=pod +=begin html + + +

HMCCUCHN

+
    + The module implements client devices for HMCCU. A HMCCU device must exist + before a client device can be defined. +

    + + Define +
      +
      + define <name> HMCCUCHN {<channel-name>|<channel-address>} [readonly] +

      + If readonly parameter is specified no set command will be available. +

      + Examples:
      + define window_living HMCCUCHN WIN-LIV-1 readonly
      + define temp_control HMCCUCHN BidCos-RF.LEQ1234567:1 +
      +
    +
    + + + Set
    +
      +
      +
    • set <name> devstate <value> [...]
      + Set state of a CCU device channel. Channel datapoint must be defined + by setting attribute 'statedatapoint'. +

      + Example:
      + set light_entrance devstate on +

    • +
    • set <name> <statevalue>
      + State of a CCU device channel is set to StateValue. State datapoint + must be defined as attribute statedatapoint. State values can be replaced + by setting attribute statevals. +

      + Example:
      + + attr myswitch statedatapoint TEST
      + attr myswitch statevals on:true,off:false
      + set myswitch on +
      +

    • +
    • set <name> toggle
      + Toggles between values defined by attribute 'statevals'. +

    • +
    • set <name> datapoint <datapoint> <value> [...]
      + Set value of a datapoint of a CCU device channel. +

      + Example:
      + set temp_control datapoint SET_TEMPERATURE 21 +

    • +
    • set <name> config [<rpcport>] <parameter>=<value>] [...]
      + Set config parameters of CCU channel. +
    • +
    +
    + + + Get
    +
      +
      +
    • get <name> devstate +
      + Get state of CCU device. Default datapoint STATE can be changed by setting + attribute 'statedatapoint'. +

    • +
    • get <name> datapoint <datapoint> +
      + Get value of a CCU device datapoint. +

    • +
    • get <name> config [<rpcport>] +
      + Get configuration parameters of CCU channel. If attribute ccureadings is 0 results will be + displayed in browser window. +

    • +
    • get <name> configdesc [<rpcport>] +
      + Get description of configuration parameters of CCU channel. +

    • +
    • get <name> update [{'State'|'Value'}]
      + Update all datapoints / readings of channel. +
    • +
    +
    + + + Attributes
    +
    +
      +
    • ccuget <State | Value>
      + Set read access method for CCU channel datapoints. Method 'State' is slower than 'Value' because + each request is sent to the device. With method 'Value' only CCU is queried. Default is 'Value'. +

    • +
    • ccureadings <0 | 1>
      + If set to 1 values read from CCU will be stored as readings. Default is 1. +

    • +
    • ccureadingfilter <datapoint-expr>
      + Only datapoints matching specified expression are stored as readings. +

    • +
    • ccuverify <0 | 1>
      + If set to 1 a datapoint is read for verification after set operation. +

    • +
    • controldatapoint <datapoint>
      + Set datapoint for device control. Can be use to realize user defined control elements for + setting control datapoint. For example if datapoint of thermostat control is + SET_TEMPERATURE one can define a slider for setting the destination temperature with + following attributes:

      + attr mydev controldatapoint SET_TEMPERATURE + attr mydev webCmd control + attr mydev widgetOverride control:slider,10,1,25 +

    • +
    • statedatapoint <datapoint>
      + Set datapoint for devstate commands. +

    • +
    • statevals <text>:<text>[,...]
      + Define substitution for set commands values. The parameters <text> + are available as set commands. Example:
      + attr my_switch statevals on:true,off:false
      + set my_switch on +

    • +
    • substitude <subst-rule>[;...]
      + Define substitions for reading values. Substitutions for parfile values must + be specified in parfiles. Syntax of subst-rule is

      + [datapoint!]<regexp1>:<text1>[,...] +
    • +
    +
+ +=end html +=cut +