";
Log3 $name, 1,"[Shelly_Set] ".$msg;
return $msg;
}
$cmd = $cmd."&timer=$time";
}
if( $cmd eq "toggle"){
$cmd = (ReadingsVal($name,"state","off") eq "on") ? "off" : "on";
}
Shelly_dim($hash,"color/0","?turn=".$cmd);
#Shelly_onoff($hash,"color/0","?turn=".$cmd);
}
if( $cmd eq "hsv" ){
my($hue,$saturation,$value)=split(',',$value);
#-- rescale
if( $hue>1 ){
$hue = $hue/360;
}
my ($red,$green,$blue)=Color::hsv2rgb($hue,$saturation,$value);
$cmd=sprintf("red=%d&green=%d&blue=%d",int($red*255+0.5),int($green*255+0.5),int($blue*255+0.5));
$cmd .= "&gain=100" if ($model eq "shellybulb");
Shelly_dim($hash,"color/0","?".$cmd);
}elsif( $cmd eq "rgb" ){
my $red=hex(substr($value,0,2));
my $green=hex(substr($value,2,2));
my $blue=hex(substr($value,4,2));
$cmd=sprintf("red=%d&green=%d&blue=%d",$red,$green,$blue);
$cmd .= "&gain=100" if ($model eq "shellybulb");
Shelly_dim($hash,"color/0","?".$cmd);
}elsif( $cmd eq "rgbw" ){
my $red=hex(substr($value,0,2));
my $green=hex(substr($value,2,2));
my $blue=hex(substr($value,4,2));
my $white=hex(substr($value,6,2));
$cmd=sprintf("red=%d&green=%d&blue=%d&white=%d",$red,$green,$blue,$white);
$cmd .= "&gain=100" if ($model eq "shellybulb");
Shelly_dim($hash,"color/0","?".$cmd);
}elsif( $cmd eq "white" ){
$cmd=sprintf("white=%d",$value);
Shelly_dim($hash,"color/0","?".$cmd);
}
}
return undef;
}
########################################################################################
#
# Shelly_pwd - retrieve the credentials if set
#
# Parameter hash
#
########################################################################################
sub Shelly_pwd($){
my ($hash) = @_;
my $name = $hash->{NAME};
my $user = AttrVal($name, "shellyuser", '');
return "" if(!$user);
my ($err, $pw) = getKeyValue("SHELLY_PASSWORD_$name");
return $user.":".$pw."@";
}
########################################################################################
#
# Shelly_configure - Configure Shelly device or read general configuration
# acts as callable program Shelly_configure($hash,$channel,$cmd)
# and as callback program Shelly_configure($hash,$channel,$cmd,$err,$data)
#
# Parameter hash, channel = 0,1 cmd = command
#
########################################################################################
sub Shelly_configure {
my ($hash, $cmd, $err, $data) = @_;
my $name = $hash->{NAME};
my $state = $hash->{READINGS}{state}{VAL};
my $net = $hash->{READINGS}{network}{VAL};
return
if( $net !~ /connected/ );
my $model = AttrVal($name,"model","generic");
my $creds = Shelly_pwd($hash);
if ( $hash && !$err && !$data ){
my $url = "http://$creds".$hash->{TCPIP}."/".$cmd;
my $timeout = AttrVal($name,"timeout",4);
Log3 $name, 5,"[Shelly_configure] Issue a non-blocking call to $url";
HttpUtils_NonblockingGet({
url => $url,
timeout => $timeout,
callback => sub($$$){ Shelly_configure($hash,$cmd,$_[1],$_[2]) }
});
return undef;
}elsif ( $hash && $err ){
#Log3 $name, 1,"[Shelly_configure] has error $err";
readingsSingleUpdate($hash,"state","Error",1);
return;
}
Log3 $name, 5,"[Shelly_configure] has obtained data $data";
my $json = JSON->new->utf8;
my $jhash = eval{ $json->decode( $data ) };
if( !$jhash ){
Log3 $name,1,"[Shelly_configure] has invalid JSON data";
readingsSingleUpdate($hash,"state","Error",1);
return;
}
#-- if settings command, we obtain only basic settings
if( $cmd eq "settings/" ){
$hash->{SHELLYID} = $jhash->{'device'}{'hostname'};
return
}
#-- isolate register name
my $reg = substr($cmd,index($cmd,"?")+1);
my $chan= substr($cmd,index($cmd,"?")-1,1);
$reg = substr($reg,0,index($reg,"="))
if(index($reg,"=") > 0);
my $val = $jhash->{$reg};
$val = ""
if(!defined($val));
$chan = " [channel $chan]";
readingsSingleUpdate($hash,"config",$reg."=".$val.$chan,1);
return undef;
}
########################################################################################
#
# Shelly_status - Retrieve data from device
# acts as callable program Shelly_status($hash)
# and as callback program Shelly_status($hash,$err,$data)
#
# Parameter hash
#
########################################################################################
sub Shelly_status {
my ($hash, $err, $data) = @_;
my $name = $hash->{NAME};
my $state = $hash->{READINGS}{state}{VAL};
my $model = AttrVal($name,"model","generic");
my $creds = Shelly_pwd($hash);
if ( $hash && !$err && !$data ){
my $url = "http://$creds".$hash->{TCPIP}."/status";
my $timeout = AttrVal($name,"timeout",4);
Log3 $name, 5,"[Shelly_status] Issue a non-blocking call to $url";
HttpUtils_NonblockingGet({
url => $url,
timeout => $timeout,
callback => sub($$$){ Shelly_status($hash,$_[1],$_[2]) }
});
return undef;
}elsif ( $hash && $err ){
Log3 $name, 1,"[Shelly_status] has error $err";
readingsSingleUpdate($hash,"state","Error",1);
readingsSingleUpdate($hash,"network","not connected",1);
#-- cyclic update nevertheless
RemoveInternalTimer($hash);
InternalTimer(gettimeofday()+$hash->{INTERVAL}, "Shelly_status", $hash, 1)
if( $hash->{INTERVAL} ne "0" );
return $err;
}
Log3 $name, 5,"[Shelly_status] has obtained data $data";
my $json = JSON->new->utf8;
my $jhash = eval{ $json->decode( $data ) };
if( !$jhash ){
Log3 $name,1,"[Shelly_status] invalid JSON data";
readingsSingleUpdate($hash,"state","Error",1);
return;
}
my $mode = AttrVal($name,"mode","");
my $channels = $shelly_models{$model}[0];
my $rollers = $shelly_models{$model}[1];
my $dimmers = $shelly_models{$model}[2];
my $meters = $shelly_models{$model}[3];
my ($subs,$ison,$overpower,$rpower,$rstate,$power,$energy,$rstopreason,$rcurrpos,$position,$rlastdir,$pct,$pctnormal);
readingsBeginUpdate($hash);
readingsBulkUpdateIfChanged($hash,"network","connected to {TCPIP}."\">".$hash->{TCPIP}."",1);
#-- we have a Shelly 1/1pw, Shelly 4, Shelly 2/2.5, ShellyPlug or ShellyEM switch type device
if( ($model =~ /shelly1.*/) || ($model eq "shellyuni") || ($model eq "shellyplug") || ($model eq "shelly4") || ($model eq "shellyem") || (($model =~ /shelly2.*/) && ($mode eq "relay")) ){
for( my $i=0;$i<$channels;$i++){
$subs = (($channels == 1) ? "" : "_".$i);
$ison = $jhash->{'relays'}[$i]{'ison'};
$ison =~ s/0|(false)/off/;
$ison =~ s/1|(true)/on/;
$overpower = $jhash->{'relays'}[$i]{'overpower'};
readingsBulkUpdateIfChanged($hash,"relay".$subs,$ison);
readingsBulkUpdateIfChanged($hash,"overpower".$subs,$overpower)
if(defined($overpower));
if($model =~ /shelly(1|(plug)).*/){
readingsBulkUpdateIfChanged($hash,"state",$ison)
}else{
readingsBulkUpdateIfChanged($hash,"state","OK");
}
}
my $metern = ($model eq "shellyem")?"emeters":"meters";
for( my $i=0;$i<$meters;$i++){
$subs = ($meters == 1) ? "" : "_".$i;
$power = $jhash->{$metern}[$i]{'power'};
$energy = int($jhash->{$metern}[$i]{'total'}/6)/10;
readingsBulkUpdateIfChanged($hash,"power".$subs,$power);
readingsBulkUpdateIfChanged($hash,"energy".$subs,$energy);
if ($model eq "shellyem") {
my $voltage = $jhash->{$metern}[$i]{'voltage'};
readingsBulkUpdateIfChanged($hash,'voltage'.$subs,$voltage);
}
}
#-- we have a Shelly 2 roller type device
}elsif( ($model =~ /shelly2.*/) && ($mode eq "roller") ){
for( my $i=0;$i<$rollers;$i++){
$subs = ($rollers == 1) ? "" : "_".$i;
#-- weird data: stop, close or open
$rstate = $jhash->{'rollers'}[$i]{'state'};
$rstate =~ s/stop/stopped/;
$rstate =~ s/close/moving_down/;
$rstate =~ s/open/moving_up/;
$hash->{MOVING} = $rstate;
$hash->{DURATION} = 0;
#-- weird data: close or open
$rlastdir = $jhash->{'rollers'}[$i]{'last_direction'};
$rlastdir =~ s/close/down/;
$rlastdir =~ s/open/up/;
$rpower = $jhash->{'rollers'}[$i]{'power'};
$rstopreason = $jhash->{'rollers'}[$i]{'stop_reason'};
#-- open 100% or 0% ?
$pctnormal = (AttrVal($name,"pct100","open") eq "open");
#-- possibly no data
$rcurrpos = $jhash->{'rollers'}[$i]{'current_pos'};
#-- we have data from the device, take that one
if( defined($rcurrpos) && ($rcurrpos =~ /\d\d?\d?/) ){
$pct = $pctnormal ? $rcurrpos : 100-$rcurrpos;
$position = ($rcurrpos==100) ? "open" : ($rcurrpos==0 ? "closed" : $pct);
#-- we have no data from the device
}else{
Log3 $name,1,"[Shelly_status] device $name with model=$model returns no blind position, consider chosing a different model=shelly2/2.5"
if( $model !~ /shelly2.*/ );
$pct = ReadingsVal($name,"pct",undef);
#-- we have a reading
if( defined($pct) && $pct =~ /\d\d?\d?/ ){
$rcurrpos = $pctnormal ? $pct : 100-$pct;
$position = ($rcurrpos==100) ? "open" : ($rcurrpos==0 ? "closed" : $pct);
#-- we have no reading
}else{
if( $rstate eq "stopped" && $rstopreason eq "normal"){
if($rlastdir eq "up" ){
$rcurrpos = 100;
$pct = $pctnormal?100:0;
$position = "open"
}else{
$rcurrpos = 0;
$pct = $pctnormal?0:100;
$position = "closed";
}
}
}
}
readingsBulkUpdateIfChanged($hash,"state".$subs,$rstate);
readingsBulkUpdateIfChanged($hash,"pct".$subs,$pct);
readingsBulkUpdateIfChanged($hash,"position".$subs,$position);
readingsBulkUpdateIfChanged($hash,"power".$subs,$rpower);
readingsBulkUpdateIfChanged($hash,"stop_reason".$subs,$rstopreason);
readingsBulkUpdateIfChanged($hash,"last_dir".$subs,$rlastdir);
}
#-- we have a Shelly dimmer or RGBW white device
}elsif( ($model eq "shellydimmer") || ($model eq "shellyrgbw" && $mode eq "white") ){
for( my $i=0;$i<$dimmers;$i++){
$subs = (($dimmers == 1) ? "" : "_".$i);
$ison = $jhash->{'lights'}[$i]{'ison'};
$ison =~ s/0|(false)/off/;
$ison =~ s/1|(true)/on/;
my $bri = $jhash->{'lights'}[$i]{'brightness'};
$overpower = $jhash->{'lights'}[$i]{'overpower'};
$power = $jhash->{'meters'}[$i]{'power'};
$energy = (defined($jhash->{'meters'}[$i]{'total'}))?int($jhash->{'meters'}[$i]{'total'}/6)/10:"undefined";
readingsBulkUpdateIfChanged($hash,"state".$subs,$ison);
readingsBulkUpdateIfChanged($hash,"pct".$subs,$bri);
readingsBulkUpdateIfChanged($hash,"overpower".$subs,$overpower);
readingsBulkUpdateIfChanged($hash,"power".$subs,$power);
readingsBulkUpdateIfChanged($hash,"energy".$subs,$energy);
}
readingsBulkUpdateIfChanged($hash,"state","OK")
if ($dimmers > 1);
#-- we have a Shelly Bulb / Duo in white mode
}elsif( $model eq "shellybulb" && $mode eq "white" ){
for( my $i=0;$i<$dimmers;$i++){
$subs = (($dimmers == 1) ? "" : "_".$i);
$ison = $jhash->{'lights'}[$i]{'ison'};
$ison =~ s/0|(false)/off/;
$ison =~ s/1|(true)/on/;
my $bri = $jhash->{'lights'}[$i]{'brightness'};
my $ct = $jhash->{'lights'}[$i]{'temp'};
$overpower = $jhash->{'lights'}[$i]{'overpower'};
$power = $jhash->{'meters'}[$i]{'power'};
$energy = (defined($jhash->{'meters'}[$i]{'total'}))?int($jhash->{'meters'}[$i]{'total'}/6)/10:"undefined";
readingsBulkUpdateIfChanged($hash,"state".$subs,$ison);
readingsBulkUpdateIfChanged($hash,"pct".$subs,$bri);
readingsBulkUpdateIfChanged($hash,"ct".$subs,$ct);
readingsBulkUpdateIfChanged($hash,"overpower".$subs,$overpower);
readingsBulkUpdateIfChanged($hash,"power".$subs,$power);
readingsBulkUpdateIfChanged($hash,"energy".$subs,$energy);
}
readingsBulkUpdateIfChanged($hash,"state","OK")
if ($dimmers > 1);
#-- we have a Shelly RGBW color device
}elsif( $model =~ /shelly(rgbw|bulb)/ && $mode eq "color" ){
$ison = $jhash->{'lights'}[0]{'ison'};
$ison =~ s/0|(false)/off/;
$ison =~ s/1|(true)/on/;
$overpower = $jhash->{'lights'}[0]{'overpower'};
my $red = $jhash->{'lights'}[0]{'red'};
my $green = $jhash->{'lights'}[0]{'green'};
my $blue = $jhash->{'lights'}[0]{'blue'};
my $white = $jhash->{'lights'}[0]{'white'};
my $gain = $jhash->{'lights'}[0]{'gain'};
if ( defined $gain && $gain != 100 ) {
$red = int ($red*$gain/100.0);
$blue = int ($blue*$gain/100.0);
$green = int ($green*$gain/100.0);
$white = int ($white*$gain/100.0);
}
my $rgb = sprintf("%02X%02X%02X", $red,$green,$blue);
readingsBulkUpdateIfChanged($hash,"rgb",$rgb);
readingsBulkUpdateIfChanged($hash,"L-red",$red);
readingsBulkUpdateIfChanged($hash,"L-green",$green);
readingsBulkUpdateIfChanged($hash,"L-blue",$blue);
readingsBulkUpdateIfChanged($hash,"L-white",$white);
readingsBulkUpdateIfChanged($hash,"overpower",$overpower);
readingsBulkUpdateIfChanged($hash,"state",$ison);
for( my $i=0;$i<$meters;$i++){
$subs = ($meters == 1) ? "" : "_".$i;
$power = $jhash->{'meters'}[$i]{'power'};
$energy = (defined($jhash->{'meters'}[$i]{'total'}))?int($jhash->{'meters'}[$i]{'total'}/6)/10:"undefined";
readingsBulkUpdateIfChanged($hash,"power".$subs,$power);
readingsBulkUpdateIfChanged($hash,"energy".$subs,$energy);
}
}
#-- common to all Shelly models
my $hasupdate = $jhash->{'update'}{'has_update'};
my $firmware = $jhash->{'update'}{'old_version'};
$firmware =~ /.*\/(v[0-9.]+(-rc\d|)).*/;
$firmware = $1;
if( $hasupdate ){
my $newfw = $jhash->{'update'}{'new_version'};
$newfw =~ /.*\/(v[0-9.]+(-rc\d|)).*/;
$newfw = $1;
$firmware .= "(update needed to $newfw)";
}
readingsBulkUpdateIfChanged($hash,"firmware",$firmware);
my $hascloud = $jhash->{'cloud'}{'enabled'};
if( $hascloud ){
my $hasconn = ($jhash->{'cloud'}{'connected'}) ? "connected" : "not connected";
readingsBulkUpdateIfChanged($hash,"cloud","enabled($hasconn)");
}else{
readingsBulkUpdateIfChanged($hash,"cloud","disabled");
}
#-- look for external sensors
if ($jhash->{'ext_temperature'}) {
my %sensors = %{$jhash->{'ext_temperature'}};
foreach my $temp (keys %sensors){
readingsBulkUpdate($hash,"temperature_".$temp,$sensors{$temp}->{'tC'});
}
}
if ($jhash->{'ext_humidity'}) {
my %sensors = %{$jhash->{'ext_humidity'}};
foreach my $hum (keys %sensors){
readingsBulkUpdate($hash,"humidity_".$hum,$sensors{$hum}->{'hum'});
}
}
readingsEndUpdate($hash,1);
#-- cyclic update
RemoveInternalTimer($hash);
InternalTimer(gettimeofday()+$hash->{INTERVAL}, "Shelly_status", $hash, 1)
if( $hash->{INTERVAL} ne "0" );
return undef;
}
########################################################################################
#
# Shelly_dim - Set Shelly dimmer state
# acts as callable program Shelly_dim($hash,$channel,$cmd)
# and as callback program Shelly_dim($hash,$channel,$cmd,$err,$data)
#
# Parameter hash, channel = 0,1 cmd = command
#
########################################################################################
sub Shelly_dim {
my ($hash, $channel, $cmd, $err, $data) = @_;
my $name = $hash->{NAME};
my $state = $hash->{READINGS}{state}{VAL};
my $net = $hash->{READINGS}{network}{VAL};
return
if( $net !~ /connected/ );
my $model = AttrVal($name,"model","generic");
my $creds = Shelly_pwd($hash);
if ( $hash && !$err && !$data ){
my $url = "http://$creds".$hash->{TCPIP}."/$channel$cmd";
my $timeout = AttrVal($name,"timeout",4);
Log3 $name, 5,"[Shelly_dim] Issue a non-blocking call to $url";
HttpUtils_NonblockingGet({
url => $url,
timeout => $timeout,
callback => sub($$$){ Shelly_dim($hash,$channel,$cmd,$_[1],$_[2]) }
});
return undef;
}elsif ( $hash && $err ){
#Log3 $name, 1,"[Shelly_dim] has error $err";
readingsSingleUpdate($hash,"state","Error",1);
return;
}
Log3 $name, 5,"[Shelly_dim] has obtained data $data";
my $json = JSON->new->utf8;
my $jhash = eval{ $json->decode( $data ) };
if( !$jhash ){
if( ($model =~ /shellyrgbw.*/) && ($data =~ /Device mode is not dimmer!/) ){
Log3 $name,1,"[Shelly_dim] Device $name is not a dimmer";
readingsSingleUpdate($hash,"state","Error",1);
return
}else{
Log3 $name,1,"[Shelly_dim] has invalid JSON data";
readingsSingleUpdate($hash,"state","Error",1);
return;
}
}
my $ison = $jhash->{'ison'};
my $bright = $jhash->{'brightness'};
my $hastimer = $jhash->{'has_timer'};
my $overpower = $jhash->{'overpower'};
if( $cmd =~ /\?turn=((on)|(off))/ ){
my $cmd2 = $1;
$ison =~ s/0|(false)/off/;
$ison =~ s/1|(true)/on/;
#-- timer command
if( index($cmd,"&") ne "-1"){
$cmd = substr($cmd,0,index($cmd,"&"));
if( $hastimer && $hastimer ne "1" ){
Log3 $name,1,"[Shelly_dim] returns with problem, timer not set";
}
}
if( $ison ne $cmd2 ) {
Log3 $name,1,"[Shelly_dim] returns without success, cmd=$cmd but ison=$ison";
}
}elsif( $cmd =~ /\?brightness=(.*)/){
my $cmd2 = $1;
if( $bright ne $cmd2 ) {
Log3 $name,1,"[Shelly_dim] returns without success, desired brightness $cmd, but device brightness=$bright";
}
}
if( defined($overpower) && $overpower eq "1") {
Log3 $name,1,"[Shelly_dim] switched off automatically because of overpower signal";
}
readingsBeginUpdate($hash);
readingsBulkUpdate($hash,"overpower",$overpower)
if( $shelly_models{$model}[3] > 0);
readingsEndUpdate($hash,1);
#-- Call status after switch.
InternalTimer(int(gettimeofday()+1.5), "Shelly_status", $hash,0);
return undef;
}
########################################################################################
#
# Shelly_updown - Move roller blind
# acts as callable program Shelly_updown($hash,$cmd)
# and as callback program Shelly_updown($hash,$cmd,$err,$data)
#
# Parameter hash, channel = 0,1 cmd = command
#
########################################################################################
sub Shelly_updown {
my ($hash, $cmd, $err, $data) = @_;
my $name = $hash->{NAME};
my $state = $hash->{READINGS}{state}{VAL};
my $net = $hash->{READINGS}{network}{VAL};
return
if( $net !~ /connected/ );
my $model = AttrVal($name,"model","generic");
my $creds = Shelly_pwd($hash);
#-- empty cmd parameter
$cmd = ""
if( !defined($cmd) );
if ( $hash && !$err && !$data ){
my $url = "http://$creds".$hash->{TCPIP}."/roller/0".$cmd;
my $timeout = AttrVal($name,"timeout",4);
Log3 $name, 5,"[Shelly_updown] Issue a non-blocking call to $url";
HttpUtils_NonblockingGet({
url => $url,
timeout => $timeout,
callback => sub($$$){ Shelly_updown($hash,$cmd,$_[1],$_[2]) }
});
return undef;
}elsif ( $hash && $err ){
#Log3 $name, 1,"[Shelly_updown] has error $err";
readingsSingleUpdate($hash,"state","Error",1);
return;
}
Log3 $name, 5,"[Shelly_updown] has obtained data $data";
my $json = JSON->new->utf8;
my $jhash = eval{ $json->decode( $data ) };
if( !$jhash ){
if( ($model =~ /shelly2.*/) && ($data =~ /Device mode is not roller!/) ){
Log3 $name,1,"[Shelly_updown] Device $name is not in roller mode";
readingsSingleUpdate($hash,"state","Error",1);
return
}else{
Log3 $name,1,"[Shelly_updown] has invalid JSON data";
readingsSingleUpdate($hash,"state","Error",1);
return;
}
}
#-- immediately after starting movement
if( $cmd ne ""){
#-- open 100% or 0% ?
my $pctnormal = (AttrVal($name,"pct100","open") eq "open");
my $targetpct = $hash->{TARGETPCT};
my $targetposition = $targetpct;
if( $targetpct == 100 ){
$targetposition = $pctnormal ? "open" : "closed";
}elsif( $targetpct == 0 ){
$targetposition = $pctnormal ? "closed" : "open";
}
readingsBeginUpdate($hash);
readingsBulkUpdate($hash,"state",$hash->{MOVING});
readingsBulkUpdate($hash,"pct",$targetpct);
readingsBulkUpdate($hash,"position",$targetposition);
readingsEndUpdate($hash,1);
#-- after 1 second call power measurement
InternalTimer(gettimeofday()+1, "Shelly_updown2", $hash,1);
}
return undef;
}
sub Shelly_updown2($){
my ($hash) =@_;
Shelly_meter($hash,0);
InternalTimer(gettimeofday()+$hash->{DURATION}, "Shelly_status", $hash,1);
}
########################################################################################
#
# Shelly_onoff - Switch Shelly relay
# acts as callable program Shelly_onoff($hash,$channel,$cmd)
# and as callback program Shelly_onoff($hash,$channel,$cmd,$err,$data)
#
# Parameter hash, channel = 0,1 cmd = command
#
########################################################################################
sub Shelly_onoff {
my ($hash, $channel, $cmd, $err, $data) = @_;
my $name = $hash->{NAME};
my $state = $hash->{READINGS}{state}{VAL};
my $net = $hash->{READINGS}{network}{VAL};
return
if( $net !~ /connected/ );
my $model = AttrVal($name,"model","generic");
my $creds = Shelly_pwd($hash);
if ( $hash && !$err && !$data ){
my $url = "http://$creds".$hash->{TCPIP}."/relay/".$channel.$cmd;
my $timeout = AttrVal($name,"timeout",4);
Log3 $name, 5,"[Shelly_onoff] Issue a non-blocking call to $url";
HttpUtils_NonblockingGet({
url => $url,
timeout => $timeout,
callback => sub($$$){ Shelly_onoff($hash,$channel,$cmd,$_[1],$_[2]) }
});
return undef;
}elsif ( $hash && $err ){
#Log3 $name, 1,"[Shelly_onoff] has error $err";
readingsSingleUpdate($hash,"state","Error",1);
return;
}
Log3 $name, 5,"[Shelly_onoff] has obtained data $data";
my $json = JSON->new->utf8;
my $jhash = eval{ $json->decode( $data ) };
if( !$jhash ){
if( ($model =~ /shelly2.*/) && ($data =~ /Device mode is not relay!/) ){
Log3 $name,1,"[Shelly_onoff] Device $name is not in relay mode";
readingsSingleUpdate($hash,"state","Error",1);
return
}else{
Log3 $name,1,"[Shelly_onoff] has invalid JSON data";
readingsSingleUpdate($hash,"state","Error",1);
return;
}
}
my $ison = $jhash->{'ison'};
my $hastimer = $jhash->{'has_timer'};
my $overpower = $jhash->{'overpower'};
$ison =~ s/0|(false)/off/;
$ison =~ s/1|(true)/on/;
$cmd =~ s/\?turn=//;
#-- timer command
if( index($cmd,"&") ne "-1"){
$cmd = substr($cmd,0,index($cmd,"&"));
if( $hastimer ne "1" ){
Log3 $name,1,"[Shelly_onoff] returns with problem, timer not set";
}
}
if( $ison ne $cmd ) {
Log3 $name,1,"[Shelly_onoff] returns without success, cmd=$cmd but ison=$ison";
}
if( defined($overpower) && $overpower eq "1") {
Log3 $name,1,"[Shelly_onoff] switched off automatically because of overpower signal";
}
#--
my $subs = ($shelly_models{$model}[0] ==1) ? "" : "_".$channel;
readingsBeginUpdate($hash);
if($model =~ /shelly(1|(plug)).*/){
readingsBulkUpdateIfChanged($hash,"state",$ison)
}else{
readingsBulkUpdate($hash,"state","OK");
}
readingsBulkUpdate($hash,"relay".$subs,$ison);
readingsBulkUpdate($hash,"overpower".$subs,$overpower)
if( $shelly_models{$model}[3] > 0);
readingsEndUpdate($hash,1);
#-- Call status after switch.
InternalTimer(int(gettimeofday()+1.5), "Shelly_status", $hash,0);
return undef;
}
########################################################################################
#
# Shelly_meter - Retrieve data from meter
# acts as callable program Shelly_meter($hash,$channel,cmd)
# and as callback program Shelly_meter0($hash,$channel,$cmd,$err,$data)
#
# Parameter hash, channel, cmd = command
#
########################################################################################
sub Shelly_meter {
my ($hash, $channel, $err, $data) = @_;
my $name = $hash->{NAME};
my $state = $hash->{READINGS}{state}{VAL};
my $net = $hash->{READINGS}{network}{VAL};
return
if( $net !~ /connected/ );
my $model = AttrVal($name,"model","generic");
my $creds = Shelly_pwd($hash);
if ( $hash && !$err && !$data ){
my $url = "http://$creds".$hash->{TCPIP}."/meter/".$channel;
my $timeout = AttrVal($name,"timeout",4);
Log3 $name, 5,"[Shelly_meter] Issue a non-blocking call to $url";
HttpUtils_NonblockingGet({
url => $url,
timeout => $timeout,
callback => sub($$$){ Shelly_meter($hash,$channel,$_[1],$_[2]) }
});
return undef;
}elsif ( $hash && $err ){
Log3 $name, 1,"[Shelly_meter has error $err";
readingsSingleUpdate($hash,"state","Error",1);
return;
}
Log3 $name, 5,"[Shelly_meter] has obtained data $data";
my $json = JSON->new->utf8;
my $jhash = eval{ $json->decode( $data ) };
if( !$jhash ){
Log3 $name,1,"[Shelly_meter] invalid JSON data";
readingsSingleUpdate($hash,"state","Error",1);
return;
}
my $subs = ($shelly_models{$model}[3] ==1) ? "" : "_".$channel;
my $power = $jhash->{'power'};
my $energy = int($jhash->{'total'}/6)/10;
readingsSingleUpdate($hash,"power".$subs,$power,1);
readingsSingleUpdate($hash,"energy".$subs,$energy,1);
return undef;
}
1;
=pod
=item device
=item summary to communicate with a Shelly switch/roller actuator
=begin html
Shelly
FHEM module to communicate with a Shelly switch/roller actuator/dimmer/RGBW controller
Define
define <name> Shelly <IP address>
Defines the Shelly device.
Notes:
- The attribute
model
must be set
- This module needs the JSON package
- In Shelly button, switch, roller or dimmer devices one may set URL values that are "hit" when the input or output status changes.
This is useful to transmit status changes arising from locally pressed buttons directly to FHEM by setting
- Button switched ON url: http://<FHEM IP address>:<Port>/fhem?XHR=1&cmd=get%20<Devicename>%20status
If one wants to detach the button from the output, one may generate an additional reading button by setting in the Shelly
- For Button switched ON url: http://<FHEM IP address>:<Port>/fhem?XHR=1&cmd=set%20<Devicename>%20button_on%20[<channel>]
- For Button switched OFF url: http://<FHEM IP address>:<Port>/fhem?XHR=1&cmd=set%20<Devicename>%20button_off%20[<channel>]
Attention: Of course, a csrfToken must be included as well - or a proper allowed device declared.
- If the model attribute is set to generic, the device does not contain any actors, it is just a placeholder for arbitrary sensors
Set
For all Shelly devices
set <name> config <registername> <value> [<channel>]
set the value of a configuration register
- password <password>
This is the only way to set the password for the Shelly web interface
For Shelly switching devices (model=shelly1|shelly1pm|shellyuni|shelly4|shellyplug|shellyem or (model=shelly2/2.5 and mode=relay))
-
set <name> on|off|toggle [<channel>]
switches channel <channel> on or off. Channel numbers are 0 and 1 for model=shelly2/2.5, 0..3 for model=shelly4. If the channel parameter is omitted, the module will switch the channel defined in the defchannel attribute.
-
set <name> on-for-timer|off-for-timer <time> [<channel>]
switches <channel> on or off for <time> seconds. Channel numbers are 0 and 1 for model=shelly2/2.5 or model=shellyuni, and 0..3 model=shelly4. If the channel parameter is omitted, the module will switch the channel defined in the defchannel attribute.
-
set <name> xtrachannels
create readingsProxy devices for switching device with more than one channel
For Shelly roller blind devices (model=shelly2/2.5 and mode=roller)
-
set <name> open|closed|stop [<duration>]
drives the roller blind open, closed or to a stop. The commands open and closed take a optional parameter that determines the drive time in seconds
-
set <name> pct <integer percent value>
drives the roller blind to a partially closed position (normally 100=open, 0=closed, see attribute pct100). If the integer percent value
carries a sign + or - the following number will be added to the current value of the position to acquire the target value.
-
set <name> zero
calibration of roller device (only for model=shelly2/2.5)
For Shelly dimmer devices model=shellydimmer or (model=shellyrgbw and mode=white)
-
set <name> on|off [<channel>]
switches channel <channel> on or off. Channel numbers are 0..3 for model=shellyrgbw. If the channel parameter is omitted, the module will switch the channel defined in the defchannel attribute.
-
set <name> on-for-timer|off-for-timer <time> [<channel>]
switches <channel> on or off for <time> seconds. Channel numbers 0..3 for model=shellyrgbw. If the channel parameter is omitted, the module will switch the channel defined in the defchannel attribute.
-
set <name> pct <0..100> [<channel>]
percent value to set brightness value. Channel numbers 0..3 for model=shellyrgbw. If the channel parameter is omitted, the module will dim the channel defined in the defchannel attribute.
For Shelly RGBW devices (model=shellyrgbw and mode=color)
-
set <name> on|off
switches device <channel> on or off
-
set <name> on-for-timer|off-for-timer <time>
switches device on or off for <time> seconds.
-
set <name> hsv <hue value 0..360>,<saturation value 0..1>,<brightness value 0..1>
comma separated list of hue, saturation and value to set the color. Note, that 360° is the same hue as 0° = red.
Hue values smaller than 1 will be treated as fraction of the full circle, e.g. 0.5 will give the same hue as 180°.
-
set <name> rgb <rrggbb>
6-digit hex string to set the color
-
set <name> rgbw <rrggbbww>
8-digit hex string to set the color and white value
-
set <name> white <integer>
number 0..255 to set the white value
Get
-
get <name> config [<registername>] [<channel>]
get the value of a configuration register and writes it in reading config. If the register name is omitted, only general data like e.g. the SHELLYID are fetched.
-
get <name> registers
displays the names of the configuration registers for this device
-
get <name> status
returns the current devices status.
-
get <name> version
display the version of the module
Attributes
attr <name> shellyuser <shellyuser>
username for addressing the Shelly web interface
- <
attr <name> model generic|shelly1|shelly1pm|shelly2|shelly2.5|shellyuni|shelly4|shellyplug|shellydimmer|shellyrgbw
type of the Shelly device. >If the model attribute is set to generic, the device does not contain any actors,
it is just a placeholder for arbitrary sensors
attr <name> mode relay|roller (only for model=shelly2/2.5) mode white|color (only for model=shellyrgbw)
type of the Shelly device
-
<interval>
Update interval for reading in seconds. The default is 60 seconds, a value of 0 disables the automatic update.
-
<timeout>
Timeout in seconds for HttpUtils_NonblockingGet. The default is 4 seconds. Careful: Use this attribute only if you get timeout errors in your log.
For Shelly switching devices (mode=relay for model=shelly2/2.5, standard for all other switching models)
attr <name> defchannel
only for model=shelly2|shelly2.5|shelly4 or multi-channel switches: Which channel will be switched, if a command is received without channel number
For Shelly roller blind devices (mode=roller for model=shelly2/2.5)
attr <name> maxtime <float>
time needed for a complete drive upward or downward
attr <name> pct100 open|closed (default:open)
is pct=100 open or closed ?
Standard attributes
=end html
=begin html_DE
Shelly
Absichtlich keine deutsche Dokumentation vorhanden, die englische Version gibt es hier: Shelly
=end html_DE
=cut