From 4437a7b7488155d917d22e3b275c52bea60abe30 Mon Sep 17 00:00:00 2001 From: fhemzap <> Date: Sun, 14 Feb 2016 13:19:47 +0000 Subject: [PATCH] HMCCU: moved to FHEM directory git-svn-id: https://svn.fhem.de/fhem/trunk@10842 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/contrib/HMCCU/FHEM/88_HMCCUDEV.pm | 673 +++++++++++++++++++++++++ 1 file changed, 673 insertions(+) create mode 100644 fhem/contrib/HMCCU/FHEM/88_HMCCUDEV.pm diff --git a/fhem/contrib/HMCCU/FHEM/88_HMCCUDEV.pm b/fhem/contrib/HMCCU/FHEM/88_HMCCUDEV.pm new file mode 100644 index 000000000..c99183b1a --- /dev/null +++ b/fhem/contrib/HMCCU/FHEM/88_HMCCUDEV.pm @@ -0,0 +1,673 @@ +################################################################ +# +# 88_HMCCUDEV.pm +# +# $Id:$ +# +# Version 2.7 +# +# (c) 2016 zap (zap01 t-online de) +# +################################################################ +# +# define HMCCUDEV [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 ccureadingformat { address | name } +# attr ccureadingfilter +# attr ccuverify { 0 | 1 } +# attr controldatapoint . +# attr statechannel +# attr statedatapoint +# attr statevals :[,...] +# attr substitute :[,...] +# +################################################################ +# Requires module 88_HMCCU +################################################################ + +package main; + +use strict; +use warnings; +use SetExtensions; +# use Data::Dumper; + +use Time::HiRes qw( gettimeofday usleep ); + +sub HMCCUDEV_Define ($@); +sub HMCCUDEV_Set ($@); +sub HMCCUDEV_Get ($@); +sub HMCCUDEV_Attr ($@); +sub HMCCUDEV_SetError ($$); + +##################################### +# Initialize module +##################################### + +sub HMCCUDEV_Initialize ($) +{ + my ($hash) = @_; + + $hash->{DefFn} = "HMCCUDEV_Define"; + $hash->{SetFn} = "HMCCUDEV_Set"; + $hash->{GetFn} = "HMCCUDEV_Get"; + $hash->{AttrFn} = "HMCCUDEV_Attr"; + + $hash->{AttrList} = "IODev ccureadingfilter ccureadingformat:name,address ccureadings:0,1 ccustate ccuget:State,Value ccuverify:0,1 statevals substitute statechannel statedatapoint controldatapoint stripnumber:0,1,2 ". $readingFnAttributes; +} + +##################################### +# Define device +##################################### + +sub HMCCUDEV_Define ($@) +{ + my ($hash, $def) = @_; + my $name = $hash->{NAME}; + my @a = split("[ \t][ \t]*", $def); + + my $usage = "Usage: define HMCCUDEV {|} [] [readonly]"; + return $usage if (@a < 3); + + my $devname = shift @a; + my $devtype = shift @a; + my $devspec = shift @a; + + return "Invalid or unknown CCU device name or address" if (! HMCCU_IsValidDevice ($devspec)); + + if ($devspec =~ /^(.+)\.([A-Z]{3,3}[0-9]{7,7})$/) { + # CCU Device address with interface + $hash->{ccuif} = $1; + $hash->{ccuaddr} = $2; + $hash->{ccuname} = HMCCU_GetDeviceName ($hash->{ccuaddr}, ''); + } + elsif ($devspec =~ /^[A-Z]{3,3}[0-9]{7,7}$/) { + # CCU Device address without interface + $hash->{ccuaddr} = $devspec; + $hash->{ccuname} = HMCCU_GetDeviceName ($devspec, ''); + $hash->{ccuif} = HMCCU_GetDeviceInterface ($hash->{ccuaddr}, 'BidCos-RF'); + } + else { + # CCU Device name + $hash->{ccuname} = $devspec; + my ($add, $chn) = HMCCU_GetAddress ($devspec, '', ''); + return "Name is a channel name" if ($chn ne ''); + $hash->{ccuaddr} = $add; + $hash->{ccuif} = HMCCU_GetDeviceInterface ($hash->{ccuaddr}, 'BidCos-RF'); + } + + return "CCU device address not found for $devspec" if ($hash->{ccuaddr} eq ''); + return "CCU device name not found for $devspec" if ($hash->{ccuname} eq ''); + + $hash->{ccutype} = HMCCU_GetDeviceType ($hash->{ccuaddr}, ''); + $hash->{channels} = HMCCU_GetDeviceChannels ($hash->{ccuaddr}); + $hash->{statevals} = 'devstate'; + + my $n = 0; + my $arg = shift @a; + while (defined ($arg)) { + return $usage if ($n == 2); + if ($arg eq 'readonly') { + $hash->{statevals} = $arg; + $n++; + } + elsif ($arg =~ /^[0-9]+$/) { + $attr{$name}{statechannel} = $arg; + $n++; + } + else { + return $usage; + } + $arg = shift @a; + } + + # Inform HMCCU device about client device + AssignIoPort ($hash); + + readingsSingleUpdate ($hash, "state", "Initialized", 1); + $hash->{ccudevstate} = 'Active'; + + return undef; +} + +##################################### +# Set attribute +##################################### + +sub HMCCUDEV_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 HMCCUDEV_Set ($@) +{ + my ($hash, @a) = @_; + my $name = shift @a; + my $opt = shift @a; + + if (!exists ($hash->{IODev})) { + return HMCCUDEV_SetError ($hash, "No IO device defined"); + } + return undef if ($hash->{statevals} eq 'readonly'); + + my $hmccu_hash = $hash->{IODev}; + my $hmccu_name = $hash->{IODev}->{NAME}; + + if (HMCCU_IsRPCStateBlocking ($hmccu_hash)) { + return undef if ($opt eq '?'); + return "HMCCUDEV: CCU busy"; + } + + my $statechannel = AttrVal ($name, "statechannel", ''); + my $statedatapoint = AttrVal ($name, "statedatapoint", 'STATE'); + my $statevals = AttrVal ($name, "statevals", ''); + 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) || $objname !~ /^[0-9]+\..+$/ || !defined ($objvalue)) { + return HMCCUDEV_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 HMCCUDEV_SetError ($hash, $rc) if ($rc < 0); + + if ($ccuverify) { + usleep (100000); + ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); + return HMCCUDEV_SetError ($hash, $rc) if ($rc < 0); + } + + HMCCU_SetState ($hash, "OK"); + return undef; + } + elsif ($opt eq 'control') { + return HMCCUDEV_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); + + return HMCCUDEV_SetError ($hash, "No state channel specified") if ($statechannel eq ''); + return HMCCUDEV_SetError ($hash, "Usage: set devstate [...]") if (!defined ($objvalue)); + + $objvalue = HMCCU_Substitute ($objvalue, $statevals, 1, ''); + + # Build datapoint address + my $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.':'.$statechannel.'.'.$statedatapoint; + + $rc = HMCCU_SetDatapoint ($hash, $objname, $objvalue); + return HMCCUDEV_SetError ($hash, $rc) if ($rc < 0); + + if ($ccuverify) { + usleep (100000); + ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); + return HMCCUDEV_SetError ($hash, $rc) if ($rc < 0); + } + + HMCCU_SetState ($hash, "OK"); + return undef; + } + elsif ($opt eq 'toggle') { + return HMCCUDEV_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}.':'.$statechannel.'.'.$statedatapoint; + ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); + return HMCCUDEV_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 HMCCUDEV_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 HMCCUDEV_SetError ($hash, $rc) if ($rc < 0); + + HMCCU_SetState ($hash, "OK"); + return undef; + } + elsif ($opt eq 'config') { + return HMCCUDEV_SetError ($hash, "Usage: set $name config [{channel-number}] {parameter}={value} [...]") if (@a < 1);; + my $objname = $hash->{ccuaddr}; + $objname .= ':'.shift @a if ($a[0] =~ /^[0-9]$/); + + my $rc = HMCCU_RPCSetConfig ($hash, $objname, \@a); + return HMCCUDEV_SetError ($hash, $rc) if ($rc < 0); + + HMCCU_SetState ($hash, "OK"); + return undef; + } + else { + my $retmsg = "HMCCUDEV: Unknown argument $opt, choose one of config control datapoint"; + return undef if ($hash->{statevals} eq 'readonly'); + + if ($statechannel ne '') { + $retmsg .= " devstate"; + 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 HMCCUDEV_Get ($@) +{ + my ($hash, @a) = @_; + my $name = shift @a; + my $opt = shift @a; + + if (!defined ($hash->{IODev})) { + return HMCCUDEV_SetError ($hash, "No IO device defined"); + } + + my $hmccu_hash = $hash->{IODev}; + + if (HMCCU_IsRPCStateBlocking ($hmccu_hash)) { + return undef if ($opt eq '?'); + return "HMCCUDEV: CCU busy"; + } + + my $statechannel = AttrVal ($name, 'statechannel', ''); + my $statedatapoint = AttrVal ($name, 'statedatapoint', 'STATE'); + my $ccureadings = AttrVal ($name, 'ccureadings', 1); + + my $result = ''; + my $rc; + + if ($opt eq 'devstate') { + if ($statechannel eq '') { + return HMCCUDEV_SetError ($hash, "No state channel specified"); + } + + my $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.':'.$statechannel.'.'.$statedatapoint; + ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); + + return HMCCUDEV_SetError ($hash, $rc) if ($rc < 0); + return $ccureadings ? undef : $result; + } + elsif ($opt eq 'datapoint') { + my $objname = shift @a; + if (!defined ($objname) || $objname !~ /^[0-9]+\..*$/) { + return HMCCUDEV_SetError ($hash, "Usage: get datapoint ."); + } + + $objname = $hash->{ccuif}.'.'.$hash->{ccuaddr}.':'.$objname; + ($rc, $result) = HMCCU_GetDatapoint ($hash, $objname); + + return HMCCUDEV_SetError ($hash, $rc) if ($rc < 0); + + HMCCU_SetState ($hash, "OK") if (exists ($hash->{STATE}) && $hash->{STATE} eq "Error"); + return $ccureadings ? undef : $result; + } + elsif ($opt eq 'channel') { + my @chnlist; + foreach my $objname (@a) { + last if (!defined ($objname)); + if ($objname =~ /^([0-9]+)/ && exists ($hash->{channels})) { + return HMCCUDEV_SetError ($hash, "Invalid channel number: $objname") if ($1 >= $hash->{channels}); + } + else { + return HMCCUDEV_SetError ($hash, "Invalid channel number: $objname"); + } + if ($objname =~ /^[0-9]{1,2}.*=/) { + $objname =~ s/=/ /; + } + push (@chnlist, $hash->{ccuif}.'.'.$hash->{ccuaddr}.':'.$objname); + } + if (@chnlist == 0) { + return HMCCUDEV_SetError ($hash, "Usage: get $name channel {channel-number}[.{datapoint-expr}] [...]"); + } + + ($rc, $result) = HMCCU_GetChannel ($hash, \@chnlist); + return HMCCUDEV_SetError ($hash, $rc) if ($rc < 0); + + HMCCU_SetState ($hash, "OK") if (exists ($hash->{STATE}) && $hash->{STATE} eq "Error"); + return $ccureadings ? undef : $result; + } + elsif ($opt eq 'update') { + my $ccuget = shift @a; + $ccuget = 'Attr' if (!defined ($ccuget)); + if ($ccuget !~ /^(Attr|State|Value)$/) { + return HMCCUDEV_SetError ($hash, "Usage: get $name update [{'State'|'Value'}]"); + } + $rc = HMCCU_GetUpdate ($hash, $hash->{ccuaddr}, $ccuget); + return HMCCUDEV_SetError ($hash, $rc) if ($rc < 0); + + return undef; + } + elsif ($opt eq 'deviceinfo') { + my $ccuget = shift @a; + $ccuget = 'Attr' if (!defined ($ccuget)); + if ($ccuget !~ /^(Attr|State|Value)$/) { + return HMCCUDEV_SetError ($hash, "Usage: get $name deviceinfo [{'State'|'Value'}]"); + } + $result = HMCCU_GetDeviceInfo ($hash, $hash->{ccuaddr}, $ccuget); + return HMCCUDEV_SetError ($hash, -2) if ($result eq ''); + return $result; + } + elsif ($opt eq 'config') { + my $channel = undef; + my $port = undef; + my $par = shift @a; + if ($par =~ /^[0-9]$/) { + $channel = $par; + $port = shift @a; + } + else { + $port = $par; + } + + my $ccuobj = $hash->{ccuaddr}; + $ccuobj .= ':'.$channel if (defined ($channel)); + $port = 2001 if (!defined ($port)); + + my ($rc, $res) = HMCCU_RPCGetConfig ($hash, $ccuobj, "getParamset", $port); + return HMCCUDEV_SetError ($hash, $rc) if ($rc < 0); + HMCCU_SetState ($hash, "OK") if (exists ($hash->{STATE}) && $hash->{STATE} eq "Error"); + return $ccureadings ? undef : $res; + } + elsif ($opt eq 'configdesc') { + my $channel = undef; + my $port = undef; + my $par = shift @a; + if ($par =~ /^[0-9]$/) { + $channel = $par; + $port = shift @a; + } + else { + $port = $par; + } + + my $ccuobj = $hash->{ccuaddr}; + $ccuobj .= ':'.$channel if (defined ($channel)); + $port = 2001 if (!defined ($port)); + + my ($rc, $res) = HMCCU_RPCGetConfig ($hash, $ccuobj, "getParamsetDescription", $port); + return HMCCUDEV_SetError ($hash, $rc) if ($rc < 0); + HMCCU_SetState ($hash, "OK") if (exists ($hash->{STATE}) && $hash->{STATE} eq "Error"); + return $res; + } + else { + my $retmsg = "HMCCUDEV: Unknown argument $opt, choose one of datapoint channel update:noArg config configdesc deviceinfo:noArg"; + if ($statechannel ne '') { + $retmsg .= ' devstate:noArg'; + } + return $retmsg; + } +} + +##################################### +# Set error status +##################################### + +sub HMCCUDEV_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 = "HMCCUDEV: ".$name." ". $msg; + readingsSingleUpdate ($hash, "state", "Error", 1); + Log3 $name, 1, $msg; + return $msg; +} + +1; + +=pod +=begin html + + +

HMCCUDEV

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

    + + Define +
      +
      + define <name> HMCCUDEV {<device-name>|<device-address>} [<statechannel>] [readonly] +

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

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

      + Example:
      + set light_entrance devstate on +

    • +
    • set <name> <statevalue>
      + State of a CCU device channel is set to 'statevalue'. Channel must + be defined as attribute 'statechannel'. Default datapoint STATE can be + modified by setting attribute 'statedatapoint'. Values for statevalue + are defined by setting attribute 'statevals'. +

      + Example:
      + + attr myswitch statechannel 1
      + attr myswitch statevals on:true,off:false
      + set myswitch on +
      +

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

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

      + Example:
      + set temp_control datapoint 1.SET_TEMPERATURE 21 +

    • +
    • set <name> config [<channel-number>] [<port>] <parameter>=<value> [...]
      + Set configuration parameter of CCU device or channel. +
    • +
    +
    + + + Get
    +
      +
      +
    • get <name> devstate +
      + Get state of CCU device. Attribute 'statechannel' must be set. +

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

    • +
    • get <name> config [<channel-number>] [<rpcport>] +
      + Get configuration parameters of CCU device. If attribute ccureadings is set to 0 + parameters are displayed in browser window (no readings set). +

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

    • +
    • get <name> update [{'State'|'Value'}]
      + Update datapoints / readings of device. +

    • +
    • get <name> deviceinfo [{'State'|'Value'}]
      + Display all channels and datapoints of device. +
    • +
    +
    + + + 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. +

    • +
    • ccureadingformat <address | name>
      + Set format of readings. Default is 'name'. +

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

    • +
    • controldatapoint <channel-number.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 + 2.SET_TEMPERATURE one can define a slider for setting the destination temperature with + following attributes:

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

    • +
    • statechannel <channel-number> +
      + Channel for setting device state by devstate command. +

    • +
    • statedatapoint <datapoint> +
      + Datapoint for setting device state by devstate command. +

    • +
    • 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 +