################################################################ # # 88_HMCCUDEV.pm # # $Id:$ # # Version 2.6 # # (c) 2016 zap (zap01 t-online de) # ################################################################ # # define HMCCUDEV [readonly] # # set control # set datapoint . # set devstate # set # 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 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 statevals substitute statechannel statedatapoint controldatapoint stripnumber:0,1,2 loglevel:0,1,2,3,4,5,6 ". $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"); } if ($hash->{statevals} eq 'readonly') { return undef; } my $statechannel = AttrVal ($name, "statechannel", ''); my $statedatapoint = AttrVal ($name, "statedatapoint", 'STATE'); my $statevals = AttrVal ($name, "statevals", ''); my $controldatapoint = AttrVal ($name, "controldatapoint", ''); my $hmccu_hash = $hash->{IODev}; my $hmccu_name = $hash->{IODev}->{NAME}; 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); 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); 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 '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'; } } } 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 $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 = shift @a; my $ccuobj = $hash->{ccuaddr}; $ccuobj .= ':'.$channel if (defined ($channel)); my ($rc, $res) = HMCCU_RPCGetConfig ($hash, $ccuobj, "getParamset"); 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 'configdesc') { my $channel = shift @a; my $ccuobj = $hash->{ccuaddr}; $ccuobj .= ':'.$channel if (defined ($channel)); my ($rc, $res) = HMCCU_RPCGetConfig ($hash, $ccuobj, "getParamsetDescription"); 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' ); if (exists ($errlist{$text})) { $msg = $errlist{$text}; } else { $msg = $text; } $msg = "HMCCUDEV: ".$name." ". $msg; readingsSingleUpdate ($hash, "state", "Error", 1); Log 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> 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>] <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
      Get configuration parameters of CCU device.

    • get <name> configdesc
      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'.

    • 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