################################################################ # # 88_HMCCU.pm # # $Id$ # # Version 1.8 # # (c) 2015 zap (zap01 t-online de) # ################################################################ # # define HMCCU # # set devstate # set datapoint :. # set var # set execute # set clearmsg # # get devstate [] # get vars [,...] # get channel :[.] # get datapoint . [] # get parfile [] # # attr ccureadings { 0 | 1 } # attr stripchar # attr parfile # attr stateval :[,...] # attr substitute :[,...] # ################################################################ package main; use strict; use warnings; use SetExtensions; use XML::Simple qw(:strict); # use Data::Dumper; sub HMCCU_Define ($$); sub HMCCU_Undef ($$); sub HMCCU_Set ($@); sub HMCCU_Get ($@); sub HMCCU_Attr ($@); sub HMCCU_ParseCCUDev ($); sub HMCCU_SetError ($$); sub HMCCU_SetState ($$); sub HMCCU_Substitute ($$); sub HMCCU_UpdateClientReading ($$$); ##################################### # Initialize module ##################################### sub HMCCU_Initialize ($) { my ($hash) = @_; $hash->{DefFn} = "HMCCU_Define"; $hash->{UndefFn} = "HMCCU_Undef"; $hash->{SetFn} = "HMCCU_Set"; $hash->{GetFn} = "HMCCU_Get"; $hash->{AttrFn} = "HMCCU_Attr"; $hash->{AttrList} = "stripchar ccureadings:0,1 parfile stateval substitute units:0,1 loglevel:0,1,2,3,4,5,6 ". $readingFnAttributes; } ##################################### # Define device ##################################### sub HMCCU_Define ($$) { my ($hash, $def) = @_; my $name = $hash->{NAME}; my @a = split("[ \t][ \t]*", $def); return "Define CCU hostname or IP address as a parameter" if(@a < 3); my $host = $a[2]; $hash->{host} = $host; $hash->{Clients} = ':HMCCUDEV:'; HMCCU_SetState ($hash, "Initialized"); return undef; } ##################################### # Set attribute ##################################### sub HMCCU_Attr ($@) { my ($cmd, $name, $attrname, $attrval) = @_; return undef; } ##################################### # Delete device ##################################### sub HMCCU_Undef ($$) { my ($hash, $arg) = @_; my $name = $hash->{NAME}; # Delete reference to IO module in client devices foreach my $d (sort keys %defs) { if (defined ($defs{$d}) && defined($defs{$d}{IODev}) && $defs{$d}{IODev} == $hash) { delete $defs{$d}{IODev}; } } return undef; } ##################################### # Set commands ##################################### sub HMCCU_Set ($@) { my ($hash, @a) = @_; my $name = shift @a; my $opt = shift @a; my $options = "devstate datapoint var execute clearmsg"; my $host = $hash->{host}; my $stripchar = AttrVal ($name, "stripchar", ''); my $stateval = AttrVal ($name, "stateval", ''); # process set command par1 par2 ... # if more than one parameter is specified parameters # are concatenated by blanks if ($opt eq 'state' || $opt eq 'devstate' || $opt eq 'datapoint' || $opt eq 'var') { my $objname = shift @a; my $objvalue = join ('%20', @a); my $url = 'http://'.$host.':8181/do.exe?r1=dom.GetObject("'; if (!defined ($objname) || !defined ($objvalue)) { return HMCCU_SetError ($hash, "Usage: set $opt "); } $objname =~ s/$stripchar$// if ($stripchar ne ''); $objvalue = HMCCU_Substitute ($objvalue, $stateval); my ($dev, $chn, $dpt, $flags) = HMCCU_ParseCCUDev ($objname); if ($opt eq 'devstate' || $opt eq 'state' || $opt eq 'var') { if ($flags > 3) { return HMCCU_SetError ($hash, "Specify varname or device:channel"); } $url = $url.$objname.'").State("'.$objvalue.'")'; } else { if ($flags != 7) { return HMCCU_SetError ($hash, "Specify device:channel.datapoint"); } $url = $url.$dev.':'.$chn.'").DPByHssDP("'.$dpt.'").State("'.$objvalue.'")'; } my $response = GetFileFromURL ($url); if ($response =~ /null execute "); } # Query program ID $response = GetFileFromURL ($url); my $xmlin = XMLin ($response, ForceArray => 0, KeyAttr => ['name']); if (!defined ($xmlin->{program}->{$program}->{id})) { return HMCCU_SetError ($hash, "Program not found"); } else { $programid = $xmlin->{program}->{$program}->{id}; } GetFileFromURL ($runurl . $programid); HMCCU_SetState ($hash, "OK"); return undef; } elsif ($opt eq 'clearmsg') { my $url = 'http://'.$host.'/config/xmlapi/systemNotificationClear.cgi'; GetFileFromURL ($url); HMCCU_SetState ($hash, "OK"); return undef; } else { return "HMCCU: Unknown argument $opt, choose one of ".$options; } } ##################################### # Get commands ##################################### sub HMCCU_Get ($@) { my ($hash, @a) = @_; my $name = shift @a; my $opt = shift @a; my $options = "devstate datapoint vars channel parfile"; my $host = $hash->{host}; my $readings = AttrVal ($name, "ccureadings", 1); my $parfile = AttrVal ($name, "parfile", ''); if ($opt eq 'state' || $opt eq 'devstate') { my $ccuobj = shift @a; my $reading = shift @a; my $response; my $retcode; if (!defined ($ccuobj)) { return HMCCU_SetError ($hash, "Usage: get devstate : []"); } my ($dev, $chn, $dpt, $flags) = HMCCU_ParseCCUDev ($ccuobj); if ($flags != 3) { return HMCCU_SetError ($hash, "Specify :"); } my $url = 'http://'.$host.':8181/do.exe?r1=dom.GetObject("'.$ccuobj.'").State()'; $reading = $ccuobj.".STATE" if (!defined ($reading)); $response = GetFileFromURL ($url); $response =~ m/(.*)<\/r1>/; $retcode = $1; if (defined ($retcode) && $retcode ne '' && $retcode ne 'null') { HMCCU_SetState ($hash, "OK"); $retcode = HMCCU_UpdateClientReading ($hash, $reading, $retcode); return $readings ? undef : $retcode; } else { return HMCCU_SetError ($hash, "Error during CCU request"); } } elsif ($opt eq 'datapoint') { my $ccuobj = shift @a; my $reading = shift @a; my $response; my $retcode; if (!defined ($ccuobj)) { return HMCCU_SetError ($hash, "Usage: get datapoint :. []"); } my ($dev, $chn, $dpt, $flags) = HMCCU_ParseCCUDev ($ccuobj); if ($flags != 7) { return HMCCU_SetError ($hash, "Specify device:channel.datapoint"); } my $url = 'http://'.$host.':8181/do.exe?r1=dom.GetObject("'.$dev.':'.$chn.'").DPByHssDP("'.$dpt.'").Value()'; $reading = $ccuobj if (!defined ($reading)); $response = GetFileFromURL ($url); $response =~ m/(.*)<\/r1>/; $retcode = $1; if (defined ($retcode) && $retcode ne '' && $retcode ne 'null') { HMCCU_SetState ($hash, "OK"); $retcode = HMCCU_UpdateClientReading ($hash, $ccuobj, $retcode); return $readings ? undef : $retcode; } else { return HMCCU_SetError ($hash, "Error during CCU request"); } } elsif ($opt eq 'vars') { my $varname = shift @a; my $url = 'http://'.$host.'/config/xmlapi/sysvarlist.cgi'; my $response; my $result = ''; if (!defined ($varname)) { return HMCCU_SetError ($hash, "Usage: get vars [,...]"); } my @varlist = split /,/, $varname; $response = GetFileFromURL ($url); my $xmlin = XMLin ($response, ForceArray => 0, KeyAttr => ['systemVariable']); if ($readings) { readingsBeginUpdate ($hash); } foreach my $variable (@{$xmlin->{systemVariable}}) { foreach my $varexp (@varlist) { if ($variable->{name} =~ /$varexp/) { $result .= $variable->{name}."=".$variable->{value}."\n"; if ($readings) { readingsBulkUpdate ($hash, $variable->{name}, $variable->{value}); } } } } if ($readings) { readingsEndUpdate ($hash, 1); return undef; } else { return $result; } } elsif ($opt eq 'channel') { my $param = shift @a; my $url = 'http://'.$host.'/config/xmlapi/statelist.cgi'; my $response; my $result = ''; my $rname; my ($dev, $chn, $dpt, $flags) = HMCCU_ParseCCUDev ($param); if (($flags & 3 ) != 3) { return HMCCU_SetError ($hash, "Usage: get channel :[.]"); } my $devch = $dev.':'.$chn; $response = GetFileFromURL ($url); my $xmlin = XMLin ($response, ForceArray => ['device', 'channel', 'datapoint'], KeyAttr => { device => 'name', channel => 'name' } ); if (!defined (@{$xmlin->{device}->{$dev}->{channel}->{$devch}->{datapoint}})) { return HMCCU_SetError ($hash, "Device or channel not defined"); } my @dps = @{$xmlin->{device}->{$dev}->{channel}->{$devch}->{datapoint}}; foreach my $dp (@dps) { if ($dp->{name} =~ /$dpt/) { my $dpname = $dp->{name}; my $v = $dp->{value}; $dpname =~ /.*\.(.*)$/; $rname = $devch.".$1"; $v = HMCCU_UpdateClientReading ($hash, $rname, $v); $result .= $rname."=".$v."\n"; } } HMCCU_SetState ($hash, "OK"); return $readings ? undef : $result; } elsif ($opt eq 'parfile') { my $par_parfile = shift @a; my $url = 'http://'.$host.'/config/xmlapi/statelist.cgi'; my @parameters; my $parcount; my $response; my $result = ''; my $rname; my $text; if (defined ($par_parfile)) { $parfile = $par_parfile; } else { if ($parfile eq '') { return HMCCU_SetError ($hash, "No parameter file specified"); } } # Read parameter file if (open (PARFILE, "<$parfile")) { @parameters = ; $parcount = scalar @parameters; close (PARFILE); } else { return HMCCU_SetError ($hash, "Can't open file $parfile"); } if ($parcount < 1) { return HMCCU_SetError ($hash, "Empty parameter file"); } $response = GetFileFromURL ($url); my $xmlin = XMLin ( $response, ForceArray => ['device', 'channel', 'datapoint'], KeyAttr => { device => 'name', channel => 'name' } ); foreach my $param (@parameters) { my @partoks = split /\s+/, $param; # Ignore empty lines or comments next if (scalar @partoks == 0); next if ($partoks[0] =~ /^#/); # Ignore wrong CCU device specifications my ($dev, $chn, $dpt, $flags) = HMCCU_ParseCCUDev ($partoks[0]); next if (($flags & 3) != 3); my $devch = $dev.':'.$chn; # Ignore not existing devices/channels/datapoints next if (!defined (@{$xmlin->{device}->{$dev}->{channel}->{$devch}->{datapoint}})); my @dps = @{$xmlin->{device}->{$dev}->{channel}->{$devch}->{datapoint}}; foreach my $dp (@dps) { if ($dp->{name} =~ /$dpt/) { my $dpname = $dp->{name}; my $v = $dp->{value}; $v = HMCCU_Substitute ($v, $partoks[1]) if (@partoks > 1); $dpname =~ /.*\.(.*)$/; $rname = $devch.".$1"; $v = HMCCU_UpdateClientReading ($hash, $rname, $v); $result .= $rname."=".$v.' '.$dp->{valueunit}."\n"; } } } HMCCU_SetState ($hash, "OK"); return $readings ? undef : $result; } else { return "HMCCU: Unknown argument $opt, choose one of ".$options; } } ################################################################## # Parse CCU object name. # # Input: object name in format device:channel.datapoint # # Output: array with ( device, channel, datapoint, flags ) # flag bits: 0=Device 1=Channel 2=Datapoint ################################################################## sub HMCCU_ParseCCUDev ($) { my $param = $_[0]; my $p1 = '?'; my $p2 = '?'; my $p3 = '.*'; my $p4 = 0; # Syntax of CCU device specification is: # devicename:channelno[.datapoint] if ($param =~ /^.+:/) { $param =~ /^(.*):/; $p1 = $1; $p4 = 1; if ($param =~ /^.+:[0-9]+/) { $param =~ /^.*:([0-9]+)/; $p2 = $1; $p4 |= 2; if ($param =~ /\..*$/) { $param =~ /\.(.*)$/; $p3 = $1; $p4 |= 4; } } } return ($p1, $p2, $p3, $p4); } sub HMCCU_SetError ($$) { my ($hash, $text) = @_; my $name = $hash->{NAME}; $text = "HMCCU: ".$name." ". $text; HMCCU_SetState ($hash, "Error"); Log 1, $text; return $text; } sub HMCCU_SetState ($$) { my ($hash, $text) = @_; if (defined ($hash) && defined ($text)) { readingsSingleUpdate ($hash, "state", $text, 1); } } sub HMCCU_Substitute ($$) { my ($value, $substitutes) = @_; return $value if (!defined ($substitutes) || $substitutes eq ''); my @sub_list = split /,/,$substitutes; foreach my $s (@sub_list) { my ($regexp, $text) = split /:/,$s; next if (!defined ($regexp) || !defined($text)); if ($value =~ /$regexp/) { $value =~ s/$regexp/$text/; last; } } return $value; } #################################################### # Update HMCCU readings and client readings. # Reading values are substituted with client priority #################################################### sub HMCCU_UpdateClientReading ($$$) { my ($hash, $reading, $value) = @_; my $name = $hash->{NAME}; my $hmccu_substitute = AttrVal ($name, 'substitute', ''); my $hmccu_updreadings = AttrVal ($name, 'ccureadings', 1); # Check syntax return 0 if (!defined ($hash) || !defined ($reading) || !defined ($value)); # Get CCU device name from reading name my ($dev, $chn, $dpt, $flags) = HMCCU_ParseCCUDev ($reading); return 0 if ($flags == 0); # Update HMCCU reading my $hmccu_value = HMCCU_Substitute ($value, $hmccu_substitute); if ($hmccu_updreadings) { readingsSingleUpdate ($hash, $reading, $hmccu_value, 1); } # Update client readings foreach my $d (keys %defs) { # Get hash of device my $ch = $defs{$d}; if (defined ($ch->{IODev}) && $ch->{IODev} == $hash && defined ($ch->{ccudev}) && $ch->{ccudev} eq $dev) { my $upd = AttrVal ($ch->{NAME}, 'ccureadings', 1); my $substitute = AttrVal ($ch->{NAME}, 'substitute', ''); # Client substitute attribute has priority if ($substitute ne '') { $value = HMCCU_Substitute ($value, $substitute); } else { $value = $hmccu_value; } if ($upd == 1) { readingsSingleUpdate ($ch, $reading, $value, 1); if ($dpt eq 'STATE') { HMCCU_SetState ($ch, $value); } } last; } } return $hmccu_value; } 1; =pod =begin html

HMCCU

    The module provides an easy get/set interface for Homematic CCU.

    Define

      define <name> HMCCU <HostOrIP>

      Example:
      define myccu HMCCU 192.168.1.10

      HostOrIP - Hostname or IP address of Homematic CCU.

    Set

    • set <Name> devstate <Device:Channel> <Value>
      Set state of a CCU device or value of a CCU system variable.

      Example:
      set d_ccu devstate ST-WZ-Bass:1 0

    • set <Name> datapoint <device:channel.datapoint> <Value>
      Set value of a datapoint of a CCU device channel.

      Example:
      set d_ccu datapoint THERMOSTAT_WOHNEN:2.SET_TEMPERATURE 21

    • set <Name> var <variable> <Value>
      Set CCU variable value.

    • set <Name> clearmsg
      Clear CCU messages.

    • set <Name> execute <Program>
      Execute CCU program.

      Example:
      set d_ccu execute PR-TEST


    Get


    • get <Name> devstate <Device:Channel> [<Reading>]
      Get state of a CCU device or value of a CCU system variable. If Reading is specified the value will be stored using this name.

    • get <Name> vars <RegExp>[,...]
      Get CCU system variables matching RegExp and store them as readings.

    • get <Name> channel <Device:Channel[.Datapoint]>
      Get value of channel datapoint. If no datapoint is specified all datapoints of specified channel is read. Datapoint can be specified as a regular expression.

    • get <Name> parfile [<ParFile>]
      Get values of all channels / datapoints specified in ParFile. ParFile can also be defined as an attribute. The file must contain one channel / datapoint definition per line. Datapoints are optional (for syntax see command get channel). After the channel definition a list of string substitution rules for datapoint values can be specified.
      The syntax of Parfile entries is:

      Device:Channel[Datapoint] RegExp:SubstString[,...]

      Empty lines or lines starting with a # are ignored.


    Attributes

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

    • parfile <Filename>
      Define parameter file for command get parfile.

    • stateval <text:text[,...]>
      Define substitions for values in set devstate/datapoint command.

    • substitude <expression:string[,...]>
      Define substitions for reading values. Substitutions for parfile values must be specified in parfiles.

    • stripchar <Character>
      Strip the specified character from variable or device name in set commands. This is useful if a variable should be set in CCU using the reading with trailing colon.
    • units <0 | 1>
      If set to 1 value units will be appended to readings.

=end html =cut