}elsif( $dadtype eq "Browser" ){
#Log 1,"===========> [speakDAD] for Browser name=$name daddev=$daddev dadtype=$dadtype text=$text\n";
#-- start TTS server, 2 seconds delay
my $cmd = "/opt/fhem/tts_run.sh ".$ttsDir.$ttsTarget.".mp3 ".$ttsDur." 2 &";
Log 1,"===========> cmd= $cmd";
system($cmd);
funktioniert jetzt astrein, getestet unter Firefox und Chrome. ZitatWelche Consumer, werden in welcher Reihenfolge abgeschaltet?Ja, in gewisser Weise. Die Consumer werden bei jeden Zyklus sequentiell aufsteigend entsprechend ihrer Nummer 01, 02, 03 ... behandelt.
Gibt es da ein Timing? Oder eine Priorisierung?
ZitatEtwas OT, aber so ist mir das in den Kopf gekommen:Das ist quasi eine Chain die aufgebaut werden soll. Kann man machen. Dazu kann man z.B. das auto-Reading benutzen.
Meine Anforderung wäre, dass die Verbraucher in einer gewissen Reihenfolge abgeschaltet werden. (unabhängig von der Leistung)
Angedachte Lösung: Meine Überlegung wäre jetzt, dass ich interruptable vom jeweils vorherigen Verbraucher abhängig mache.
ZitatIst es bei surpmeth=median auch möglich eine Anzahl anzugeben? Wenn ja, wie?Nein. Macht auch keinen Sinn. Median verwendet den mittleren Wert eines nach Größe geordneten Arrays aller gespeicherten Werte. Bei z.B. 7 vorhandenen Werten ist es der Wert an der 4. Stelle (Mitte bei ungeraden Zahlen). Bei gerader Anzahl der Werte ist es der Durchschnitt der beiden mittleren Werte, also z.B. Durchschnitt Wert 5 und 6 bei 10 vorhandenen Werten.
sub myLineChart {
my (
$name,
$value_01, $value_02, $value_03,
$label_01, $label_02, $label_03,
$unit_01, $unit_02, $unit_03,
$numValues_mobile,
$window_mobile,
$numValues_desktop,
$window_desktop
) = @_;
$label_01 //= "Currently";
$label_02 //= "Today";
$label_03 //= "Total out";
$unit_01 //= "W";
$unit_02 //= "Wh";
$unit_03 //= "kWh";
$numValues_mobile //= 40;
$window_mobile //= 3;
$numValues_desktop //= 120;
$window_desktop //= 3;
my @all_values = grep { defined && $_ ne '' } split /,/, ReadingsVal($name,'chartArray','');
# ---------------- MOBILE --------------------
my @v = @all_values[-$numValues_mobile..-1];
my @v_sma;
for my $i (0 .. $#v) {
my $start = $i - $window_mobile + 1;
$start = 0 if $start < 0;
my $count = $i - $start + 1;
my $sum = 0;
$sum += $v[$_] for $start .. $i;
push @v_sma, $sum / $count;
}
@v = @v_sma;
my ($vmin, $vmax) = (sort { $a <=> $b } @v)[0, -1];
$vmin //= 0; $vmax //= 0;
my $min = $vmin < 0 ? $vmin : 0;
my $max = $vmax > 0 ? $vmax : 0;
my $range = $max - $min || 1;
my $width = 155;
my $svg_total_height = 70;
my $padding_top = 2;
my $padding_bottom = 2;
my $height = $svg_total_height - $padding_top - $padding_bottom;
my $w = $width / (@v - 1 || 1);
my $zeroY = $padding_top + ($height - ((0 - $min) / $range * $height));
my @points = map {
my $x = $_ * $w;
my $y = $padding_top + ($height - (($v[$_] - $min) / $range * $height));
[$x, $y];
} 0 .. $#v;
my $path_d = "M" . join(" L", map { sprintf("%.2f,%.2f", @$_) } @points);
my $area_path_d = $path_d . sprintf(" L%.2f,%.2f L0,%.2f Z", $width, $zeroY, $zeroY);
my $gradient_def = '';
if ($vmin < 0 && $vmax > 0) {
$gradient_def = qq{
<linearGradient id="${name}_gradientFill" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#3b82f6" stop-opacity="0.2" />
<stop offset="50%" stop-color="#3b82f6" stop-opacity="0" />
<stop offset="100%" stop-color="#3b82f6" stop-opacity="0.2" />
</linearGradient>
};
} elsif ($vmin >= 0) {
$gradient_def = qq{
<linearGradient id="${name}_gradientFill" x1="0" y1="1" x2="0" y2="0">
<stop offset="0%" stop-color="#3b82f6" stop-opacity="0" />
<stop offset="100%" stop-color="#3b82f6" stop-opacity="0.6" />
</linearGradient>
};
} else {
$gradient_def = qq{
<linearGradient id="${name}_gradientFillDesktop" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#3b82f6" stop-opacity="0.6" />
<stop offset="100%" stop-color="#3b82f6" stop-opacity="0" />
</linearGradient>
};
}
(my $gradient_def_d = $gradient_def) =~ s/_gradientFill/_gradientFillDesktop/g;
# ---------------- DESKTOP --------------------
my @v_d = @all_values[-$numValues_desktop..-1];
my @v_sma_d;
for my $i (0 .. $#v_d) {
my $start = $i - $window_desktop + 1;
$start = 0 if $start < 0;
my $count = $i - $start + 1;
my $sum = 0;
$sum += $v_d[$_] for $start .. $i;
push @v_sma_d, $sum / $count;
}
@v_d = @v_sma_d;
my ($vmin_d, $vmax_d) = (sort { $a <=> $b } @v_d)[0, -1];
$vmin_d //= 0; $vmax_d //= 0;
my $min_d = $vmin_d < 0 ? $vmin_d : 0;
my $max_d = $vmax_d > 0 ? $vmax_d : 0;
my $range_d = $max_d - $min_d || 1;
my $width_d = 360;
my $height_d = $svg_total_height - $padding_top - $padding_bottom;
my $w_d = $width_d / (@v_d - 1 || 1);
my $zeroY_d = $padding_top + ($height_d - ((0 - $min_d) / $range_d * $height_d));
my @points_d = map {
my $x = $_ * $w_d;
my $y = $padding_top + ($height_d - (($v_d[$_] - $min_d) / $range_d * $height_d));
[$x, $y];
} 0 .. $#v_d;
my $path_d_d = "M" . join(" L", map { sprintf("%.2f,%.2f", @$_) } @points_d);
my $area_path_d_d = $path_d_d . sprintf(" L%.2f,%.2f L0,%.2f Z", $width_d, $zeroY_d, $zeroY_d);
return qq{
<style>
div#$name-mobile { display:block; }
div#$name-desktop { display:none; }
div#$name { pointer-events:none; }
@{[ chr(64) ]}media screen and (min-width: 900px) {
div#$name-mobile { display:none !important; }
div#$name-desktop { display:block !important; }
}
</style>
<!-- Mobile -->
<div id="$name-mobile" style="width:365px; padding:10px; border:1px solid #ddd; border-radius:6px; font-family:sans-serif; box-shadow:0 2px 4px rgba(0,0,0,0.1); box-sizing:border-box; height:90px; position:relative;">
<div style="position:absolute; top:10px; left:10px; width:90px; font-size:14px; text-align:left; line-height:26px;">
<div>${label_01}</div><div>${label_02}</div><div>${label_03}</div>
</div>
<div style="position:absolute; top:10px; left:80px; width:120px; font-size:14px; font-weight:bold; text-align:center; line-height:26px; display:flex; flex-direction:column;">
<div>${value_01} ${unit_01}</div><div>${value_02} ${unit_02}</div><div>${value_03} ${unit_03}</div>
</div>
<div style="position:absolute; top:10px; right:10px; width:160px; height:70px; overflow:hidden;">
<svg style="width:160px!important; height:70px!important; display:block;" viewBox="0 0 160 70" preserveAspectRatio="xMidYMid meet">
<defs>$gradient_def</defs>
<path d="$area_path_d" fill="url(#${name}_gradientFill)" />
<line x1="0" y1="@{[sprintf '%.2f', $zeroY]}" x2="$width" y2="@{[sprintf '%.2f', $zeroY]}" stroke="white" stroke-dasharray="2,2" stroke-width="1" />
<path d="$path_d" fill="none" stroke="#3b82f6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</div>
</div>
<!-- Desktop -->
<div id="$name-desktop" style="width:700px; padding:10px; border:1px solid #ddd; border-radius:6px; font-family:sans-serif; box-shadow:0 2px 4px rgba(0,0,0,0.1); box-sizing:border-box; height:90px; position:relative;">
<div style="position:absolute; top:10px; left:10px; width:100px; font-size:14px; text-align:left; line-height:26px;">
<div>${label_01}</div><div>${label_02}</div><div>${label_03}</div>
</div>
<div style="position:absolute; top:10px; left:90px; width:130px; font-size:14px; font-weight:bold; text-align:center; line-height:26px; display:flex; flex-direction:column;">
<div>${value_01} ${unit_01}</div><div>${value_02} ${unit_02}</div><div>${value_03} ${unit_03}</div>
</div>
<div style="position:absolute; top:10px; right:10px; width:${width_d}px; height:70px; overflow:hidden;">
<svg style="width:${width_d}px!important; height:70px!important; display:block;" viewBox="0 0 ${width_d} 70" preserveAspectRatio="xMidYMid meet">
<defs>$gradient_def_d</defs>
<path d="$area_path_d_d" fill="url(#${name}_gradientFillDesktop)" />
<line x1="0" y1="@{[sprintf '%.2f', $zeroY_d]}" x2="$width_d" y2="@{[sprintf '%.2f', $zeroY_d]}" stroke="white" stroke-dasharray="2,2" stroke-width="1" />
<path d="$path_d_d" fill="none" stroke="#3b82f6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</div>
</div>
};
}
{
my $val1 = ReadingsVal($name, 'total.Power.v', '--');
my $val2 = ReadingsVal($name, 'total.YieldDay.v', '--');
my $val3 = ReadingsVal($name, 'total.YieldTotal.v', '--');
myLineChart(
$name,
$val1, $val2, $val3,
'Currently', 'Today', 'Total out',
'W', 'Wh', 'kWh',
40, 3, # mobile: 40 Werte, Average 3
120, 3 # desktop: 120 Werte, Average 3
);
}
package FHEM::EZHI;
my $cvsid = '$Id: 74_EZHI.pm 29928 2025-07-10 08:22:04Z $';
use strict;
use warnings;
use POSIX;
use GPUtils qw(:all);
use Time::HiRes qw(gettimeofday);
use Time::Local;
my $EMPTY = q{};
my $missingModul = $EMPTY;
## no critic (ProhibitConditionalUseStatements)
eval { use Readonly; 1 } or $missingModul .= 'Readonly ';
Readonly my $APIPORT => '80';
Readonly my $SPACE => q{ };
Readonly $EMPTY => q{};
Readonly my $MAIN_INTERVAL => 30;
Readonly my $LONG_INTERVAL => 600;
Readonly my $YEARSEC => 31536000;
eval { use JSON; 1 } or $missingModul .= 'JSON ';
## use critic
require HttpUtils;
BEGIN {
GP_Import(
qw(
AttrVal
FW_ME
SVG_FwFn
getKeyValue
InternalTimer
InternalVal
IsDisabled
Log3
Log
attr
defs
init_done
readingFnAttributes
readingsBeginUpdate
readingsBulkUpdate
readingsBulkUpdateIfChanged
readingsDelete
readingsEndUpdate
ReadingsNum
readingsSingleUpdate
ReadingsVal
RemoveInternalTimer
)
);
}
GP_Export(
qw(
Initialize
)
);
##############################################################
sub Initialize() {
my ($hash) = @_;
$hash->{DefFn} = \&Define;
$hash->{UndefFn} = \&Undefine;
$hash->{DeleteFn} = \&Delete;
$hash->{SetFn} = \&Set;
$hash->{FW_detailFn} = \&FW_detailFn;
$hash->{AttrFn} = \&Attr;
$hash->{AttrList} =
'disable:1,0 ' .
'disabledForIntervals ' .
'SVG_PlotsToShow:textField-long ' .
$::readingFnAttributes;
return;
}
#########################
sub Define{
my ( $hash, $def ) = @_;
my @val = split( "[ \t]+", $def );
my $name = $val[0];
my $type = $val[1];
my $iam = "$type $name Define:";
my $ip = '';
my $tod = gettimeofday();
return "$iam Cannot define $type device. Perl modul $missingModul is missing." if ( $missingModul );
return "$iam too few parameters: define <NAME> $type <email>" if( @val < 3 );
$ip = $val[2];
%$hash = (%$hash,
helper => {
cmds => {
getOutputData => {
lasttime => 0,
interval => $MAIN_INTERVAL * 10,
readings => {
batS => 'battery_Status',
batSoc => 'battery_state_of_charge',
pvP => 'pv_input_power',
batP => 'battery_power',
ogP => 'on-grid_power',
ofgP => 'off-grid_power'
},
},
getDeviceInfo => {
lasttime => 0,
interval => $YEARSEC,
readings => {
deviceId => 'inverter_Id',
type => 'inverter_type',
ssid => 'inverter_SSID',
ip => 'inverter_IpAddress'
},
},
getPower => {
lasttime => 0,
interval => $YEARSEC,
readings => {
Power => 'on-grid_power-setting'
},
},
setPower => {
lasttime => 0,
interval => $YEARSEC,
quantity => 'p',
readings => {
Power => 'on-grid_power-setting'
},
},
},
timeout_ => 2,
retry_count => 0,
retry_max => 6,
call_delay => 3,
callStack => [
'getOutputData',
'getDeviceInfo',
'getPower'
],
url => "http://${ip}:${APIPORT}/",
inverter => {
start => $tod,
stop => $tod,
duration => 0
}
}
);
# $attr{$name}{disabledForIntervals} = '{sunset_abs("HORIZON=0")}-24 00-{sunrise_abs("HORIZON=0")}' if( !defined( $attr{$name}{disabledForIntervals} ) );
$attr{$name}{stateFormat} = 'Power' if( !defined( $attr{$name}{stateFormat} ) );
$attr{$name}{room} = 'APsystemsEZHI' if( !defined( $attr{$name}{room} ) );
$attr{$name}{icon} = 'inverter' if( !defined( $attr{$name}{icon} ) );
( $hash->{VERSION} ) = $cvsid =~ /\.pm (.*)Z/;
readingsSingleUpdate( $hash, '.associatedWith', $attr{$name}{SVG_PlotsToShow}, 0 ) if ( defined $attr{$name}{SVG_PlotsToShow} );
RemoveInternalTimer($hash);
InternalTimer( gettimeofday() + 2, \&callAPI, $hash, 1);
readingsSingleUpdate( $hash, 'state', 'defined', 1 );
return;
}
#########################
sub FW_detailFn { ## no critic (ProhibitExcessComplexity [complexity core maintenance])
my ($FW_wname, $name, $room, $pageHash) = @_; # pageHash is set for summaryFn.
my $hash = $defs{$name};
my $type = $hash->{TYPE};
my $iam = "$type $name FW_detailFn:";
return if ( !AttrVal( $name, 'SVG_PlotsToShow', 0 ) || AttrVal( $name, 'disable', 0 ) || !$init_done || !$FW_ME );
my @plots = split( " ", AttrVal( $name, 'SVG_PlotsToShow', 0 ) );
my $ret = "<div id='${type}_${name}_plots' ><table>";
for my $plot ( @plots ) {
$ret .= '<tr><td>' . SVG_FwFn( $FW_wname, $plot, "", {} ) . '</td></tr>';
}
$ret .= '</table></div>';
return $ret;
}
#########################
sub callAPI {
my ( $hash, $update ) = @_;
my $name = $hash->{NAME};
my $type = $hash->{TYPE};
my $iam = "$type $name callAPI:";
my @states = ('undefined', 'disabled', 'temporarily disabled', 'inactive' );
my $tod = gettimeofday();
if( IsDisabled( $name ) ) {
readingsSingleUpdate( $hash, 'state', $states[ IsDisabled( $name ) ], 1 ) if ( ReadingsVal( $name, 'state', '' ) !~ /disabled|inactive/ );
RemoveInternalTimer( $hash );
InternalTimer( $tod + $MAIN_INTERVAL, \&callAPI, $hash, 0 );
return;
}
if ( scalar @{ $hash->{helper}{callStack} } == 0 ) {
my @cmds = qw( getDeviceInfo getPower getOutputData );
for ( @cmds ) {
push @{ $hash->{helper}{callStack} }, $_ if ( ( $hash->{helper}{cmds}{$_}{lasttime} + $hash->{helper}{cmds}{$_}{interval} ) < $tod );
}
if ( scalar @{ $hash->{helper}{callStack} } == 0 ) {
RemoveInternalTimer( $hash, \&callAPI );
InternalTimer( gettimeofday() + $MAIN_INTERVAL, \&callAPI, $hash, 0 );
return;
}
}
if ( !$update && $::init_done ) {
readingsSingleUpdate( $hash, 'state', 'initialized', 1 ) if ( $hash->{READINGS}{state}{VAL} !~ /initialized|connected/ );
my $url = $hash->{helper}{url};
my $timeout = $hash->{helper}{timeout_api};
my $command = $hash->{helper}{callStack}[0];
Log3 $name, 1, join(" | ", @{ $hash->{helper}{callStack} } ) . "\n$url$command";
::HttpUtils_NonblockingGet( {
url => $url . $command,
timeout => $timeout,
hash => $hash,
method => 'GET',
callback => \&APIresponse,
t_begin => $tod
} );
} else {
RemoveInternalTimer( $hash, \&callAPI );
InternalTimer( gettimeofday() + $MAIN_INTERVAL, \&callAPI, $hash, 0 );
}
return;
}
#########################
sub APIresponse {
my ($param, $err, $data) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
my $type = $hash->{TYPE};
my $statuscode = $param->{code} // '';
my $call_delay = $hash->{helper}{call_delay};
my $iam = "$type $name APIresponse:";
my $tod = gettimeofday();
my $duration = sprintf( "%.2f", ( $tod - $param->{t_begin} ) );
Log3 $name, 4, "$iam response time ". $duration . ' s';
Log3 $name, 4, "$iam \$statuscode >$statuscode< \$err >$err< \$param->url $param->{url}\n\$data >$data<\n";
if ( !$err && $statuscode == 200 && $data ) {
my $result = eval { decode_json( $data ) };
if ($@) {
Log3 $name, 2, "$iam JSON error [ $@ ]";
readingsSingleUpdate( $hash, 'state', 'error JSON', 1 );
} else {
if ( $hash->{READINGS}{state}{VAL} ne 'connected' ) {
$hash->{helper}{inverter}{start} = $tod;
readingsSingleUpdate( $hash, 'state', "connected", 1 );
}
$hash->{helper}{inverter}{duration} = $tod - $hash->{helper}{inverter}{start};
my $cmd = $hash->{helper}{callStack}[0];
$cmd = $1 if ( $cmd =~ /(.*)\?/ );
$hash->{helper}->{response}{$cmd} = $result;
if ( $result->{message} eq 'SUCCESS' ) {
$hash->{helper}{cmds}{$cmd}{lasttime} = $tod;
readingsBeginUpdate($hash);
for my $ky ( keys %{ $result->{data} } ) {
if ( "$cmd$ky" =~ /getOutputDatap(1|2)|Power/ ) {
readingsBulkUpdateIfChanged( $hash, $hash->{helper}{cmds}{$cmd}{readings}{$ky}, $result->{data}{$ky} );
} elsif ( "$cmd$ky" =~ /getOutputDatae(1|2)/ ) {
readingsBulkUpdateIfChanged( $hash, $hash->{helper}{cmds}{$cmd}{readings}{$ky}, sprintf( "%.1f", $result->{data}{$ky} ) );
} elsif ( "$cmd$ky" =~ /getOutputDatate(1|2)/ ) {
readingsBulkUpdate( $hash, $hash->{helper}{cmds}{$cmd}{readings}{$ky}, sprintf( "%.1f", $result->{data}{$ky} ), 0 );
} else {
readingsBulkUpdate( $hash, $hash->{helper}{cmds}{$cmd}{readings}{$ky}, $result->{data}{$ky}, 0 );
}
}
# if ( $hash->{helper}{callStack}[0] eq 'getOutputData' ) {
# readingsBulkUpdate( $hash, 'inverter_OnlineTime', int( $hash->{helper}{inverter}{duration} /3600 ) , 0 );
# readingsBulkUpdateIfChanged( $hash, 'Power', $result->{data}{p1} + $result->{data}{p2} );
# readingsBulkUpdateIfChanged( $hash, 'Energy', sprintf( "%.1f", $result->{data}{e1} + $result->{data}{e2} ) );
# readingsBulkUpdateIfChanged( $hash, 'LifeTimeEnergy', sprintf( "%.1f", $result->{data}{te1} + $result->{data}{te2} ) );
# readingsBulkUpdate( $hash, 'PowerDensity', int( ( $result->{data}{p1} + $result->{data}{p2} ) / AttrVal( $name, 'activeArea', 4.10592 ) ), 0 ) if ( AttrVal( $name, 'activeArea', 0 ) );
# }
readingsEndUpdate($hash, 1);
shift @{ $hash->{helper}{callStack} };
if ( scalar @{ $hash->{helper}{callStack} } ) {
RemoveInternalTimer( $hash, \&calAPI );
InternalTimer( $tod + $call_delay, \&callAPI, $hash, 0 );
return;
}
RemoveInternalTimer( $hash, \&calAPI );
InternalTimer( $tod + $MAIN_INTERVAL, \&callAPI, $hash, 0 );
return;
}
$hash->{helper}{retry_count}++;
if ( $hash->{helper}{retry_count} > $hash->{helper}{retry_max} - 1 ) {
shift @{ $hash->{helper}{callStack} };
$hash->{helper}{retry_count} = 0;
if ( scalar @{ $hash->{helper}{callStack} } ) {
RemoveInternalTimer( $hash, \&calAPI );
InternalTimer( $tod + $call_delay, \&callAPI, $hash, 0 );
return;
}
RemoveInternalTimer( $hash, \&calAPI );
InternalTimer( $tod + ( $result->{data}{batS} < 1 ? $LONG_INTERVAL : $MAIN_INTERVAL ), \&callAPI, $hash, 0 );
return;
}
}
} elsif ( !$statuscode && !$data && $err =~ /\(113\)$|timed out/ ) {
if ( $hash->{READINGS}{state}{VAL} ne 'disconnected' ) {
$hash->{helper}{inverter}{stop} = $tod;
$hash->{helper}{inverter}{duration} = $tod - $hash->{helper}{inverter}{start};
readingsSingleUpdate( $hash, 'state', "disconnected", 1 );
}
RemoveInternalTimer( $hash, \&callAPI );
InternalTimer( $tod + $LONG_INTERVAL, \&callAPI, $hash, 0 );
return;
}
readingsSingleUpdate( $hash, 'state', "error", 1 );
Log3 $name, 1, "$iam \$statuscode >$statuscode< \$err >$err< \$param->url $param->{url}\n\$data >$data<\n";
$hash->{helper}{retry_count}++;
if ( $hash->{helper}{retry_count} > $hash->{helper}{retry_max} ) {
CommandAttr( $hash, "$name disable 1" );
$hash->{helper}{retry_count} = 0;
}
RemoveInternalTimer( $hash, \&callAPI );
InternalTimer( $tod + $MAIN_INTERVAL, \&callAPI, $hash, 0 );
my $txt = AttrVal( $name, 'disable', $EMPTY ) ? "$iam: Device is disabled now." : "$iam failed, retry in $MAIN_INTERVAL seconds.";
Log3 $name, 1, $txt;
return;
}
#########################
sub Set {
my ($hash,@val) = @_;
my $type = $hash->{TYPE};
my $name = $hash->{NAME};
my $iam = "$type $name Set:";
return "$iam: needs at least one argument" if ( @val < 2 );
return "Unknown argument, $iam is disabled, choose one of none:noArg" if ( IsDisabled( $name ) );
my ($pname,$setName,$setVal,$setVal2,$setVal3) = @val;
Log3 $name, 4, "$iam called with $setName";
my $minpow = ReadingsNum( $name, 'minPower', -1200 );
my $maxpow = ReadingsNum( $name, 'maxPower', 1200 );
$setVal = 0 if ( defined( $setVal ) && $setVal eq 'on' );
$setVal = 1 if ( defined( $setVal ) && $setVal eq 'off' );
if ( $setName eq 'setOnOff' && ( $setVal == 0 || $setVal == 1 ) || $setName eq 'setPower' && $setVal >= $minpow && $setVal <= $maxpow ) {
my $cmd = $setName . '?' . $hash->{helper}{cmds}{$setName}{quantity} . '=' . $setVal;
unshift @{ $hash->{helper}{callStack} }, $cmd;
# Log3 $name, 1, "$iam called with $cmd | ".join(" | ", @{ $hash->{helper}{callStack} } );
return;
} elsif ( $setName eq 'getUpdate') {
my @cmds = qw( getDeviceInfo getPower getOutputData );
push @{ $hash->{helper}{callStack} }, @cmds;
return;
}
my $ret = ' setPower:selectnumbers,' . $minpow . ',10,' . $maxpow . ',0,lin setOnOff:on,off getUpdate:noArg ';
return "Unknown argument $setName, choose one of".$ret;
}
#########################
sub Undefine {
my ( $hash, $arg ) = @_;
my $name = $hash->{NAME};
my $type = $hash->{TYPE};
RemoveInternalTimer( $hash );
readingsSingleUpdate( $hash, 'state', 'undefined', 1 );
return;
}
##########################
sub Delete {
my ( $hash, $arg ) = @_;
my $name = $hash->{NAME};
my $type = $hash->{TYPE};
my $iam ="$type $name Delete: ";
Log3( $name, 5, "$iam called" );
return;
}
##########################
sub Attr {
my ( $cmd, $name, $attrName, $attrVal ) = @_;
my $hash = $defs{$name};
my $type = $hash->{TYPE};
my $iam = "$type $name Attr:";
##########
if ( $attrName eq 'disable' ) {
if( $cmd eq "set" and $attrVal eq "1" ) {
readingsSingleUpdate( $hash,'state','disabled',1);
Log3 $name, 3, "$iam $cmd $attrName disabled";
} elsif( $cmd eq "del" or $cmd eq 'set' and !$attrVal ) {
RemoveInternalTimer( $hash, \&callAPI);
InternalTimer( gettimeofday() + 1, \&callAPI, $hash, 0 );
Log3 $name, 3, "$iam $cmd $attrName enabled";
}
return;
##########
} elsif ( $attrName eq 'SVG_PlotsToShow' ) {
readingsSingleUpdate( $hash, '.associatedWith', $attrVal, 0 ) if ( $cmd eq 'set' && $attrVal );
delete $hash->{READINGS}{'.associatedWith'} if ( $cmd eq 'del' );
return;
##########
} elsif ( $attrName eq 'intervalMultiplier' ) {
if( $cmd eq 'set' ) {
my @ivals = split( /\R/, $attrVal );
for my $ival (@ivals) {
if ( my ($cm, $val) = $ival =~ /^(getDeviceInfo|getPower|getOutputData)=(\d+)$/ ) {
$hash->{helper}{cmds}{$cm}{interval} = $MAIN_INTERVAL * $val;
} else {
Log3 $name, 1, "$iam $cmd $attrName wrong syntax for $ival, default is used.";
}
}
} elsif( $cmd eq 'del' ) {
my @ivals = qw( getDeviceInfo=1051200 getPower=1051200 getOutputData=10 );
for my $ival ( @ivals ) {
if ( my ($cm, $val) = $ival =~ /^(getDeviceInfo|getPower|getOutputData)=(\d+)$/ ) {
$hash->{helper}{cmds}{$cm}{interval} = $MAIN_INTERVAL * $val;
}
}
}
Log3 $name, 3, "$iam $cmd $attrName to default interval multiplier.";
return;
}
return;
}
1;