##############################################
# 36_ShellyMonitor.pm
#
# Parses the MultiCast "COAP" messages of Shellys and updates
# devices accordingly
#
# $Id$
#
package main;
use strict;
use warnings;
no warnings 'portable'; # Support for 64-bit ints required
use vars qw{%attr %defs};
my $COIOT_OPTION_GLOBAL_DEVID = 3332;
my $COIOT_OPTION_STATUS_VALIDITY = $COIOT_OPTION_GLOBAL_DEVID+80;
my $COIOT_OPTION_STATUS_SERIAL = $COIOT_OPTION_GLOBAL_DEVID+88;
my $SHELLY_DEF_SEN = {
# Old version 1:
"111" => { "type"=>"P", "desc"=>"power_0", "unit"=>"W"},
"121" => { "type"=>"P", "desc"=>"power_1", "unit"=>"W"},
"131" => { "type"=>"P", "desc"=>"power_2", "unit"=>"W"},
"141" => { "type"=>"P", "desc"=>"power_3", "unit"=>"W"},
"112" => { "type"=>"S", "desc"=>"output_0"},
"122" => { "type"=>"S", "desc"=>"output_1"},
"132" => { "type"=>"S", "desc"=>"output_2"},
"142" => { "type"=>"S", "desc"=>"output_3"},
"113" => { "type"=>"T", "desc"=>"deviceTemp", "unit"=>"C"},
"114" => { "type"=>"T", "desc"=>"deviceTempF", "unit"=>"F"},
"214" => { "type"=>"E", "desc"=>"energy_0", "unit"=>"Wmin"},
# Version 2, since FW >= 1.6
"1101" => { "type"=>"S", "desc"=>"output_0"},
# Used by Shelly 2 SHSW-21 roller-mode, Shelly 2.5 SHSW-25 roller-mode:
"1102" => { "type"=>"S", "desc"=>"roller"},
# Used by Shelly 2 SHSW-21 roller-mode, Shelly 2.5 SHSW-25 roller-mode:
"1103" => { "type"=>"S", "desc"=>"rollerPos"},
# Used by Shelly Air SHAIR-1:
"1104" => { "type"=>"S", "desc"=>"totalWorkTime", "unit"=>"s"},
# Used by Shelly Gas SHGS-1:
"1105" => { "type"=>"S", "desc"=>"valve"},
# Used by Shelly 2.5 SHSW-25 relay-mode, Shelly 2 SHSW-21 relay-mode, Shelly RGBW2-white SHRGBW2-white, Shelly 2LED SH2LED-1:
"1201" => { "type"=>"S", "desc"=>"output_1"},
# Used by Shelly RGBW2-white SHRGBW2-white:
"1301" => { "type"=>"S", "desc"=>"output_2"},
# Used by Shelly RGBW2-white SHRGBW2-white:
"1401" => { "type"=>"S", "desc"=>"output_3"},
"2101" => { "type"=>"S", "desc"=>"input_0"},
"2102" => { "type"=>"EV", "desc"=>"inputEvent_0"},
"2103" => { "type"=>"EVC", "desc"=>"inputEventCnt_0"},
"2201" => { "type"=>"S", "desc"=>"input_1"},
"2202" => { "type"=>"EV", "desc"=>"inputEvent_1"},
"2203" => { "type"=>"EVC", "desc"=>"inputEventCnt_1"},
# Used by Shelly i3 SHIX3-1:
"2301" => { "type"=>"S", "desc"=>"input_2"},
# Used by Shelly i3 SHIX3-1:
"2302" => { "type"=>"EV", "desc"=>"inputEvent_2"},
# Used by Shelly i3 SHIX3-1:
"2303" => { "type"=>"EVC", "desc"=>"inputEventCnt_2"},
"3101" => { "type"=>"T", "desc"=>"extTemp_0", "unit"=>"C"},
"3102" => { "type"=>"T", "desc"=>"extTemp_0f", "unit"=>"F"},
# Used by Shelly TRV1
"3103trv" => { "type"=>"T", "desc"=>"targetTemp", "unit"=>"C"},
"3104trv" => { "type"=>"T", "desc"=>"targetTempF", "unit"=>"F"},
# Used by Shelly HT
"3103" => { "type"=>"H", "desc"=>"humidity"},
"3104" => { "type"=>"T", "desc"=>"deviceTemp", "unit"=>"C"},
"3105" => { "type"=>"T", "desc"=>"deviceTempF", "unit"=>"F"},
# Used by Shelly Sense SHSEN-1, Shelly Door Window SHDW-1, Shelly Door Window 2 SHDW-2:
"3106" => { "type"=>"L", "desc"=>"luminosity", "unit"=>"lux"},
# Used by Shelly Gas SHGS-1:
"3107" => { "type"=>"C", "desc"=>"concentration", "unit"=>"ppm"},
# Used by Shelly Door Window SHDW-1, Shelly Door Window 2 SHDW-2:
"3108" => { "type"=>"S", "desc"=>"dwIsOpened"},
# Used by Shelly Door Window SHDW-1, Shelly Door Window 2 SHDW-2:
"3109" => { "type"=>"S", "desc"=>"tilt", "unit"=>"deg"},
# Used by Shelly Door Window SHDW-1, Shelly Door Window 2 SHDW-2:
"3110" => { "type"=>"S", "desc"=>"luminosityLevel"},
"3111" => { "type"=>"B", "desc"=>"battery"},
# Used by Shelly Sense SHSEN-1, Shelly Button SHBTN-1:
"3112" => { "type"=>"S", "desc"=>"charger"},
# Used by Shelly Gas SHGS-1:
"3113" => { "type"=>"S", "desc"=>"sensorOp"},
# Used by Shelly Gas SHGS-1:
"3114" => { "type"=>"S", "desc"=>"selfTest"},
"3115" => { "type"=>"S", "desc"=>"sensorError"},
# Used by Shelly Spot SHSPOT-1, Shelly Spot 2 SHSPOT-2:
"3116" => { "type"=>"S", "desc"=>"dayLight"},
"3117" => { "type"=>"S", "desc"=>"extInput"},
# Used by Shelly TRV1
"3116trv" => { "type"=>"S", "desc"=>"valveError"},
"3117trv" => { "type"=>"S", "desc"=>"mode"},
"3118" => { "type"=>"S", "desc"=>"mode"},
"3119" => { "type"=>"S", "desc"=>"timestamp"},
"3120" => { "type"=>"S", "desc"=>"active"},
"3121" => { "type"=>"S", "desc"=>"valvePos", "unit"=>"%"},
"3201" => { "type"=>"T", "desc"=>"extTemp_1", "unit"=>"C"},
"3202" => { "type"=>"T", "desc"=>"extTemp_1f", "unit"=>"F"},
"3301" => { "type"=>"T", "desc"=>"extTemp_2", "unit"=>"C"},
"3302" => { "type"=>"T", "desc"=>"extTemp_2f", "unit"=>"F"},
"4101" => { "type"=>"P", "desc"=>"power_0", "unit"=>"W"},
# Used by Shelly 2 SHSW-21 roller-mode, Shelly 2.5 SHSW-25 roller-mode:
"4102" => { "type"=>"P", "desc"=>"rollerPower", "unit"=>"W"},
"4103" => { "type"=>"E", "desc"=>"energy_0", "unit"=>"Wmin"},
# Used by Shelly 2 SHSW-21 roller-mode, Shelly 2.5 SHSW-25 roller-mode:
"4104" => { "type"=>"E", "desc"=>"rollerEnergy", "unit"=>"Wmin"},
# Used by Shelly 3EM SHEM-3, Shelly EM SHEM:
"4105" => { "type"=>"P", "desc"=>"power_0", "unit"=>"W"},
# Used by Shelly 3EM SHEM-3, Shelly EM SHEM:
"4106" => { "type"=>"E", "desc"=>"energy_0", "unit"=>"Wh"},
# Used by Shelly 3EM SHEM-3, Shelly EM SHEM:
"4107" => { "type"=>"E", "desc"=>"energyReturned_0", "unit"=>"Wh"},
# Used by Shelly 3EM SHEM-3, Shelly EM SHEM:
"4108" => { "type"=>"V", "desc"=>"voltage_0", "unit"=>"V"},
# Used by Shelly 3EM SHEM-3:
"4109" => { "type"=>"I", "desc"=>"current_0", "unit"=>"A"},
# Used by Shelly 3EM SHEM-3:
"4110" => { "type"=>"S", "desc"=>"powerFactor_0"},
# Used by Shelly 2.5 SHSW-25 relay-mode, Shelly RGBW2-white SHRGBW2-white:
"4201" => { "type"=>"P", "desc"=>"power_1", "unit"=>"W"},
# Used by Shelly 2.5 SHSW-25 relay-mode, Shelly RGBW2-white SHRGBW2-white:
"4203" => { "type"=>"E", "desc"=>"energy_1", "unit"=>"Wmin"},
# Used by Shelly 3EM SHEM-3, Shelly EM SHEM:
"4205" => { "type"=>"P", "desc"=>"power_1", "unit"=>"W"},
# Used by Shelly 3EM SHEM-3, Shelly EM SHEM:
"4206" => { "type"=>"E", "desc"=>"energy_1", "unit"=>"Wh"},
# Used by Shelly 3EM SHEM-3, Shelly EM SHEM:
"4207" => { "type"=>"E", "desc"=>"energyReturned_1", "unit"=>"Wh"},
# Used by Shelly 3EM SHEM-3, Shelly EM SHEM:
"4208" => { "type"=>"V", "desc"=>"voltage_1", "unit"=>"V"},
# Used by Shelly 3EM SHEM-3:
"4209" => { "type"=>"I", "desc"=>"current_1", "unit"=>"A"},
# Used by Shelly 3EM SHEM-3:
"4210" => { "type"=>"S", "desc"=>"powerFactor_1"},
# Used by Shelly RGBW2-white SHRGBW2-white:
"4301" => { "type"=>"P", "desc"=>"power_2", "unit"=>"W"},
# Used by Shelly RGBW2-white SHRGBW2-white:
"4303" => { "type"=>"E", "desc"=>"energy_2", "unit"=>"Wmin"},
# Used by Shelly 3EM SHEM-3:
"4305" => { "type"=>"P", "desc"=>"power_2", "unit"=>"W"},
# Used by Shelly 3EM SHEM-3:
"4306" => { "type"=>"E", "desc"=>"energy_2", "unit"=>"Wh"},
# Used by Shelly 3EM SHEM-3:
"4307" => { "type"=>"E", "desc"=>"energyReturned_2", "unit"=>"Wh"},
# Used by Shelly 3EM SHEM-3:
"4308" => { "type"=>"V", "desc"=>"voltage_2", "unit"=>"V"},
# Used by Shelly 3EM SHEM-3:
"4309" => { "type"=>"I", "desc"=>"current_2", "unit"=>"A"},
# Used by Shelly 3EM SHEM-3:
"4310" => { "type"=>"S", "desc"=>"powerFactor_2"},
# Used by Shelly RGBW2-white SHRGBW2-white:
"4401" => { "type"=>"P", "desc"=>"power_3", "unit"=>"W"},
# Used by Shelly RGBW2-white SHRGBW2-white:
"4403" => { "type"=>"E", "desc"=>"energy_3", "unit"=>"Wmin"},
"5101" => { "type"=>"S", "desc"=>"brightness_0"},
"5102" => { "type"=>"S", "desc"=>"gain"},
"5103" => { "type"=>"S", "desc"=>"colorTemp", "unit"=>"K"},
# Used by Shelly Duo SHBDUO-1:
"5104" => { "type"=>"S", "desc"=>"whiteLevel"},
"5105" => { "type"=>"S", "desc"=>"L-red"},
"5106" => { "type"=>"S", "desc"=>"L-green"},
"5107" => { "type"=>"S", "desc"=>"L-blue"},
"5108" => { "type"=>"S", "desc"=>"L-white"},
# Used by Shelly RGBW2-white SHRGBW2-white, Shelly 2LED SH2LED-1:
"5201" => { "type"=>"S", "desc"=>"brightness_1"},
# Used by Shelly RGBW2-white SHRGBW2-white:
"5301" => { "type"=>"S", "desc"=>"brightness_2"},
# Used by Shelly RGBW2-white SHRGBW2-white:
"5401" => { "type"=>"S", "desc"=>"brightness_3"},
"6101" => { "type"=>"A", "desc"=>"overtemp"},
"6102" => { "type"=>"A", "desc"=>"overpower_0"},
# Used by Shelly 2 SHSW-21 roller-mode, Shelly 2.5 SHSW-25 roller-mode:
"6103" => { "type"=>"A", "desc"=>"rollerStopReason"},
# Used by Shelly Dimmer SHDM-1, Shelly Dimmer W1 SHDIMW-1:
"6104" => { "type"=>"A", "desc"=>"loadError"},
# Used by Shelly Smoke 2 SHSM-02, Shelly Smoke SHSM-01:
"6105" => { "type"=>"A", "desc"=>"smoke"},
# Used by Shelly Flood SHWT-1:
"6106" => { "type"=>"A", "desc"=>"flood"},
# Used by Shelly Sense SHSEN-1, Shelly Spot SHSPOT-1, Shelly Spot 2 SHSPOT-2:
"6107" => { "type"=>"A", "desc"=>"motion"},
# Used by Shelly Gas SHGS-1:
"6108" => { "type"=>"A", "desc"=>"gas"},
"6109" => { "type"=>"P", "desc"=>"overpowerValue", "unit"=>"W"},
# Used by Shelly Door Window SHDW-1, Shelly Door Window 2 SHDW-2:
"6110" => { "type"=>"A", "desc"=>"vibration"},
# Used by Shelly 2.5 SHSW-25 relay-mode, Shelly 2 SHSW-21 relay-mode:
"6202" => { "type"=>"A", "desc"=>"overpower_1"},
"9101" => { "type"=>"S", "desc"=>"mode"},
"9102" => { "type"=>"EV", "desc"=>"wakeupEvent"},
"9103" => { "type"=>"EVC", "desc"=>"cfgChanged"}
};
# Copied from 36_Shelly, keep up to date..:
my %shelly_models = (
#(relays,rollers,dimmers,meters,NG)
"generic" => [4,4,4,4,0],
"shellygeneric" => [4,4,4,4,0],
"shelly1" => [1,0,0,0,0],
"shelly1pm" => [1,0,0,1,0],
"shelly2" => [2,1,0,1,0],
"shelly2.5" => [2,1,0,2,0],
"shellyplug" => [1,0,0,1,0],
"shelly4" => [4,0,0,4,0],
"shellypro4" => [4,0,0,4,0],
"shellyrgbw" => [0,0,4,1,0],
"shellydimmer" => [0,0,1,1,0],
"shellyem" => [1,0,0,2,0],
"shelly3em" => [1,0,0,3,0],
"shellybulb" => [0,0,1,1,0],
"shellyuni" => [2,0,0,1,0],
#-- 2nd generation devices
"shellyplus1" => [1,0,0,0,1],
"shellyplus1pm" => [1,0,0,1,1],
"shellypro4pm" => [4,0,0,4,1]
);
my %shelly_models_by_mod_shelly = ();
# Mapping of DeviceId in Multicast to Shelly model attr
my %DEVID_MODEL = (
"SHPLG-S" => "shellyplug",
"SHSW-PM" => "shelly1pm",
"SHSW-L" => "shelly1pm",
"SHSW-1" => "shelly1",
"SHSW-21" => "shelly2",
"SHSW-25" => "shelly2.5",
"SHDM-2" => "shellydimmer",
"SHSW-44" => "shelly4",
"SHRGBW2" => "shellyrgbw",
"SHCB-1" => "shellybulb",
"SHBLB-1" => "shellybulb",
"SHBDUO-1" => "shellybulb",
"SHEM" => "shellyem",
"SHEM-3" => "shelly3em",
"SHMOS-01" => "generic",
"SHWT-1" => "generic",
"SHHT-1" => "generic",
"SHGS-1" => "generic",
"SHBTN-2" => "generic",
"SHIX3-1" => "generic",
"SHTRV-01" => "generic"
);
# Mapping of DeviceId in Multicast to suggested generic name
my %DEVID_PREFIX = (
"SHPLG-S" => "shelly_plug_s",
"SHSW-PM" => "shelly_1pm",
"SHSW-L" => "shelly_1l",
"SHSW-1" => "shelly_1",
"SHSW-21" => "shelly_2",
"SHSW-25" => "shelly_25",
"SHDM-2" => "shelly_dimmer",
"SHSW-44" => "shelly_4",
"SHRGBW2" => "shelly_rgbw",
"SHBLB-1" => "shelly_bulb",
"SHCB-1" => "shelly_duo",
"SHBDUO-1" => "shelly_duo",
"SHWT-1" => "shelly_flood",
"SHHT-1" => "shelly_ht",
"SHGS-1" => "shelly_gas",
"SHBTN-2" => "shelly_button",
"SHIX3-1" => "shelly_i3",
"SHMOS-01" => "shelly_motion",
"SHTRV-01" => "shelly_trv",
"SHEM" => "shelly_em",
"SHEM-3" => "shelly_em3"
);
# Mapping of DeviceId in Multicast to additional attributes on creation
my $devicon_lametta_pm0 = 'devStateIcon {my $lderr = ReadingsVal($name,"network","-")' .
' !~ /^.*>connected.*/?"10px-kreis-rot":"10px-kreis-gruen"; ' .
'my $light = ReadingsVal($name,"relay_0","off"); ' .
'my $cons = ReadingsVal($name,"power","unknown");' .
'my $kwh = sprintf("%.2f kWh", ReadingsVal($name,"energy",0)/1000.0);' .
'FW_makeImage($lderr)."' .
'".FW_makeImage($light)."
$cons W / $kwh
"}';
my $devicon_lametta_pm = $devicon_lametta_pm0;
$devicon_lametta_pm =~ s/_0//;
my %DEVID_ATTRS = (
"SHDM-2" => [ ( "webCmd pct:on:off", "widgetOverride pct:slider,0,1,100", $devicon_lametta_pm0 ) ],
"SHBDUO-1" => [ ( "widgetOverride ct:colorpicker,CT,2700,10,6500", $devicon_lametta_pm0 ) ],
"SHCB-1" => [ ( $devicon_lametta_pm0 ) ],
"SHSW-PM" => [ ( $devicon_lametta_pm ) ],
);
my %DEVID_TTL_OVERRIDE = (
"SHWT-1" => 90000,
"SHHT-1" => 90000,
"SHGS-1" => 90000,
"SHBTN-2" => 90000
);
# SHWT-1 = Shelly Flood, should go to generic
# SHHT-1 = Shelly H&T, should go to generic
# SHGS-1 = Shelly Gas, should go to generic
# SHBTN-2 = Shelly Button, should go to generic
my %ROLLER_STATUS_MAP = (
"open" => "moving_up",
"close" => "moving_down",
"stop" => "stopped"
);
#####################################
sub ShellyMonitor_Initialize
{
my $hash = shift;
$hash->{Match} = "^\/(?s:.*)\!\$";
$hash->{ReadFn} = "ShellyMonitor_Read";
$hash->{ReadyFn} = "ShellyMonitor_Ready";
$hash->{DefFn} = "ShellyMonitor_Define";
$hash->{UndefFn} = "ShellyMonitor_Undef";
$hash->{AttrFn} = "ShellyMonitor_Attr";
$hash->{NotifyFn}= "ShellyMonitor_Notify";
$hash->{SetFn}= "ShellyMonitor_Set";
$hash->{AttrList}= "ignoreDevices ". $readingFnAttributes;
$hash->{FW_detailFn} = "ShellyMonitor_detailFn";
# Check which models are available in Mod_Shelly
LoadModule "Shelly";
my $fn = $modules{"Shelly"}{"AttrList"};
if($fn && $fn=~/(^| )model:([^ ]+)( |$)/) {
map { $shelly_models_by_mod_shelly{$_} = 1 } split (/,/, $2);
Log3 $hash->{NAME}, 2, "Shelly-Module loaded supports models: " . join(',', keys %shelly_models_by_mod_shelly);
}
}
sub MCast_Open {
my ($hash, $interface) = @_;
my $name = $hash->{NAME};
my $dev = $hash->{".McastInterface"};
my $reopen = 0;
my $conn;
my $err;
eval {
$err = "Perl-Module IO::Socket::Multicast not found. Either execute \"sudo apt-get install libio-socket-multicast-perl\" (for Raspbian Buster), or \"sudo cpan install IO::Socket::Multicast\"";
require IO::Socket::Multicast;
$err = "Perl-Module JSON. Either execute \"sudo apt-get install libjson-perl\" (for Raspbian Buster), or \"sudo cpan install JSON\"";
require JSON;
$conn = IO::Socket::Multicast->new(LocalPort=>5683, ReuseAddr=>1) or $err = "Cannot open Multicast socket: $^E";
if (defined $interface) {
$err = "Error adding mcast interface $interface";
$conn->mcast_add('224.0.1.187', $interface);
} else {
$err = "Error adding mcast interface";
$conn->mcast_add('224.0.1.187') or $err = "Cannot open Multicast socket: $^E";
}
$conn->sockopt(IO::Socket::SO_RCVBUF, 65535);
$err = undef;
$hash->{".JSON"} = JSON->new->utf8;
1;
};
if($@) {
Log3 $name, 1, $err;
return $err;
# return &$doCb($@);
}
if(!$conn) {
Log3 $name, 1, "$name: Can't connect to $dev: $^E" if(!$reopen);
$readyfnlist{"$name.$dev"} = $hash;
# DevIo_setStates($hash, "disconnected");
return $err;
# return &$doCb("");
}
$hash->{MCastDev} = $conn;
$hash->{FD} = $conn->fileno();
$dev = "" if (! defined $dev);
delete($readyfnlist{"$name.$dev"});
$selectlist{"$name.$dev"} = $hash;
return;
}
sub MCast_Close {
my $hash = shift;
my $name = $hash->{NAME};
my $dev = $hash->{".McastInterface"};
my $conn = $hash->{MCastDev};
$conn->close() if ($conn);
return unless defined $dev;
delete($selectlist{"$name.$dev"});
delete($readyfnlist{"$name.$dev"});
delete($hash->{FD});
delete($hash->{EXCEPT_FD});
delete($hash->{PARTIAL});
delete($hash->{NEXT_OPEN});
}
#####################################
sub ShellyMonitor_Define {
my ($hash, $def) = @_;
my ($a, $h) = parseParams($def);
return 'wrong syntax: define ShellyMonitor [interface]' if(@{$a} < 2);
MCast_Close($hash);
RemoveInternalTimer($hash);
my $name = ${$a}[0];
my $dev;
my $wantAuto;
if (@{$a}>2) {
$wantAuto = (${$a}[2]=~/^auto/);
$dev = $wantAuto ? (@{$a}>3 ? ${$a}[3] : undef ) : ${$a}[2];
}
$hash->{NAME} = $name;
$hash->{".McastInterface"} = $dev;
$hash->{".Ignored"}=0;
$hash->{".Received"}=0;
$hash->{".ReceivedBroken"}=0;
$hash->{".ReceivedByIp"}=();
$hash->{".ip2device"}=();
$hash->{NOTIFYDEV} = "global";
# if ($wantAuto && !AttrVal( $name, 'alexaMapping', undef ) ) {
# CommandAttr(undef,"$name autoCreate Shelly");
# }
my $device_name = "ShellyMonitor_".$name;
$modules{ShellyMonitor}{defptr}{$device_name} = $hash;
Log3 $hash,5,"ShellyMonitor ($name) - Opening device...";
return MCast_Open($hash, $dev);
}
sub ShellyMonitor_Init
{
Log 3,"Init done";
return;
}
#####################################
sub ShellyMonitor_Undef
{
my ($hash, $arg) = @_;
MCast_Close($hash);
return;
}
sub ShellyMonitor_DoRead
{
my $hash = shift;
my $name = $hash->{NAME};
my $conn = $hash->{MCastDev};
my $data;
my $pinfo = $conn->recv($data,1400);
my ($port, $ip_address) = unpack_sockaddr_in $pinfo;
my $sending_ip = inet_ntoa ($ip_address);
Log3 ($name, 4, "Received data from $sending_ip");
$hash->{".Received"}++;
$hash->{".ReceivedByIp"}->{$sending_ip}++;
my $ip2devicesDirty = 0;
my @devrefs = getDevicesForIp($hash, $sending_ip);
if (! @devrefs ) {
Log3 ($name, 4, "$sending_ip not found in cache");
# Search for defined devices by IP:
@devrefs = ();
my @devNames = devspec2array("TYPE=Shelly:FILTER=DEF=$sending_ip");
foreach ( @devNames ) {
my $model = AttrVal($_, "model", "generic");
my %d = (
name => $_ ,
isDefined => 1,
model => $model,
mode => AttrVal($_, "mode", undef)
);
push @devrefs, \%d;
Log3 $name, 2, "Defined real device $_ for $sending_ip as model $model";
}
$hash->{".ip2device"}->{$sending_ip} = [ @devrefs ];
$ip2devicesDirty = 1;
} else {
Log3 $name, 4, "$sending_ip: in cache, devices=" . join (' ', map { scalar $_->{name} } @devrefs) . " (size=" . scalar @devrefs . ")";
}
my $autoCreate = (defined $hash->{".autoCreate"}) ? 1 : 0;
# Now lets unpack the raw packet data...
my $opt1byte;
my ($b1,$b2,$msgid,$remain) = unpack('CCSA*', $data);
if ($b1 < 0x50 || $b1 > 0x5f) {
Log3 $name, 3, "Unexpected byte at pos 0: " . sprintf("0x%X", $b1) . ", expecting Non-Confirm. w/o token";
$hash->{".ReceivedBroken"}++;
return;
}
if ($b2 != 30) {
$hash->{".ReceivedBroken"}++;
Log3 $name, 3, "Unexpected byte at pos 1: " . sprintf("0x%X", $b2) . ", expecting Code 30";
return;
}
my $tokenlen = $b1 & 0xf;
my $token;
if ($tokenlen>0) {
my $unpackstr = "A${tokenlen}B8A*";
($token,$opt1byte,$remain) = unpack($unpackstr, $remain);
} else {
($opt1byte,$remain) = unpack('B8A*', $remain);
}
my $option = 0;
my $uri = "";
my $global_devid;
my $validity;
my $serial;
# Parsing the options in COAP format...
while ($opt1byte ne "11111111") {
my $optiondelta = oct('0b' . substr($opt1byte, 0, 4));
my $optionlen = oct('0b' . substr($opt1byte, 4));
if ($optiondelta == 13) {
($optiondelta,$remain) = unpack('CA*', $remain);
$optiondelta += 13;
} elsif ($optiondelta == 14) {
($optiondelta,$remain) = unpack('nA*', $remain);
$optiondelta += 269;
}
if ($optionlen == 13) {
($optionlen,$remain) = unpack('CA*', $remain);
$optionlen += 13;
} elsif ($optionlen == 14) {
($optionlen,$remain) = unpack('nA*', $remain);
$optionlen += 269;
}
$option += $optiondelta;
if ($option == 11) {
my $str;
($str,$opt1byte,$remain) = unpack ('A' . $optionlen . 'B8A*', $remain);
$uri .= '/' . $str;
} elsif ($option == $COIOT_OPTION_GLOBAL_DEVID) {
($global_devid,$opt1byte,$remain) = unpack ('A' . $optionlen . 'B8A*', $remain);
} elsif ($option == $COIOT_OPTION_STATUS_VALIDITY) {
($validity,$opt1byte,$remain) = unpack ('nB8A*', $remain);
if ($validity & 1) {
$validity *= 4;
} else {
$validity /= 10;
}
} elsif ($option == $COIOT_OPTION_STATUS_SERIAL) {
($serial,$opt1byte,$remain) = unpack ('vB8A*', $remain);
} else {
$hash->{".ReceivedBroken"}++;
Log3 $name, 3, "Unexpected option $option, only CoIoT V2-options supported";
return;
}
}
# Header parsed, processing data...
my ($devtype, $devid, $devversion) = split (/#/, $global_devid);
foreach ( @devrefs ) {
$_->{expires} = time() + ( $DEVID_TTL_OVERRIDE{$devtype} // $validity );
}
# Handle ignoring of devices
my $ignoreRegexp = $hash->{".ignoreDevices"};
if ($ignoreRegexp) {
@devrefs = grep { $_->{name} !~ qr/$ignoreRegexp/ } @devrefs;
Log3 ($name, 4, "Applied RegExp $ignoreRegexp");
if (! @devrefs || scalar @devrefs == 0) {
Log3 ($name, 4, "Shelly-devices found by IP match ignoreRule");
$hash->{".Ignored"}++;
return;
}
}
Log3 $name, 5, "URI: $uri, global_devid = $global_devid, validity=$validity, serial=$serial";
my $json = $hash->{".JSON"};
return unless ($json);
$data = $json->decode($remain);
my $shellyCoIoTModel;
my $shellyId;
if ($global_devid=~ /(SH[^#]+)#([A-F0-9]{6,12})#/) {
$shellyCoIoTModel = $1;
$shellyId = $2;
}
# Iterate over Shellys found by IP and x-check ID
foreach ( @devrefs ) {
$_->{expires} = time()+$validity;
next unless $_->{isDefined};
my $device = $defs{$_->{name}};
next unless ($device);
if (defined $device->{SHELLYID}) {
if ($device->{SHELLYID} !~ qr/.*$shellyId$/) {
Log3 $name, 1, "Device $_ has ID " . $device->{SHELLYID} . " which does not match $shellyId";
my $dName = $_;
@devrefs = grep { $_->{name} ne $dName } @devrefs;
$hash->{".ip2device"}->{$sending_ip} = [ @devrefs ];
}
} else {
$device->{SHELLYID} = $shellyId;
Log3 $name, 1, "Assigning device $_->{name} SHELLYID $shellyId";
}
}
my %devModel = ();
my $haveAutoCreated = 0;
# Hopefully, all Shellys have an ID with 6-12 Chars...
if (scalar @devrefs==0 && $global_devid=~ /(SH[^#]+)#([A-F0-9]{6,12})#/) {
my $shellyCoIoTModel = $1;
my $shellyId = $2;
my @devsByShellyId = devspec2array("TYPE=Shelly:FILTER=SHELLYID=.*".$shellyId);
if (scalar @devsByShellyId == 1 ) {
# The Shelly-device is already existing, but has changed IP, so lets change the IP
my $oname = $devsByShellyId[0];
delete $hash->{".ip2device"}->{$sending_ip};
my $oldip = $defs{$oname}->{DEF};
CommandDefMod ( undef , $oname . " Shelly $sending_ip");
if (defined $oldip && $oldip =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) {
delete $hash->{".ip2device"}->{$oldip};
Log3 $name, 2, "Removing old ip $oldip for device $oname";
}
my $model = AttrVal($oname, "model", "generic");
my %d = (
name => $oname,
isDefined => 1,
model => $model,
mode => AttrVal($oname, "mode", undef)
);
push @devrefs, \%d;
$hash->{".ip2device"}->{$sending_ip} = [ @devrefs ];
$ip2devicesDirty = 1;
Log3 $name, 2, "Changed IP for device '" . $oname . "' to $sending_ip";
} else {
# No Shelly known by IP nor ID, so lets create a dummy
my $model = $DEVID_MODEL{$shellyCoIoTModel};
my $dname;
if (defined $model) {
$dname = $DEVID_PREFIX{$shellyCoIoTModel};
} else {
$dname = "shelly_generic_" . lc($shellyCoIoTModel);
$dname =~ s/-/_/g;
$dname =~ s/[^a-zA-Z0-9_]//g;
$model = "generic";
}
$dname .= '_' . lc($shellyId);
# Search if a shadow device is already existing with a different IP
my @ips = ( keys %{$hash->{".ip2device"}} );
foreach my $ip ( @ips ) {
my @devrefs2 = getDevicesForIp($hash, $ip);
foreach my $dev ( @devrefs2 ) {
if ($dev->{name} eq $dname && $dev->{isDefined}==0) {
Log3 $name, 2, "shadow device $dname found on old ip $ip, removing";
delete $hash->{".ip2device"}->{$ip};
}
}
}
Log3 $name, 2, "Defined shadow device $dname for $sending_ip as model $model";
my %d = (
name => $dname,
isDefined => 0,
model => $model,
expires => time()+$validity,
attrs => $DEVID_ATTRS{$shellyCoIoTModel},
mcastName => $shellyCoIoTModel
);
push @devrefs, \%d;
$hash->{".ip2device"}->{$sending_ip} = [ @devrefs ];
$ip2devicesDirty = 1;
}
}
foreach ( @devrefs ) {
my $device = $defs{$_->{name}};
next unless ($device);
if ($_->{isDefined}) {
readingsBeginUpdate($device);
$_->{model} = AttrVal($_->{name}, "model", undef);
Log3 $name, 5, "Found device $_->{name}, model $_->{model}";
}
}
my %rgb = ();
my %rgbdevices = ();
foreach my $i ( keys %{$data}) {
if ($i ne "G") {
Log3 $name, 4, "Unexpected JSON array '$i' in data";
next;
}
my $arr = $data->{"G"};
foreach my $j ( @{$arr}) {
my $channel = @{$j}[0];
my $sensorid = @{$j}[1];
my $svalue = @{$j}[2];
my $defarr = $SHELLY_DEF_SEN->{$sensorid};
if ($shellyCoIoTModel=~/^SHTRV.*/ && defined $SHELLY_DEF_SEN->{$sensorid . "trv"}) {
$defarr = $SHELLY_DEF_SEN->{$sensorid . "trv"};
}
if (defined $defarr) {
my $rname = $defarr->{"desc"};
if ($rname =~ /^(power|output|energy|energyReturned|brightness|extTemp)_(.).*/ || $rname =~ /^(roller.*|mode|L-.*|colorTemp)$/) {
my $rtype = $1;
my $rno = $2;
foreach ( @devrefs ) {
# We want to set the mode also for shadow devices
my $model = $_->{model};
if ($rtype eq "mode") {
$_->{mode} = $svalue unless $_->{isDefined}==1;
}
next unless $_->{isDefined}==1;
# Only real devices from here on..
my $device = $defs{$_->{name}};
next unless ($device);
if ($rtype eq "power") {
my $subs = ($shelly_models{$model}[3] ==1) ? "" : "_".$rno;
readingsBulkUpdateIfChanged($device, "power" . $subs, $svalue);
} elsif ($rtype =~ /^energy/) {
my $subs = ($shelly_models{$model}[3] ==1) ? "" : "_".$rno;
readingsBulkUpdateIfChanged($device, $rtype . $subs,
$defarr->{"unit"} eq "Wmin" ? int($svalue/6)/10 : $svalue);
} elsif ($rtype eq "output") {
my $state = ( $svalue == 0 ? "off" : ( $svalue == 1 ? "on" : undef ));
my $no_relais = $shelly_models{$model}[0];
if ($no_relais == 0) {
readingsBulkUpdateIfChanged($device, "state", $state);
} else {
if ($no_relais == 1) {
readingsBulkUpdateIfChanged($device, "relay", $state);
} else {
my $sname = "relay_" . $rno;
readingsBulkUpdateIfChanged($device, $sname, $state);
}
}
} elsif ($rtype eq "brightness") {
my $subs = ($shelly_models{$model}[3] ==1) ? "" : "_".$rno;
readingsBulkUpdateIfChanged($device, "pct" . $subs, $svalue);
} elsif ($rtype eq "rollerStopReason") {
readingsBulkUpdateIfChanged($device, "stop_reason", $svalue);
} elsif ($rtype eq "rollerEnergy") {
readingsBulkUpdateIfChanged($device, "energy", int($svalue/6)/10);
} elsif ($rtype eq "rollerPower") {
readingsBulkUpdateIfChanged($device, "power", $svalue);
} elsif ($rtype eq "rollerPos") {
readingsBulkUpdateIfChanged($device, "pct", $svalue);
my $v = $svalue;
$v = "open" if ($svalue == 100);
$v = "closed" if ($svalue == 0);
readingsBulkUpdateIfChanged($device, "position", $v);
} elsif ($rtype eq "roller") {
my $v = $ROLLER_STATUS_MAP{$svalue};
$v = $svalue unless defined $v;
readingsBulkUpdateIfChanged($device, "state", $v);
readingsBulkUpdateIfChanged($device, "last_dir", "down") if ($svalue eq "close") ;
readingsBulkUpdateIfChanged($device, "last_dir", "up") if ($svalue eq "open") ;
} elsif ($rtype eq "mode" && $haveAutoCreated==1) {
CommandAttr ( undef, $_->{name} . ' mode ' . $svalue);
} elsif ($rtype eq "colorTemp") {
readingsBulkUpdateIfChanged($device, "ct", $svalue);
} elsif ($rtype eq "extTemp") {
readingsBulkUpdateIfChanged($device, $rname, $svalue);
} elsif ($rtype =~ /L-(red|green|blue|white)/) {
$rgb{$1} = $svalue;
$rgbdevices{$_->{name}} = 1;
readingsBulkUpdateIfChanged($device, $rtype, $svalue);
}
}
} else {
# Generic Shelly Device gets any reading in native form
foreach ( @devrefs ) {
# We want to set the mode also for shadow devices
my $model = $_->{model};
my $device = $defs{$_->{name}};
next unless ($device);
$svalue = join (' ', @$svalue) if (ref $svalue eq "ARRAY");
# In case of inputEventCnt_x, we have to determine the old device reading
# to check for changes:
if ($rname =~ /^inputEventCnt_(\d)$/) {
if (ReadingsVal($_->{name}, $rname, "-1") ne $svalue && $svalue>0) {
$_->{inputEventCnt} = {} unless ($_->{inputEventCnt});
$_->{inputEventCnt}->{$1} = 1;
}
}
if ($rname eq "wakeupEvent" && $svalue eq "button") {
# inputEventCnt_0 will be always one, so we force an update:
$_->{inputEventCnt} = { "0" => "1" };
}
readingsBulkUpdateIfChanged($device, $rname, $svalue)
if (defined $device && (( ! defined $model ) || ($model eq "generic")));
}
}
Log3 $name, 5, "$rname = $svalue";
} else {
Log3 $name, 4, "Unknown: c=$channel, sensorid=$sensorid, value=$svalue";
}
}
}
foreach ( @devrefs ) {
if ($_->{isDefined}) {
my $device = $defs{$_->{name}};
if ($rgbdevices{$_->{name}} &&
defined $rgb{"red"} && defined $rgb{"green"} && defined $rgb{"blue"}) {
readingsBulkUpdateIfChanged($device, "rgb",
sprintf("%02X%02X%02X", $rgb{"red"},$rgb{"green"},$rgb{"blue"}));
}
if ($_->{inputEventCnt}) {
foreach my $i ( keys %{$_->{inputEventCnt}}) {
my $rname = "inputEvent_$i";
# Old news is recent news :-)
readingsBulkUpdate ($device, $rname, ReadingsVal($device->{NAME}, $rname, undef));
}
delete $_->{inputEventCnt};
}
readingsEndUpdate($device, 1) if ($device);
}
}
my $nstate = "Statistics: " . $hash->{".Received"} . " msg received, " . $hash->{".ReceivedBroken"} .
" broken, " . $hash->{".Ignored"} . " ignored, " . (0 + (keys %{$hash->{".ReceivedByIp"}})) . " devices";
readingsSingleUpdate ($hash, "state", $nstate, 0);
FW_directNotify("FILTER=$name", "#FHEMWEB:WEB", "location.reload('true')", "") if ($ip2devicesDirty>0);
return(undef);
}
sub ShellyMonitor_detailFn {
my ($FW_wname, $deviceName, $FW_room) = @_;
my $hash = $defs{$deviceName};
my $haveUnsupported = 0;
my $nstate = "\n";
$nstate .= "Identified DevicesIP | Name | Model | |
";
my $cnt = 0;
my @ips = ( keys %{$hash->{".ip2device"}} );
@ips = map substr($_, 4), sort map pack('C4a*', split(/\./), $_), @ips;
my $now = time();
my $formNo = 1;
foreach my $ip ( @ips ) {
my @devrefs = getDevicesForIp($hash, $ip);
foreach my $dev ( @devrefs ) {
if ($dev->{expires} < $now) {
Log3 $hash->{NAME}, 1, "Device " . $dev->{name} . " has expired, no messages seen";
if (scalar @devrefs == 1) {
delete $hash->{".ip2device"}->{$ip};
} else {
@devrefs = grep { $_->{expires} > $now } @devrefs;
$hash->{".ip2device"}->{$ip} = \ @devrefs;
}
next;
}
$nstate .= "";
$formNo++;
}
# ($dev->{isDefined} ? "" : "Create" ) .
}
}
$nstate .= "
";
$nstate .= "
n.s. = not supported by Mod_Shelly" if ($haveUnsupported);
$nstate .= "
";
return $nstate;
}
#####################################
sub ShellyMonitor_Read
{
my $hash = shift;
if( $init_done ) {
ShellyMonitor_DoRead($hash);
# my $new_state = "Statistics: " . $hash->{".Received"} . " msg received, " . $hash->{".ReceivedBroken"} . " broken, " . $hash->{".Ignored"} . " ignored, " . (0 + (keys %{$hash->{".ReceivedByIp"}})) . " devices";
# readingsSingleUpdate($hash,"state",$new_state,1);
}
return(undef);
}
#####################################
sub ShellyMonitor_Notify
{
my ($hash, $dev_hash) = @_;
my $ownName = $hash->{NAME}; # own name / hash
return "" if(IsDisabled($ownName)); # Return without any further action if the module is disabled
my $devName = $dev_hash->{NAME}; # Device that created the events
my $events = deviceEvents($dev_hash,1);
return if( !$events );
my $ip2devicesDirty = 0;
foreach my $event (@{$events}) {
$event = "" if(!defined($event));
next unless ($event =~ /^(RENAMED|DELETED|DEFINED|MODIFIED).*/);
my ($evType, $evDev1, $evDev2) = split (/ /, $event);
my @ips = ( keys %{$hash->{".ip2device"}} );
$ip2devicesDirty = 0;
foreach my $ip ( @ips ) {
my @devrefs = getDevicesForIp($hash, $ip);
foreach my $dev ( @devrefs ) {
next unless ($dev->{name} eq $evDev1);
$ip2devicesDirty = 1;
delete $hash->{".ip2device"}->{$ip} if ($evType eq "DELETED");
$dev->{name} = $evDev2 if ($evType eq "RENAMED");
$dev->{isDefined} = 1 if ($evType eq "DEFINED");
}
}
if ($ip2devicesDirty==0) {
Log3 $hash->{NAME}, 4, "Did not find device $evDev1 in cache...";
my $ohash = $defs{$evDev1};
if (defined $ohash && $ohash->{TYPE} eq "Shelly") {
# We did not find it, and it was something about Shellys:
# Be paranoic, clear cache...
Log3 $hash->{NAME}, 4, "... but its a shelly, so clear cache";
$hash->{".ip2device"} = ();
$ip2devicesDirty = 1
}
}
Log3 $hash->{NAME}, 4, "Modified ip2device-cache on event: $event";
}
FW_directNotify("#FHEMWEB:WEB", "location.reload('true')", "") if ($ip2devicesDirty>0);
}
#####################################
sub ShellyMonitor_Ready
{
my ($hash) = @_;
my $name = $hash->{NAME};
my $dev=$hash->{".McastInterface"};
# This is relevant for windows/USB only
my $po = $hash->{USBDev};
my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags);
return if (!$po);
($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags) = $po->status;
return ($InBytes>0);
}
sub ShellyMonitor_Set
{
my ($hash, $name, $sName, $sValue, $devName) = @_;
return "autocreate create" if ($sName eq "?");
return "only autocreate and create vailable" if ($sName !~ /(auto|)create/);
if ($sName eq "autocreate") {
$sValue = ".*" unless (defined $sValue);
return "autocreate only takes an IP address pattern as optional value" if (defined $devName);
} else {
$sValue = '^' . $sValue . '$';
$sValue =~ s/\./\\./g;
}
my $created = 0;
my @ips = grep { $_ =~ qr/^$sValue$/ } ( keys %{$hash->{".ip2device"}} );
return "Provided IP $sValue did not match any IPs" unless (scalar @ips>=1);
foreach my $ip ( @ips ) {
my @devrefs = getDevicesForIp($hash, $ip);
next unless (@devrefs);
my $device = $devrefs[0];
next if ($device->{isDefined});
Log3 $name, 1, "AutoCreate called for IP $ip, #devices=" . scalar @devrefs;
my $dname = defined $devName ? $devName : $device->{name};
$device->{name} = $dname;
my $model = $device->{model};
my $mode = $device->{mode} if ($model =~ /(shelly2|shelly2.5|shellyrgbw|shellybulb)/);
my $r;
if ($sName eq "autocreate") {
$r = DoTrigger("global", "UNDEFINED $dname Shelly $ip");
} else {
$r = CommandDefine(undef, "$dname Shelly $ip");
}
Log3 $name, 1, "AutoCreating $dname returned $r" if ($r);
if (defined $shelly_models_by_mod_shelly{$model}) {
CommandAttr ( undef, $dname . ' model ' . $model);
} elsif ($shelly_models_by_mod_shelly{"shellygeneric"}) {
CommandAttr ( undef, $dname . ' model shellygeneric');
} elsif ($shelly_models_by_mod_shelly{"generic"}) {
CommandAttr ( undef, $dname . ' model generic');
}
return "Creation of device '$dname' failed" unless ($defs{$dname});
my $attrs = $device->{attrs};
if ($attrs) {
foreach (@{$attrs}) {
CommandAttr ( undef, $dname . ' ' . $_ );
}
}
if (defined $mode) {
CommandAttr ( undef, $dname . ' mode ' . $mode) if ($model ne "generic");;
if ($model =~ /shellybulb/) {
CommandAttr ( undef, $dname . ' webCmd ' . ($mode eq "white" ? 'pct:ct:on:off' : 'rgb:on:off' ) );
}
if ($model =~ /shellyrgb.*/ && ($mode eq "color") ) {
CommandAttr ( undef, $dname . ' webCmd rgb:on:off' );
}
}
if ($model =~ /shellydimmer.*/) {
CommandAttr ( undef, $dname . ' webCmd pct:on:off' );
}
CommandAttr ( undef, $dname . ( $model eq 'generic' ? ' interval 0' : ' interval 600' ) );
$created++;
}
FW_directNotify("#FHEMWEB:WEB", "location.reload('true')", "") if ($created>0);
return;
}
sub ShellyMonitor_Attr
{
my ($cmd,$name,$aName,$aVal) = @_;
# $cmd can be "del" or "set"
# $name is device name
# aName and aVal are Attribute name and value
my $hash = $defs{$name};
my $dev=$hash->{".McastInterface"};
if ($aName eq "ignoreDevices") {
$hash->{".ignoreDevices"} = ($cmd eq "set" ) ? $aVal : undef;
# } elsif ($aName eq "autoCreate") {
# $hash->{".autoCreate"} = ($cmd eq "set" && $aVal eq "Shelly") ? $aVal : undef;
}
return;
}
sub getDevicesForIp {
my ($hash, $ip) = @_;
my $ip2devices = $hash->{".ip2device"}->{$ip};
return unless ($ip2devices);
my @devrefs = @{$ip2devices};
foreach ( @devrefs ) {
if (ref $_ ne "HASH") {
delete $hash->{".ip2device"}->{$ip};
@devrefs = ();
Log3 $hash->{NAME}, 1, "Panic, it happened: Cache for $ip did contain a none-hash, $_ instead";
}
}
return @devrefs;
}
sub completecheck {
my ($hash) = @_;
my @is = ( keys %{$hash->{".ip2device"}} );
foreach my $ip ( @is ) {
my @devrefs = getDevicesForIp($hash, $ip);
Log3 $hash->{NAME}, 4, "IP: $ip " . scalar @devrefs . " entries";
}
}
1;
=pod
=item device
=item summary Listens to CoIoT-Messages sent by Shellys and updates readings
=item summary_DE Wertet CoIoT-Pakete von Shelly-Geräten aus und aktualisiert die Readings
=begin html
ShellyMonitor
This module is for Shelly-devices, that report their data in the CoIoT-"standard" (based on COAP).
Defined devices are updated in their readings, non-defined devices found are displayed in FHEMWEB
in a table, where they might be created with a click.
Requirements
ShellyMonitor needs two additional Perl modules:
- JSON
For Raspian Buster by sudo apt-get install libjson-perl
installable,
or by sudo cpan install JSON
- IO::Socket::Multicast
For Raspian Buster by sudo apt-get install libio-socket-multicast-perl
installable,
or with sudo cpan install IO::Socket::Multicast
Define
define <name> ShellyMonitor [interface]
<interface> is necessary if the computers primary interface is not the one
with the multicast messages. E.g., it might be "wlan0".
Set
set <name> autocreate [<ip regexp>]
Setting this command triggers the creation of all discovered, but not yet defined
devices that have an IP address matching to pattern. Without a pattern,
all devices are created.
Creation implies:
- a define via the autocreate module mechanism with the systematic name displayed in the WEB table
- setting the model attribute for the device
- setting the mode attribute if applicable
- setting the webCmd attribute if applicable
set <name> create <ip address> [<device name>]
The device specified by its IP address is created with the optionally given
device name. Unlike autocreate
, a direct define
is
executed, and the features of the autocreate module (FileLog-device, room-attribute)
are not assigned.
The other attributes described for the autocreate
-set command are assigned.
Attributes
-
ignoreDevices
Regular expression for Shelly device (names) that shall be ignored.
E.g., setting this to ".*
" will not update any devices
=end html
=begin html_DE
ShellyMonitor
Dieses Modul aktualisiert die Readings von Shelly-Geräten, die ihre Daten im CoIoT-"Standard" (Abwandlung von COAP) im Netzwerk versenden. Die gefundenen Geräte werden in FHEMWEB in einer Tabelle angezeigt, wo sie sich
mit einem Klick erzeugen und anschließend ggf. umbenennen lassen.
Anforderungen
ShellyMonitor benötigt zwei zusätzliche Perl-Pakete:
- JSON
Unter Raspian Buster per sudo apt-get install libjson-perl
installierbar,
oder per sudo cpan install JSON
- IO::Socket::Multicast
Unter Raspian Buster per sudo apt-get install libio-socket-multicast-perl
installierbar,
oder per sudo cpan install IO::Socket::Multicast
Define
define <name> ShellyMonitor [interface]
<interface> ist nötig, falls das primäre Netzwerk-Interface nicht das
Netz ist, in dem die Multicast-Pakete versendet werden.
Beispielsweise "wlan0" oder "eth0"
Set
set <name> autocreate [<ip regexp>]
Mit diesem Kommando werden alle gefundenen Shelly-Geräte, die noch nicht angelegt
wurden, erzeugt, sofern ihre aktuelle IP-Adresse dem regulären Ausdruck entspricht.
Ohne diesen Parameter werden alle gefundenen Geräte erzeugt.
Die Erzeugung umfasst:
- a define über das autocreate-Modul mit dem systematischen Namen, der in
der Tabelle angezeigt wird
- Setzen des model-Attributs für das Gerät
- Setzen des mode-Attributs, sofern beim Gerät vorhanden
- Setzen eines webCmd-Attributs, falls sinnvoll
set <name> create <ip address> [deviceName]
Mit diesem Kommando wird das durch die angegebene IP-Adresse spezifizierte Gerät
unter dem als deviceName optional angegebenen Namen erzeugt.
Anders als bei autocreate
wird das Gerät nicht über das
autocreate-Modul erzeugt. Es wird daher kein Raum zugewiesen und kein FileLog-Device
angelegt. Die Attribute werden hingegen wie bei autocreate
beschrieben zugewiesen.
Attribute
-
ignoreDevices
Regulärer Ausdruck, welche Shelly-Geräte nicht aktualisiert werden sollen.
Beispielsweise werden mit ".*
" alle Geräte ignoriert.
Der Ausdruck bezieht sich auf den Gerätenamen.
=end html_DE
=cut