# $Id$
##############################################################################
#
# 67_ECMDDevice.pm
# Copyright by Dr. Boris Neubert
# e-mail: omega at online dot de
#
# This file is part of fhem.
#
# Fhem is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# Fhem is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with fhem. If not, see .
#
##############################################################################
package main;
use strict;
use warnings;
use Time::HiRes qw(gettimeofday);
sub ECMDDevice_Get($@);
sub ECMDDevice_Set($@);
sub ECMDDevice_Define($$);
my %gets= (
);
my %sets= (
);
###################################
sub
ECMDDevice_Initialize($)
{
my ($hash) = @_;
$hash->{GetFn} = "ECMDDevice_Get";
$hash->{SetFn} = "ECMDDevice_Set";
$hash->{DefFn} = "ECMDDevice_Define";
$hash->{AttrList} = "loglevel:0,1,2,3,4,5 ".
$readingFnAttributes;
}
###################################
sub
ECMDDevice_AnalyzeCommand($)
{
my ($ecmd)= @_;
Log 5, "ECMDDevice: Analyze command >$ecmd<";
return AnalyzePerlCommand(undef, $ecmd);
}
#############################
sub
ECMDDevice_GetDeviceParams($)
{
my ($hash)= @_;
my $classname= $hash->{fhem}{classname};
my $IOhash= $hash->{IODev};
if(defined($IOhash->{fhem}{classDefs}{$classname}{params})) {
my $params= $IOhash->{fhem}{classDefs}{$classname}{params};
return split("[ \t]+", $params);
}
return;
}
sub
ECMDDevice_DeviceParams2Specials($)
{
my ($hash)= @_;
my %specials= (
"%NAME" => $hash->{NAME},
"%TYPE" => $hash->{TYPE}
);
my @deviceparams= ECMDDevice_GetDeviceParams($hash);
foreach my $param (@deviceparams) {
$specials{"%".$param}= $hash->{fhem}{params}{$param};
}
return %specials;
}
###################################
sub
ECMDDevice_Changed($$$)
{
my ($hash, $cmd, $value)= @_;
readingsBeginUpdate($hash);
readingsBulkUpdate($hash, $cmd, $value, 1) if(defined($value));
my $state= $cmd;
$state.= " $value" if(defined($value));
readingsBulkUpdate($hash, "state", $state, 0);
readingsEndUpdate($hash, 1);
my $name= $hash->{NAME};
Log GetLogLevel($name, 4), "ECMDDevice $name $state";
return $state;
}
###################################
sub
ECMDDevice_PostProc($$$)
{
my ($hash, $postproc, $value)= @_;
# the following lines are commented out because we do not want specials to be evaluated
# this is mainly due to the unwanted substitution of single semicolons by double semicolons
#my %specials= ECMDDevice_DeviceParams2Specials($hash);
#my $command= EvalSpecials($postproc, %specials);
# we pass the command verbatim instead
# my $command= $postproc;
if($postproc) {
my %specials= ECMDDevice_DeviceParams2Specials($hash);
my $command= EvalSpecials($postproc, %specials);
$_= $value;
Log 5, "Postprocessing $value with perl command $command.";
$value= AnalyzePerlCommand(undef, $command);
}
return $value;
}
###################################
sub
ECMDDevice_Get($@)
{
my ($hash, @a)= @_;
my $name= $hash->{NAME};
my $type= $hash->{TYPE};
return "get $name needs at least one argument" if(int(@a) < 2);
my $cmdname= $a[1];
my $IOhash= $hash->{IODev};
my $classname= $hash->{fhem}{classname};
if(!defined($IOhash->{fhem}{classDefs}{$classname}{gets}{$cmdname})) {
return "$name error: unknown command $cmdname";
}
my $ecmd= $IOhash->{fhem}{classDefs}{$classname}{gets}{$cmdname}{cmd};
my $params= $IOhash->{fhem}{classDefs}{$classname}{gets}{$cmdname}{params};
my $postproc= $IOhash->{fhem}{classDefs}{$classname}{gets}{$cmdname}{postproc};
my %specials= ECMDDevice_DeviceParams2Specials($hash);
# add specials for command
if($params) {
shift @a; shift @a;
my @params= split('[\s]+', $params);
return "Wrong number of parameters." if($#a != $#params);
my $i= 0;
foreach my $param (@params) {
Log 5, "Parameter %". $param . " is " . $a[$i];
$specials{"%".$param}= $a[$i++];
}
}
$ecmd= EvalSpecials($ecmd, %specials);
my $r = ECMDDevice_AnalyzeCommand($ecmd);
my $v= IOWrite($hash, $r);
$v= ECMDDevice_PostProc($hash, $postproc, $v);
return ECMDDevice_Changed($hash, $cmdname, $v);
}
#############################
sub
ECMDDevice_Set($@)
{
my ($hash, @a)= @_;
my $name= $hash->{NAME};
my $type= $hash->{TYPE};
return "set $name needs at least one argument" if(int(@a) < 2);
my $cmdname= $a[1];
my $IOhash= $hash->{IODev};
my $classname= $hash->{fhem}{classname};
if(!defined($IOhash->{fhem}{classDefs}{$classname}{sets}{$cmdname})) {
my $sets= $IOhash->{fhem}{classDefs}{$classname}{sets};
return "Unknown argument ?, choose one of " . join(' ', keys %$sets);
}
my $ecmd= $IOhash->{fhem}{classDefs}{$classname}{sets}{$cmdname}{cmd};
my $params= $IOhash->{fhem}{classDefs}{$classname}{sets}{$cmdname}{params};
my $postproc= $IOhash->{fhem}{classDefs}{$classname}{sets}{$cmdname}{postproc};
my %specials= ECMDDevice_DeviceParams2Specials($hash);
# add specials for command
if($params) {
shift @a; shift @a;
my @params= split('[\s]+', $params);
return "Wrong number of parameters." if($#a != $#params);
my $i= 0;
foreach my $param (@params) {
$specials{"%".$param}= $a[$i++];
}
}
$ecmd= EvalSpecials($ecmd, %specials);
my $r = ECMDDevice_AnalyzeCommand($ecmd);
my $v= IOWrite($hash, $r);
$v= ECMDDevice_PostProc($hash, $postproc, $v);
ECMDDevice_Changed($hash, $cmdname, $v); # was: return ECMDDevice_Changed($hash, $cmdname, $v);
return undef;
}
#############################
sub
ECMDDevice_Define($$)
{
my ($hash, $def) = @_;
my @a = split("[ \t]+", $def);
return "Usage: define ECMDDevice [...]" if(int(@a) < 3);
my $name= $a[0];
my $classname= $a[2];
AssignIoPort($hash);
my $IOhash= $hash->{IODev};
if(!defined($IOhash->{fhem}{classDefs}{$classname}{filename})) {
my $err= "$name error: unknown class $classname.";
Log 1, $err;
return $err;
}
$hash->{fhem}{classname}= $classname;
my @prms= ECMDDevice_GetDeviceParams($hash);
my $numparams= 0;
$numparams= $#prms+1 if(defined($prms[0]));
#Log 5, "ECMDDevice $classname requires $numparams parameter(s): ". join(" ", @prms);
# keep only the parameters
shift @a; shift @a; shift @a;
# verify identical number of parameters
if($numparams != $#a+1) {
my $err= "$name error: wrong number of parameters";
Log 1, $err;
return $err;
}
# set parameters
for(my $i= 0; $i< $numparams; $i++) {
$hash->{fhem}{params}{$prms[$i]}= $a[$i];
}
return undef;
}
1;
=pod
=begin html
ECMDDevice
Define
define <name> ECMDDevice <classname> [<parameter1> [<parameter2> [<parameter3> ... ]]]
Defines a logical ECMD device. The number of given parameters must match those given in
the class definition of the device class <classname>
.
Examples:
define myADC ECMDDevice ADC
define myRelais1 ECMDDevice relais 8
Set
set <name> <commandname> [<parameter1> [<parameter2> [<parameter3> ... ]]]
The number of given parameters must match those given for the set command <commandname>
definition in
the class definition.
If set <commandname>
is invoked the perl special in curly brackets from the command definition
is evaluated and the result is sent to the physical ECMD device.
Example:
set myRelais1 on
set myDisplay text This\x20text\x20has\x20blanks!
Get
get <name> <commandname> [<parameter1> [<parameter2> [<parameter3> ... ]]]
The number of given parameters must match those given for the get command <commandname>
definition in
the class definition.
If get <commandname>
is invoked the perl special in curly brackets from the command definition
is evaluated and the result is sent to the physical ECMD device. The response from the physical ECMD device is returned
and the state of the logical ECMD device is updated accordingly.
Example:
Attributes
Example 1
The following example shows how to access the ADC of the AVR-NET-IO board from
Pollin with
ECMD-enabled
Ethersex firmware.
The class definition file /etc/fhem/ADC.classdef
looks as follows:
get value cmd {"adc get %channel"}
get value params channel
In the fhem configuration file or on the fhem command line we do the following:
define AVRNETIO ECMD telnet 192.168.0.91:2701 # define the physical device
set AVRNETIO classdef ADC /etc/fhem/ADC.classdef # define the device class ADC
define myADC ECDMDevice ADC # define the logical device myADC with device class ADC
get myADC value 1 # retrieve the value of analog/digital converter number 1
The get command is evaluated as follows: get value
has one named parameter
channel
. In the example the literal 1
is given and thus %channel
is replaced by 1
to yield "adc get 1"
after macro substitution. Perl
evaluates this to a literal string which is send as a plain ethersex command to the AVR-NET-IO. The
board returns something like 024
for the current value of analog/digital converter number 1.
Example 2
The following example shows how to switch a relais driven by pin 3 (bit mask 0x08) of I/O port 2 on for
one second and then off again.
The class definition file /etc/fhem/relais.classdef
looks as follows:
params pinmask
set on cmd {"io set ddr 2 ff\nioset port 2 0%pinmask\nwait 1000\nio set port 2 00"}
set on postproc {s/^OK\nOK\nOK\nOK$/success/; "$_" eq "success" ? "ok" : "error"; }
In the fhem configuration file or on the fhem command line we do the following:
define AVRNETIO ECMD telnet 192.168.0.91:2701 # define the physical device
set AVRNETIO classdef relais /etc/fhem/relais.classdef # define the device class relais
define myRelais ECMDDevice 8 # define the logical device myRelais with pin mask 8
set myRelais on # execute the "on" command
The set command is evaluated as follows: %pinmask
is replaced by 8
to yield
"io set ddr 2 ff\nioset port 2 08\nwait 1000\nio set port 2 00"
after macro substitution. Perl
evaluates this to a literal string. This string is split into lines (without trailing newline characters)
- io set ddr 2 ff
- ioset port 2 08
- wait 1000
- io set port 2 00
These lines are sent as a plain ethersex commands to the AVR-NET-IO one by one. Each line is terminated with
a newline character unless the nonl
attribute of the ECMDDevice is set. After
each line the answer from the ECMDDevice is read back. They are concatenated with newlines and returned
for further processing, e.g. by the postproc
command.
For any of the four plain ethersex commands, the AVR-NET-IO returns the string OK
. They are
concatenated and separated by line breaks (\n). The postprocessor takes the result from $_
,
substitutes it by the string success
if it is OK\nOK\nOK\nOK
, and then either
returns the string ok
or the string error
.
=end html
=cut