mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-05-04 22:19:38 +00:00
98_HTTPMOD: new version with many internal changes, own namespace and functions in a separate utils module
git-svn-id: https://svn.fhem.de/fhem/trunk/fhem@22997 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
3fc241c56c
commit
dcb50b8974
3109
FHEM/98_HTTPMOD.pm
3109
FHEM/98_HTTPMOD.pm
File diff suppressed because it is too large
Load Diff
@ -27,20 +27,28 @@ use warnings;
|
||||
use GPUtils qw(:all);
|
||||
use Time::HiRes qw(gettimeofday);
|
||||
use Encode qw(decode encode);
|
||||
use Scalar::Util qw(looks_like_number);
|
||||
use DevIo;
|
||||
|
||||
use Exporter ('import');
|
||||
our @EXPORT_OK = qw(UpdateTimer FhemCaller
|
||||
StopQueueTimer
|
||||
StartQueueTimer
|
||||
ValidRegex ValidExpr
|
||||
EvalExpr
|
||||
FormatVal
|
||||
MapConvert MapToHint
|
||||
CheckRange
|
||||
ReverseWordOrder
|
||||
SwapByteOrder
|
||||
ReadKeyValue StoreKeyValue
|
||||
ManageUserAttr
|
||||
MemReading
|
||||
FlattenJSON
|
||||
BodyDecode
|
||||
IsOpen
|
||||
FmtTime
|
||||
FmtTimeMs
|
||||
ReadableArray
|
||||
);
|
||||
|
||||
our %EXPORT_TAGS = (all => [@EXPORT_OK]);
|
||||
@ -104,43 +112,108 @@ sub UpdateTimer {
|
||||
|
||||
if ($cmd eq 'stop' || !$intvl) { # stop timer
|
||||
RemoveInternalTimer("update:$name");
|
||||
if ($hash->{TRIGGERTIME}) {
|
||||
if ($hash->{'.TRIGGERTIME'}) {
|
||||
Log3 $name, 4, "$name: UpdateTimer called from " . FhemCaller() . " with cmd $cmd and interval $intvl stops timer";
|
||||
delete $hash->{TRIGGERTIME};
|
||||
delete $hash->{TRIGGERTIME_FMT};
|
||||
delete $hash->{lastUpdate};
|
||||
delete $hash->{'.TRIGGERTIME'};
|
||||
#delete $hash->{TRIGGERTIME_FMT};
|
||||
delete $hash->{'.LastUpdate'};
|
||||
}
|
||||
return;
|
||||
}
|
||||
if ($cmd eq 'next') {
|
||||
$hash->{lastUpdate} = $now; # start timer from now, ignore potential last update time
|
||||
$hash->{'.LastUpdate'} = $now; # start timer from now, ignore potential last update time
|
||||
}
|
||||
my $nextUpdate;
|
||||
if ($hash->{TimeAlign}) { # TimeAlign: do as if interval started at time w/o drift ...
|
||||
my $count = int(($now - $hash->{TimeAlign}) / $intvl); # $intvl <> 0,has been checked above
|
||||
$nextUpdate = $count * $intvl + $hash->{TimeAlign}; # next aligned time >= now, lastUpdate doesn't matter with alignment
|
||||
if ($hash->{'.TimeAlign'}) { # TimeAlign: do as if interval started at time w/o drift ...
|
||||
my $count = int(($now - $hash->{'.TimeAlign'}) / $intvl); # $intvl <> 0,has been checked above
|
||||
$nextUpdate = $count * $intvl + $hash->{'.TimeAlign'}; # next aligned time >= now, lastUpdate doesn't matter with alignment
|
||||
$nextUpdate += $intvl if ($nextUpdate <= $now); # works for initial alignment as welas for next round
|
||||
}
|
||||
else { # no align time -> just add the interval to now
|
||||
if ($hash->{lastUpdate}) {
|
||||
$nextUpdate = $hash->{lastUpdate} + $intvl;
|
||||
if ($hash->{'.LastUpdate'}) {
|
||||
$nextUpdate = $hash->{'.LastUpdate'} + $intvl;
|
||||
}
|
||||
else {
|
||||
$nextUpdate = $now; # first call -> don't wait for interval to pass
|
||||
}
|
||||
}
|
||||
$hash->{TRIGGERTIME} = $nextUpdate;
|
||||
$hash->{TRIGGERTIME_FMT} = FmtDateTime($nextUpdate);
|
||||
$hash->{'.TRIGGERTIME'} = $nextUpdate;
|
||||
#$hash->{TRIGGERTIME_FMT} = FmtDateTime($nextUpdate);
|
||||
|
||||
my $delay = sprintf ("%.1f", $nextUpdate - $now);
|
||||
Log3 $name, 4, "$name: UpdateTimer called from " . FhemCaller() . " with cmd $cmd" .
|
||||
" sets timer to call update function in $delay sec at $hash->{TRIGGERTIME_FMT}, interval $intvl";
|
||||
" sets timer to call update function in $delay sec at " . FmtDateTime($nextUpdate) . ", interval $intvl";
|
||||
RemoveInternalTimer("update:$name");
|
||||
InternalTimer($nextUpdate, $updFn, "update:$name", 0); # now set the timer
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
######################################################
|
||||
# set internal timer for next queue processing
|
||||
# to now + passed delay (if delay is passed)
|
||||
# if no delay is passed, use attribute queueDelay if no shorter timer is already set
|
||||
#
|
||||
# startQueueTimer is called from Modbus:
|
||||
# - in queueRequest when something got added to the queue
|
||||
# - end of get/set to set it to immediate processing
|
||||
# - at the end of HandleResponse
|
||||
# - in processRequestQueue to set a new delay
|
||||
# - in checkDelay called from processRequestQueue
|
||||
# before it returns 1 (to ask the caller to return because delay is not over yet)
|
||||
# but startQueueTimer does only set the timer if the queue contains something
|
||||
#
|
||||
sub StartQueueTimer {
|
||||
my $ioHash = shift;
|
||||
my $pFunc = shift; # e.g. \&Modbus::ProcessRequestQueue
|
||||
my $oRef = shift; # optional hash ref for passing options
|
||||
my $name = $ioHash->{NAME};
|
||||
my $pDelay = $oRef->{'delay'} // AttrVal($name, 'queueDelay', 1); # delay until queue processing call
|
||||
my $silent = $oRef->{'silent'} // 0;
|
||||
my $msg = $oRef->{'log'} // '';
|
||||
my $qlen = ($ioHash->{QUEUE} ? scalar(@{$ioHash->{QUEUE}}) : 0);
|
||||
|
||||
if ($qlen) {
|
||||
my $now = gettimeofday();
|
||||
my $delay = (defined($pDelay) ? $pDelay : AttrVal($name, 'queueDelay', 1));
|
||||
return if ($ioHash->{nextQueueRun} && $ioHash->{nextQueueRun} < $now+$delay);
|
||||
RemoveInternalTimer ("queue:$name");
|
||||
InternalTimer($now+$delay, $pFunc, "queue:$name", 0);
|
||||
$ioHash->{nextQueueRun} = $now+$delay;
|
||||
Log3 $name, 5, "$name: StartQueueTimer called from " . FhemCaller() .
|
||||
' sets internal timer to process queue in ' .
|
||||
sprintf ('%.3f', $delay) . ' seconds' . ($msg ? ", $msg" : '') if (!$silent);
|
||||
}
|
||||
else {
|
||||
Log3 $name, 5, "$name: StartQueueTimer called from " . FhemCaller() .
|
||||
' removes internal timer because queue is empty' if ($ioHash->{nextQueueRun} && !$silent);
|
||||
delete $ioHash->{nextQueueRun};
|
||||
RemoveInternalTimer ("queue:$name");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
########################################################################################
|
||||
# remove internal timer for next queue processing
|
||||
# called at the end of open and close (initialized state, queue should be empty)
|
||||
# end when queue becomes empty while processing the queue (not really ... todo:)
|
||||
# when processRequestQueue gets called from fhem.pl via internal timer,
|
||||
# this timer is removed internally -> only nextQueueRun is deleted in processRequestQueue
|
||||
sub StopQueueTimer {
|
||||
my $ioHash = shift;
|
||||
my $oRef = shift; # optional hash ref for passing options
|
||||
my $silent = $oRef->{'silent'} // 0;
|
||||
my $name = $ioHash->{NAME};
|
||||
if ($ioHash->{nextQueueRun}) {
|
||||
RemoveInternalTimer ("queue:$name");
|
||||
delete $ioHash->{nextQueueRun};
|
||||
Log3 $name, 5, "$name: StopQueueTimer called from " . FhemCaller() .
|
||||
' removes internal timer for queue processing' if (!$silent);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
#########################################################################
|
||||
# check if a regex is valid
|
||||
@ -161,39 +234,6 @@ sub ValidRegex {
|
||||
}
|
||||
|
||||
|
||||
##################################################################################
|
||||
# evaluate perl expression and make variables available for the expression
|
||||
# call like $new = EvalExpr($hash, $exp, $rawVal, {'%setValArr' => \@setValArr});
|
||||
#
|
||||
# Problem: can not pass $val and @val at the same time because evalSpecials
|
||||
# expects %val for both.
|
||||
# also $hash can not be passed but will become %hash. Same reason.
|
||||
#
|
||||
sub EvalExprWithFhemFunctions {
|
||||
my $hash = shift; # the current device hash
|
||||
my $exp = shift; # the expression to be used
|
||||
my $text = shift; # the original value to be avaliable as val / old / rawVal
|
||||
my $val_ref = shift; # the values to be passed via EvalSpecials into eval
|
||||
my $name = $hash->{NAME};
|
||||
my %vHash;
|
||||
|
||||
$val_ref = \%vHash if (!$val_ref); # if no value hash is passed create one
|
||||
$val_ref->{'%hash'} = $hash;
|
||||
$val_ref->{'%name'} = $name;
|
||||
$val_ref->{'%val'} = $text if (!exists $val_ref->{'%val'});
|
||||
$val_ref->{'%old'} = $text if (!exists $val_ref->{'%old'});
|
||||
$val_ref->{'%rawVal'} = $text if (!exists $val_ref->{'%rawVal'});
|
||||
$val_ref->{'%inCheckEval'} = 1 if (!exists $val_ref->{'%inCheckEval'});
|
||||
|
||||
if ($exp) {
|
||||
$exp = EvalSpecials($exp, %{$val_ref});
|
||||
$text = AnalyzePerlCommand(undef, $exp);
|
||||
#Log3 $name, 5, "$name: eval $exp resulted in $text";
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
|
||||
###################################################################
|
||||
# new combined function for evaluating perl expressions
|
||||
# pass values via hash reference similar to fhem EvalSpecials
|
||||
@ -213,56 +253,45 @@ sub EvalExprWithFhemFunctions {
|
||||
#
|
||||
sub EvalExpr {
|
||||
my $hash = shift; # the current device hash
|
||||
my $exp = shift; # the expression to be used
|
||||
my $vRef = shift; # optional setValArr as reference for use in expressions
|
||||
|
||||
my $oRef = shift; # optional hash ref for passing options and variables for use in expressions
|
||||
my $name = $hash->{NAME};
|
||||
my $val = 0;
|
||||
my @val = ($val);
|
||||
my %vHash;
|
||||
|
||||
my $r = ref $vRef;
|
||||
if (ref $vRef eq '') {
|
||||
$val = $vRef;
|
||||
$vRef = shift;
|
||||
#Log3 $name, 5, "$name: old syntax used, ref is $r, val is $val";
|
||||
} else {
|
||||
$val = $vRef->{'val'} // ''; # need input value already now as potential return value
|
||||
#Log3 $name, 5, "$name: new syntax used, ref is $r, val is $val";
|
||||
}
|
||||
$vRef = \%vHash if (ref ($vRef) ne 'HASH'); # create hash if not passed (rare case)
|
||||
my $action = $vRef->{'action'} // 'perl expression eval'; # context for logging
|
||||
my $checkOnly = $vRef->{'checkOnly'} // 0; # only syntax check
|
||||
return $val if (!$exp);
|
||||
|
||||
my $old = $val; # predefined variables, can be overwritten in %vHash
|
||||
my $val = $oRef->{'val'} // ''; # need input value already now as potential return value
|
||||
my $checkOnly = $oRef->{'checkOnly'} // 0; # only syntax check
|
||||
my $NlIfNoExp = $oRef->{'nullIfNoExp'} // 0; # return 0 if expression is missing
|
||||
my $exp = $oRef->{'expr'} // ''; # the expression to be used
|
||||
my $action = $oRef->{'action'} // 'perl expression eval'; # context for logging
|
||||
my @val = ($val); # predefined variables, can be overwritten in %vHash
|
||||
my $old = $val;
|
||||
my $rawVal = $val;
|
||||
my $text = $val;
|
||||
return 0 if ($NlIfNoExp && !$exp);
|
||||
return $val if (!$exp);
|
||||
|
||||
my $inCheckEval = ($checkOnly ? 0 : 1);
|
||||
|
||||
my $assign = '';
|
||||
foreach my $key (keys %{$vRef}) {
|
||||
my $type = ref $vRef->{$key};
|
||||
foreach my $key (keys %{$oRef}) {
|
||||
my $type = ref $oRef->{$key};
|
||||
my $vName = substr($key,1);
|
||||
my $vType = substr($key,0,1);
|
||||
|
||||
if ($type eq 'SCALAR') {
|
||||
$assign .= "my \$$vName = \${\$vRef->{'$key'}};"; # assign ref to scalar as scalar
|
||||
$assign .= "my \$$vName = \${\$oRef->{'$key'}};"; # assign ref to scalar as scalar
|
||||
}
|
||||
elsif ($type eq 'ARRAY' && $vType eq '$') {
|
||||
$assign .= "my \$$vName = \$vRef->{'$key'};"; # assign array ref as array ref
|
||||
$assign .= "my \$$vName = \$oRef->{'$key'};"; # assign array ref as array ref
|
||||
}
|
||||
elsif ($type eq 'ARRAY') {
|
||||
$assign .= "my \@$vName = \@{\$vRef->{'$key'}};"; # assign array ref as array
|
||||
$assign .= "my \@$vName = \@{\$oRef->{'$key'}};"; # assign array ref as array
|
||||
}
|
||||
elsif ($type eq 'HASH' && $vType eq '$') {
|
||||
$assign .= "my \$$vName = \$vRef->{'$key'};"; # assign hash ref as hash ref
|
||||
$assign .= "my \$$vName = \$oRef->{'$key'};"; # assign hash ref as hash ref
|
||||
}
|
||||
elsif ($type eq 'HASH') {
|
||||
$assign .= "my \%$vName = \%{\$vRef->{'$key'}};"; # assign hash ref as hash
|
||||
$assign .= "my \%$vName = \%{\$oRef->{'$key'}};"; # assign hash ref as hash
|
||||
}
|
||||
elsif ($type eq '' && $vType eq '$') {
|
||||
$assign .= "my \$$vName = \$vRef->{'$key'};"; # assign scalar as scalar
|
||||
$assign .= "my \$$vName = \$oRef->{'$key'};"; # assign scalar as scalar
|
||||
}
|
||||
}
|
||||
$exp = $assign . ($checkOnly ? 'return undef;' : '') . $exp;
|
||||
@ -280,7 +309,6 @@ sub EvalExpr {
|
||||
}
|
||||
|
||||
|
||||
|
||||
###########################################################
|
||||
# return the name of the caling function for debug output
|
||||
sub FhemCaller {
|
||||
@ -295,19 +323,26 @@ sub FhemCaller {
|
||||
#########################################
|
||||
# Try to convert a value with a map
|
||||
# called from Set and FormatReading
|
||||
# todo: also pass map as named parameter
|
||||
sub MapConvert {
|
||||
my $hash = shift;
|
||||
my $map = shift;
|
||||
my $val = shift;
|
||||
my $reverse = shift;
|
||||
my $oRef = shift; # hash ref for passing options and variables for use in expressions
|
||||
|
||||
my $map = $oRef->{'map'} // ''; # map to use
|
||||
my $reverse = $oRef->{'reverse'} // 0; # use reverse map
|
||||
my $action = $oRef->{'action'} // 'apply map'; # context for logging
|
||||
my $UndefIfNoMatch = $oRef->{'undefIfNoMatch'} // 0; # return undef if map is not matching,
|
||||
my $inVal = $oRef->{'val'} // ''; # input value
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
return $inVal if (!$map); # don't change anyting if map is empty
|
||||
|
||||
$map =~ s/\s+/ /g; # substitute all \t \n etc. by one space only
|
||||
if ($reverse) {
|
||||
$map =~ s/([^, ][^,\$]*):([^,][^,\$]*),? */$2:$1, /g; # reverse map
|
||||
}
|
||||
# spaces in words allowed, separator is ',' or ':'
|
||||
$val = decode ('UTF-8', $val); # convert nbsp from fhemweb
|
||||
my $val = decode ('UTF-8', $inVal); # convert nbsp from fhemweb
|
||||
$val =~ s/\s| / /g; # back to normal spaces in case it came from FhemWeb with coded Blank
|
||||
|
||||
my %mapHash = split (/, *|:/, $map); # reverse hash aus dem reverse string
|
||||
@ -321,7 +356,8 @@ sub MapConvert {
|
||||
else {
|
||||
Log3 $name, 3, "$name: MapConvert called from " . FhemCaller() . " did not find $val in" .
|
||||
($reverse ? " reversed" : "") . " map $map";
|
||||
return;
|
||||
return if ($UndefIfNoMatch);
|
||||
return $inVal;
|
||||
}
|
||||
}
|
||||
|
||||
@ -336,6 +372,102 @@ sub MapToHint {
|
||||
}
|
||||
|
||||
|
||||
#####################################################################
|
||||
# check that a value is in a defined range
|
||||
sub CheckRange {
|
||||
my $hash = shift;
|
||||
my $oRef = shift; # optional hash ref for passing options and variables for use in expressions
|
||||
my $val = $oRef->{'val'} // ''; # input value
|
||||
my $min = $oRef->{'min'} // ''; # min value
|
||||
my $max = $oRef->{'max'} // ''; # max value
|
||||
my $name = $hash->{NAME};
|
||||
$val =~ s/\s+//g; # remove spaces just to be sure
|
||||
|
||||
# if either min or max are specified, val has to be numeric
|
||||
if (!looks_like_number $val && (looks_like_number $min || looks_like_number $max)) {
|
||||
Log3 $name, 5, "$name: checkRange for " . FhemCaller() . " rejects $val because it is not numeric";
|
||||
return;
|
||||
}
|
||||
if (looks_like_number $min) {
|
||||
Log3 $name, 5, "$name: checkRange for " . FhemCaller() . " checks $val against min $min";
|
||||
return if ($val < $min);
|
||||
}
|
||||
if (looks_like_number $max) {
|
||||
Log3 $name, 5, "$name: checkRange for " . FhemCaller() . " checks $val against max $max";
|
||||
return if ($val > $max);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
#####################################################################
|
||||
# check that a value is in a defined range
|
||||
sub FormatVal {
|
||||
my $hash = shift;
|
||||
my $oRef = shift; # optional hash ref for passing options and variables for use in expressions
|
||||
my $val = $oRef->{'val'} // ''; # input value
|
||||
my $format = $oRef->{'format'} // ''; # format string
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
return $val if (!$format);
|
||||
my $newVal = sprintf($format, $val);
|
||||
Log3 $name, 5, "$name: FormatVal for " . FhemCaller() . " formats $val with $format, result is $newVal";
|
||||
return $newVal;
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
# called from send and parse
|
||||
# reverse order of word registers
|
||||
sub ReverseWordOrder {
|
||||
my $hash = shift; # hash only needed for logging
|
||||
my $buffer = shift;
|
||||
my $len = shift;
|
||||
my $name = $hash->{NAME}; # name of device for logging
|
||||
|
||||
use bytes;
|
||||
$len = length($buffer) if (!defined $len); # optional parameter
|
||||
return $buffer if ($len < 2 || length ($buffer) < 3); # nothing to be done if only one register
|
||||
Log3 $name, 5, "$name: ReverseWordOrder is reversing order of up to $len registers";
|
||||
my $work = substr($buffer, 0, $len * 2); # the first 2*len bytes of buffer
|
||||
my $rest = substr($buffer, $len * 2); # everything after len
|
||||
|
||||
my $new = '';
|
||||
while ($work) {
|
||||
$new = substr($work, 0, 2) . $new; # prepend first two bytes of work to new
|
||||
$work = substr($work, 2); # remove first word from work
|
||||
}
|
||||
my $newBuffer = $new . $rest;
|
||||
Log3 $name, 5, "$name: ReverseWordOrder for " . FhemCaller() . " is transforming "
|
||||
. unpack ('H*', $buffer) . " to " . unpack ('H*', $newBuffer);
|
||||
return $newBuffer;
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
# called from send and parse
|
||||
# reverse byte order in word registers
|
||||
sub SwapByteOrder {
|
||||
my $hash = shift; # hash only needed for logging
|
||||
my $buffer = shift;
|
||||
my $len = shift;
|
||||
my $name = $hash->{NAME}; # name of device for logging
|
||||
|
||||
use bytes;
|
||||
$len = length($buffer) if (!defined $len); # optional parameter
|
||||
Log3 $name, 5, "$name: SwapByteOrder is reversing byte order of up to $len registers";
|
||||
my $rest = substr($buffer, $len * 2); # everything after len
|
||||
my $nval = '';
|
||||
for (my $i = 0; $i < $len; $i++) {
|
||||
$nval = $nval . substr($buffer,$i*2 + 1,1) . substr($buffer,$i*2,1);
|
||||
};
|
||||
my $newBuffer = $nval . $rest;
|
||||
Log3 $name, 5, "$name: SwapByteOrder for " . FhemCaller() . " is transforming "
|
||||
. unpack ('H*', $buffer) . " to " . unpack ('H*', $newBuffer);
|
||||
return $newBuffer;
|
||||
}
|
||||
|
||||
|
||||
#########################################################################
|
||||
# set userAttr-Attribute for Regex-Attrs
|
||||
# pass device hash and new attr based on a regex attr
|
||||
@ -547,22 +679,22 @@ sub BodyDecode {
|
||||
if ($bodyDecode eq 'auto' or $bodyDecode eq 'Auto') {
|
||||
if ($header =~/Content-Type:.*charset=([\w\-\.]+)/i) {
|
||||
$bodyDecode = $1;
|
||||
Log3 $name, 4, "$name: Read found charset header and set decoding to $bodyDecode (bodyDecode was set to auto)";
|
||||
Log3 $name, 4, "$name: BodyDecode found charset header and set decoding to $bodyDecode (bodyDecode was set to auto)";
|
||||
}
|
||||
else {
|
||||
$bodyDecode = "";
|
||||
Log3 $name, 4, "$name: Read found no charset header (bodyDecode was set to auto)";
|
||||
Log3 $name, 4, "$name: BodyDecode found no charset header (bodyDecode was set to auto)";
|
||||
}
|
||||
}
|
||||
if ($bodyDecode) {
|
||||
if ($bodyDecode =~ m{\A [Nn]one \z}xms) {
|
||||
Log3 $name, 4, "$name: Read is not decoding the response body (set to none)";
|
||||
Log3 $name, 4, "$name: BodyDecode is not decoding the response body (set to none)";
|
||||
}
|
||||
else {
|
||||
$body = decode($bodyDecode, $body);
|
||||
Log3 $name, 4, "$name: Read is decoding the response body as $bodyDecode ";
|
||||
Log3 $name, 4, "$name: BodyDecode is decoding the response body as $bodyDecode ";
|
||||
}
|
||||
#Log3 $name, 5, "$name: Read callback " . ($body ? "new body as utf-8 is: \n" . encode ('utf-8', $body) : "body empty");
|
||||
#Log3 $name, 5, "$name: BodyDecode callback " . ($body ? "new body as utf-8 is: \n" . encode ('utf-8', $body) : "body empty");
|
||||
}
|
||||
return $body;
|
||||
}
|
||||
@ -583,8 +715,7 @@ sub IsOpen {
|
||||
|
||||
####################################################
|
||||
# format time as string with msecs as fhem.pl does
|
||||
sub FmtTime($)
|
||||
{
|
||||
sub FmtTimeMs {
|
||||
my $time = shift // 0;
|
||||
my $seconds;
|
||||
my $mseconds;
|
||||
@ -607,6 +738,15 @@ sub FmtTime($)
|
||||
}
|
||||
|
||||
|
||||
#########################################################
|
||||
sub ReadableArray {
|
||||
my $val = shift;
|
||||
my $vString = '';
|
||||
foreach my $v (@{$val}) {
|
||||
$vString .= ($vString eq '' ? '' : ', ') . ($v =~ /^[[:print:]]+$/ ? $v : 'hex ' . unpack ('H*', $v));
|
||||
}
|
||||
return $vString
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
|
Loading…
x
Reference in New Issue
Block a user