############################################################################### # $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 #TODO #- implement key pinning to improve security for self-signed certificate #- rtype replace of special characters that device cannot display: # 0x00A0 -> " " space # 0x202F -> " " space # 0x00B2 -> "2" m2 # 0x00B3 -> "3" m3 # 0x0025 -> "%" # 0x00B0 -> "°" #- msgSchema (überlappende Parameter wie priority) #-mehrere metric/chart/goal/msg frames des gleichen typs (derzeit gehen per msg nur 1x zusätzlich metric,chart,goal) -> reihenfolge? package main; use strict; use warnings; use POSIX; use Encode; use FHEM::Meta; use HttpUtils; use IO::Socket::SSL; use SetExtensions; use Time::HiRes qw(gettimeofday time); use Time::Local; use Unit; use utf8; #use Data::Dumper; # try to use JSON::MaybeXS wrapper # for chance of better performance + open code eval { require JSON::MaybeXS; import JSON::MaybeXS qw( decode_json encode_json ); 1; }; if ($@) { $@ = undef; # try to use JSON wrapper # for chance of better performance eval { # JSON preference order local $ENV{PERL_JSON_BACKEND} = 'Cpanel::JSON::XS,JSON::XS,JSON::PP,JSON::backportPP' unless ( defined( $ENV{PERL_JSON_BACKEND} ) ); require JSON; import JSON qw( decode_json encode_json ); 1; }; if ($@) { $@ = undef; # In rare cases, Cpanel::JSON::XS may # be installed but JSON|JSON::MaybeXS not ... eval { require Cpanel::JSON::XS; import Cpanel::JSON::XS qw(decode_json encode_json); 1; }; if ($@) { $@ = undef; # In rare cases, JSON::XS may # be installed but JSON not ... eval { require JSON::XS; import JSON::XS qw(decode_json encode_json); 1; }; if ($@) { $@ = undef; # Fallback to built-in JSON which SHOULD # be available since 5.014 ... require JSON::PP; import JSON::PP qw(decode_json encode_json); 1; } } } } 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 => '', app => '', 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', inputUp => ':noArg', inputDown => ':noArg', statusRequest => ':noArg', screensaver => ':off,when_dark,time_based', ); my %LaMetric2_setsHidden = ( msgCancel => 1, input => 1, refresh => 1, channelUp => 1, channelDown => 1, play => 1, pause => 1, stop => 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, }; return FHEM::Meta::InitMod( __FILE__, $hash ); } #------------------------------------------------------------------------------ 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})$/ ); # Initialize the device return $@ unless ( FHEM::Meta::SetInternals($hash) ); $hash->{VERSION} = "v2.1.0"; $hash->{HOST} = $host; $hash->{".API_KEY"} = $apikey; $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} = 'play:rc_PLAY channelDown:rc_PREVIOUS channelUp:rc_NEXT stop:rc_STOP muteT:rc_MUTE inputUp:rc_RIGHT inputDown: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:play:stop:channelUp:inputDown:input:inputUp'; # 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} = "illumination"; $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 .= " input:,"; $usage .= encode_utf8( join( ',', map $hash->{helper}{inputs}{$_}{name}, sort keys %{ $hash->{helper}{inputs} } ) ) if ( defined( $hash->{helper}{inputs} ) && keys %{ $hash->{helper}{inputs} } > 0 ); $usage .= " play:noArg stop:noArg channelUp:noArg channelDown:noArg" if ( defined( $hash->{helper}{apps}{'com.lametric.radio'} ) && keys %{ $hash->{helper}{apps}{'com.lametric.radio'} } > 0 ); return $usage; } return "Unable to set $cmd: Device is unreachable" if ( ReadingsVal( $name, 'presence', 'absent' ) eq 'absent' && lc($cmd) ne 'refresh' && lc($cmd) ne 'statusrequest' ); 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, $h, @$a ) if ( $cmd eq 'app' || lc($cmd) eq 'channelup' || lc($cmd) eq 'channeldown' || $cmd eq 'input' || lc($cmd) eq 'inputup' || lc($cmd) eq 'inputdown' || $cmd eq 'play' || $cmd eq 'pause' || $cmd eq 'stop' ); 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; my $httpNoShutdown = ( defined( $attr{$name}{"http-noshutdown"} ) && $attr{$name}{"http-noshutdown"} eq "0" ) ? 0 : 1; my $timeout = 5; my $http_proto; Log3 $name, 5, "LaMetric2 $name: called function LaMetric2_SendCommand()"; # API version was included to service if ( $service =~ /^v\d+\// ) { $apiVersion = ""; } # dev options currently only via API v1 elsif ( $service =~ /^dev\// ) { $apiVersion = "v1/"; } # module internal pre-defined version else { $apiVersion = "v2/"; } $data = ( defined($data) ) ? $data : ""; 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', 'Cache-Control' => 'no-cache', ); if ( defined( $info->{token} ) ) { $header{'X-Access-Token'} = $info->{token}; } else { $header{'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: " . ( defined($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" . ( defined($param) && ref($param) && defined( $param->{httpheader} ) ? $param->{httpheader} : "" ) . "\n\nHTTP RESPONSE BODY:\n" . ( defined($data) ? $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( m/[\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 my $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} ) { my $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} ) ); } # API version >= 2.1.0 elsif ( $service eq "device/apps" && $method eq "GET" ) { $hash->{helper}{apps} = $response; delete $hash->{helper}{inputs} if ( defined( $hash->{helper}{inputs} ) ); foreach my $app ( sort keys %{$response} ) { # widgets foreach my $widgetId ( sort keys %{ $response->{$app}{widgets} } ) { my $inputName; if ( $response->{$app}{widgets}{$widgetId}{settings} {_title} ) { $inputName .= $response->{$app}{widgets}{$widgetId}{settings} {_title}; } else { $inputName .= $response->{$app}{title}; } $inputName =~ s/\s/_/g; my $i = 1; my $inputName2 = lc($inputName); while ( defined( $hash->{helper}{inputs}{$inputName2} ) ) { $i++; $inputName2 = lc( $inputName . "_" . $i ); } $inputName .= "_" . $i if ( $i > 1 ); my $vendorId; my $appId; if ( $response->{$app}{package} =~ /^(.+)\.([^.]+)$/ ) { $vendorId = $1; $appId = $2; } $hash->{helper}{inputs}{ lc($inputName) } = ( { 'name' => $inputName, 'package_id' => $response->{$app}{package}, 'vendor_id' => $vendorId, 'app_id' => $appId, 'widget_id' => $widgetId, } ); } } } elsif ( $service =~ /^device\/apps\/.+\/widgets\/.+\/actions/ ) { } # Update readings # # If we received a response to a write command, # make that data available if ( $method ne "GET" && $method ne "DELETE" && defined( $response->{success} ) && defined( $response->{success}{path} ) && defined( $response->{success}{data} ) ) { 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 ( ref($response) eq "HASH" ) { 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_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", "illumination" ); $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 ); if ( looks_like_number($volume) ) { $body{volume} = $volume; } elsif ( lc($cmd) eq "volumeup" ) { $currVolume = $currVolume + 10; $currVolume = 100 if ( $currVolume > 100 ); $body{volume} = $currVolume; } elsif ( lc($cmd) eq "volumedown" ) { $currVolume = $currVolume - 10; $currVolume = 0 if ( $currVolume < 0 ); $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 ); $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 $h = shift; my $package = decode_utf8(shift); my $action = shift; my $name = $hash->{NAME}; # inject action for Radio app if ( lc($cmd) eq "channeldown" ) { $cmd = "app"; $package = "com.lametric.radio"; $action = "radio.prev"; } elsif ( lc($cmd) eq "channelup" ) { $cmd = "app"; $package = "com.lametric.radio"; $action = "radio.next"; } elsif ( lc($cmd) eq "play" ) { $cmd = "app"; $package = "com.lametric.radio"; $action = "radio.play"; } elsif ( lc($cmd) eq "stop" || lc($cmd) eq "pause" ) { $cmd = "app"; $package = "com.lametric.radio"; $action = "radio.stop"; } Log3 $name, 5, "LaMetric2 $name: called function LaMetric2_SetApp() " . $cmd . " / " . $package; if ( lc($cmd) eq "inputup" ) { LaMetric2_SendCommand( $hash, "device/apps/next", "PUT", "" ); } elsif ( lc($cmd) eq "inputdown" ) { LaMetric2_SendCommand( $hash, "device/apps/prev", "PUT", "" ); } elsif ( ( $cmd eq "app" || $cmd eq "input" ) && $package ) { my $packageId; my $widgetId; my $vendorId; my $appId; my $actionId; # user gave widget display name as package name if ( defined( $hash->{helper}{inputs}{ lc($package) } ) ) { $packageId = $hash->{helper}{inputs}{ lc($package) }{package_id}; $widgetId = $hash->{helper}{inputs}{ lc($package) }{widget_id}; $vendorId = $hash->{helper}{inputs}{ lc($package) }{vendor_id}; $appId = $hash->{helper}{inputs}{ lc($package) }{app_id}; } else { # user gave packageId as package name if ( defined( $hash->{helper}{apps}{$package} ) ) { $packageId = $package; } # find packageId else { foreach my $id ( keys %{ $hash->{helper}{apps} } ) { if ( $hash->{helper}{apps}{$id}{package} =~ /\.$package$/ ) { $packageId = $hash->{helper}{apps}{$id}{package}; last; } } } # if we now know the packageId, find widgetId if ($packageId) { my %widgetlist = (); foreach my $id ( keys %{ $hash->{helper}{apps}{$packageId}{widgets} } ) { $widgetlist{ $hash->{helper}{apps}{$packageId}{widgets}{$id} {index} } = $id; } # best guess for widgetId: # use ID with lowest index my @widgets = sort keys %widgetlist; $widgetId = $widgetlist{ $widgets[0] }; } # user gave widgetId as package name unless ($widgetId) { foreach my $id ( keys %{ $hash->{helper}{inputs} } ) { if ( $hash->{helper}{inputs}{$id}{widget_id} eq $id ) { $packageId = $hash->{helper}{inputs}{$id}{package_id}; $widgetId = $hash->{helper}{inputs}{$id}{widget_id}; last; } } } } # only continue if widget exists unless ( $packageId && $widgetId ) { return "Unable to find widget for $package"; } # get vendor and app ID if ( $packageId && ( !$vendorId || !$appId ) ) { if ( $packageId =~ /^(.+)\.([^.]+)$/ ) { $vendorId = $1; $appId = $2; } } # user wants to push data to a non-public indicator app if ( $action && ( $action eq "push" || $action eq $appId . ".push" ) && !defined( $hash->{helper}{apps}{$packageId}{actions}{$action} ) && !defined( $hash->{helper}{apps}{$packageId}{actions} { $appId . "." . $action } ) ) { # Ready to send to app if ( defined( $h->{model} ) && defined( $h->{model}{frames} ) ) { return "Missing app token" unless ( defined( $h->{token} ) ); return "Missing frame data" unless ( ref( $h->{model}{frames} ) eq "ARRAY" ); my %body = (); my $info; $info->{token} = $h->{token}; $body{frames} = $h->{model}{frames}; LaMetric2_SendCommand( $hash, "dev/widget/update/$packageId/" . $hash->{helper}{apps}{$packageId}{version_code} . ( $h->{channels} ? "?channels=" . $h->{channels} : "" ), "POST", encode_json( \%body ), $info ); } # first parse it using msg setter else { $h->{app} = $packageId; return LaMetric2_SetNotification( $hash, \@_, $h ); } } # user gave action parameter for public app elsif ($action) { # find actionId if ( defined( $hash->{helper}{apps}{$packageId}{actions}{$action} ) ) { $actionId = $action; } elsif ( defined( $hash->{helper}{apps}{$packageId}{actions} { $appId . "." . $action } ) ) { $actionId = $appId . "." . $action; } return "Unknown action $action" unless ($actionId); my %body = ( id => $actionId ); $body{params} = $h if ($h); LaMetric2_SendCommand( $hash, "device/apps/$packageId/widgets/$widgetId/actions", "POST", encode_json( \%body ) ); } # user wants to switch to widget else { LaMetric2_SendCommand( $hash, "device/apps/$packageId/widgets/$widgetId/activate", "PUT", "" ); } } else { # There was a problem with the arguments return "Syntax: set $name $cmd [ [param1=value param2=value ...] ]"; } } #------------------------------------------------------------------------------ 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 my $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} = defined( $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 ) ); $values{message} = "" if ( $values{message} eq "none" ); # chart frame if ( $h->{chart} ) { my $str = $h->{chart}; $str =~ s/[^\d,.]//g; foreach ( split( /,/, $str ) ) { push @{ $values{chart}{chartData} }, 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}; # 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" ); } } } # Push to private/shared indicator app if ( defined( $h->{app} ) ) { $notificationType = "app"; $values{priority} = ""; $values{icontype} = ""; $values{lifetime} = ""; $values{sound} = ""; } return "Usage: $name msg [ option1= option2='' ... ]" unless ( $values{title} ne "" || ( defined( $values{message} ) && $values{message} ne "" ) || ( defined( $values{icon} ) && $values{icon} ne "" ) || defined( $values{chart} ) || defined( $values{goal} ) || defined( $values{metric} ) ); # 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 ); } # Building notification # my %notification = ( priority => $values{priority}, icon_type => $values{icontype}, lifetime => round( $values{lifetime} * 1000, 0 ), 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} ); 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" ); } my $index = 0; if ( $values{title} ne "" ) { push @{ $notification{model}{frames} }, ( { icon => $values{icon}, text => $values{title}, index => $index++, } ); readingsBulkUpdate( $hash, "lastNotificationTitle", $values{title} ); } else { readingsBulkUpdate( $hash, "lastNotificationTitle", "" ); } if ( defined( $values{message} ) ) { # empty frame if ( $values{message} eq "" ) { push @{ $notification{model}{frames} }, ( { icon => $values{icon}, text => "", index => $index++, } ) # only if there is no other # non-text frame afterwards if ( !defined( $values{metric} ) && !defined( $values{chart} ) && !defined( $values{goal} ) ); } # regular frames else { 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} || $line eq "" ); #TODO define icon inline per frame. # Must be compatible with FHEM-msg command } push @{ $notification{model}{frames} }, ( { icon => $ico, text => decode_utf8($line), index => $index++, } ); } } readingsBulkUpdate( $hash, "lastMessage", $values{message} ); } if ( $values{metric} ) { $values{metric}{index} = $index++; push @{ $notification{model}{frames} }, $values{metric}; readingsBulkUpdate( $hash, "lastMetric", $values{metric}{text} ); } if ( $values{goal} ) { $values{goal}{index} = $index++; 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} ) { $values{chart}{index} = $index++; push @{ $notification{model}{frames} }, $values{chart}; readingsBulkUpdate( $hash, "lastChart", join( ',', @{ $values{chart}{chartData} } ) ); } readingsEndUpdate( $hash, 1 ); # push frames to private/shared indicator app if ( defined( $h->{app} ) ) { $notification{package} = $h->{app}; $notification{token} = $h->{token} if ( defined( $h->{token} ) ); $notification{channels} = $h->{channels} if ( defined( $h->{channels} ) ); return LaMetric2_SetApp( $hash, "app", \%notification, $h->{app}, "push" ); } # send frames as notification else { LaMetric2_SendCommand( $hash, "device/notifications", "POST", encode_json( \%notification ), $info ); } } #------------------------------------------------------------------------------ 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 at the device.
        If cycles is set to a text string, a sticky notification is created that may also be dismissed using 'set msgCancel' command (find its description below). 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.

        app - type: text - app_name to push this message to that particular app. Requires matching token parameter (see below).
        token - type: text - Private access token to be used when pushing data to an app. Can be retrieved from developer.lametric.com/applications/app/<app_number> of the corresponding app.

        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

          set lametric msg app=MyPrivateFHEMapp token=ASDFGHJKL23456789 Show this message to my app.
          set lametric msg app=MyPrivateFHEMapp token=ASDFGHJKL23456789 icon=i334 Show this message to my app and use my icon.
          set lametric msg app=MyPrivateFHEMapp token=ASDFGHJKL23456789 Show this message to my app.\nThis is a second frame.
          set lametric msg app=MyPrivateFHEMapp token=ASDFGHJKL23456789 title="This is the head frame" This text goes to the 2nd frame.



      msgCancel
        set <LaMetric2_device> msgCancel '<cancelID>'


          set LaMetric21 msgCancel 'cancelID'


        Note: Setter will only appear when a notification was sent using the cycles parameter like cycles=<cancelID> while candelID may be any custom string you would like to use for cancellation afterwards.


      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


      app
        set <LaMetric2_device> app <app_name> <action_id> [param1=value param2=value]

        Some apps can be controlled by specific actions. Those can be controlled by pre-defined actions and might have optional or mandatory parameters as well.
        Examples:
          set lametric app clock alarm enabled=true time=10:00:00 wake_with_radio=false
          set lametric app clock alarm enabled=false

          set lametric app clock clockface icon='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAOklEQVQYlWNUVFBgwAeYcEncv//gP04FMEmsCmCSiooKjHAFMEF0SRQTsEnCFcAE0SUZGBgYGAl5EwA+6RhuHb9bggAAAABJRU5ErkJggg=='

          set lametric app stopwatch start
          set lametric app stopwatch pause
          set lametric app stopwatch reset

          set lametric app countdown configure duration=1800 start_now=true
          set lametric app countdown start
          set lametric app countdown pause
          set lametric app countdown reset



        To send data to a private/shared app, use 'push' as action_id. It will require the access token as parameter so that the device will accept data for that particular app:

        token - type: text - Private access token to be used when pushing data to an app. Can be retrieved from developer.lametric.com/applications/app/<app_number> of the corresponding app.

        Examples:
          set lametric app MyPrivateFHEMapp push token=ASDFGHJKL23456789 Show this message to my app.
          set lametric app MyPrivateFHEMapp push token=ASDFGHJKL23456789 icon=i334 Show this message to my app and use my icon.
          set lametric app MyPrivateFHEMapp push token=ASDFGHJKL23456789 Show this message to my app.\nThis is a second frame.
          set lametric app MyPrivateFHEMapp push token=ASDFGHJKL23456789 title="This is the head frame" This text goes to the 2nd frame.


        If you have configured channels for your app and would like to address a specific one, you may add the parameter 'channels' accordingly:
          set lametric app MyPrivateFHEMapp push token=ASDFGHJKL23456789 channels=ch1,ch3 Show this message in 2 of 3 channels in my app.


      play
        set <LaMetric2_device> play

        Will switch to the Radio app and start playback.


      stop
        set <LaMetric2_device> stop

        Will stop Radio playback.


      channelDown
        set <LaMetric2_device> channelDown

        When the Radio app is active, it will switch to the previous radio station.


      channelUp
        set <LaMetric2_device> channelUp

        When the Radio app is active, it will switch to the next radio station.


      bluetooth
        set <LaMetric2_device> bluetooth <on|off>


      brightness
        set <LaMetric2_device> brightness <1-100>


      brightnessMode
        set <LaMetric2_device> brightness <auto|manual>


      input
        set <LaMetric2_device> input <input_name>

        Will switch to a specific app. <input_name> may either be a display name, app ID or package ID.


      inputDown
        set <LaMetric2_device> inputDown

        Will switch to the previous app.


      inputUp
        set <LaMetric2_device> inputUp

        Will switch to the next app.


      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 'illumination'.
  • 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 lifetime 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 lifetime 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 =for :application/json;q=META.json 70_LaMetric2.pm { "version": "v2.3.3", "release_status": "stable", "author": [ "Julian Pawlowski " ], "x_fhem_maintainer": [ "loredo" ], "x_fhem_maintainer_github": [ "jpawlowski" ], "keywords": [ "Clock", "Display", "Time", "Watch" ], "prereqs": { "runtime": { "requires": { "perl": 5.014, "Encode": 0, "FHEM::Meta": 0, "HttpUtils": 0, "IO::Socket::SSL": 0, "JSON::PP": 0, "POSIX": 0, "SetExtensions": 0, "strict": 0, "SubProcess": 0, "Time::HiRes": 0, "Time::Local": 0, "Unit": 0, "utf8": 0, "warnings": 0 }, "recommends": { "JSON": 0 }, "suggests": { "Cpanel::JSON::XS": 0, "JSON::XS": 0 } } } } =end :application/json;q=META.json =cut