diff --git a/fhem/CHANGED b/fhem/CHANGED index 56b28d4d7..26745e688 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - new: 70_LaMetric2: add official module for LaMetric Smart Clock - feature: 73_AutoShuttersControl: change ReadingsVal to ReadingsNum for read position Reading, add setDriveReading for wiggle - bugfix: 73_AutoShuttersControl: Code clean, fix unwanted rollo rides diff --git a/fhem/FHEM/70_LaMetric2.pm b/fhem/FHEM/70_LaMetric2.pm new file mode 100644 index 000000000..66f5cc757 --- /dev/null +++ b/fhem/FHEM/70_LaMetric2.pm @@ -0,0 +1,2377 @@ +############################################################################### +# $Id$ +############################################################################### +# +# A module to control LaMetric. +# +# Based on version 1, written 2017 by +# Matthias Kleine +# +# Also see API documentation: +# https://developer.lametric.com/ +# http://lametric-documentation.readthedocs.io/en/latest/reference-docs/device-notifications.html + +# Changes for version 2.0.0: +# - Code refactoring using perltidy +# - use HTTPS connectivity as default +# - Evaluate state changes based on command results to avoid additional query +# - pre-configure device attributes during initial define +# - add missing HTTP request headers for non-GET methods +# - new attribute notificationSound +# - new implementation of msg setter for flexible use of messaging options +# (using parseParams); backwards compatible to legacy static setter +# - allow better integration with FHEM msg-command +# - Multi-line support for msg setter when using new format +# - introduce message title handling as separate frame +# - Use an internal notificationIcon if no attribute was set +# - Allow to overwrite default values from device attributes withing every msg command +# - Setters to support new features +# - screensaver +# - metric +# - goal +# - New setters to be FHEM (AV media) standards compliant +# - brightness (rename reading displayBrightness to brightness) +# - mute (to mute/unmute explicitly) +# - muteT (to toggle volume) +# - bluetooth (rename reading bluetoothActive to bluetooth) +# - volume (rename reading audioVolume to volume) +# - volumeUp +# - volumeDown +# - channel +# - channelUp +# - channelDown +# - Harmonize setters and readings to reflect FHEM standards +# - brightnessMode (to separate function that from the brightness setter) +# - statusRequest (refresh kept for backwards compatibility) +# - write model to INTERNAL and attributes to accomodate FHEM device statistics +# - presence: present/absent +# - Add FHEM web frontend widgets to setters + +#TODO +#- rtype replace of special characters that device cannot display: +# 0x00A0 -> " " space +# 0x202F -> " " space +# 0x00B2 -> "2" m2 +# 0x00B3 -> "3" m3 +# 0x0025 -> "%" +# 0x00B0 -> "°" +#- notification attributes auf tatsächliche funktion prüfen +#- app interaction +#- radio setter einbinden +#- sticky/cycle message prüfen +#- msgSchema +#-notificationIcons may be set to "none" and then send an empty icon +#-mehrere metric/chart/goal/msg frames des gleichen typs (derzeit gehen per msg nur 1x zusätzlich metric,chart,goal) -> reihenfolge? + +package main; + +use HttpUtils; +use utf8; +use Data::Dumper; +use HttpUtils; +use SetExtensions; +use Encode; +use Unit; +use JSON qw(decode_json encode_json); + +my %LaMetric2_sounds = ( + notifications => [ + 'bicycle', 'car', 'cash', 'cat', + 'dog', 'dog2', 'energy', 'knock-knock', + 'letter_email', 'lose1', 'lose2', 'negative1', + 'negative2', 'negative3', 'negative4', 'negative5', + 'notification', 'notification2', 'notification3', 'notification4', + 'open_door', 'positive1', 'positive2', 'positive3', + 'positive4', 'positive5', 'positive6', 'statistic', + 'thunder', 'water1', 'water2', 'win', + 'win2', 'wind', 'wind_short' + ], + alarms => [ + 'alarm1', 'alarm2', 'alarm3', 'alarm4', 'alarm5', 'alarm6', + 'alarm7', 'alarm8', 'alarm9', 'alarm10', 'alarm11', 'alarm12', + 'alarm13' + ], +); + +my %LaMetric2_sets = ( + msg => '', + chart => '', + goal => '', + metric => '', + on => ':noArg', + off => ':noArg', + toggle => ':noArg', + power => ':on,off', + volume => ':slider,0,1,100', + volumeUp => ':noArg', + volumeDown => ':noArg', + mute => ':on,off', + muteT => ':noArg', + brightness => ':slider,1,1,100', + brightnessMode => ':auto,manual', + bluetooth => ':on,off', + channelUp => ':noArg', + channelDown => ':noArg', + statusRequest => ':noArg', + screensaver => ':off,when_dark,time_based', +); + +my %LaMetric2_setsHidden = ( + msgCancel => 1, + channel => 1, + app => 1, + refresh => 1, +); + +my %LaMetric2_metrictype_icons = ( + + # # length / Länge + # 0 => { + # lm_icon => '', # nothing found :-( + # }, + + # mass / Masse + 1 => { + lm_icon => 'i2721', + }, + + # time / Zeit + 2 => { + lm_icon => 'i1820', + }, + + # electric current / elektrische Stromstärke + 3 => { + lm_icon => 'a21256', + }, + + # absolute temperature / absolute Temperatur + 4 => { + lm_icon => 'i2355', + }, + + # amount of substance / Stoffmenge + 5 => { + lm_icon => 'i9027', + }, + + # luminous intensity / Lichtstärke + 6 => { + lm_icon => 'a3711', + }, + + # energy / Energie + 7 => { + lm_icon => 'a23725', + }, + + # frequency / Frequenz + 8 => { + lm_icon => 'a14428', + }, + + # power / Leistung + 9 => { + lm_icon => 'a21256', + }, + + # pressure / Druck + 10 => { + lm_icon => 'i2356', + }, + + # absolute pressure / absoluter Druck + 11 => { + lm_icon => 'i20768', + }, + + # air pressure / Luftdruck + 12 => { + lm_icon => 'i2644', + }, + + # electric voltage / elektrische Spannung + 13 => { + lm_icon => 'a15124', + }, + + # plane angular / ebener Winkel + 14 => { + lm_icon => 'i8974', + }, + + # speed / Geschwindigkeit + 15 => { + lm_icon => 'a21688', + }, + + # illumination intensity / Beleuchtungsstärke + 16 => { + lm_icon => 'a24894', + }, + + # luminous flux / Lichtstrom + 17 => { + lm_icon => 'a7876', + }, + + # volume / Volumen + 18 => { + lm_icon => 'a3401', + }, + + # logarithmic level / Logarithmische Größe + 19 => { + lm_icon => 'i12247', + }, + + # electric charge / elektrische Ladung + 20 => { + lm_icon => 'a24125', + }, + + # electric capacity / elektrische Kapazität + 21 => { + lm_icon => 'i389', + }, + + # electric resistance / elektrischer Widerstand + 22 => { + lm_icon => 'i21860', + }, + + # surface area / Flächeninhalt + 23 => { + lm_icon => 'a17519', + }, + + # currency / Währung + 24 => { + lm_icon => 'i23003', + }, + + # numbering / Zahlen + 25 => { + lm_icon => 'i9027', + }, +); + +#------------------------------------------------------------------------------ +sub LaMetric2_Initialize($$) { + my ($hash) = @_; + + $hash->{DefFn} = "LaMetric2_Define"; + $hash->{UndefFn} = "LaMetric2_Undefine"; + $hash->{SetFn} = "LaMetric2_Set"; + + my $notifications = + join( ',', @{ $LaMetric2_sounds{notifications} } ); + my $alarms = join( ',', @{ $LaMetric2_sounds{alarms} } ); + + $hash->{AttrList} = + 'disable:0,1 disabledForIntervals do_not_notify:0,1 model ' + . 'defaultOnStatus:always,illumination defaultScreensaverEndTime:00:00,00:15,00:30,00:45,01:00,01:15,01:30,01:45,02:00,02:15,02:30,02:45,03:00,03:15,03:30,03:45,04:00,04:15,04:30,04:45,05:00,05:15,05:30,05:45,06:00,06:15,06:30,06:45,07:00,07:15,07:30,07:45,08:00,08:15,08:30,08:45,09:00,09:15,09:30,09:45,10:00,10:15,10:30,10:45,11:00,11:15,11:30,11:45,12:00,12:15,12:30,12:45,13:00,13:15,13:30,13:45,14:00,14:15,14:30,14:45,15:00,15:15,15:30,15:45,16:00,16:15,16:30,16:45,17:00,17:15,17:30,17:45,18:00,18:15,18:30,18:45,19:00,19:15,19:30,19:45,20:00,20:15,20:30,20:45,21:00,21:15,21:30,21:45,22:00,22:15,22:30,22:45,23:00,23:15,23:30,23:45 defaultScreensaverStartTime:00:00,00:15,00:30,00:45,01:00,01:15,01:30,01:45,02:00,02:15,02:30,02:45,03:00,03:15,03:30,03:45,04:00,04:15,04:30,04:45,05:00,05:15,05:30,05:45,06:00,06:15,06:30,06:45,07:00,07:15,07:30,07:45,08:00,08:15,08:30,08:45,09:00,09:15,09:30,09:45,10:00,10:15,10:30,10:45,11:00,11:15,11:30,11:45,12:00,12:15,12:30,12:45,13:00,13:15,13:30,13:45,14:00,14:15,14:30,14:45,15:00,15:15,15:30,15:45,16:00,16:15,16:30,16:45,17:00,17:15,17:30,17:45,18:00,18:15,18:30,18:45,19:00,19:15,19:30,19:45,20:00,20:15,20:30,20:45,21:00,21:15,21:30,21:45,22:00,22:15,22:30,22:45,23:00,23:15,23:30,23:45 defaultVolume:slider,1,1,100 https:1,0 ' + . "notificationIcon:none,i8919,a11893,i22392,a12764 notificationIconType:none,info,alert notificationLifeTime notificationPriority:info,warning,critical notificationSound:off,$notifications,$alarms " + . "notificationChartIconType:none,info,alert notificationChartLifeTime notificationChartPriority:info,warning,critical notificationChartSound:off,$notifications,$alarms " + . "notificationGoalIcon:none,a11460 notificationGoalIconType:none,info,alert notificationGoalLifeTime notificationGoalPriority:info,warning,critical notificationGoalSound:off,$notifications,$alarms notificationGoalStart notificationGoalEnd notificationGoalUnit " + . "notificationMetricIcon:none,i9559 notificationMetricIconType:none,info,alert notificationMetricLang:en,de notificationMetricLifeTime notificationMetricPriority:info,warning,critical notificationMetricSound:off,$notifications,$alarms notificationMetricUnit " + . $readingFnAttributes; + + #$hash->{parseParams} = 1; # not possible due to legacy msg command schema + $hash->{'.msgParams'} = { parseParams => 1, }; +} + +#------------------------------------------------------------------------------ +sub LaMetric2_Define($$) { + my ( $hash, $def ) = @_; + + my @args = split( "[ \t]+", $def ); + + return +"Invalid number of arguments: define LaMetric2 [] []" + if ( int(@args) < 2 ); + + my ( $name, $type, $host, $apikey, $port, $interval ) = @args; + + if ( defined($host) && defined($apikey) ) { + + return "$apikey does not seem to be a valid key" + if ( $apikey !~ /^([a-f0-9]{64})$/ ); + + $hash->{HOST} = $host; + $hash->{".API_KEY"} = $apikey; + $hash->{VERSION} = "2.0.0"; + $hash->{INTERVAL} = + $interval && looks_like_number($interval) ? $interval : 60; + $hash->{PORT} = $port && looks_like_number($port) ? $port : 4343; + + # set default settings on first define + if ( $init_done && !defined( $hash->{OLDDEF} ) ) { + + # presets for FHEMWEB + $attr{$name}{cmdIcon} = + 'muteT:rc_MUTE channelUp:rc_RIGHT channelDown:rc_LEFT'; + $attr{$name}{devStateIcon} = +'on:rc_GREEN@green:off off:rc_STOP:on absent:rc_RED playing:rc_PLAY@green:pause paused:rc_PAUSE@green:play muted:rc_MUTE@green:muteT fast-rewind:rc_REW@green:play fast-forward:rc_FF@green:play interrupted:rc_PAUSE@yellow:play'; + $attr{$name}{icon} = 'time_statistic'; + $attr{$name}{stateFormat} = 'stateAV'; + $attr{$name}{webCmd} = 'volume:muteT:channelDown:channel:channelUp'; + + # set those to make it easier for users to see the + # default values. However, deleting those will + # still use the same defaults in the code below. + $attr{$name}{defaultOnStatus} = "always"; + $attr{$name}{defaultScreensaverEndTime} = "06:00"; + $attr{$name}{defaultScreensaverStartTime} = "00:00"; + $attr{$name}{defaultVolume} = "50"; + $attr{$name}{https} = 1; + + $attr{$name}{notificationIcon} = 'i8919'; + $attr{$name}{notificationIconType} = 'info'; + $attr{$name}{notificationLifeTime} = '120'; + $attr{$name}{notificationPriority} = 'info'; + + $attr{$name}{notificationGoalIcon} = 'a11460'; + $attr{$name}{notificationGoalIconType} = 'info'; + $attr{$name}{notificationGoalLifeTime} = '120'; + $attr{$name}{notificationGoalPriority} = 'info'; + $attr{$name}{notificationGoalStart} = 0; + $attr{$name}{notificationGoalEnd} = 100; + $attr{$name}{notificationGoalUnit} = '%'; + + $attr{$name}{notificationMetricIcon} = 'i9559'; + $attr{$name}{notificationMetricIconType} = 'info'; + $attr{$name}{notificationMetricLang} = 'en'; + $attr{$name}{notificationMetricLifeTime} = '120'; + $attr{$name}{notificationMetricPriority} = 'info'; + } + + # start Validation Timer + RemoveInternalTimer( $hash, 'LaMetric2_CheckState' ); + InternalTimer( gettimeofday() + 2, 'LaMetric2_CheckState', $hash, 0 ); + + return undef; + } + else { + return "IP or ApiKey missing"; + } +} + +#------------------------------------------------------------------------------ +sub LaMetric2_Undefine($$) { + my ( $hash, $name ) = @_; + + RemoveInternalTimer($hash); + + return undef; +} + +#------------------------------------------------------------------------------ +sub LaMetric2_Set($@) { + my ( $hash, $name, $cmd, @args ) = @_; + my ( $a, $h ) = parseParams( join " ", @args ); + + if ( !defined( $LaMetric2_sets{$cmd} ) + && !defined( $LaMetric2_setsHidden{$cmd} ) ) + { + my $usage = + "Unknown argument " + . $cmd + . ", choose one of " + . join( " ", map "$_$LaMetric2_sets{$_}", sort keys %LaMetric2_sets ); + + $usage .= + " msgCancel:" . join( ',', sort keys %{ $hash->{helper}{cancelIDs} } ) + if ( defined( $hash->{helper}{cancelIDs} ) + && keys %{ $hash->{helper}{cancelIDs} } > 0 ); + + $usage .= " channel:,"; + $usage .= join( ',', + map $hash->{helper}{channels}{$_}{name}, + sort keys %{ $hash->{helper}{channels} } ) + if ( defined( $hash->{helper}{channels} ) + && keys %{ $hash->{helper}{channels} } > 0 ); + + return $usage; + } + + return "Unable to set $cmd: Device is unreachable" + if ( ReadingsVal( $name, 'presence', 'absent' ) eq 'absent' ); + + return "Unable to set $cmd: Device is disabled" + if ( IsDisabled($name) ); + + return LaMetric2_SetOnOff( $hash, $cmd, @args ) + if ( $cmd eq 'on' + || $cmd eq 'off' + || $cmd eq 'toggle' + || $cmd eq 'power' ); + return LaMetric2_SetScreensaver( $hash, @args ) + if ( $cmd eq 'screensaver' ); + return LaMetric2_SetVolume( $hash, $cmd, @args ) + if ( $cmd eq 'volume' + || lc($cmd) eq 'volumeup' + || lc($cmd) eq 'volumedown' ); + return LaMetric2_SetMute( $hash, @args ) + if ( $cmd eq 'mute' + || lc($cmd) eq 'mutet' ); + return LaMetric2_SetBrightness( $hash, @args ) + if ( $cmd eq 'brightness' || lc($cmd) eq 'brightnessmode' ); + return LaMetric2_SetBluetooth( $hash, @args ) if ( $cmd eq 'bluetooth' ); + return LaMetric2_SetApp( $hash, $cmd, @args ) + if ( $cmd eq 'app' + || $cmd eq 'channel' + || lc($cmd) eq 'channelup' + || lc($cmd) eq 'channeldown' ); + return LaMetric2_CheckState( $hash, @args ) + if ( $cmd eq 'refresh' || lc($cmd) eq 'statusrequest' ); + + return LaMetric2_SetCancelMessage( $hash, @args ) + if ( lc($cmd) eq 'msgcancel' ); + + if ( $cmd eq 'msg' ) { + + # use new flexible msg command + return LaMetric2_SetNotification( $hash, $a, $h ) + if ( join( " ", @args ) !~ m/^(".*"|'.*').*$/ + || ( defined($h) && keys %{$h} > 0 ) ); + + # backwards compatibility for old-style msg command + # of LaMetric2 v1 module + return LaMetric2_SetMessage( $hash, @args ); + } + elsif ( $cmd eq 'chart' ) { + $h->{chart} = join( " ", @$a ) unless ( defined( $h->{chart} ) ); + return LaMetric2_SetNotification( $hash, undef, $h ); + } + elsif ( $cmd eq 'goal' ) { + $h->{goal} = join( " ", @$a ) unless ( defined( $h->{goal} ) ); + $h->{goalstart} = $h->{start} if ( defined( $h->{start} ) ); + $h->{goalend} = $h->{end} if ( defined( $h->{end} ) ); + $h->{goalunit} = $h->{unit} if ( defined( $h->{unit} ) ); + $h->{goaltype} = $h->{type} if ( defined( $h->{type} ) ); + return LaMetric2_SetNotification( $hash, undef, $h ); + } + elsif ( $cmd eq 'metric' ) { + $h->{metric} = join( " ", @$a ) unless ( defined( $h->{metric} ) ); + $h->{metricold} = $h->{old} if ( defined( $h->{old} ) ); + $h->{metricunit} = $h->{unit} if ( defined( $h->{unit} ) ); + $h->{metrictype} = $h->{type} if ( defined( $h->{type} ) ); + $h->{metriclang} = $h->{lang} if ( defined( $h->{lang} ) ); + $h->{metriclong} = 1 + if ( defined( $h->{txt} ) || defined( $h->{long} ) ); + return LaMetric2_SetNotification( $hash, undef, $h ); + } +} + +#------------------------------------------------------------------------------ +sub LaMetric2_SendCommand { + my ( $hash, $service, $httpMethod, $data, $info ) = @_; + + my $apiKey = $hash->{".API_KEY"}; + my $name = $hash->{NAME}; + my $host = $hash->{HOST}; + my $port = $hash->{PORT}; + my $apiVersion = "v2"; + my $httpNoShutdown = ( defined( $attr{$name}{"http-noshutdown"} ) + && $attr{$name}{"http-noshutdown"} eq "0" ) ? 0 : 1; + my $timeout = 5; + my $http_proto; + + $data = ( defined($data) ) ? $data : ""; + + Log3 $name, 5, "LaMetric2 $name: called function LaMetric2_SendCommand()"; + + my $https = AttrVal( $name, "https", 1 ); + if ($https) { + $http_proto = "https"; + $port = 4343 if ( $port == 8080 ); + } + else { + $http_proto = "http"; + $port = 8080 if ( $port == 4343 ); + } + $hash->{PORT} = $port; + + my %header = ( + Agent => 'FHEM-LaMetric2/' . $hash->{VERSION}, + 'User-Agent' => 'FHEM-LaMetric2/' . $hash->{VERSION}, + Accept => 'application/json;charset=UTF-8', + 'Accept-Charset' => 'UTF-8', + 'Authorization' => 'Basic ' . encode_base64( 'dev:' . $apiKey, "" ), + ); + + my $url = + $http_proto . "://" + . $host . ":" + . $port . "/api/" + . $apiVersion . "/" + . $service; + + $httpMethod = "GET" + if ( $httpMethod eq "" ); + + # Append data to URL if method is GET + if ( $httpMethod eq "GET" ) { + $url .= "?" . $data; + $data = undef; + } + elsif ( $httpMethod eq "POST" || $httpMethod eq "PUT" ) { + $header{'Content-Type'} = 'application/json;charset=UTF-8'; + } + + # send request + Log3 $name, 5, + "LaMetric2 $name: " + . $httpMethod . " " + . urlDecode($url) + . " (DATA: " + . $data + . " (noshutdown=" + . $httpNoShutdown . ")"; + + HttpUtils_NonblockingGet( + { + method => $httpMethod, + url => $url, + timeout => $timeout, + noshutdown => $httpNoShutdown, + data => $data, + info => $info, + hash => $hash, + service => $service, + header => \%header, + callback => \&LaMetric2_ReceiveCommand, + httpversion => "1.1", + loglevel => AttrVal( $name, "httpLoglevel", 4 ), + sslargs => { + SSL_verify_mode => 0, + }, + } + ); + + return; +} + +#------------------------------------------------------------------------------ +sub LaMetric2_ReceiveCommand($$$) { + my ( $param, $err, $data ) = @_; + + my $hash = $param->{hash}; + my $name = $hash->{NAME}; + + my $method = $param->{method}; + my $service = $param->{service}; + my $info = $param->{info}; + my $code = $param->{code}; + my $result = (); + + Log3 $name, 5, +"LaMetric2 $name: called function LaMetric2_ReceiveCommand() for service '$service':" + . "\n\nERROR: $err\n" + . "HTTP RESPONSE HEADER:\n" + . $param->{httpheader} + . "\n\nHTTP RESPONSE BODY:\n" + . $data; + + my $state = ReadingsVal( $name, "state", "initialized" ); + my $power = ReadingsVal( $name, "power", "off" ); + + readingsBeginUpdate($hash); + + # service not reachable + if ($err) { + readingsBulkUpdateIfChanged( $hash, "presence", "absent" ); + $state = "absent"; + $power = "off"; + } + elsif ($data) { + readingsBulkUpdateIfChanged( $hash, "presence", "present" ); + + $result = "success"; + + if ( $code >= 200 && $code < 300 ) { + + # set response data as reading if verbose level is >3 + $result = encode_utf8($data) + if ( $method ne "GET" && AttrVal( $name, "verbose", 3 ) > 3 ); + + my $response = decode_json( encode_utf8($data) ); + + # React on device return data + # + + if ( $service eq "device/notifications" && $method eq "POST" ) { + my $cancelID = $info->{cancelID}; + $hash->{helper}{cancelIDs}{$cancelID} = $response->{success}{id} + if ($cancelID); + } + elsif ( $service eq "device/notifications" && $method eq "GET" ) { + my $cancelIDs = {}; + my $notificationIDs = {}; + my $oldestTimestamp = time; + my $oldestNotificationID = ""; + my $oldestCancelID = ""; + + # Get a hash of all IDs and their infos in the response + foreach my $notification ( @{$response} ) { + my ( $year, $mon, $mday, $hour, $min, $sec ) = + split( /[\s-:T]+/, $notification->{created} ); + my $time = + timelocal( $sec, $min, $hour, $mday, $mon - 1, $year ); + + $notificationIDs->{ $notification->{id} } = { + time => $time, + text => $notification->{model}{frames}[0]{text}, + icon => $notification->{model}{frames}[0]{icon}, + }; + } + + # Filter local cancelIDs by only keeping + # the ones that still exist on the lametric device + foreach my $key ( keys %{ $hash->{helper}{cancelIDs} } ) { + my $value = $hash->{helper}{cancelIDs}{$key}; + + if ( exists $notificationIDs->{$value} ) { + $cancelIDs->{$key} = $value; + + # Determinate oldest notification for auto-cycling + $timestamp = $notificationIDs->{$value}{time}; + + if ( $timestamp < $oldestTimestamp ) { + $oldestCancelID = $key; + $oldestNotificationID = $value; + $oldestTimestamp = $timestamp; + } + } + } + + $hash->{helper}{cancelIDs} = $cancelIDs; + + # Update was triggered by LaMetric2_SetCancelMessage? + # Send DELETE request if notification still exists on device + my $cancelID = $info->{cancelID}; + if ( exists $info->{cancelID} + && exists $hash->{helper}{cancelIDs}{$cancelID} ) + { + $notificationID = $hash->{helper}{cancelIDs}{$cancelID}; + delete $hash->{helper}{cancelIDs}{$cancelID}; + + LaMetric2_SendCommand( $hash, + "device/notifications/$notificationID", "DELETE" ); + } + + # Update was triggered by LaMetric2_CycleMessage? + # -> Remove oldest (currently displayed) message and post + # it again at the end of the queue + if ( exists $info->{caller} + && $info->{caller} eq "CycleMessage" ) + { + delete $hash->{helper}{cancelIDs}{$oldestCancelID}; + LaMetric2_SendCommand( $hash, + "device/notifications/$oldestNotificationID", + "DELETE" ); + LaMetric2_SetMessage( $hash, +"'$notificationIDs->{$oldestNotificationID}{icon}' '$notificationIDs->{$oldestNotificationID}{text}' '' '' '$oldestCancelID'" + ); + } + } + elsif ( $service eq "device/display" && $method eq "PUT" ) { + + # screensaver time was updated but final + # mode should not be time_based + return LaMetric2_SetScreensaver( $hash, $info->{caller} ) + if ( defined( $info->{caller} ) ); + + #FIXME there is a bug in the current firmware that will set + # start_time to the same value as end_time when changing + # from mode time_based to when_dark. It will not happen when + # changing from time_based to off. I opened a ticket with the + # manufacturer on 2018-11-30. + # As a result, the original start_time value cannot be kept + # correctly when switching between the modes, especially when + # using the on/off setter. + } + + # API version >= 2.1.0 + elsif ( $service eq "device/apps" && $method eq "GET" ) { + $hash->{helper}{apps} = $response; + delete $hash->{helper}{channels} + if ( defined( $hash->{helper}{channels} ) ); + + foreach my $app ( sort keys %{$response} ) { + foreach + my $widget ( sort keys %{ $response->{$app}{widgets} } ) + { + my $channelName; + + if ( $response->{$app}{widgets}{$widget}{settings} + {_title} ) + { + $channelName .= + $response->{$app}{widgets}{$widget}{settings} + {_title}; + } + else { + $channelName .= $response->{$app}{title}; + } + $channelName =~ s/\s/_/g; + + my $i = 1; + my $channelName2 = lc($channelName); + while ( + defined( $hash->{helper}{channels}{$channelName2} ) + ) + { + $i++; + $channelName2 = lc( $channelName . "_" . $i ); + } + $channelName .= "_" . $i if ( $i > 1 ); + + $hash->{helper}{channels}{ lc($channelName) } = ( + { + 'name' => $channelName, + 'package' => $response->{$app}{package}, + 'widget' => $widget, + } + ); + } + } + } + elsif ( $service =~ /^device\/apps\/.+\/widgets\/.+\/actions/ ) { + + } + elsif ( $service =~ /^device\/apps\/.+\/widgets\/.+\/activate/ ) { + + } + + # Update readings + # + + # If we received a response to a write command, + # make that data available + if ( $method ne "GET" ) { + my $endpoint = $response->{success}->{path}; + $endpoint =~ s/^(.*[\\\/])//; + $response->{$endpoint} = $response->{success}{data}; + } + elsif ( $service eq "device/display" ) { + $response->{display} = $response; + } + + if ( $service eq "device" ) { + readingsBulkUpdateIfChanged( $hash, "deviceName", + $response->{name} ); + readingsBulkUpdateIfChanged( $hash, "deviceSerialNumber", + $response->{serial_number} ); + readingsBulkUpdateIfChanged( $hash, "deviceOsVersion", + $response->{os_version} ); + readingsBulkUpdateIfChanged( $hash, "deviceMode", + $response->{mode} ); + + # write model to INTERNAL and attribute + # to accomodate FHEM device use statistics + $hash->{MODEL} = $response->{model}; + $attr{$name}{model} = $response->{model}; + + # Trigger update of additional readings + LaMetric2_SendCommand( $hash, "device/apps", "GET", "" ); + LaMetric2_SendCommand( $hash, "device/display", "GET", "" ); + } + + if ( defined( $response->{audio} ) ) { + + # audio is muted + if ( $response->{audio}{volume} == 0 ) { + readingsBulkUpdateIfChanged( $hash, "mute", "on" ); + + my $currVolume = ReadingsVal( $name, "volume", 50 ); + $hash->{helper}{lastVolume} = $currVolume + if ( $currVolume > 0 ); + } + + # audio is not muted + else { + readingsBulkUpdateIfChanged( $hash, "mute", "off" ); + delete $hash->{helper}{lastVolume} + if ( defined( $hash->{helper}{lastVolume} ) ); + } + + readingsBulkUpdateIfChanged( $hash, "volume", + $response->{audio}{volume} ); + } + + if ( defined( $response->{bluetooth} ) ) { + if ( $response->{bluetooth}{active} == 1 ) { + readingsBulkUpdateIfChanged( $hash, "bluetooth", "on" ); + } + else { + readingsBulkUpdateIfChanged( $hash, "bluetooth", "off" ); + } + + readingsBulkUpdateIfChanged( $hash, "bluetoothAvailable", + $response->{bluetooth}{available} ); + readingsBulkUpdateIfChanged( $hash, "bluetoothName", + $response->{bluetooth}{name} ); + readingsBulkUpdateIfChanged( $hash, "bluetoothDiscoverable", + $response->{bluetooth}{discoverable} ); + readingsBulkUpdateIfChanged( $hash, "bluetoothPairable", + $response->{bluetooth}{pairable} ); + readingsBulkUpdateIfChanged( $hash, "bluetoothAddress", + $response->{bluetooth}{address} ); + } + + if ( defined( $response->{display} ) ) { + readingsBulkUpdateIfChanged( $hash, "brightness", + $response->{display}{brightness} ); + readingsBulkUpdateIfChanged( $hash, "brightnessMode", + $response->{display}{brightness_mode} ); + + $state = "on"; + $power = "on"; + + # only API version >= 2.1.0 + if ( defined( $response->{display}{screensaver} ) ) { + my $screensaver = "off"; + + if ( $response->{display}{screensaver}{enabled} == 1 ) { + foreach ( + keys %{ $response->{display}{screensaver}{modes} } ) + { + if ( $response->{display}{screensaver}{modes} + {$_}{enabled} == 1 ) + { + $screensaver = $_; + last; + } + } + } + my $screensaverStartTime = LaMetric2_gmtime_str2local( + $response->{display}{screensaver}{modes}{time_based} + {start_time} ); + my $screensaverEndTime = LaMetric2_gmtime_str2local( + $response->{display}{screensaver}{modes}{time_based} + {end_time} ); + + readingsBulkUpdateIfChanged( $hash, "screensaver", + $screensaver ); + readingsBulkUpdateIfChanged( $hash, "screensaverStartTime", + $screensaverStartTime ); + readingsBulkUpdateIfChanged( $hash, "screensaverEndTime", + $screensaverEndTime ); + + if ( + $screensaver eq "time_based" + && LaMetric2_IsDuringTimeframe( + $screensaverStartTime, $screensaverEndTime + ) + ) + { + $state = "off"; + $power = "off"; + } + } + } + + if ( defined( $response->{wifi} ) ) { + readingsBulkUpdateIfChanged( $hash, "wifiActive", + $response->{wifi}{active} ); + readingsBulkUpdateIfChanged( $hash, "wifiAddress", + $response->{wifi}{address} ); + readingsBulkUpdateIfChanged( $hash, "wifiAvailable", + $response->{wifi}{available} ); + readingsBulkUpdateIfChanged( $hash, "wifiEncryption", + $response->{wifi}{encryption} ); + readingsBulkUpdateIfChanged( $hash, "wifiEssid", + $response->{wifi}{essid} ); + readingsBulkUpdateIfChanged( $hash, "wifiIp", + $response->{wifi}{ip} ); + readingsBulkUpdateIfChanged( $hash, "wifiMode", + $response->{wifi}{mode} ); + readingsBulkUpdateIfChanged( $hash, "wifiNetmask", + $response->{wifi}{netmask} ); + + # Always trigger notification to allow + # plotting of this value + readingsBulkUpdate( $hash, "wifiStrength", + $response->{wifi}{strength} ); + } + + } + else { + $result = "Server error " . $code . ": " . encode_utf8($data); + } + + # Do not show read-only commands in readings + if ( $method ne "GET" || $result ne "success" ) { + readingsBulkUpdate( $hash, "lastCommand", + $service . " (" . $method . ")" ); + readingsBulkUpdate( $hash, "lastCommandResult", $result ); + } + } + + readingsBulkUpdateIfChanged( $hash, "power", $power ); + readingsBulkUpdateIfChanged( $hash, "state", $state ); + readingsBulkUpdateIfChanged( $hash, "stateAV", + LaMetric2_GetStateAV($hash) ); + + readingsEndUpdate( $hash, 1 ); + + return; +} + +#------------------------------------------------------------------------------ +sub LaMetric2_CheckState($;$) { + my ( $hash, $update ) = @_; + + my $name = $hash->{NAME}; + + Log3 $name, 5, "LaMetric2 $name: called function LaMetric2_CheckState()"; + + RemoveInternalTimer( $hash, 'LaMetric2_CheckState' ); + + if ( AttrVal( $name, "disable", 0 ) == 1 ) { + + # Retry in INTERVAL*5 seconds + InternalTimer( gettimeofday() + ( $hash->{INTERVAL} * 5 ), + 'LaMetric2_CheckState', $hash, 0 ); + + return; + } + else { + # only get specific fields and exclude those we query sequentially + LaMetric2_SendCommand( $hash, "device", "GET", +"fields=name,serial_number,os_version,mode,model,audio,bluetooth,wifi" + ); + + InternalTimer( gettimeofday() + $hash->{INTERVAL}, + 'LaMetric2_CheckState', $hash, 0 ); + } + + return; +} + +#------------------------------------------------------------------------------ +sub LaMetric2_CycleMessage { + my $hash = shift; + my $name = $hash->{NAME}; + my $info = {}; + my $count = keys %{ $hash->{helper}{cancelIDs} }; + + $info->{caller} = "CycleMessage"; + + Log3 $name, 5, "LaMetric2 $name: called function LaMetric2_CycleMessage()"; + + if ( $count >= 2 ) { + InternalTimer( gettimeofday() + 5, "LaMetric2_CycleMessage", $hash, 0 ); + + # Update notification queue first to see which is the + # oldest notification. Callback will send the real cycle + LaMetric2_SendCommand( $hash, "device/notifications", "GET", undef, + $info ); + } + + return; +} + +#------------------------------------------------------------------------------ +sub LaMetric2_SetBrightness { + my $hash = shift; + my $name = $hash->{NAME}; + my %values = (); + + Log3 $name, 5, "LaMetric2 $name: called function LaMetric2_SetBrightness()"; + + my ($brightness) = @_; + + if ($brightness) { + my %body = ( brightness_mode => $brightness ); + + if ( looks_like_number($brightness) ) { + $body{brightness} = $brightness; + $body{brightness_mode} = "manual"; + } + + LaMetric2_SendCommand( $hash, "device/display", "PUT", + encode_json( \%body ) ); + + return; + } + else { + # There was a problem with the arguments + return "Syntax: set $name brightness 1-100|auto|manual"; + } +} + +#------------------------------------------------------------------------------ +sub LaMetric2_SetScreensaver { + my $hash = shift; + my $name = $hash->{NAME}; + my $info = {}; + my %values = (); + + Log3 $name, 5, + "LaMetric2 $name: called function LaMetric2_SetScreensaver()"; + + my ( $screensaver, $startTime, $endTime ) = @_; + + if ( $screensaver eq "time_based" || ( $startTime && $endTime ) ) { + my $sT = + ReadingsVal( $name, "screensaverStartTime", + AttrVal( $name, "defaultScreensaverStartTime", "00:00:00" ) ); + my $eT = + ReadingsVal( $name, "screensaverEndTime", + AttrVal( $name, "defaultScreensaverEndTime", "00:06:00" ) ); + + if ( $startTime && $endTime ) { + $startTime .= ":00" if ( $startTime =~ /^\d{2}:\d{2}$/ ); + $endTime .= ":00" if ( $endTime =~ /^\d{2}:\d{2}$/ ); + $sT = $startTime; + $eT = $endTime; + } + + my %body = ( + screensaver => { + enabled => 1, + mode => 'time_based', + mode_params => { + enabled => 1, + start_time => LaMetric2_localtime_str2gm($sT), + end_time => LaMetric2_localtime_str2gm($eT), + }, + }, + ); + + $info->{caller} = $screensaver if ( $screensaver ne "time_based" ); + + LaMetric2_SendCommand( $hash, "device/display", "PUT", + encode_json( \%body ), $info ); + } + elsif ( $screensaver eq "off" || $screensaver ne "time_based" ) { + my %body = ( + screensaver => { + enabled => 0, + }, + ); + + if ( $screensaver ne "off" ) { + $body{screensaver}{enabled} = 1; + $body{screensaver}{mode} = $screensaver; + $body{screensaver}{mode}{mode_params} = ( + { + enabled => 1, + } + ); + } + + LaMetric2_SendCommand( $hash, "device/display", "PUT", + encode_json( \%body ), $info ); + } + else { + return +"Syntax: set $name screensaver off|when_dark|time_based [startTime] [endTime]"; + } +} + +#------------------------------------------------------------------------------ +sub LaMetric2_SetBluetooth { + my $hash = shift; + my $name = $hash->{NAME}; + my %values = (); + + Log3 $name, 5, "LaMetric2 $name: called function LaMetric2_SetBluetooth()"; + + my ($bluetooth) = @_; + + if ( $bluetooth eq "on" || $bluetooth eq "off" ) { + my %body = ( active => 0 ); + + if ( $bluetooth eq "on" ) { + $body{active} = 1; + } + + LaMetric2_SendCommand( $hash, "device/bluetooth", "PUT", + encode_json( \%body ) ); + + return; + } + else { + # There was a problem with the arguments + return "Syntax: set $name bluetooth on|off ['new name']"; + } +} + +#------------------------------------------------------------------------------ +sub LaMetric2_SetOnOff { + my $hash = shift; + my $cmd = shift; + my $name = $hash->{NAME}; + + Log3 $name, 5, "LaMetric2 $name: called function LaMetric2_SetOnOff() $cmd"; + + my $body; + my ($power) = @_; + $cmd = $power if ($power); + my $currPower = ReadingsVal( $name, "power", "on" ); + + if ( $cmd eq "toggle" ) { + $cmd = "on" if ( $currPower eq "off" ); + $cmd = "off" if ( $currPower eq "on" ); + } + + if ( $cmd eq "off" ) { + my $currScreensaverStartTime = + ReadingsVal( $name, "screensaverStartTime", undef ); + my $currScreensaverEndTime = + ReadingsVal( $name, "screensaverEndTime", undef ); + + if ( defined($currScreensaverStartTime) + && defined($currScreensaverEndTime) + && $currScreensaverStartTime ne "00:00:00" + && $currScreensaverEndTime ne "23:59:59" ) + { + $hash->{helper}{lastScreensaverStartTime} = + $currScreensaverStartTime; + $hash->{helper}{lastScreensaverEndTime} = $currScreensaverEndTime; + } + + return LaMetric2_SetScreensaver( $hash, "time_based", "00:00:00", + "23:59:59" ); + } + elsif ( $cmd eq "on" ) { + my $screensaver = "when_dark"; + my $onStatus = AttrVal( $name, "defaultOnStatus", "always" ); + $screensaver = "off" if ( $onStatus eq "always" ); + + my $ret = LaMetric2_SetScreensaver( + $hash, + $screensaver, + defined( $hash->{helper}{lastScreensaverStartTime} ) + ? $hash->{helper}{lastScreensaverStartTime} + : AttrVal( $name, "defaultScreensaverStartTime", "00:00:00" ), + defined( $hash->{helper}{lastScreensaverEndTime} ) + ? $hash->{helper}{lastScreensaverEndTime} + : AttrVal( $name, "defaultScreensaverEndTime", "06:00:00" ) + ); + + delete $hash->{helper}{lastScreensaverStartTime} + if ( defined( $hash->{helper}{lastScreensaverStartTime} ) ); + delete $hash->{helper}{lastScreensaverEndTime} + if ( defined( $hash->{helper}{lastScreensaverEndTime} ) ); + + return $ret; + } + else { + # There was a problem with the arguments + return "Syntax: set $name power on|off|toggle"; + } +} + +#------------------------------------------------------------------------------ +sub LaMetric2_SetVolume { + my $hash = shift; + my $cmd = shift; + my $name = $hash->{NAME}; + + Log3 $name, 5, + "LaMetric2 $name: called function LaMetric2_SetVolume() $cmd"; + + my %body = (); + my ($volume) = @_; + my $currVolume = ReadingsVal( $name, "volume", 0 ); + + #FIXME Due to a bug in the firmware, we need to increase + # the desired volume by one if it is between 1 and 99 ... + # Although the resulting volume will be +1 sometimes, but + # that happens less often than the other way around ... + # Opened a ticket with the manufacturer on 2018-11-30 + if ( looks_like_number($volume) ) { + $volume++ if ( $volume > 0 && $volume < 100 ); #FIXME + $body{volume} = $volume; + } + elsif ( lc($cmd) eq "volumeup" ) { + $currVolume = $currVolume + 10; + $currVolume = 100 if ( $currVolume > 100 ); + $currVolume++ if ( $currVolume > 0 && $currVolume < 100 ); #FIXME + $body{volume} = $currVolume; + } + elsif ( lc($cmd) eq "volumedown" ) { + $currVolume = $currVolume - 10; + $currVolume = 0 if ( $currVolume < 0 ); + $currVolume++ if ( $currVolume > 0 && $currVolume < 100 ); #FIXME + $body{volume} = $currVolume; + } + else { + # There was a problem with the arguments + return "Syntax: set $name volume 1-100"; + } + + LaMetric2_SendCommand( $hash, "device/audio", "PUT", + encode_json( \%body ) ); +} + +#------------------------------------------------------------------------------ +sub LaMetric2_SetMute { + my $hash = shift; + my $name = $hash->{NAME}; + + Log3 $name, 5, "LaMetric2 $name: called function LaMetric2_SetMute()"; + + my ($mute) = @_; + + my %body = (); + my $volume = ReadingsVal( $name, "volume", 0 ); + if ( $mute eq "on" || ( $mute eq "" && $volume != 0 ) ) { + $body{volume} = "0"; + } + elsif ( $mute eq "off" || ( $mute eq "" && $volume == 0 ) ) { + $volume = + $hash->{helper}{lastVolume} + ? $hash->{helper}{lastVolume} + : AttrVal( $name, "defaultVolume", 50 ); + $volume++ if ( $volume > 0 && $volume < 100 ); #FIXME + $body{volume} = $volume; + } + else { + # There was a problem with the arguments + return "Syntax: set $name [mute|muteT] [on|off]"; + } + + LaMetric2_SendCommand( $hash, "device/audio", "PUT", + encode_json( \%body ) ); +} + +#------------------------------------------------------------------------------ +sub LaMetric2_SetApp { + my $hash = shift; + my $cmd = shift; + my $name = $hash->{NAME}; + + my ( $subCommand, $appId ) = @_; + + Log3 $name, 5, + "LaMetric2 $name: called function LaMetric2_SetApp() " + . $cmd . " / " + . $subCommand; + + if ( lc($cmd) eq "channelup" || $subCommand eq "next" ) { + LaMetric2_SendCommand( $hash, "device/apps/next", "PUT", "" ); + + return; + } + elsif ( lc($cmd) eq "channeldown" || $subCommand eq "prev" ) { + LaMetric2_SendCommand( $hash, "device/apps/prev", "PUT", "" ); + return; + } + elsif ( $subCommand + && defined( $hash->{helper}{channels}{ lc($subCommand) } ) ) + { + $package = $hash->{helper}{channels}{ lc($subCommand) }{package}; + $widget = $hash->{helper}{channels}{ lc($subCommand) }{widget}; + LaMetric2_SendCommand( $hash, + "device/apps/$package/widgets/$widget/activate", + "PUT", "" ); + return; + } + else { + # There was a problem with the arguments + return "Syntax: set $name $cmd [app_name]"; + } +} + +#------------------------------------------------------------------------------ +sub LaMetric2_SetMessage { + my $hash = shift; + my $name = $hash->{NAME}; + my %values = (); + my $info = {}; + + Log3 $name, 5, "LaMetric2 $name: called function LaMetric2_SetMessage()"; + + #Split parameters + my $param = join( " ", @_ ); + my $argc = 0; + + if ( $param =~ +/(".*"|'.*')\s*(".*"|'.*')\s*(".*"|'.*')\s*(".*"|'.*')\s*(".*"|'.*')\s*$/s + ) + { + $argc = 5; + } + elsif ( + $param =~ /(".*"|'.*')\s*(".*"|'.*')\s*(".*"|'.*')\s*(".*"|'.*')\s*$/s ) + { + $argc = 4; + } + elsif ( $param =~ /(".*"|'.*')\s*(".*"|'.*')\s*(".*"|'.*')\s*$/s ) { + $argc = 3; + } + elsif ( $param =~ /(".*"|'.*')\s*(".*"|'.*')\s*$/s ) { + $argc = 2; + } + elsif ( $param =~ /(".*"|'.*')\s*$/s ) { + $argc = 1; + } + + Log3 $name, 4, "LaMetric2 $name: Found $argc argument(s)"; + + if ( $argc == 1 ) { + $values{message} = $1; + Log3 $name, 4, "LaMetric2 $name: message = $values{message}"; + } + else { + $values{icon} = $1 if ( $argc >= 1 ); + $values{message} = $2 if ( $argc >= 2 ); + $values{sound} = $3 if ( $argc >= 3 ); + $values{repeat} = $4 if ( $argc >= 4 ); + $values{cycles} = $5 if ( $argc >= 5 ); + } + + #Remove quotation marks + if ( $values{icon} =~ /^['"](.*)['"]$/s ) { + $values{icon} = $1; + } + if ( $values{message} =~ /^['"](.*)['"]$/s ) { + $values{message} = $1; + } + if ( $values{sound} =~ /^['"](.*)['"]$/s ) { + $values{sound} = $1; + } + if ( $values{repeat} =~ /^['"](.*)['"]$/s ) { + $values{repeat} = $1; + } + if ( $values{cycles} =~ /^['"](.*)['"]$/s ) { + $values{cycles} = $1; + } + + # inject to new function + return LaMetric2_SetNotification( $hash, undef, \%values ); +} + +#------------------------------------------------------------------------------ +sub LaMetric2_SetNotification { + my ( $hash, $a, $h ) = @_; + my $name = $hash->{NAME}; + my %values = (); + my $info = {}; + + Log3 $name, 5, + "LaMetric2 $name: called function LaMetric2_SetNotification()"; + + # Set defaults for object + $notificationType = "msg"; + $values{icontype} = + $h->{icontype} + ? $h->{icontype} + : AttrVal( $name, "notificationIconType", "info" ); + $values{icontype} = "none" if ( $values{title} && $values{title} ne "" ); + $values{lifetime} = + $h->{lifetime} + ? $h->{lifetime} + : AttrVal( $name, "notificationLifeTime", 120 ); + $values{priority} = + $h->{priority} + ? $h->{priority} + : AttrVal( $name, "notificationPriority", "info" ); + + # Set defaults for model + $values{sound} = + $h->{sound} + ? $h->{sound} + : AttrVal( $name, "notificationSound", "" ); + $values{sound} = "" + if ( $values{sound} eq "none" || $values{sound} eq "off" ); + $values{repeat} = + h->{repeat} + ? $h->{repeat} + : 1; + $values{cycles} = + h->{cycles} + ? $h->{cycles} + : 1; + + # Set defaults for frames + $values{icon} = + $h->{icon} ? $h->{icon} : AttrVal( $name, "notificationIcon", "i8919" ); + $values{icon} = "" if ( $values{icon} eq "none" ); + + # special text frame at the beginning + $values{title} = $h->{title} ? $h->{title} : ""; + + # text frame(s) + $values{message} = + $h->{message} ? $h->{message} + : ( + $h->{msg} ? $h->{msg} + : ( $h->{text} ? $h->{text} : join ' ', @$a ) + ); + + # chart frame + if ( $h->{chart} ) { + my $str = $h->{chart}; + $str =~ s/[^\d,.]//g; + foreach ( split( /,/, $str ) ) { + push @{ $values{chart} }, round( $_, 0 ); + } + + # take object+model defaults for this frame type + # if there is no text frame in this notification + unless ( defined( $values{message} ) && $values{message} ne "" ) { + $notificationType = "chart"; + + $values{icontype} = + $h->{icontype} + ? $h->{icontype} + : AttrVal( $name, "notificationChartIconType", + $values{icontype} ); + $values{icontype} = "none" + if ( $values{title} + && $values{title} ne "" + && !$h->{forceicontype} ); + + $values{lifetime} = + $h->{lifetime} + ? $h->{lifetime} + : AttrVal( $name, "notificationChartLifeTime", + $values{lifetime} ); + + $values{priority} = + $h->{priority} + ? $h->{priority} + : AttrVal( $name, "notificationChartPriority", + $values{priority} ); + + $values{sound} = + $h->{sound} + ? $h->{sound} + : AttrVal( $name, "notificationChartSound", $values{sound} ); + $values{sound} = "" + if ( $values{sound} eq "none" || $values{sound} eq "off" ); + } + } + + # goal frame + if ( defined( $h->{goal} ) ) { + $h->{goal} = ( $h->{goal} =~ /(-?\d+(\.\d+)?)/ ? $1 : "" ); + + if ( looks_like_number( $h->{goal} ) ) { + + # goaltype + if ( $h->{goaltype} ) { + my $descr = readingsDesc( "", $h->{goaltype} ); + + if ($descr) { + my $ref_base = $descr->{ref_base}; + + # Use icon from goaltype DB if none was explicitly given + $h->{goalicon} = + $LaMetric2_goaltype_icons{$ref_base}{lm_icon} + if ( + !defined( $h->{goalicon} ) + && defined( + $LaMetric2_goaltype_icons{$ref_base}{lm_icon} + ) + ); + + # Format number with Unit.pm + my ( $txt, $txt_long, $value, $value_num, $unit ) = + formatValue( "", $h->{goaltype}, $h->{goal}, $descr, + undef, undef, "en" ); + + $h->{goal} = $value_num ? $value_num : $value; + $h->{goalunit} = $unit unless ( defined( $h->{goalunit} ) ); + } + } + + # Construct request + $values{goal} = ( + { + icon => $h->{goalicon} ? $h->{goalicon} + : ( + defined( $values{message} ) + && $values{message} ne "" ? "" + : AttrVal( $name, "notificationGoalIcon", "a11460" ) + ), + goalData => { + start => round( + $h->{goalstart} ? $h->{goalstart} + : AttrVal( $name, "notificationGoalStart", 0 ), + 0 + ), + current => round( $h->{goal}, 0 ), + end => round( + $h->{goalend} ? $h->{goalend} + : AttrVal( $name, "notificationGoalEnd", 100 ), + 0 + ), + unit => trim( + ( + $h->{goalunit} ? $h->{goalunit} + : AttrVal( $name, "notificationGoalUnit", '%' ) + ) + ) + } + } + ); + + $values{goal}{start} = $h->{goal} + if ( $h->{goal} < $values{goal}{start} ); + $values{goal}{end} = $h->{goal} + if ( $h->{goal} > $values{goal}{end} ); + + $values{goal}{icon} = '' if ( $values{goal}{icon} eq 'none' ); + + # take object+model defaults for this frame type + # if there is no text frame in this notification + unless ( defined( $values{message} ) && $values{message} ne "" ) { + $notificationType = "goal"; + + $values{icontype} = + $h->{icontype} + ? $h->{icontype} + : AttrVal( $name, "notificationGoalIconType", + $values{icontype} ); + $values{icontype} = "none" + if ( $values{title} + && $values{title} ne "" + && !$h->{forceicontype} ); + + $values{lifetime} = + $h->{lifetime} + ? $h->{lifetime} + : AttrVal( $name, "notificationGoalLifeTime", + $values{lifetime} ); + + $values{priority} = + $h->{priority} + ? $h->{priority} + : AttrVal( $name, "notificationGoalPriority", + $values{priority} ); + + $values{sound} = + $h->{sound} + ? $h->{sound} + : AttrVal( $name, "notificationGoalSound", $values{sound} ); + $values{sound} = "" + if ( $values{sound} eq "none" + || $values{sound} eq "off" ); + } + } + } + + # metric frame + if ( defined( $h->{metric} ) ) { + my $metric = $h->{metric}; + my $metricold = + defined( $h->{metricold} ) + ? ( $h->{metricold} =~ /(-?\d+(\.\d+)?)/ ? $1 : undef ) + : undef; + $metric = ( $metric =~ /(-?\d+(\.\d+)?)/ ? $1 : "" ); + + if ( looks_like_number($metric) ) { + my $icon = ""; + if ( defined($metricold) ) { + if ( $metric < $metricold ) { + $icon = "i124"; + } + elsif ( $metric > $metricold ) { + $icon = "i120"; + } + else { + $icon = "i401"; + } + } + + # metrictype + if ( $h->{metrictype} ) { + my $descr = readingsDesc( "", $h->{metrictype} ); + + if ($descr) { + my $ref_base = $descr->{ref_base}; + + # Use icon from metrictype DB if none was explicitly given + $h->{metricicon} = + $LaMetric2_metrictype_icons{$ref_base}{lm_icon} + if ( + !defined( $h->{metricicon} ) + && defined( + $LaMetric2_metrictype_icons{$ref_base}{lm_icon} + ) + ); + + # Format number with Unit.pm + my $lang = lc( + $h->{metriclang} + ? $h->{metriclang} + : AttrVal( + $name, + "notificationMetricLang", + AttrVal( "global", "language", "en" ) + ) + ); + my ( $txt, $txt_long, $value, $value_num, $unit ) = + formatValue( "", $h->{metrictype}, $metric, $descr, + undef, undef, $lang ); + + if ( defined( $h->{metricunit} ) ) { + $h->{metric} = $value_num ? $value_num : $value; + } + else { + #FIXME special characters need to be removed to enable this + # $h->{metric} = $h->{metriclong} ? $txt_long : $txt; + $h->{metric} = + $h->{metriclong} + ? $txt_long + : ( $value_num ? $value_num : $value ) . " " . $unit; + } + } + } + + # Construct request + $values{metric} = ( + { + icon => $h->{metricicon} ? $h->{metricicon} + : ( + defined( $values{message} ) + && $values{message} ne "" ? "" + : ( + defined($metricold) ? $icon + : AttrVal( + $name, "notificationMetricIcon", "i9559" + ) + ) + ), + text => $h->{metric}, + } + ); + + $values{metric}{icon} = "" + if ( $values{metric}{icon} eq "none" ); + + # take object+model defaults for this frame type + # if there is no text frame in this notification + unless ( defined( $values{message} ) && $values{message} ne "" ) { + $notificationType = "metric"; + + $values{icontype} = + $h->{icontype} + ? $h->{icontype} + : AttrVal( $name, "notificationMetricIconType", + $values{icontype} ); + $values{icontype} = "none" + if ( $values{title} + && $values{title} ne "" + && !$h->{forceicontype} ); + + $values{lifetime} = + $h->{lifetime} + ? $h->{lifetime} + : AttrVal( $name, "notificationMetricLifeTime", + $values{lifetime} ); + + $values{priority} = + $h->{priority} + ? $h->{priority} + : AttrVal( $name, "notificationMetricPriority", + $values{priority} ); + + $values{sound} = + $h->{sound} + ? $h->{sound} + : AttrVal( $name, "notificationMetricSound", $values{sound} ); + $values{sound} = "" + if ( $values{sound} eq "none" + || $values{sound} eq "off" ); + } + } + } + + return +"Usage: $name msg [ option1= option2='' ... ]" + unless ( ( defined( $values{message} ) && $values{message} ne "" ) + || $values{title} ne "" + || defined( $values{chart} ) + || defined( $values{goal} ) + || defined( $values{metric} ) ); + + # Building notification + # + + my %notification = ( + priority => $values{priority}, + icon_type => $values{icontype}, + lifeTime => $values{lifetime} * 1000, + model => { + cycles => $values{cycles}, + }, + ); + + readingsBeginUpdate($hash); + + readingsBulkUpdate( $hash, "lastNotificationType", $notificationType ); + readingsBulkUpdate( $hash, "lastNotificationPriority", $values{priority} ); + readingsBulkUpdate( $hash, "lastNotificationIconType", $values{icontype} ); + readingsBulkUpdate( $hash, "lastNotificationLifeTime", $values{lifetime} ); + + # If a cancelID was provided, send a "sticky" notification + if ( !looks_like_number( $values{cycles} ) || $values{cycles} == 0 ) { + $info->{cancelID} = $values{cycles}; + $values{cycles} = 0; + + # start Validation Timer + RemoveInternalTimer( $hash, "LaMetric2_CycleMessage" ); + InternalTimer( gettimeofday() + 5, "LaMetric2_CycleMessage", $hash, 0 ); + } + + my $sound; + if ( $values{sound} ne "" ) { + my @sFields = split /:/, $values{sound}; + my $soundId; + my $soundCat; + + if ( defined( $sFields[1] ) ) { + $soundId = $sFields[1]; + $soundCat = $sFields[0]; + } + else { + $soundId = $sFields[0]; + foreach my $cat ( keys %LaMetric2_sounds ) { + $soundCat = $cat + if ( grep ( /^$soundId$/, @{ $LaMetric2_sounds{$cat} } ) ); + } + } + + if ( $soundId && $soundCat ) { + $notification{model}{sound} = { + category => $soundCat, + id => $soundId, + repeat => $values{repeat}, + }; + readingsBulkUpdate( $hash, "lastNotificationSound", + "$soundCat:$soundId" ); + } + } + else { + readingsBulkUpdate( $hash, "lastNotificationSound", "off" ); + } + + if ( $values{title} ne "" ) { + push @{ $notification{model}{frames} }, + ( + { + icon => $values{icon}, + text => $values{title}, + } + ); + readingsBulkUpdate( $hash, "lastNotificationTitle", $values{title} ); + } + else { + readingsBulkUpdate( $hash, "lastNotificationTitle", "" ); + } + + if ( defined( $values{message} ) && $values{message} ne "" ) { + foreach my $line ( split /\\n/, $values{message} ) { + $line = trim($line); + next if ( !$line || $line eq "" ); + + my $ico = $values{icon}; + + if ( $notification{model}{frames} ) { + $ico = "" unless ( $h->{forceicon} ); + + #TODO define icon inline per frame. + # Must be compatible with FHEM-msg command + } + + push @{ $notification{model}{frames} }, + ( + { + icon => $ico, + text => $line, + } + ); + } + readingsBulkUpdate( $hash, "lastMessage", $values{message} ); + } + + if ( $values{metric} ) { + push @{ $notification{model}{frames} }, $values{metric}; + readingsBulkUpdate( $hash, "lastMetric", $values{metric}{text} ); + } + + if ( $values{goal} ) { + if ( $notification{model}{frames} ) { + $values{goal}{icon} = "" unless ( $h->{goalicon} ); + } + push @{ $notification{model}{frames} }, $values{goal}; + readingsBulkUpdate( $hash, "lastGoal", + $values{goal}{goalData}{current} . " " + . $values{goal}{goalData}{unit} ); + } + + if ( $values{chart} ) { + push @{ $notification{model}{frames} }, + ( { chartData => $values{chart} } ); + readingsBulkUpdate( $hash, "lastChart", + join( ',', @{ $values{chart} } ) ); + } + + readingsEndUpdate( $hash, 1 ); + + LaMetric2_SendCommand( $hash, "device/notifications", "POST", + encode_json( \%notification ), $info ); + + return; +} + +#------------------------------------------------------------------------------ +sub LaMetric2_SetCancelMessage { + my $hash = shift; + my $name = $hash->{NAME}; + my $info = {}; + my $notificationID; + + my ($cancelID) = @_; + + # Remove quotation marks + if ( $cancelID =~ /^['"](.*)['"]$/s ) { + $cancelID = $1; + } + + $info->{cancelID} = $cancelID; + + Log3 $name, 5, + "LaMetric2 $name: called function LaMetric2_SetCancelMessage()"; + + # Update notification queue first to see if the notification still exists. + # Callback will send the real DELETE request + LaMetric2_SendCommand( $hash, "device/notifications", "GET", undef, $info ); + + return; +} + +#------------------------------------------------------------------------------ +sub LaMetric2_GetStateAV($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + + if ( ReadingsVal( $name, "presence", "absent" ) eq "absent" ) { + return "absent"; + } + elsif ( ReadingsVal( $name, "mute", "off" ) eq "on" ) { + return "muted"; + } + elsif ( ReadingsVal( $name, "power", "off" ) eq "off" ) { + return "off"; + } + elsif ( ReadingsVal( $name, "playStatus", "stopped" ) ne "stopped" ) { + return ReadingsVal( $name, "playStatus", "stopped" ); + } + else { + return ReadingsVal( $name, "power", "off" ); + } +} + +#------------------------------------------------------------------------------ +sub LaMetric2_gmtime_str2local($) { + my ($str) = @_; + my @a = split( /:/, $str ); + $a[2] = 0 unless ( $a[2] ); + + return strftime( '%H:%M:%S', + localtime( timegm( $a[2], $a[1], $a[0], 1, 0, 0 ) ) ); +} + +#------------------------------------------------------------------------------ +sub LaMetric2_localtime_str2gm($) { + my ($str) = @_; + my @a = split( /:/, $str ); + $a[2] = 0 unless ( $a[2] ); + + return strftime( '%H:%M:%S', + gmtime( timelocal( $a[2], $a[1], $a[0], 1, 0, 0 ) ) ); +} + +#------------------------------------------------------------------------------ +sub LaMetric2_IsDuringTimeframe($$;$) { + my ( $start, $end, $currTime ) = @_; + my @aStart = split( /:/, $start ); + $aStart[2] = 0 unless ( $aStart[2] ); + my @aEnd = split( /:/, $end ); + $aEnd[2] = 0 unless ( $aEnd[2] ); + + my $currTimestamp = time; + my ( $secs, $mins, $hours, $mday, $mon, $year, $wday, $yday, $isdst ) = + localtime($currTimestamp); + + my $startTimestamp = + timelocal( $aStart[2], $aStart[1], $aStart[0], $mday, $mon, $year ); + + my $endTimestamp = + timelocal( $aEnd[2], $aEnd[1], $aEnd[0], $mday, $mon, $year ); + + # if endTime is before start time + if ( $endTimestamp < $startTimestamp ) { + + # end is tomorrow + if ( $currTimestamp >= $endTimestamp ) { + $endTimestamp = $endTimestamp + ( 60 * 60 * 24 ); + } + + # start was yesterday + elsif ( $currTimestamp < $startTimestamp ) { + $startTimestamp = $startTimestamp - ( 60 * 60 * 24 ); + } + } + + if ( $currTimestamp < $endTimestamp + && $currTimestamp >= $startTimestamp ) + { + return 1; + } + + return 0; +} + +1; + +############################################################################### + +=pod +=item device +=item summary Controls for LaMetric Time devices via API +=item summary_DE Steuert LaMetric Time Geräte über die offizielle Schnittstelle +=begin html + + +

+ LaMetric2 +

+
    + LaMetric is a smart clock with retro design. It may be used to display different information and can receive notifications.
    + A a developer account is required to use this module.
    + Visit developer.lametric.comfor further information.
    +
    +
    + Define +
      + define <name> LaMetric2 <ip> <apikey> [<port>]
      +
      + Please create an accountto receive the API key.
      + You will find the api key in the account menu My Devices
      +
      + The attribute port is optional. Port 4343 will be used by default and connection will be encrypted.
      + Examples: +
        + define lametric LaMetric2 192.168.2.31 a20205cb7eace9c979c27fd55413296b8ac9dafbfb5dae2022a1dc6b77fe9d2d +
      +
        + define lametric LaMetric2 192.168.2.31 a20205cb7eace9c979c27fd55413296b8ac9dafbfb5dae2022a1dc6b77fe9d2d 4343 +
      +
        + define lametric LaMetric2 192.168.2.31 a20205cb7eace9c979c27fd55413296b8ac9dafbfb5dae2022a1dc6b77fe9d2d 8080 +
      +

    + Set +
      + msg +
        + set <LaMetric2_device> msg <text> [<option1>=<value> <option2>="<value with space in it>" ...]
        +
        + The following options may be used to adjust message content and notification behavior:
        +
        + message   - type: text - Your message text. Using this option takes precedence; non-option text content will be discarded.
        + title     - type: text - This text will be the first part of the notification. It will normally replace the first frame defined as 'icontype' if 'forceicontype' was not explicitly set.
        + icon     - type: text - Icon for the message frame. Icon can be defined as ID or in binary format. Icon ID looks like <prefix>XXX, where <prefix> is “i” (for static icon) or “a” (for animation). XXX is the number of the icon and can be found at developer.lametric.com/icons
        + icontype     - type: text - Represents the nature of notification. Defaults to 'info'. +++ [none] no notification icon will be shown. +++ [info] “i” icon will be displayed prior to the notification. Means that notification contains information, no need to take actions on it. +++ [alert] “!!!” icon will be displayed prior to the notification. Use it when you want the user to pay attention to that notification as it indicates that something bad happened and user must take immediate action.
        + forceicontype    - type: boolean - Will display the icontype before the title frame. Otherwise the title will be the first frame. Defaults to 0.
        + lifetime - type: text - The time notification lives in queue to be displayed in seconds. If notification stayed in queue for longer than lifetime seconds – it will not be displayed. Defaults to 120.
        + priority  - type: info,warning,critical - Priority of the message +++ [info] This priority means that notification will be displayed on the same “level” as all other notifications on the device that come from apps (for example facebook app). This notification will not be shown when screensaver is active. By default message is sent with “info” priority. This level of notification should be used for notifications like news, weather, temperature, etc. +++ [warning] Notifications with this priority will interrupt ones sent with lower priority (“info”). Should be used to notify the user about something important but not critical. For example, events like “someone is coming home” should use this priority when sending notifications from smart home. +++ [critical] The most important notifications. Interrupts notification with priority info or warning and is displayed even if screensaver is active. Use with care as these notifications can pop in the middle of the night. Must be used only for really important notifications like notifications from smoke detectors, water leak sensors, etc. Use it for events that require human interaction immediately.
        + sound     - type: text - Name of the sound to play. See attribute notificationSound for full list.
        + repeat    - type: integer - Defines the number of times sound must be played. If set to 0 sound will be played until notification is dismissed. Defaults to 1.
        + cycles - type: integer - The number of times message should be displayed. If cycles is set to 0, notification will stay on the screen until user dismisses it manually or a 'set msgCancel' command was sent. By default it is set to 1.
        +
        + chart - type: integer-array - Adds a frame to display a chart. Must contain a comma separated list of numbers.
        +
        + goal - type: float - Add a goal frame to display the status within a measuring scale.
        + goal* - type: n/a - All other options described for the goal-setter can be used here by adding the prefix 'goal' to it.
        +
        + metric - type: float - The number to be shown.
        + metric* - type: n/a - All other options described for the metric-setter can be used here by adding the prefix 'metric' to it.
        +
        + Examples: +
          + set lametric msg My first LaMetric message.
          + set lametric msg My second LaMetric message.\nThis time with two text frames.
          + set lametric msg Message with own frame icon. icon=i334
          + set lametric msg "Another LaMetric message in double quotes."
          + set lametric msg 'Another LaMetric message in single quotes.'
          + set lametric msg message="LaMetric message using explicit option for text content." This part of the text will be ignored.
          + set lametric msg This is a message with a title. title="This is a subject"
          + set lametric msg title="This is a subject, too!" This is another message with a title set at the beginning of the command.
          +
          + set lametric msg chart=1,2,3,4,5,7 title='Some Data'
          +
          + set lametric msg goal=97.8765 title='Goal to 100%'
          + set lametric msg goal=45.886 goalend=50 title='Goal to 50%'
          + set lametric msg goal=45.886 goalend=50 goaltype=m title='Goal to 50 meters' using FHEM RType auto format and symbol
          + set lametric msg goal=45.886 goalend=50 goalunit=m title='Goal to 50 meters' using manual unit symbol and format
          +
          + set lametric msg metric=21.87 title='Temperature' without unit
          + set lametric msg metric=21.87 metrictype=c title='Temperature' using FHEM RType auto format and symbol
          + set lametric msg metric=21.87 metricunit='°C' title='Temperature' using manual unit symbol and format
          +

        +
      +

    +
    +
      + msgCancel +
        + set <LaMetric2_device> msgCancel '<cancelID>'
        +
        +
        +
          + set LaMetric21 msgCancel 'cancelID'
          +
        +
      +

    +
    +
      + chart +
        + set <LaMetric2_device> chart <1,2,3,4,5,6> [<option1>=<value> <option2>="<value with space in it>" ...]
        +
        + Any option from the msg-setter can be used to modify the chart notification.
        +
        + Examples: +
          + set lametric chart 1,2,3,4,5,7 title='Some Data'
          +
        +
      +

    +
    +
      + goal +
        + set <LaMetric2_device> goal <number> [<option1>=<value> <option2>="<value with space in it>" ...]
        +
        + In addition to any option from the msg-setter, the following options may be used to further adjust a goal notification:
        +
        + start - type: text - The beginning of the measuring scale. Defaults to '0'.
        + end - type: text - The end of the measuring scale. Defaults to '100'.
        + unit - type: text - The unit value to be displayed after the number. Defaults to '%'.
        + type - type: text - Defines this number as a FHEM readings type (RType). Can be either a reading name or an actual RType (e.g. 'c', 'f', 'temperature' or 'temperaturef' will result in '°C' resp. '°F'). The number will be automatically re-formatted based on SI definition. An appropriate frame icon will be set. It may be explicitly overwritten by using the respective option in the message.
        +
        + Examples: +
          + set lametric goal 97.8765 title='Goal to 100%'
          + set lametric goal 45.886 end=50 title='Goal to 50%'
          + set lametric goal 45.886 end=50 type=m title='Goal to 50 meters' using FHEM RType auto format and symbol
          + set lametric goal 45.886 end=50 unit=m title='Goal to 50 meters' using manual unit symbol and format
          +

        +
      +

    +
    +
      + metric +
        + set <LaMetric2_device> metric <number> [<option1>=<value> <option2>="<value with space in it>" ...]
        +
        + In addition to any option from the msg-setter, the following options may be used to further adjust a metric notification:
        +
        + old - type: text - when set to the old number, a frame icon for higher, lower, or equal will be set automatically.
        + unit - type: text - The unit value to be displayed after the number. Defaults to ''.
        + type - type: text - Defines this number as a FHEM readings type (RType). Can be either a reading name or an actual RType (e.g. 'c', 'f', 'temperature' or 'temperaturef' will result in 'xx.x °C' resp. 'xx.x °F'). The number will be automatically re-formatted based on SI definition. The correct unit symbol as well as and an appropriate frame icon will be set. They may be explicitly overwritten by using the respective other options in the message.
        + lang - type: text - The base language to be used when 'type' is evaluating the number. Defaults to 'en'.
        + long - type: boolean - When set and used together with 'type', the unit name will be added in text format instead of using the unit symbol. Defaults to '0'.
        +
        + Examples: +
          + set lametric metric 21.87 title='Temperature' without unit
          +
          + set lametric metric 21.87 type=c title='Temperature' using FHEM RType auto format and symbol
          + set lametric metric 21.87 type=temperature title='Temperature' using FHEM RType auto format and symbol
          + set lametric metric 21.87 unit='°C' title='Temperature' using manual unit symbol and format
          +
          + set lametric metric 81.76 type=f title='Temperature' using FHEM RType auto format and symbol
          + set lametric metric 81.76 type=temperaturef title='Temperature' using FHEM RType auto format and symbol
          + set lametric metric 81.76 unit='°F' title='Temperature' using manual unit symbol and format
          +

        +
      +

    +
      + bluetooth +
        + set <LaMetric2_device> bluetooth <on|off>
        +
      +

    +
    +
      + brightness +
        + set <LaMetric2_device> brightness <1-100>
        +
      +

    +
    +
      + brightnessMode +
        + set <LaMetric2_device> brightness <auto|manual> +
      +

    +
    +
      + channel +
        + set <LaMetric2_device> channel <channel_name>
        +
      +

    +
    +
      + channelDown +
        + set <LaMetric2_device> channelDown
        +
      +

    +
    +
      + channelUp +
        + set <LaMetric2_device> channelUp
        +
      +

    +
    +
      + mute +
        + set <LaMetric2_device> mute <on|off> +
      +

    +
    +
      + muteT +
        + set <LaMetric2_device> muteT
        +
      +

    +
    +
      + on +
        + set <LaMetric2_device> on
        +
      +

    +
    +
      + off +
        + set <LaMetric2_device> off
        +
      +

    +
    +
      + power +
        + set <LaMetric2_device> power <on|off>
        +
      +

    +
    +
      + screensaver +
        + set <LaMetric2_device> screensaver <off|when_dark|time_based> [<begin hh:mm or hh:mm:ss> <end hh:mm or hh:mm:ss>]
        +
      +

    +
    +
      + statusRequest +
        + set <LaMetric2_device> statusRequest
        +
      +

    +
    +
      + toggle +
        + set <LaMetric2_device> toggle
        +
      +

    +
    +
      + volume +
        + set <LaMetric2_device> volume <0-100>
        +
      +

    +
    +
      + volumeDown +
        + set <LaMetric2_device> volumeDown
        +
      +

    +
    +
      + volumeUp +
        + set <LaMetric2_device> volumeUp
        +
      +

    +
+

+
+
+

+

+ Get +

+
    +
  • N/A +
  • +
+

+
+

+

+ Attributes +

+
    +
  • + defaultOnStatus
    + When the device is turned on, this will be the screensaver status to put it in to. Defaults to 'off'. +
  • +
  • + defaultScreensaverStartTime
    + When FHEM was rebooted, it will not know the last status of the device before the screensaver was enabled. This will be the fallback value for 'start'. Defaults to '00:00'. +
  • +
  • + defaultScreensaverEndTime
    + When FHEM was rebooted, it will not know the last status of the device before the screensaver was enabled. This will be the fallback value for 'start'. Defaults to '06:00'. +
  • +
  • + defaultVolume
    + When FHEM was rebooted, it will not know the last status of the device before volume was muted. This will be the fallback value. Defaults to '50'. +
  • +
  • + https
    + Set this to 0 to disable encrypted connectivity and enforce unsecure connection via port 8080. When a port was set explicitly when defining the device, this attribute controls explicit enable/disable of encryption. +
  • +
  • + notificationIcon
    + Fallback value for icon when sending text notifications. +
  • +
  • + notificationIconType
    + Fallback value for icontype when sending text notifications. Defaults to 'info'. +
  • +
  • + notificationLifeTime
    + Fallback value for lifetype when sending text notifications. Defaults to '120'. +
  • +
  • + notificationPriority
    + Fallback value for priority when sending text notifications. Defaults to 'info'. +
  • +
  • + notificationSound
    + Fallback value for sound when sending text notifications. Defaults to 'off'. +
  • +
  • + notificationChartIconType
    + Fallback value for icontype when sending chart notifications. Defaults to 'info'. +
  • +
  • + notificationChartLifeTime
    + Fallback value for lifetype when sending chart notifications. Defaults to '120'. +
  • +
  • + notificationChartPriority
    + Fallback value for priority when sending chart notifications. Defaults to 'info'. +
  • +
  • + notificationChartSound
    + Fallback value for sound when sending text notifications. Defaults to 'off'. +
  • +
  • + notificationGoalIcon
    + Fallback value for icon when sending goal notifications. +
  • +
  • + notificationGoalIconType
    + Fallback value for icontype when sending goal notifications. Defaults to 'info'. +
  • +
  • + notificationGoalLifeTime
    + Fallback value for lifetype when sending goal notifications. Defaults to '120'. +
  • +
  • + notificationGoalPriority
    + Fallback value for priority when sending goal notifications. Defaults to 'info'. +
  • +
  • + notificationGoalSound
    + Fallback value for sound when sending goal notifications. Defaults to 'off'. +
  • +
  • + notificationGoalStart
    + Fallback value for measuring scale start when sending goal notifications. Defaults to '0'. +
  • +
  • + notificationGoalEnd
    + Fallback value for measuring scale end when sending goal notifications. Defaults to '100'. +
  • +
  • + notificationGoalUnit
    + Fallback value for unit when sending goal notifications. Defaults to '%'. +
  • +
  • + notificationMetricIcon
    + Fallback value for icon when sending metric notifications. +
  • +
  • + notificationMetricIconType
    + Fallback value for icontype when sending metric notifications. Defaults to 'info'. +
  • +
  • + notificationMetricLang
    + Default language when evaluating metric notifications. Defaults to 'en'. +
  • +
  • + notificationMetricLifeTime
    + Fallback value for lifetype when sending metric notifications. Defaults to '120'. +
  • +
  • + notificationMetricPriority
    + Fallback value for priority when sending metric notifications. Defaults to 'info'. +
  • +
  • + notificationMetricSound
    + Fallback value for sound when sending metric notifications. Defaults to 'off'. +
  • +
  • + notificationMetricUnit
    + Fallback value for unit when sending metric notifications. Defaults to ''. +
  • +
+

+
+

+

+ Generated events: +

+
    +
  • N/A +
  • +
+ +=end html +=begin html_DE + + +

LaMetric2

+
    +Leider keine deutsche Dokumentation vorhanden. Die englische Version gibt es hier: LaMetric2 +
+ +=end html_DE +=cut diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt index efb1e76a1..e139a6b86 100644 --- a/fhem/MAINTAINER.txt +++ b/fhem/MAINTAINER.txt @@ -307,6 +307,7 @@ FHEM/70_EGPM.pm alexus Sonstiges FHEM/70_ENIGMA2.pm loredo Multimedia FHEM/70_Jabber.pm BioS Unterstuetzende Dienste FHEM/70_JSONMETER.pm tupol Sonstiges (Link als PM an tupol) +FHEM/70_LaMetric2.pm loredo Multimedia FHEM/70_MEDIAPORTAL.pm Reinerlein Multimedia FHEM/70_PHTV.pm loredo Multimedia FHEM/70_ONKYO_AVR.pm loredo Multimedia