mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-03-10 03:06:37 +00:00
72_UBUS_XXX: new modules to communicate with uBus devices
git-svn-id: https://svn.fhem.de/fhem/trunk@25708 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
1ef193ab4a
commit
89211c7da9
@ -1,5 +1,7 @@
|
|||||||
# Add changes at the top of the list. Keep it in ASCII, and 80-char wide.
|
# Add changes at the top of the list. Keep it in ASCII, and 80-char wide.
|
||||||
# Do not insert empty lines here, update check depends on it.
|
# Do not insert empty lines here, update check depends on it.
|
||||||
|
- new: 72_UBUS_CALL: module to send call requests via uBus client
|
||||||
|
- new: 72_UBUS_CLIENT: module to communicate with uBus devices
|
||||||
- bugfix: 88_HMCCU: bugfixes and improvements
|
- bugfix: 88_HMCCU: bugfixes and improvements
|
||||||
- bugfix: 70_ESCVP21net: bugfixes, handle IMEVENT
|
- bugfix: 70_ESCVP21net: bugfixes, handle IMEVENT
|
||||||
- feature: 73_GardenaSmartBridge: asyncron response processing,
|
- feature: 73_GardenaSmartBridge: asyncron response processing,
|
||||||
|
566
fhem/FHEM/72_UBUS_CALL.pm
Normal file
566
fhem/FHEM/72_UBUS_CALL.pm
Normal file
@ -0,0 +1,566 @@
|
|||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# 72_UBUS_CALL.pm
|
||||||
|
#
|
||||||
|
# Performs "call" requests to the uBus command line / JSON-RPC interface.
|
||||||
|
#
|
||||||
|
# $Id$
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
package FHEM::UBUS_CALL; ## no critic "Package declaration"
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Exporter qw(import);
|
||||||
|
use Carp qw(carp);
|
||||||
|
use JSON qw(encode_json decode_json);
|
||||||
|
use GPUtils qw(GP_Import);
|
||||||
|
use Data::Dumper;
|
||||||
|
|
||||||
|
BEGIN {
|
||||||
|
GP_Import (
|
||||||
|
qw(
|
||||||
|
AssignIoPort
|
||||||
|
IOWrite
|
||||||
|
Log3
|
||||||
|
Debug
|
||||||
|
IsDisabled
|
||||||
|
InternalTimer
|
||||||
|
RemoveInternalTimer
|
||||||
|
EvalSpecials
|
||||||
|
AnalyzePerlCommand
|
||||||
|
AttrVal
|
||||||
|
ReadingsVal
|
||||||
|
ReadingsNum
|
||||||
|
ReadingsAge
|
||||||
|
readingsSingleUpdate
|
||||||
|
readingsBeginUpdate
|
||||||
|
readingsBulkUpdate
|
||||||
|
readingsBulkUpdateIfChanged
|
||||||
|
readingsEndUpdate
|
||||||
|
readingsDelete
|
||||||
|
makeReadingName
|
||||||
|
deviceEvents
|
||||||
|
gettimeofday
|
||||||
|
json2nameValue
|
||||||
|
)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
sub ::UBUS_CALL_Initialize { goto &Initialize };
|
||||||
|
|
||||||
|
sub Initialize
|
||||||
|
{
|
||||||
|
my $hash = shift // return;
|
||||||
|
|
||||||
|
$hash->{DefFn} = \&Define;
|
||||||
|
$hash->{UndefFn} = \&Undef;
|
||||||
|
$hash->{SetFn} = \&Set;
|
||||||
|
$hash->{AttrFn} = \&Attr;
|
||||||
|
$hash->{ParseFn} = \&Parse;
|
||||||
|
$hash->{RenameFn} = \&Rename;
|
||||||
|
|
||||||
|
$hash->{AttrList} = 'disable disabledForIntervals IODev interval readings:textField-long ' . $main::readingFnAttributes;
|
||||||
|
$hash->{parseParams} = 1;
|
||||||
|
|
||||||
|
$hash->{Match} = '.*:call:.*';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Define
|
||||||
|
{
|
||||||
|
my $hash = shift;
|
||||||
|
my $apar = shift;
|
||||||
|
my $hpar = shift;
|
||||||
|
|
||||||
|
if(int(@{$apar}) != 4)
|
||||||
|
{
|
||||||
|
return "Correct syntax: 'define <name> UBUS_CALL <module> <function> [<parameters>]'";
|
||||||
|
}
|
||||||
|
|
||||||
|
$hash->{module} = $apar->[2];
|
||||||
|
$hash->{function} = $apar->[3];
|
||||||
|
$hash->{params} = $hpar;
|
||||||
|
|
||||||
|
AssignIoPort($hash);
|
||||||
|
|
||||||
|
return $main::init_done ? GetUpdate($hash) : InternalTimer(gettimeofday() + 1, \&GetUpdate, $hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Undef
|
||||||
|
{
|
||||||
|
my $hash = shift // return;
|
||||||
|
|
||||||
|
Disconnect($hash);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Rename
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Set
|
||||||
|
{
|
||||||
|
my $hash = shift;
|
||||||
|
my $apar = shift;
|
||||||
|
my $hpar = shift;
|
||||||
|
|
||||||
|
my $name = shift @{$apar} // return;
|
||||||
|
my $cmd = shift @{$apar} // return qq{"set $name" needs at least one argument};
|
||||||
|
|
||||||
|
if($cmd eq 'update')
|
||||||
|
{
|
||||||
|
GetUpdate($hash);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($cmd eq 'disable')
|
||||||
|
{
|
||||||
|
RemoveInternalTimer($hash, \&GetUpdate);
|
||||||
|
readingsSingleUpdate($hash, 'state', 'inactive', 1);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($cmd eq 'enable')
|
||||||
|
{
|
||||||
|
readingsSingleUpdate($hash, 'state', 'active', 1);
|
||||||
|
GetUpdate($hash);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Unknown argument $cmd, choose one of disable:noArg enable:noArg update:noArg";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Attr
|
||||||
|
{
|
||||||
|
my $cmd = shift // return;
|
||||||
|
my $name = shift // return;
|
||||||
|
my $attr = shift // return;
|
||||||
|
my $value = shift // return;
|
||||||
|
|
||||||
|
if($cmd eq 'set')
|
||||||
|
{
|
||||||
|
if($attr eq 'IODev')
|
||||||
|
{
|
||||||
|
my $iohash = $main::defs{$value};
|
||||||
|
return "Unknown physical device $value." if !defined $iohash;
|
||||||
|
return "Physical device $value must be of type UBUS_CLIENT." if $iohash->{TYPE} ne "UBUS_CLIENT";
|
||||||
|
}
|
||||||
|
|
||||||
|
if($attr eq 'interval')
|
||||||
|
{
|
||||||
|
return "$attr must be non-negative." if $value < 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub GetUpdate
|
||||||
|
{
|
||||||
|
my $hash = shift // return;
|
||||||
|
my $name = $hash->{NAME};
|
||||||
|
my $module = $hash->{module};
|
||||||
|
my $function = $hash->{function};
|
||||||
|
my $params = $hash->{params};
|
||||||
|
|
||||||
|
if(!$module || !$function || IsDisabled($name))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Clean up possible previous / stale call IDs.
|
||||||
|
|
||||||
|
RemoveInternalTimer($hash, \&GetUpdate);
|
||||||
|
|
||||||
|
$hash->{rpc} = {};
|
||||||
|
$hash->{rpccount} = 0;
|
||||||
|
|
||||||
|
# Check for Perl code.
|
||||||
|
|
||||||
|
if($module =~ m/^{.*}$/)
|
||||||
|
{
|
||||||
|
my $emodule = EvalSpecials(
|
||||||
|
$module,
|
||||||
|
(
|
||||||
|
'%NAME' => $name
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$module = AnalyzePerlCommand(undef, $emodule);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($function =~ m/^{.*}$/)
|
||||||
|
{
|
||||||
|
my $efunction = EvalSpecials(
|
||||||
|
$function,
|
||||||
|
(
|
||||||
|
'%NAME' => $name
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$function = AnalyzePerlCommand(undef, $efunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach my $key (keys %{$params})
|
||||||
|
{
|
||||||
|
if($params->{$key} =~ m/^{.*}$/)
|
||||||
|
{
|
||||||
|
my $eparam = EvalSpecials(
|
||||||
|
$params->{$key},
|
||||||
|
(
|
||||||
|
'%NAME' => $name
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$params->{$key} = AnalyzePerlCommand(undef, $eparam);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Expand comma-separated lists / array references.
|
||||||
|
|
||||||
|
my @calls = ({module => $module, function => $function, params => $params});
|
||||||
|
|
||||||
|
my @modules = (ref $module eq 'ARRAY' ? @{$module} : split(',', $module));
|
||||||
|
if(scalar @modules > 1)
|
||||||
|
{
|
||||||
|
my @ecalls = ();
|
||||||
|
foreach my $call (@calls)
|
||||||
|
{
|
||||||
|
foreach my $m (@modules)
|
||||||
|
{
|
||||||
|
push(@ecalls, {module => $m, function => $call->{function}, params => $call->{params}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@calls = @ecalls;
|
||||||
|
}
|
||||||
|
|
||||||
|
my @functions = (ref $function eq 'ARRAY' ? @{$function} : split(',', $function));
|
||||||
|
if(scalar @functions > 1)
|
||||||
|
{
|
||||||
|
my @ecalls = ();
|
||||||
|
foreach my $call (@calls)
|
||||||
|
{
|
||||||
|
foreach my $f (@functions)
|
||||||
|
{
|
||||||
|
push(@ecalls, {module => $call->{module}, function => $f, params => $call->{params}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@calls = @ecalls;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach my $key (keys %{$params})
|
||||||
|
{
|
||||||
|
my @pvals = (ref $params->{$key} eq 'ARRAY' ? @{$params->{$key}} : split(',', $params->{$key}));
|
||||||
|
if(scalar @pvals > 1)
|
||||||
|
{
|
||||||
|
my @ecalls = ();
|
||||||
|
foreach my $call (@calls)
|
||||||
|
{
|
||||||
|
foreach my $p (@pvals)
|
||||||
|
{
|
||||||
|
my %par = %{$call->{params}};
|
||||||
|
$par{$key} = $p;
|
||||||
|
push(@ecalls, {module => $call->{module}, function => $call->{function}, params => \%par});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@calls = @ecalls;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Send calls to physical module.
|
||||||
|
|
||||||
|
foreach my $call (@calls)
|
||||||
|
{
|
||||||
|
Log3($name, 5, "UBUS_CALL ($name) - sending call: " . Dumper($call));
|
||||||
|
|
||||||
|
my $id = IOWrite($hash, $name, 'call', $call->{module}, $call->{function}, $call->{params});
|
||||||
|
|
||||||
|
next if(!defined $id);
|
||||||
|
|
||||||
|
if($id =~ m/^$name:call:(.*)$/)
|
||||||
|
{
|
||||||
|
$hash->{rpc}{$1} = $call;
|
||||||
|
$hash->{rpccount}++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log3($name, 2, "UBUS_CALL ($name) - UBUS_CLIENT returned unexpected call ID $id");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($hash->{rpccount} == 0)
|
||||||
|
{
|
||||||
|
readingsSingleUpdate($hash, 'state', 'disconnected', 1);
|
||||||
|
}
|
||||||
|
elsif($hash->{rpccount} == scalar @calls)
|
||||||
|
{
|
||||||
|
readingsSingleUpdate($hash, 'state', 'updating', 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
readingsSingleUpdate($hash, 'state', 'unknown', 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
my $interval = AttrVal($name, 'interval', 60);
|
||||||
|
|
||||||
|
if($interval)
|
||||||
|
{
|
||||||
|
InternalTimer(gettimeofday() + $interval, \&GetUpdate, $hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Parse
|
||||||
|
{
|
||||||
|
my $iohash = shift // return;
|
||||||
|
my $buf = shift // return;
|
||||||
|
my $ioname = $iohash->{NAME};
|
||||||
|
|
||||||
|
my $data;
|
||||||
|
eval { $data = decode_json($buf); };
|
||||||
|
|
||||||
|
if($@)
|
||||||
|
{
|
||||||
|
Log3($ioname, 1, "UBUS - decode_json error: $@");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $error = $data->{result}[0];
|
||||||
|
my $result = $data->{result}[1];
|
||||||
|
my $id = $data->{id};
|
||||||
|
|
||||||
|
if($id !~ m/^(.*):call:(.*)/)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $name = $1;
|
||||||
|
$id = $2;
|
||||||
|
my $hash = $main::defs{$name};
|
||||||
|
|
||||||
|
readingsSingleUpdate($hash, 'state', 'received', 1);
|
||||||
|
|
||||||
|
if(!defined $hash)
|
||||||
|
{
|
||||||
|
Log3($ioname, 1, "UBUS - received message for unknown device $name");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($hash->{TYPE} ne 'UBUS_CALL')
|
||||||
|
{
|
||||||
|
Log3($ioname, 1, "UBUS - received message for unexpected device type " . $hash->{TYPE});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my ($module, $function, $params);
|
||||||
|
|
||||||
|
if(!defined $hash->{rpc}{$id})
|
||||||
|
{
|
||||||
|
Log3($name, 2, "UBUS_CALL ($name) - received message with unexpected ID $id");
|
||||||
|
return $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log3($name, 5, "UBUS_CALL ($name) - received message with ID $id: " . Dumper($hash->{rpc}{$id}));
|
||||||
|
$module = $hash->{rpc}{$id}{module} // q{};
|
||||||
|
$function = $hash->{rpc}{$id}{function} // q{};
|
||||||
|
$params = $hash->{rpc}{$id}{params} // {};
|
||||||
|
|
||||||
|
if($error)
|
||||||
|
{
|
||||||
|
Log3($name, 2, "UBUS_CALL ($name) - call returned error $error: " . Dumper($hash->{rpc}{$id}));
|
||||||
|
}
|
||||||
|
|
||||||
|
delete $hash->{rpc}{$id};
|
||||||
|
$hash->{rpccount}--;
|
||||||
|
|
||||||
|
# Parse response into readings
|
||||||
|
|
||||||
|
my $code = AttrVal($name, 'readings', '{FHEM::UBUS_CALL::DefaultReadings($RAW)}');
|
||||||
|
my $ecode = EvalSpecials(
|
||||||
|
$code,
|
||||||
|
(
|
||||||
|
'%RAW' => $buf,
|
||||||
|
'%DATA' => $result,
|
||||||
|
'%ERROR' => $error,
|
||||||
|
'%NAME' => $name,
|
||||||
|
'%MODULE' => $module,
|
||||||
|
'%FUNCTION' => $function,
|
||||||
|
'%PARAMS' => $params
|
||||||
|
)
|
||||||
|
);
|
||||||
|
my $ret = AnalyzePerlCommand(undef, $ecode);
|
||||||
|
|
||||||
|
if($ret && ref $ret eq 'HASH')
|
||||||
|
{
|
||||||
|
readingsBeginUpdate($hash);
|
||||||
|
foreach my $key (keys %{$ret})
|
||||||
|
{
|
||||||
|
readingsBulkUpdate($hash, makeReadingName($key), $ret->{$key});
|
||||||
|
}
|
||||||
|
readingsEndUpdate($hash, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($hash->{rpccount} == 0)
|
||||||
|
{
|
||||||
|
readingsSingleUpdate($hash, 'state', 'updated', 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub DefaultReadings
|
||||||
|
{
|
||||||
|
my $raw = shift // return {};
|
||||||
|
my $prefix = shift;
|
||||||
|
if($raw =~ m/"result"\s*:\s*\[\s*(\d+)\s*,\s*(\{.*})\s*\]/)
|
||||||
|
{
|
||||||
|
my $ret = json2nameValue($2, $prefix);
|
||||||
|
$ret->{error} = $1;
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
|
|
||||||
|
__END__
|
||||||
|
|
||||||
|
=pod
|
||||||
|
|
||||||
|
=item device
|
||||||
|
=item summary Performs calls via the JSON-RPC interface.
|
||||||
|
=item summary_DE Sendet Anfragen mittels einer JSON-RPC Schnittstelle.
|
||||||
|
|
||||||
|
=begin html
|
||||||
|
|
||||||
|
<a id="UBUS_CALL"></a>
|
||||||
|
<h3>UBUS_CALL</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<p>The <a href="http://openwrt.org/docs/guide-developer/ubus">uBus IPC/RPC system</a> is a common interconnect system used by OpenWrt. Services can connect to the bus and provide methods that can be called by other services or clients or deliver events to subscribers. This module implements the "call" type request. It is supposed to be used together with an <a href="#UBUS_CLIENT">UBUS_CLIENT</a> device, which must be defined first.</p>
|
||||||
|
|
||||||
|
<a id="UBUS_CALL-define"></a>
|
||||||
|
<h4>Define</h4>
|
||||||
|
|
||||||
|
<pre>define <name> UBUS_CALL <module> <function> [<parameters>]</pre>
|
||||||
|
<p>uBus calls are grouped under separate modules or "paths". In order to call a particular function, one needs to specify this path, the function to be called and optional parameters as <code><key>=<value></code> pairs. Examples:</p>
|
||||||
|
<ul>
|
||||||
|
<li><pre>define <name> UBUS_CALL system board</pre></li>
|
||||||
|
<li><pre>define <name> UBUS_CALL iwinfo devices</pre></li>
|
||||||
|
<li><pre>define <name> UBUS_CALL network.device status name=eth0</pre></li>
|
||||||
|
<li><pre>define <name> UBUS_CALL file list path=/tmp</pre></li>
|
||||||
|
<li><pre>define <name> UBUS_CALL file read path=/etc/hosts</pre></li>
|
||||||
|
</ul>
|
||||||
|
<p>The supported calls highly depend on the device on which the uBus daemon is running and its firmware. To get an overview of the calls supported by your device, consult the <a href="#UBUS_CLIENT-readings">readings of the UBUS_CLIENT device</a> which represents the connection to the physical device. The <code><module></code>, <code><function></code> and each <code><value></code> can be in any of the following forms:</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
A single keyword. In this case, only one call will be performed, with the module / function / parameter value set to the given content.
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
A comma-separated list. In this case, one call will be performed for every value given in the list. Example:
|
||||||
|
<pre>define <name> UBUS_CALL system board,info</pre>
|
||||||
|
This will perform two calls, one to the function <code>board</code> and another to the function <code>info</code>.
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
Perl code (enclosed in {}). The code may return a single keyword, a comma-separated list or an array reference. It is called whenever an uBus call is performed, and thus allows to set the value dynamically. If a single keyword is returned, only one call is performed, as if the keyword is given directly. If a comma-separated list or an array of keywords are returned, the call is performed for each of the returned values. Example:
|
||||||
|
<pre>define <name> UBUS_CALL network.device status name={<code returning a list of network devices>>}</pre>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>Note that the <code><module></code>, <code><function></code> and each <code><value></code> <b>must not</b> contain whitespace (since whitespace is used to separate the arguments). This also applies to Perl code. For longer pieces of code, it is recommended to define a sub in 99_myUtils.pm and call it from there.</p>
|
||||||
|
<p>If more than one comma-separated list or Perl code returning an array reference is used, calls are performed for each possible configuration. Example:</p>
|
||||||
|
<pre>define <name> UBUS_CALL file stat,read path=/etc/hosts,/etc/group</pre>
|
||||||
|
<p>This will perform four calls, to perform both <code>stat</code> and <code>read</code> on each of the two files. To distinguish the different calls when the response is received and parsed into readings, use a custom <a href="#UBUS_CALL-attr-readings">readings</a> parser code, that makes use of the variables <code>$MODULE</code>, <code>$FUNCTION</code> and <code>%PARAMS</code>. These will contain the values used for the call, for which the response has been received.</p>
|
||||||
|
<p>See the <a href="http://wiki.fhem.de/wiki/UBus">FHEM wiki</a> for further examples.</p>
|
||||||
|
|
||||||
|
<a id="UBUS_CALL-set"></a>
|
||||||
|
<h4>Set</h4>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a id="UBUS_CALL-set-disable"></a>
|
||||||
|
<pre>set <name> disable</pre>
|
||||||
|
Sets the <code>state</code> of the device to <code>inactive</code>, disables periodic updates and disconnects a websocket connection.
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a id="UBUS_CALL-set-enable"></a>
|
||||||
|
<pre>set <name> enable</pre>
|
||||||
|
Enables the device, so that automatic updates are performed.
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a id="UBUS_CALL-set-update"></a>
|
||||||
|
<pre>set <name> update</pre>
|
||||||
|
Performs an uBus call, updates the corresponding readings and resets any pending interval timer.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<a id="UBUS_CALL-get"></a>
|
||||||
|
<h4>Get</h4>
|
||||||
|
<p>There are no get commands defined.</p>
|
||||||
|
|
||||||
|
<a id="UBUS_CALL-attr"></a>
|
||||||
|
<h4>Attributes</h4>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a id="UBUS_CALL-attr-disable"></a>
|
||||||
|
<a href="#disable">disable</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a id="UBUS_CALL-attr-disabledForIntervals"></a>
|
||||||
|
<a href="#disabledForIntervals">disabledForIntervals</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a id="UBUS_CALL-attr-interval"></a>
|
||||||
|
<pre>attr <name> interval <interval></pre>
|
||||||
|
Defines the interval (in seconds) between performing consecutive calls and updating the readings.
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a id="UBUS_CALL-attr-IODev"></a>
|
||||||
|
<pre>attr <name> IODev <device></pre>
|
||||||
|
If there are multiple <a href="#UBUS_CLIENT">UBUS_CLIENT</a> devices defined, set this attribute to the value of the device which should be used to make the connection. It is not needed if there is only one device.
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a id="UBUS_CALL-attr-readings"></a>
|
||||||
|
<pre>attr <name> readings {<Perl-code>}</pre>
|
||||||
|
<p>Perl code which must return a hash of <code><key> => <value></code> pairs, where <code><key></code> is the name of the reading and <code><value></code> is its value. The following variables are available in the code:</p>
|
||||||
|
<ul>
|
||||||
|
<li><code>$NAME</code>: name of the UBUS_CALL device.</li>
|
||||||
|
<li><code>$MODULE</code>: module name used in the call (see <a href="#UBUS_CALL-define">definition</a>).</li>
|
||||||
|
<li><code>$FUNCTION</code>: function name used in the call (see <a href="#UBUS_CALL-define">definition</a>).</li>
|
||||||
|
<li><code>%PARAMS</code>: hash of parameters used in the call (see <a href="#UBUS_CALL-define">definition</a>).</li>
|
||||||
|
<li><code>$RAW</code>: raw JSON response returned by the call.</li>
|
||||||
|
<li><code>$ERROR</code>: reported error code, 0 means success.</li>
|
||||||
|
<li><code>%DATA</code>: decoded result data as Perl hash.</li>
|
||||||
|
</ul>
|
||||||
|
<p>If this attribute is omitted, its default value is <code>{FHEM::UBUS_CALL::DefaultReadings($RAW)}</code>. This function executes <code>json2nameValue</code> in the JSON result and turns all returned data into readings named by their position in the JSON tree. It is also possible to call this function in user-defined Perl code first, and then modify the returned hash, for example by deleting unwanted readings or adding additional, computed readings. The variables <code>$MODULE</code>, <code>$FUNCTION</code> and <code>%PARAMS</code> contain the values used for the call, for which the response has been received, and can be used to give unique names to the readings.</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<a id="UBUS_CALL-readings"></a>
|
||||||
|
<h4>Readings</h4>
|
||||||
|
<p>Any readings are defined by the attribute <a href="#UBUS_CALL-attr-readings">readings</a>.</p>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
=end html
|
||||||
|
|
||||||
|
=begin html_DE
|
||||||
|
|
||||||
|
<a id="UBUS_CALL"></a>
|
||||||
|
<h3>UBUS_CALL</h3>
|
||||||
|
|
||||||
|
=end html_DE
|
||||||
|
|
||||||
|
=cut
|
724
fhem/FHEM/72_UBUS_CLIENT.pm
Normal file
724
fhem/FHEM/72_UBUS_CLIENT.pm
Normal file
@ -0,0 +1,724 @@
|
|||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# 72_UBUS_CLIENT.pm
|
||||||
|
#
|
||||||
|
# Connects as a client to a server implementing the uBus command line / JSON-RPC interface.
|
||||||
|
#
|
||||||
|
# $Id$
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
package FHEM::UBUS_CLIENT; ## no critic "Package declaration"
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Exporter qw(import);
|
||||||
|
use Carp qw(carp);
|
||||||
|
use DevIo;
|
||||||
|
use HttpUtils;
|
||||||
|
use FHEM::Core::Authentication::Passwords qw(:ALL);
|
||||||
|
use JSON qw(encode_json decode_json);
|
||||||
|
use GPUtils qw(GP_Import);
|
||||||
|
|
||||||
|
BEGIN {
|
||||||
|
GP_Import (
|
||||||
|
qw(
|
||||||
|
DevIo_OpenDev
|
||||||
|
DevIo_SimpleWrite
|
||||||
|
DevIo_SimpleRead
|
||||||
|
DevIo_CloseDev
|
||||||
|
DevIo_IsOpen
|
||||||
|
HttpUtils_NonblockingGet
|
||||||
|
Log3
|
||||||
|
Debug
|
||||||
|
IsDisabled
|
||||||
|
Dispatch
|
||||||
|
InternalTimer
|
||||||
|
RemoveInternalTimer
|
||||||
|
AttrVal
|
||||||
|
ReadingsVal
|
||||||
|
ReadingsNum
|
||||||
|
ReadingsAge
|
||||||
|
readingsSingleUpdate
|
||||||
|
readingsBeginUpdate
|
||||||
|
readingsBulkUpdate
|
||||||
|
readingsEndUpdate
|
||||||
|
readingsDelete
|
||||||
|
makeReadingName
|
||||||
|
deviceEvents
|
||||||
|
gettimeofday
|
||||||
|
)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
sub ::UBUS_CLIENT_Initialize { goto &Initialize };
|
||||||
|
|
||||||
|
sub Initialize
|
||||||
|
{
|
||||||
|
my $hash = shift // return;
|
||||||
|
|
||||||
|
$hash->{DefFn} = \&Define;
|
||||||
|
$hash->{UndefFn} = \&Undef;
|
||||||
|
$hash->{SetFn} = \&Set;
|
||||||
|
$hash->{AttrFn} = \&Attr;
|
||||||
|
$hash->{ReadFn} = \&Read;
|
||||||
|
$hash->{ReadyFn} = \&Ready;
|
||||||
|
$hash->{WriteFn} = \&Write;
|
||||||
|
$hash->{RenameFn} = \&Rename;
|
||||||
|
|
||||||
|
$hash->{AttrList} = 'disable:1,0 disabledForIntervals timeout refresh username ' . $main::readingFnAttributes;
|
||||||
|
$hash->{Clients} = 'UBUS_CALL';
|
||||||
|
$hash->{MatchList} = {'1:UBUS_CALL' => '^.'};
|
||||||
|
|
||||||
|
$hash->{parseParams} = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Define
|
||||||
|
{
|
||||||
|
my $hash = shift;
|
||||||
|
my $apar = shift;
|
||||||
|
my $hpar = shift;
|
||||||
|
|
||||||
|
my $name = shift @{$apar};
|
||||||
|
my $type = shift @{$apar};
|
||||||
|
my $dev = shift @{$apar} // 'ubus';
|
||||||
|
|
||||||
|
$hash->{helper}->{passObj} = FHEM::Core::Authentication::Passwords->new($hash->{TYPE});
|
||||||
|
$hash->{helper}->{updateFunc} = sub {my $item = shift // return; GetUpdate($hash, $item); return;};
|
||||||
|
|
||||||
|
Disconnect($hash);
|
||||||
|
|
||||||
|
if($dev =~ m,^(ws|wss)://([^/:]+)(:[0-9]+)?(.*?)$,)
|
||||||
|
{
|
||||||
|
my ($proto, $host, $port, $path) = ($1, $2, $3 ? $3 : ':' . ($1 eq 'wss' ? '443' : '80'), $4);
|
||||||
|
$hash->{method} = 'websocket';
|
||||||
|
$hash->{DeviceName} = "$proto:$host$port$path";
|
||||||
|
%{$hash->{header}} = ('Sec-WebSocket-Protocol' => 'ubus-json');
|
||||||
|
}
|
||||||
|
elsif($dev =~ m,^(http|https)://([^/:]+)(:[0-9]+)?(.*?)$,)
|
||||||
|
{
|
||||||
|
$hash->{method} = 'http';
|
||||||
|
$hash->{url} = $dev;
|
||||||
|
}
|
||||||
|
elsif($dev eq 'ubus')
|
||||||
|
{
|
||||||
|
$hash->{method} = 'shell';
|
||||||
|
$hash->{cmd} = 'ubus';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "invalid device specifier $dev";
|
||||||
|
}
|
||||||
|
|
||||||
|
readingsSingleUpdate($hash, 'state', 'initialized', 1);
|
||||||
|
|
||||||
|
return $main::init_done ? Connect($hash) : InternalTimer(gettimeofday() + 1, \&Connect, $hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Undef
|
||||||
|
{
|
||||||
|
my $hash = shift // return;
|
||||||
|
|
||||||
|
Disconnect($hash);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Rename
|
||||||
|
{
|
||||||
|
my $name_new = shift // return;
|
||||||
|
my $name_old = shift // return;
|
||||||
|
|
||||||
|
my $passObj = $main::defs{$name_new}->{helper}->{passObj};
|
||||||
|
|
||||||
|
my $password = $passObj->getReadPassword($name_old) // return;
|
||||||
|
|
||||||
|
$passObj->setStorePassword($name_new, $password);
|
||||||
|
$passObj->deletePassword($name_old);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Ready
|
||||||
|
{
|
||||||
|
my $hash = shift // return;
|
||||||
|
my $name = $hash->{NAME};
|
||||||
|
|
||||||
|
return if DevIo_IsOpen($hash) || $hash->{method} ne 'websocket' || IsDisabled($name);
|
||||||
|
|
||||||
|
#Log3($name, 5, "UBUS ($name) - reconnect");
|
||||||
|
|
||||||
|
return DevIo_OpenDev($hash, 1, \&Init, \&Callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Read
|
||||||
|
{
|
||||||
|
my $hash = shift // return;
|
||||||
|
my $name = $hash->{NAME};
|
||||||
|
|
||||||
|
my $buf = DevIo_SimpleRead($hash) // return;
|
||||||
|
|
||||||
|
my @items = $buf =~ /( \{ (?: [^{}]* | (?0) )* \} )/xg;
|
||||||
|
|
||||||
|
for my $item (@items)
|
||||||
|
{
|
||||||
|
Log3($name, 5, "UBUS ($name) - received: $item");
|
||||||
|
Decode($hash, $item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Response
|
||||||
|
{
|
||||||
|
my $param = shift // return;
|
||||||
|
my $error = shift // q{};
|
||||||
|
my $data = shift // q{};
|
||||||
|
|
||||||
|
my $hash = $param->{hash};
|
||||||
|
my $name = $hash->{NAME};
|
||||||
|
|
||||||
|
if($error ne q{})
|
||||||
|
{
|
||||||
|
Log3($name, 1, "UBUS ($name) - error performing request: $error");
|
||||||
|
}
|
||||||
|
elsif($data ne q{})
|
||||||
|
{
|
||||||
|
Log3($name, 5, "UBUS ($name) - received: $data");
|
||||||
|
Decode($hash, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Set
|
||||||
|
{
|
||||||
|
my $hash = shift;
|
||||||
|
my $apar = shift;
|
||||||
|
my $hpar = shift;
|
||||||
|
|
||||||
|
my $name = shift @{$apar} // return;
|
||||||
|
my $cmd = shift @{$apar} // return qq{"set $name" needs at least one argument};
|
||||||
|
|
||||||
|
if($cmd eq 'password')
|
||||||
|
{
|
||||||
|
my $password = $apar->[0];
|
||||||
|
|
||||||
|
my ($res, $error) = defined $password ? $hash->{helper}->{passObj}->setStorePassword($name, $password) : $hash->{helper}->{passObj}->deletePassword($name);
|
||||||
|
|
||||||
|
if(defined $error && !defined $res)
|
||||||
|
{
|
||||||
|
Log3($name, 1, "UBUS ($name) - could not update password");
|
||||||
|
return "Error while updating the password - $error";
|
||||||
|
}
|
||||||
|
|
||||||
|
Disconnect($hash);
|
||||||
|
Connect($hash);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($cmd eq 'disable')
|
||||||
|
{
|
||||||
|
Disconnect($hash);
|
||||||
|
readingsSingleUpdate($hash, 'state', 'inactive', 1);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($cmd eq 'enable')
|
||||||
|
{
|
||||||
|
readingsSingleUpdate($hash, 'state', 'active', 1);
|
||||||
|
Connect($hash);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Unknown argument $cmd, choose one of disable:noArg enable:noArg password";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Attr
|
||||||
|
{
|
||||||
|
my $cmd = shift // return;
|
||||||
|
my $name = shift // return;
|
||||||
|
my $attr = shift // return;
|
||||||
|
my $value = shift // return;
|
||||||
|
|
||||||
|
if($cmd eq 'set')
|
||||||
|
{
|
||||||
|
if($attr eq 'timeout' || $attr eq 'refresh')
|
||||||
|
{
|
||||||
|
return "$attr must be non-negative." if $value < 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Init
|
||||||
|
{
|
||||||
|
my $hash = shift // return;
|
||||||
|
|
||||||
|
Login($hash);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Callback
|
||||||
|
{
|
||||||
|
my $hash = shift // return;
|
||||||
|
my $error = shift // q{};
|
||||||
|
my $name = $hash->{NAME};
|
||||||
|
|
||||||
|
Log3($name, 1, "UBUS ($name) - error while connecting: $error") if $error;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Connect
|
||||||
|
{
|
||||||
|
my $hash = shift // return;
|
||||||
|
my $name = $hash->{NAME};
|
||||||
|
|
||||||
|
return if IsDisabled($name);
|
||||||
|
|
||||||
|
if($hash->{method} eq 'websocket')
|
||||||
|
{
|
||||||
|
Log3($name, 5, "UBUS ($name) - connect");
|
||||||
|
|
||||||
|
return DevIo_OpenDev($hash, 0, \&Init, \&Callback) if !DevIo_IsOpen($hash);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
readingsSingleUpdate($hash, 'state', 'active', 1);
|
||||||
|
|
||||||
|
|
||||||
|
if($hash->{method} eq 'http')
|
||||||
|
{
|
||||||
|
Login($hash);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UpdatesStart($hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Disconnect
|
||||||
|
{
|
||||||
|
my $hash = shift // return;
|
||||||
|
my $name = $hash->{NAME};
|
||||||
|
|
||||||
|
RemoveInternalTimer($hash, \&CheckSession);
|
||||||
|
|
||||||
|
return if !defined $hash->{method};
|
||||||
|
|
||||||
|
delete $hash->{session};
|
||||||
|
delete $hash->{lastid};
|
||||||
|
|
||||||
|
if($hash->{method} eq 'websocket')
|
||||||
|
{
|
||||||
|
Log3($name, 5, "UBUS ($name) - disconnect");
|
||||||
|
|
||||||
|
return DevIo_CloseDev($hash) if DevIo_IsOpen($hash);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
readingsSingleUpdate($hash, 'state', 'stopped', 1);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Write
|
||||||
|
{
|
||||||
|
my $hash = shift // return; # Physical device hash
|
||||||
|
my $name = $hash->{NAME};
|
||||||
|
|
||||||
|
return if IsDisabled($name);
|
||||||
|
|
||||||
|
my $dev = shift // return; # Logical device name
|
||||||
|
my $method = shift // q{}; # Mehod (list, call, subscribe...)
|
||||||
|
my $id = "$dev:$method:" . (++$hash->{lastid});
|
||||||
|
my $rpcparam;
|
||||||
|
|
||||||
|
if($method ne 'cmd' && $dev ne $name) # Catch calls while not logged in.
|
||||||
|
{
|
||||||
|
return if !defined $hash->{session};
|
||||||
|
return if $hash->{session} eq '00000000000000000000000000000000';
|
||||||
|
}
|
||||||
|
|
||||||
|
if($method eq 'call')
|
||||||
|
{
|
||||||
|
my $module = shift // q{};
|
||||||
|
my $function = shift // q{};
|
||||||
|
my $param = shift // {};
|
||||||
|
|
||||||
|
if($hash->{method} eq 'cmd')
|
||||||
|
{
|
||||||
|
my $json;
|
||||||
|
eval { $json = encode_json($param); };
|
||||||
|
|
||||||
|
if($@)
|
||||||
|
{
|
||||||
|
Log3($name, 1, "UBUS ($name) - encode_json error: $@");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $ret = qx{ubus call $module $function '$json'};
|
||||||
|
|
||||||
|
InternalTimer(gettimeofday() + 1, sub () { Dispatch($hash, qq/{"jsonrpc":"2.0","id":"$id","result":[0,$ret]}/); }, $hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
$rpcparam = [
|
||||||
|
$hash->{session},
|
||||||
|
$module,
|
||||||
|
$function,
|
||||||
|
$param
|
||||||
|
];
|
||||||
|
}
|
||||||
|
elsif($method eq 'list')
|
||||||
|
{
|
||||||
|
my $pattern = shift // '*';
|
||||||
|
|
||||||
|
if($hash->{method} eq 'cmd')
|
||||||
|
{
|
||||||
|
Log3($name, 1, "UBUS ($name) - list not implemented for command line mode");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rpcparam = [
|
||||||
|
$hash->{session},
|
||||||
|
$pattern
|
||||||
|
];
|
||||||
|
}
|
||||||
|
elsif($method eq 'subscribe')
|
||||||
|
{
|
||||||
|
if($hash->{method} eq 'cmd')
|
||||||
|
{
|
||||||
|
Log3($name, 1, "UBUS ($name) - subscribe not implemented for command line mode");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $channel = shift // return;
|
||||||
|
$rpcparam = [
|
||||||
|
$hash->{session},
|
||||||
|
$channel
|
||||||
|
];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log3($name, 1, "UBUS ($name) - unknown method $method in Write");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $request = {
|
||||||
|
'jsonrpc' => '2.0',
|
||||||
|
'method' => $method,
|
||||||
|
'params' => $rpcparam,
|
||||||
|
'id' => $id
|
||||||
|
};
|
||||||
|
|
||||||
|
my $json;
|
||||||
|
eval { $json = encode_json($request); };
|
||||||
|
|
||||||
|
if($@)
|
||||||
|
{
|
||||||
|
Log3($name, 1, "UBUS ($name) - encode_json error: $@");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log3($name, 5, "UBUS ($name) - sent: $json");
|
||||||
|
|
||||||
|
$hash->{rpc}{$id} = $request;
|
||||||
|
|
||||||
|
if($hash->{method} eq 'websocket')
|
||||||
|
{
|
||||||
|
DevIo_SimpleWrite($hash, $json, 2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
my $http = {
|
||||||
|
'url' => $hash->{url},
|
||||||
|
'method' => 'POST',
|
||||||
|
'data' => $json,
|
||||||
|
'timeout' => 5,
|
||||||
|
'hash' => $hash,
|
||||||
|
'callback' => \&Response
|
||||||
|
};
|
||||||
|
|
||||||
|
HttpUtils_NonblockingGet($http);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Login
|
||||||
|
{
|
||||||
|
my $hash = shift // return;
|
||||||
|
my $name = $hash->{NAME};
|
||||||
|
|
||||||
|
my $password = $hash->{helper}->{passObj}->getReadPassword($name) // q{};
|
||||||
|
|
||||||
|
my $param = {
|
||||||
|
'username' => AttrVal($name, 'username', 'user'),
|
||||||
|
'password' => $password,
|
||||||
|
'timeout' => AttrVal($name, 'timeout', 300)
|
||||||
|
};
|
||||||
|
|
||||||
|
$hash->{session} = '00000000000000000000000000000000';
|
||||||
|
$hash->{lastid} = -1;
|
||||||
|
|
||||||
|
Log3($name, 5, "UBUS ($name) - login of user " . $param->{username});
|
||||||
|
|
||||||
|
Write($hash, $hash->{NAME}, 'call', 'session', 'login', $param);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub CheckSession
|
||||||
|
{
|
||||||
|
my $hash = shift // return;
|
||||||
|
Write($hash, $hash->{NAME}, 'call', 'session', 'list');
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Decode
|
||||||
|
{
|
||||||
|
my $hash = shift // return;
|
||||||
|
my $buf = shift // return;
|
||||||
|
my $name = $hash->{NAME};
|
||||||
|
|
||||||
|
my $data;
|
||||||
|
eval { $data = decode_json($buf); };
|
||||||
|
|
||||||
|
if($@)
|
||||||
|
{
|
||||||
|
Log3($name, 1, "UBUS ($name) - decode_json error: $@");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!defined $data->{id}) # Missing ID - response to some subscription? Dispatch it.
|
||||||
|
{
|
||||||
|
Dispatch($hash, $buf);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $id = $data->{id};
|
||||||
|
|
||||||
|
if($id !~ m/^$name:([a-z]*):([0-9]*)$/) # Was this call made by someone else?
|
||||||
|
{
|
||||||
|
Dispatch($hash, $buf);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# We made the call (login, session etc.) - handle it.
|
||||||
|
|
||||||
|
my $method = $1;
|
||||||
|
|
||||||
|
my $error = $data->{result}[0];
|
||||||
|
my $result = $data->{result}[1];
|
||||||
|
|
||||||
|
if($method eq 'call')
|
||||||
|
{
|
||||||
|
my $session = $hash->{rpc}{$id}{params}[0];
|
||||||
|
my $module = $hash->{rpc}{$id}{params}[1];
|
||||||
|
my $function = $hash->{rpc}{$id}{params}[2];
|
||||||
|
my $param = $hash->{rpc}{$id}{params}[3];
|
||||||
|
|
||||||
|
if($error == 0)
|
||||||
|
{
|
||||||
|
if($module eq 'session')
|
||||||
|
{
|
||||||
|
if($function eq 'login') # Successfully logged in.
|
||||||
|
{
|
||||||
|
$hash->{session} = $result->{ubus_rpc_session};
|
||||||
|
Write($hash, $hash->{NAME}, 'list', '*');
|
||||||
|
}
|
||||||
|
elsif($function eq 'list')
|
||||||
|
{
|
||||||
|
if(!defined $result->{ubus_rpc_session})
|
||||||
|
{
|
||||||
|
Log3($name, 3, "UBUS ($name) - no session data, consider setting attr refresh to 0");
|
||||||
|
}
|
||||||
|
elsif($hash->{session} ne $result->{ubus_rpc_session})
|
||||||
|
{
|
||||||
|
Log3($name, 3, "UBUS ($name) - unexpected session " . $result->{ubus_rpc_session} . " instead of expected " . $hash->{session});
|
||||||
|
$hash->{session} = $result->{ubus_rpc_session};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(AttrVal($name, 'refresh', 180))
|
||||||
|
{
|
||||||
|
InternalTimer(gettimeofday() + AttrVal($name, 'refresh', 180), \&CheckSession, $hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elsif($error == 6)
|
||||||
|
{
|
||||||
|
if($module eq 'session' && $function eq 'login') # Login failed. Log and disconnect.
|
||||||
|
{
|
||||||
|
Log3($name, 1, "UBUS ($name) - login error");
|
||||||
|
Disconnect($hash);
|
||||||
|
}
|
||||||
|
else # Other authentication problem - try login again.
|
||||||
|
{
|
||||||
|
Login($hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elsif($method eq 'list')
|
||||||
|
{
|
||||||
|
if($error == 0)
|
||||||
|
{
|
||||||
|
my $m = 0;
|
||||||
|
readingsBeginUpdate($hash);
|
||||||
|
for my $module (sort keys %{$result})
|
||||||
|
{
|
||||||
|
my $f = 0;
|
||||||
|
readingsBulkUpdate($hash, makeReadingName("mod_${m}_name"), $module);
|
||||||
|
for my $function (sort keys %{$result->{$module}})
|
||||||
|
{
|
||||||
|
my $p = 0;
|
||||||
|
readingsBulkUpdate($hash, makeReadingName("mod_${m}_func_${f}_name"), $function);
|
||||||
|
for my $param (sort keys %{$result->{$module}->{$function}})
|
||||||
|
{
|
||||||
|
readingsBulkUpdate($hash, makeReadingName("mod_${m}_func_${f}_param_${p}_name"), $param);
|
||||||
|
readingsBulkUpdate($hash, makeReadingName("mod_${m}_func_${f}_param_${p}_type"), $result->{$module}->{$function}->{$param});
|
||||||
|
}
|
||||||
|
$f++;
|
||||||
|
}
|
||||||
|
$m++;
|
||||||
|
}
|
||||||
|
readingsEndUpdate($hash, 1);
|
||||||
|
}
|
||||||
|
elsif($error == 6)
|
||||||
|
{
|
||||||
|
Log3($name, 1, "UBUS ($name) - list resulted in authentication failure");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete $hash->{rpc}{$id};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
|
|
||||||
|
__END__
|
||||||
|
|
||||||
|
=pod
|
||||||
|
|
||||||
|
=item device
|
||||||
|
=item summary Provides access to the uBus JSON-RPC interface.
|
||||||
|
=item summary_DE Erlaubt den Zugriff auf die uBus JSON-RPC Schnittstelle.
|
||||||
|
|
||||||
|
=begin html
|
||||||
|
|
||||||
|
<a id="UBUS_CLIENT"></a>
|
||||||
|
<h3>UBUS_CLIENT</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<p>The <a href="http://openwrt.org/docs/guide-developer/ubus">uBus IPC/RPC system</a> is a common interconnect system used by OpenWrt. Services can connect to the bus and provide methods that can be called by other services or clients or deliver events to subscribers. This module provides different methods to connect to an uBus interface, either using its command line interface or remotely via websocket or HTTP.</p>
|
||||||
|
|
||||||
|
<a id="UBUS_CLIENT-define"></a>
|
||||||
|
<h4>Define</h4>
|
||||||
|
|
||||||
|
<pre>define <name> UBUS_CLIENT <method></pre>
|
||||||
|
<p>The following connection methods for <code><method></code> are supported:</p>
|
||||||
|
<ul>
|
||||||
|
<li>For a <b>websocket</b> connection, a url of the form <code>(ws|wss)://<host>[:port][/path]</code> is used. Example:
|
||||||
|
<pre>define <name> UBUS_CLIENT ws://192.168.1.1</pre></li>
|
||||||
|
<li>For a <b>HTTP</b> connection, a url of the form <code>(http|https)://<host>[:port][/path]</code> is used. Example:
|
||||||
|
<pre>define <name> UBUS_CLIENT http://192.168.1.1/ubus</pre></li>
|
||||||
|
<!--<li>To use the ubus <b>command line</b> tool (if FHEM is running on the same device as ubus), use <code>ubus</code>. Example:
|
||||||
|
<pre>define <name> UBUS_CLIENT ubus</pre></li>-->
|
||||||
|
</ul>
|
||||||
|
<p>When using the websocket or HTTP connection methods, a valid user name and password must be provided. The user name defaults to <code>user</code>, but can be changed with an attribute:</p>
|
||||||
|
<pre>attr <name> username <username></pre>
|
||||||
|
<p>The password is set with the following command, which must be issued only once, and stored as an obfuscated value on disk:</p>
|
||||||
|
<pre>set <name> password <password></pre>
|
||||||
|
<p>When a connection and login have been performed successfully, a <code>list</code> command is executed to obtain the available calls supported by this device, and the result is filled into the <a href="#UBUS_CLIENT-readings">readings</a> of the device.</p>
|
||||||
|
<p>See the <a href="http://wiki.fhem.de/wiki/UBus">FHEM wiki</a> for further examples.</p>
|
||||||
|
|
||||||
|
<a id="UBUS_CLIENT-set"></a>
|
||||||
|
<h4>Set</h4>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a id="UBUS-set-disable"></a>
|
||||||
|
<pre>set <name> disable</pre>
|
||||||
|
Sets the <code>state</code> of the device to <code>inactive</code>, disables periodic updates and disconnects a websocket connection.
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a id="UBUS_CLIENT-set-enable"></a>
|
||||||
|
<pre>set <name> enable</pre>
|
||||||
|
Enables the device, establishing a websocket connection first if necessary.
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a id="UBUS_CLIENT-set-password"></a>
|
||||||
|
<pre>set <name> password <password></pre>
|
||||||
|
Sets the password used to authenticate via websocket or HTTP and stores it on disk.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<a id="UBUS_CLIENT-get"></a>
|
||||||
|
<h4>Get</h4>
|
||||||
|
<p>There are no get commands defined.</p>
|
||||||
|
|
||||||
|
<a id="UBUS_CLIENT-attr"></a>
|
||||||
|
<h4>Attributes</h4>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a id="UBUS_CLIENT-attr-disable"></a>
|
||||||
|
<a href="#disable">disable</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a id="UBUS_CLIENT-attr-disabledForIntervals"></a>
|
||||||
|
<a href="#disabledForIntervals">disabledForIntervals</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a id="UBUS_CLIENT-attr-refresh"></a>
|
||||||
|
<pre>attr <name> refresh <period></pre>
|
||||||
|
Automatically check the connection after <code>period</code> seconds by issuing a <code>session list</code> request. If the session is expired, a new login is attempted. Some devices do not allow the <code>session list</code> command; in this case, set the value to 0 in order to disable the periodic refresh. The default value is 180 seconds.
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a id="UBUS_CLIENT-attr-timeout"></a>
|
||||||
|
<pre>attr <name> timeout <period></pre>
|
||||||
|
Sets the timeout value in the login request, i.e., the period of inactivity after which a session expires. This should be set larger than the time between requests. The default value is 300 seconds.
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a id="UBUS_CLIENT-attr-username"></a>
|
||||||
|
<pre>attr <name> username <username></pre>
|
||||||
|
Defines the username to be used for login via websocket or HTTP. The default value is <code>user</code>.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<a id="UBUS_CLIENT-readings"></a>
|
||||||
|
<h4>Readings</h4>
|
||||||
|
<p>When the connection is established, the module executes a <code>list</code> command and creates the following readings:</p>
|
||||||
|
<ul>
|
||||||
|
<li><code>mod_<n>_name</code>: name (path) of the n'th module in the uBus tree</li>
|
||||||
|
<li><code>mod_<n>_func_<m>_name</code>: name of the m'th function supported by the n'th module</li>
|
||||||
|
<li><code>mod_<n>_func_<m>_param_<k>_name</code>: name of the k'th parameter of the m'th function of the n'th module</li>
|
||||||
|
<li><code>mod_<n>_func_<m>_param_<k>_type</code>: type of the k'th parameter of the m'th function of the n'th module</li>
|
||||||
|
</ul>
|
||||||
|
<p>These can be used to perform calls using the <a href="#UBUS_CALL">UBUS_CALL</a> module.</p>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
=end html
|
||||||
|
|
||||||
|
=begin html_DE
|
||||||
|
|
||||||
|
<a id="UBUS_CLIENT"></a>
|
||||||
|
<h3>UBUS_CLIENT</h3>
|
||||||
|
|
||||||
|
=end html_DE
|
||||||
|
|
||||||
|
=cut
|
@ -385,6 +385,8 @@ FHEM/72_FB_CALLLIST.pm markusbloch Frontends
|
|||||||
FHEM/72_FB_CALLMONITOR.pm markusbloch Unterstützende Dienste
|
FHEM/72_FB_CALLMONITOR.pm markusbloch Unterstützende Dienste
|
||||||
FHEM/72_FRITZBOX.pm tupol FRITZ!Box (link als PM an tupol)
|
FHEM/72_FRITZBOX.pm tupol FRITZ!Box (link als PM an tupol)
|
||||||
FHEM/72_TA_CMI_JSON.pm delmar Sonstige Systeme
|
FHEM/72_TA_CMI_JSON.pm delmar Sonstige Systeme
|
||||||
|
FHEM/72_UBUS_CALL.pm xenos1984 Sonstige Systeme
|
||||||
|
FHEM/72_UBUS_CLIENT.pm xenos1984 Sonstige Systeme
|
||||||
FHEM/72_XiaomiDevice.pm markus-m Sonstige Systeme
|
FHEM/72_XiaomiDevice.pm markus-m Sonstige Systeme
|
||||||
FHEM/73_AMADCommBridge.pm CoolTux Sonstige Systeme
|
FHEM/73_AMADCommBridge.pm CoolTux Sonstige Systeme
|
||||||
FHEM/73_AutoShuttersControl.pm CoolTux Automatisierung
|
FHEM/73_AutoShuttersControl.pm CoolTux Automatisierung
|
||||||
|
Loading…
x
Reference in New Issue
Block a user