############################################################################### # $Id$ # https://pushover.net/api # package main; use HttpUtils; use utf8; use Data::Dumper; use HttpUtils; use Encode; # initialize ################################################################## sub Pushover_Initialize($$) { my ($hash) = @_; $hash->{DefFn} = "Pushover_Define"; $hash->{UndefFn} = "Pushover_Undefine"; $hash->{SetFn} = "Pushover_Set"; $hash->{AttrList} = "disable:0,1 disabledForIntervals do_not_notify:0,1 timestamp:0,1 title sound:pushover,bike,bugle,cashregister,classical,cosmic,falling,gamelan,incoming,intermission,magic,mechanical,pianobar,siren,spacealarm,tugboat,alien,climb,persistent,echo,updown,none device priority:0,1,2,-1,-2 callbackUrl retry expire " . $readingFnAttributes; #$hash->{parseParams} = 1; # not possible due to legacy msg command schema $hash->{'.msgParams'} = { parseParams => 1, }; } # regular Fn ################################################################## sub Pushover_Define($$) { my ( $hash, $def ) = @_; my @a = split( "[ \t]+", $def ); my $name = shift @a; my $type = shift @a; return "Invalid number of arguments: " . "define Pushover []" if ( int(@a) < 2 ); my ( $token, $user, $infix ) = @a; return "$user does not seem to be a valid user or group token" if ( $user !~ /^([a-zA-Z0-9]{30})$/ ); if ( defined($token) && defined($user) ) { $hash->{APP_TOKEN} = $token; $hash->{USER_KEY} = $user; if ( defined($infix) && $infix ne "" ) { $hash->{fhem}{infix} = $infix; return "Could not register infix, seems to be existing" if ( !Pushover_addExtension( $name, "Pushover_CGI", $infix ) ); } # start Validation Timer RemoveInternalTimer($hash); if ( ReadingsVal( $name, "tokenState", "invalid" ) ne "valid" || ReadingsVal( $name, "userState", "invalid" ) ne "valid" || $init_done ) { InternalTimer( gettimeofday() + 5, "Pushover_ValidateUser", $hash, 0 ); } else { InternalTimer( gettimeofday() + 21600, "Pushover_ValidateUser", $hash, 0 ); } return undef; } else { return "App or user/group token missing."; } return undef; } sub Pushover_Undefine($$) { my ( $hash, $name ) = @_; if ( defined( $hash->{fhem}{infix} ) ) { Pushover_removeExtension( $hash->{fhem}{infix} ); } RemoveInternalTimer($hash); return undef; } sub Pushover_Set($@) { my ( $hash, $name, $cmd, @args ) = @_; my ( $a, $h ) = parseParams( join " ", @args ); unless ( $cmd =~ /^(msg|msgCancel|glance)$/i ) { my $usage = "Unknown argument $cmd, choose one of msg glance"; my $cancelIds; foreach my $key ( sort keys %{ $hash->{READINGS} } ) { if ( defined( $hash->{READINGS}{$key}{VAL} ) && $hash->{READINGS}{$key}{VAL} ne "" && $key =~ /^cbCancelId_(\d+)$/ ) { $cancelIds .= "," if ($cancelIds); $cancelIds .= $hash->{READINGS}{$key}{VAL}; } } $usage .= " msgCancel:" . $cancelIds if ($cancelIds); return $usage; } return "Unable to send message: Device is disabled" if ( IsDisabled($name) ); return "Unable to send message: User key is invalid" if ( ReadingsVal( $name, "userState", "valid" ) eq "invalid" ); return "Unable to send message: App token is invalid" if ( ReadingsVal( $name, "tokenState", "valid" ) eq "invalid" ); return Pushover_SetMessage2( $hash, $cmd, $a, $h ) if ( $cmd eq 'glance' || ( $cmd eq 'msg' && ( join( " ", @args ) !~ m/^(".*"|'.*').*$/ || ( defined($h) && keys %{$h} > 0 ) ) ) ); return Pushover_CancelMessage( $hash, $cmd, $a, $h ) if ( lc($cmd) eq 'msgcancel' ); return Pushover_SetMessage( $hash, @args ) if ( $cmd eq 'msg' ); return undef; } # module Fn #################################################################### sub Pushover_addExtension($$$) { my ( $name, $func, $link ) = @_; my $url = "/$link"; return 0 if ( defined( $data{FWEXT}{$url} ) && $data{FWEXT}{$url}{deviceName} ne $name ); Log3 $name, 2, "Pushover $name: Registering Pushover for webhook URI $url ..."; $data{FWEXT}{$url}{deviceName} = $name; $data{FWEXT}{$url}{FUNC} = $func; $data{FWEXT}{$url}{LINK} = $link; $name->{HASH}{FHEMWEB_URI} = $url; return 1; } sub Pushover_removeExtension($) { my ($link) = @_; my $url = "/$link"; my $name = $data{FWEXT}{$url}{deviceName}; Log3 $name, 2, "Pushover $name: Unregistering Pushover for webhook URI $url..."; delete $data{FWEXT}{$url}; delete $name->{HASH}{FHEMWEB_URI}; } sub Pushover_CGI() { my ($request) = @_; my $hash; my $name = ""; my $link = ""; my $URI = ""; # data received if ( $request =~ m,^(/[^/]+?)(?:\&|\?)(.*)?$, ) { $link = $1; $URI = $2; # get device name $name = $data{FWEXT}{$link}{deviceName} if ( $data{FWEXT}{$link} ); $hash = $defs{$name}; # return error if no such device return ( "text/plain; charset=utf-8", "NOK No Pushover device for callback $link" ) unless ($name); Log3 $name, 4, "Pushover $name callback: link='$link' URI='$URI'"; my $webArgs; my $receipt = ""; my %revReadings; # extract values from URI foreach my $pv ( split( "&", $URI ) ) { next if ( $pv eq "" ); $pv =~ s/\+/ /g; $pv =~ s/%([\dA-F][\dA-F])/chr(hex($1))/ige; my ( $p, $v ) = split( "=", $pv, 2 ); $webArgs->{$p} = $v; } if ( defined( $webArgs->{receipt} ) ) { $receipt = $webArgs->{receipt}; } elsif ( defined( $webArgs->{FhemCallbackId} ) ) { $receipt = $webArgs->{FhemCallbackId}; } else { return ( "text/plain; charset=utf-8", "NOK missing argument receipt or FhemCallbackId" ); } # search for existing receipt keys %{ $hash->{READINGS} }; while ( my ( $key, $value ) = each %{ $hash->{READINGS} } ) { $revReadings{ $value->{VAL} } = $1 if ( defined( $value->{VAL} ) && $key =~ /^cb_(\d+)$/ ); } if ( defined( $revReadings{$receipt} ) ) { my $rAct = "cbAct_" . $revReadings{$receipt}; my $rAck = "cbAck_" . $revReadings{$receipt}; my $rAckAt = "cbAckAt_" . $revReadings{$receipt}; my $rAckBy = "cbAckBy_" . $revReadings{$receipt}; my $rCancelId = "cbCancelId_" . $revReadings{$receipt}; my $rDev = "cbDev_" . $revReadings{$receipt}; return ( "text/plain; charset=utf-8", "NOK " . $receipt . ": invalid argument 'acknowledged'" ) if ( !defined( $webArgs->{acknowledged} ) || $webArgs->{acknowledged} ne "1" ); return ( "text/plain; charset=utf-8", "NOK " . $receipt . ": invalid argument 'acknowledged_by'" ) if ( !defined( $webArgs->{acknowledged_by} ) || $webArgs->{acknowledged_by} ne $hash->{USER_KEY} ); if ( ReadingsVal( $name, $rAck, "1" ) eq "0" && $revReadings{$receipt} > int( time() ) ) { delete $hash->{READINGS}{$rCancelId} if ( defined( $hash->{READINGS}{$rCancelId} ) ); readingsBeginUpdate($hash); readingsBulkUpdate( $hash, $rAck, "1" ); readingsBulkUpdate( $hash, $rAckBy, $webArgs->{acknowledged_by} ); if ( defined( $webArgs->{acknowledged_at} ) && $webArgs->{acknowledged_at} ne "" ) { readingsBulkUpdate( $hash, $rAckAt, $webArgs->{acknowledged_at} ); } else { readingsBulkUpdate( $hash, $rAckAt, int( time() ) ); } my $redirect = ""; # run FHEM command if desired if ( ReadingsVal( $name, $rAct, "pushover://" ) !~ /^[\w-]+:\/\/.*$/ ) { $redirect = "pushover://"; fhem ReadingsVal( $name, $rAct, "" ); readingsBulkUpdate( $hash, $rAct, "executed: " . ReadingsVal( $name, $rAct, "" ) ); } # redirect to presented URL if ( ReadingsVal( $name, $rAct, "none" ) =~ /^[\w-]+:\/\/.*$/ ) { $redirect = ReadingsVal( $name, $rAct, "" ); } readingsEndUpdate( $hash, 1 ); return ( "text/html; charset=utf-8", "Click here to get redirected to your destination" . "" ) if ( $redirect ne "" ); } else { Log3 $name, 4, "Pushover $name callback: " . $receipt . " has expired"; return ( "text/plain; charset=utf-8", "NOK " . $receipt . " has expired" ); } } else { Log3 $name, 4, "Pushover $name callback: unable to find existing receipt " . $receipt; return ( "text/plain; charset=utf-8", "NOK unable to find existing receipt " . $receipt ); } } # no data received else { Log3 $name, 5, "Pushover $name callback: received malformed request\n$request"; return ( "text/plain; charset=utf-8", "NOK malformed request" ); } return ( "text/plain; charset=utf-8", "OK" ); } sub Pushover_SendCommand($$;$\%) { my ( $hash, $service, $cmd, $type ) = @_; my $name = $hash->{NAME}; my $address = "api.pushover.net"; my $port = "443"; my $apiVersion = "1"; my $http_method = "POST"; my $http_noshutdown = ( defined( $attr{$name}{"http-noshutdown"} ) && $attr{$name}{"http-noshutdown"} eq "0" ) ? 0 : 1; my $timeout; $cmd = ( defined($cmd) ) ? $cmd : ""; Log3 $name, 5, "Pushover $name: called function Pushover_SendCommand()"; my $http_proto; if ( $port eq "443" ) { $http_proto = "https"; } elsif ( defined( $attr{$name}{https} ) && $attr{$name}{https} eq "1" ) { $http_proto = "https"; $port = "443" if ( $port eq "80" ); } else { $http_proto = "http"; } my %header = ( Agent => 'FHEM-Pushover/1.0.0', 'User-Agent' => 'FHEM-Pushover/1.0.0', Accept => 'application/json;charset=UTF-8', 'Accept-Charset' => 'UTF-8', ); my $multipart = 0; if ( $cmd =~ /^--(.+)\r?\nContent-Disposition:/im ) { $multipart = 1; $header{'Content-Type'} = "multipart/form-data; boundary=" . $1; Log3 $name, 5, "Pushover $name: Sending as content type " . $header{'Content-Type'}; } if ( !defined( $type->{USER_KEY} ) ) { $cmd = Pushover_HttpForm( $cmd, $multipart, { "user" => $hash->{USER_KEY}, "token" => $hash->{APP_TOKEN} } ); } else { Log3 $name, 4, "Pushover $name: USER_KEY found in device name: " . $type->{USER_KEY}; $cmd = Pushover_HttpForm( $cmd, $multipart, { "user" => $type->{USER_KEY}, "token" => $hash->{APP_TOKEN} } ); } $cmd .= "--" if ($multipart); my $URL; my $response; my $return; if ( !defined($cmd) || $cmd eq "" ) { Log3 $name, 4, "Pushover $name: REQ $service"; } else { $cmd = "?" . $cmd . "&" if ( $http_method eq "GET" || $http_method eq "" ); Log3 $name, 4, "Pushover $name: REQ $service/" . urlDecode($cmd); } $URL = $http_proto . "://" . $address . ":" . $port . "/" . $apiVersion . "/" . $service; $URL .= $cmd if ( $http_method eq "GET" || $http_method eq "" ); if ( defined( $attr{$name}{timeout} ) && $attr{$name}{timeout} =~ /^\d+$/ ) { $timeout = $attr{$name}{timeout}; } else { $timeout = 3; } # send request via HTTP-GET method if ( $http_method eq "GET" || $http_method eq "" || $cmd eq "" ) { Log3 $name, 5, "Pushover $name: GET " . urlDecode($URL) . " (noshutdown=" . $http_noshutdown . ")"; HttpUtils_NonblockingGet( { url => $URL, timeout => $timeout, noshutdown => $http_noshutdown, data => undef, hash => $hash, service => $service, cmd => $cmd, type => $type, callback => \&Pushover_ReceiveCommand, httpversion => "1.1", loglevel => AttrVal( $name, "httpLoglevel", 4 ), header => \%header, # sslargs => { # SSL_verify_mode => 'SSL_verify_PEER', # }, } ); } # send request via HTTP-POST method elsif ( $http_method eq "POST" ) { Log3 $name, 5, "Pushover $name: GET " . $URL . " (POST DATA: " . urlDecode($cmd) . ", noshutdown=" . $http_noshutdown . ")"; HttpUtils_NonblockingGet( { url => $URL, timeout => $timeout, noshutdown => $http_noshutdown, data => $cmd, hash => $hash, service => $service, cmd => $cmd, type => $type, callback => \&Pushover_ReceiveCommand, httpversion => "1.1", loglevel => AttrVal( $name, "httpLoglevel", 4 ), header => \%header, } ); } # other HTTP methods are not supported else { Log3 $name, 1, "Pushover $name: ERROR: HTTP method " . $http_method . " is not supported."; } return; } sub Pushover_ReceiveCommand($$$) { my ( $param, $err, $data ) = @_; my $hash = $param->{hash}; my $name = $hash->{NAME}; my $service = $param->{service}; my $cmd = $param->{cmd}; my $state = ReadingsVal( $name, "state", "initialized" ); my $values = $param->{type}; my $return; Log3 $name, 5, "Pushover $name: Received HttpUtils callback:\n\nPARAM:\n" . Dumper($param) . "\n\nERROR:\n" . Dumper($err) . "\n\nDATA:\n" . Dumper($data); readingsBeginUpdate($hash); # service not reachable if ($err) { $state = "disconnected"; if ( !defined($cmd) || $cmd eq "" ) { Log3 $name, 4, "Pushover $name: RCV TIMEOUT $service"; } else { Log3 $name, 4, "Pushover $name: RCV TIMEOUT $service/" . urlDecode($cmd); } } # data received elsif ($data) { $state = "connected"; if ( !defined($cmd) || $cmd eq "" ) { Log3 $name, 4, "Pushover $name: RCV $service"; } else { Log3 $name, 4, "Pushover $name: RCV $service/" . urlDecode($cmd); } if ( $data ne "" ) { if ( $data =~ /^{/ || $data =~ /^\[/ ) { if ( !defined($cmd) || ref($cmd) eq "HASH" || $cmd eq "" ) { Log3 $name, 5, "Pushover $name: RES $service\n" . $data; } else { Log3 $name, 5, "Pushover $name: RES $service/" . urlDecode($cmd) . "\n" . $data; } # Use JSON module if possible eval { require JSON; import JSON qw( decode_json ); }; unless ($@) { my $json = JSON->new->allow_nonref; my $obj = eval { $json->decode( Encode::encode_utf8($data) ) }; $return = $obj unless ($@); } } else { if ( !defined($cmd) || ref($cmd) eq "HASH" || $cmd eq "" ) { Log3 $name, 5, "Pushover $name: RES ERROR $service\n" . $data; } else { Log3 $name, 5, "Pushover $name: RES ERROR $service/" . urlDecode($cmd) . "\n" . $data; } return undef; } } $return = Encode::encode_utf8($data) if ( ref($return) ne "HASH" ); ####################### # process return data # $values{result} = "ok"; # extract API stats my $apiLimit = 7500; my $apiRemaining = 1; my $apiReset; if ( $param->{httpheader} =~ m/X-Limit-App-Limit:[\s\t]*(.*)[\s\t\n]*/ ) { $apiLimit = $1; readingsBulkUpdateIfChanged( $hash, "apiLimit", $1 ),; } if ( $param->{httpheader} =~ m/X-Limit-App-Remaining:[\s\t]*(.*)[\s\t\n]*/ ) { $apiRemaining = $1; readingsBulkUpdateIfChanged( $hash, "apiRemaining", $1 ); } if ( $param->{httpheader} =~ m/X-Limit-App-Reset:[\s\t]*(.*)[\s\t\n]*/ ) { $apiReset = $1; readingsBulkUpdateIfChanged( $hash, "apiReset", $1 ); } # Server error if ( $param->{code} >= 500 ) { $state = "error"; $values{result} = "Server Error " . $param->{code}; } # error handling elsif ( ( $param->{code} == 200 || $param->{code} >= 400 ) && ( ( ref($return) eq "HASH" && $return->{status} ne "1" ) || ( ref($return) ne "HASH" && $return !~ m/"status":1,/ ) ) ) { $values{result} = "Error " . $param->{code} . ": Unspecified error occured"; if ( ref($return) eq "HASH" && defined $return->{errors} ) { $values{result} = "Error " . $param->{code} . ": " . join( ". ", @{ $return->{errors} } ); } elsif ( ref($return) ne "HASH" && $return =~ m/"errors":\[(.*)\]/ ) { $values{result} = "Error " . $param->{code} . ": " . $1; } $state = "error"; if ( ref($return) eq "HASH" && defined( $return->{token} ) ) { $state = "unauthorized"; readingsBulkUpdate( $hash, "tokenState", $return->{token} ); } elsif ( ref($return) ne "HASH" && $return =~ m/"token":"invalid"/ ) { $state = "unauthorized"; readingsBulkUpdate( $hash, "tokenState", "invalid" ); } else { readingsBulkUpdateIfChanged( $hash, "tokenState", "valid" ); } if ( ref($return) eq "HASH" && defined( $return->{user} ) ) { $state = "unauthorized" if ( !defined( $values->{USER_KEY} ) ); readingsBulkUpdate( $hash, "userState", $return->{user} ) if ( !defined( $values->{USER_KEY} ) ); $hash->{helper}{FAILED_USERKEYS}{ $values->{USER_KEY} } = "USERKEY " . $values->{USER_KEY} . " " . $return->{user} . " - " . $values{result} if ( defined( $values->{USER_KEY} ) ); } elsif ( ref($return) ne "HASH" && $return =~ m/"user":"invalid"/ ) { $state = "unauthorized" if ( !defined( $values->{USER_KEY} ) ); readingsBulkUpdate( $hash, "userState", "invalid" ) if ( !defined( $values->{USER_KEY} ) ); $hash->{helper}{FAILED_USERKEYS}{ $values->{USER_KEY} } = "USERKEY " . $values->{USER_KEY} . " invalid - " . $values{result} if ( defined( $values->{USER_KEY} ) ); } else { readingsBulkUpdateIfChanged( $hash, "userState", "valid" ) if ( !defined( $values->{USER_KEY} ) ); delete $hash->{helper}{FAILED_USERKEYS}{ $values->{USER_KEY} } if ( !defined( $values->{USER_KEY} ) && defined( $hash->{helper}{FAILED_USERKEYS}{ $values->{USER_KEY} } ) ); } } else { $state = "limited" if ( $apiRemaining < 1 ); readingsBulkUpdateIfChanged( $hash, "tokenState", "valid" ) if ( !defined( $values->{USER_KEY} ) ); readingsBulkUpdateIfChanged( $hash, "userState", "valid" ) if ( !defined( $values->{USER_KEY} ) ); } # messages.json if ( $service eq "messages.json" ) { readingsBulkUpdate( $hash, "lastTitle", $values->{title} ); readingsBulkUpdate( $hash, "lastMessage", urlDecode( $values->{message} ) ); readingsBulkUpdate( $hash, "lastPriority", $values->{priority} ); readingsBulkUpdate( $hash, "lastAction", $values->{action} ) if ( $values->{action} ne "" ); readingsBulkUpdate( $hash, "lastAction", "-" ) if ( $values->{action} eq "" ); readingsBulkUpdate( $hash, "lastDevice", $values->{device} ) if ( $values->{device} ne "" ); readingsBulkUpdate( $hash, "lastDevice", ReadingsVal( $name, "devices", "all" ) ) if ( $values->{device} eq "" ); if ( ref($return) eq "HASH" ) { readingsBulkUpdate( $hash, "lastRequest", $return->{request} ) if ( defined $return->{request} ); if ( $values->{expire} ne "" ) { readingsBulkUpdate( $hash, "cbTitle_" . $values->{cbNr}, $values->{title} ); readingsBulkUpdate( $hash, "cbMsg_" . $values->{cbNr}, urlDecode( $values->{message} ) ); readingsBulkUpdate( $hash, "cbPrio_" . $values->{cbNr}, $values->{priority} ); readingsBulkUpdate( $hash, "cbAck_" . $values->{cbNr}, "0" ); if ( $values->{device} ne "" ) { readingsBulkUpdate( $hash, "cbDev_" . $values->{cbNr}, $values->{device} ); } else { readingsBulkUpdate( $hash, "cbDev_" . $values->{cbNr}, ReadingsVal( $name, "devices", "all" ) ); } if ( defined $return->{receipt} ) { readingsBulkUpdate( $hash, "cb_" . $values->{cbNr}, $return->{receipt} ); readingsBulkUpdate( $hash, "cbCancelId_" . $values->{cbNr}, $values->{cancel_id} ) if ( defined( $values->{cancel_id} ) && $values->{cancel_id} ne "" ); } else { readingsBulkUpdate( $hash, "cb_" . $values->{cbNr}, $values->{cbNr} ); } readingsBulkUpdate( $hash, "cbAct_" . $values->{cbNr}, $values->{action} ) if ( $values->{action} ne "" ); } } elsif ( $values{expire} ne "" ) { $values{result} = "SoftFail: Callback not supported. Please install Perl::JSON"; } } # receipts/$receipt/cancel.json elsif ( $service =~ /^receipts\/(.*)\/cancel.json$/ ) { my $receipt = $1; my @delete; foreach my $key ( %{ $hash->{READINGS} } ) { if ( $key =~ /^cb_(\d+)$/ && $hash->{READINGS}{$key}{VAL} eq $receipt ) { my $rAct = "cbAct_" . $1; my $rAck = "cbAck_" . $1; my $rAckAt = "cbAckAt_" . $1; my $rAckBy = "cbAckBy_" . $1; my $rCancelId = "cbCancelId_" . $1; if ( $param->{code} == 200 ) { readingsBulkUpdate( $hash, $rAck, "1" ); readingsBulkUpdate( $hash, $rAckAt, int( time() ) ); readingsBulkUpdate( $hash, $rAckBy, "aborted" ); push @delete, $rCancelId; } } } # cleanup foreach (@delete) { delete $hash->{READINGS}{$_} if ( defined( $hash->{READINGS}{$_} ) ); } } # glances.json elsif ( $service eq "glances.json" ) { readingsBulkUpdate( $hash, "lastTitle", $values->{title} ); readingsBulkUpdate( $hash, "lastText", urlDecode( $values->{text} ) ) if ( $values->{text} ne "" ); readingsBulkUpdate( $hash, "lastSubtext", urlDecode( $values->{subtext} ) ) if ( $values->{subtext} ne "" ); readingsBulkUpdate( $hash, "lastCount", $values->{count} ) if ( $values->{count} ne "" ); readingsBulkUpdate( $hash, "lastPercent", $values->{percent} ) if ( $values->{percent} ne "" ); readingsBulkUpdate( $hash, "lastDevice", $values->{device} ) if ( $values->{device} ne "" ); readingsBulkUpdate( $hash, "lastDevice", ReadingsVal( $name, "devices", "all" ) ) if ( $values->{device} eq "" ); if ( ref($return) eq "HASH" ) { readingsBulkUpdate( $hash, "lastRequest", $return->{request} ) if ( defined $return->{request} ); } } # users/validate.json elsif ( $service eq "users/validate.json" ) { if ( ref($return) eq "HASH" ) { my $devices = "-"; my $group = "0"; $devices = join( ",", @{ $return->{devices} } ) if ( defined( $return->{devices} ) ); $group = $return->{group} if ( defined( $return->{group} ) ); readingsBulkUpdateIfChanged( $hash, "devices", $devices ); readingsBulkUpdateIfChanged( $hash, "group", $group ); } } readingsBulkUpdate( $hash, "lastResult", $values{result} ); } # Set reading for availability # my $available = 0; $available = 1 if ( $param->{code} ne "429" && ( $state eq "connected" || $state eq "error" ) ); readingsBulkUpdateIfChanged( $hash, "available", $available ); # Set reading for state # readingsBulkUpdateIfChanged( $hash, "state", $state ); # credentials validation loop # my $nextTimer = "none"; # if we could not connect, try again in 5 minutes if ( $state eq "disconnected" ) { $nextTimer = gettimeofday() + 300; } # re-validate every 6 hours if there was no message sent during # that time elsif ( $available eq "1" ) { $nextTimer = gettimeofday() + 21600; } # re-validate after API limit was reset elsif ( $state eq "limited" || $param->{code} == 429 ) { $nextTimer = ReadingsVal( $name, "apiReset", gettimeofday() + 21277 ) + 323; } RemoveInternalTimer($hash); $hash->{VALIDATION_TIMER} = $nextTimer; InternalTimer( $nextTimer, "Pushover_ValidateUser", $hash, 0 ) if ( $nextTimer ne "none" ); readingsEndUpdate( $hash, 1 ); return; } sub Pushover_ValidateUser ($;$) { my ( $hash, $update ) = @_; my $name = $hash->{NAME}; my $device = AttrVal( $name, "device", "" ); Log3 $name, 5, "Pushover $name: called function Pushover_ValidateUser()"; RemoveInternalTimer($hash); if ( AttrVal( $name, "disable", 0 ) == 1 ) { $hash->{VALIDATION_TIMER} = "disabled"; RemoveInternalTimer($hash); InternalTimer( gettimeofday() + 900, "Pushover_ValidateUser", $hash, 0 ); return; } elsif ( $device ne "" ) { return Pushover_SendCommand( $hash, "users/validate.json", "device=$device" ); } else { return Pushover_SendCommand( $hash, "users/validate.json" ); } } sub Pushover_SetMessage { my $hash = shift; my $name = $hash->{NAME}; my %values = (); Log3 $name, 5, "Pushover $name: called function Pushover_SetMessage()"; # Set defaults $values{title} = AttrVal( $hash->{NAME}, "title", "" ); $values{message} = ""; $values{device} = AttrVal( $hash->{NAME}, "device", "" ); $values{priority} = AttrVal( $hash->{NAME}, "priority", 0 ); $values{sound} = AttrVal( $hash->{NAME}, "sound", "" ); $values{retry} = AttrVal( $hash->{NAME}, "retry", "" ); $values{expire} = AttrVal( $hash->{NAME}, "expire", "" ); $values{url_title} = ""; $values{action} = ""; # Split parameters my $param = join( " ", @_ ); my $argc = 0; if ( $param =~ /(".*"|'.*')\s*(".*"|'.*')\s*(".*"|'.*')\s*(-?\d+)\s*(".*"|'.*')\s*(\d+)\s*(\d+)\s*(".*"|'.*')\s*(".*"|'.*')\s*$/s ) { $argc = 9; } elsif ( $param =~ /(".*"|'.*')\s*(".*"|'.*')\s*(".*"|'.*')\s*(-?\d+)\s*(".*"|'.*')\s*(\d+)\s*(\d+)\s*$/s ) { $argc = 7; } elsif ( $param =~ /(".*"|'.*')\s*(".*"|'.*')\s*(".*"|'.*')\s*(-?\d+)\s*(".*"|'.*')\s*$/s ) { $argc = 5; } elsif ( $param =~ /(".*"|'.*')\s*(".*"|'.*')\s*$/s ) { $argc = 2; } elsif ( $param =~ /(".*"|'.*')\s*$/s ) { $argc = 1; } Log3 $name, 4, "Pushover $name: Found $argc argument(s)"; if ( $argc > 1 ) { $values{title} = $1; $values{message} = $2; Log3 $name, 4, "Pushover $name: title=$values{title} message=$values{message}"; if ( $argc > 2 ) { $values{device} = $3; $values{priority} = $4; $values{sound} = $5; Log3 $name, 4, "Pushover $name: device=$values{device} priority=$values{priority} sound=$values{sound}"; if ( $argc > 5 ) { $values{retry} = $6; $values{expire} = $7; Log3 $name, 4, "Pushover $name: retry=$values{retry} expire=$values{expire}"; if ( $argc > 7 ) { $values{url_title} = $8; $values{action} = $9; Log3 $name, 4, "Pushover $name: url_title=$values{url_title} action=$values{action}"; } } } } elsif ( $argc == 1 ) { $values{message} = $1; Log3 $name, 4, "Pushover $name: message=$values{message}"; } # Remove quotation marks $values{title} = $1 if ( $values{title} =~ /^['"](.*)['"]$/s ); $values{message} = $1 if ( $values{message} =~ /^['"](.*)['"]$/s ); $values{device} = $1 if ( $values{device} =~ /^['"](.*)['"]$/s ); $values{priority} = $1 if ( $values{priority} =~ /^['"](.*)['"]$/s ); $values{sound} = $1 if ( $values{sound} =~ /^['"](.*)['"]$/s ); $values{retry} = $1 if ( $values{retry} =~ /^['"](.*)['"]$/s ); $values{expire} = $1 if ( $values{expire} =~ /^['"](.*)['"]$/s ); $values{url_title} = $1 if ( $values{url_title} =~ /^['"](.*)['"]$/s ); $values{action} = $1 if ( $values{action} =~ /^['"](.*)['"]$/s ); return Pushover_SetMessage2( $hash, "msg", undef, \%values ); } sub Pushover_SetMessage2 ($$$$) { my ( $hash, $cmd, $a, $h ) = @_; my $name = $hash->{NAME}; my %values = (); Log3 $name, 5, "Pushover $name: called function Pushover_SetMessage2()"; # general values $values{title} = $h->{title} ? $h->{title} : AttrVal( $hash->{NAME}, "title", undef ); $values{device} = $h->{device} ? $h->{device} : AttrVal( $hash->{NAME}, "device", undef ); # message if ( $cmd eq "msg" ) { if ( defined( $h->{message} ) ) { $values{message} = $h->{message}; } elsif ( defined( $h->{msg} ) ) { $values{message} = $h->{msg}; } elsif ( defined( $h->{text} ) ) { $values{message} = $h->{text}; } else { $values{message} = join ' ', @$a; } return "Usage: $name msg [ option1= option2='' ... ]" unless ( defined( $values{message} ) && $values{message} ne "" ); $values{priority} = $h->{priority} ? $h->{priority} : AttrVal( $hash->{NAME}, "priority", undef ); return "parameter priority is out of scope" unless ( !$values{priority} || $values{priority} =~ m/^-?\d+$/ ); return "parameter timestamp is out of scope" unless ( !$values{timestamp} || $values{timestamp} =~ m/\d+$/ ); $values{retry} = ( $h->{retry} ? $h->{retry} : AttrVal( $name, "retry", undef ) ); return "parameter retry is out of scope" unless ( !$values{retry} || ( $values{retry} =~ m/\d+$/ && $values{retry} >= 30 ) ); $values{expire} = ( $h->{expire} ? $h->{expire} : AttrVal( $name, "expire", undef ) ); return "parameter retry is out of scope" unless ( !$values{expire} || $values{expire} =~ m/\d+$/ ); return "priority 2 messages require parameters retry and expire" if ( $values{priority} && $values{priority} == 2 && !defined( $values{retry} ) && !defined( $values{expire} ) ); return "priority 2 messages require parameter retry" if ( $values{priority} && $values{priority} == 2 && !defined( $values{retry} ) ); return "priority 2 messages require parameter expire" if ( $values{priority} && $values{priority} == 2 && !defined( $values{expire} ) ); $values{action} = $h->{action} ? $h->{action} : ( $h->{url} ? $h->{url} : undef ); $values{url_title} = ( $h->{url_title} ? $h->{url_title} : undef ); return "parameter url_title requires parameter action" if ( defined( $values{url_title} ) && !defined( $values{action} ) ); return "parameter action requires parameter url_title" if ( defined( $values{action} ) && !defined( $values{url_title} ) ); return "messages containing a URL require parameter expire" if ( defined( $values{action} ) && defined( $values{url_title} ) && !defined( $values{expire} ) ); $values{sound} = $h->{sound} ? $h->{sound} : AttrVal( $hash->{NAME}, "sound", undef ); $values{timestamp} = ( $h->{timestamp} ? $h->{timestamp} : undef ); $values{cancel_id} = $h->{cancel_id} if ( defined( $h->{cancel_id} ) && $values{priority} && $values{priority} == 2 ); $values{attachment} = $h->{attachment} ? $h->{attachment} : undef; } # glances if ( $cmd eq "glance" ) { if ( defined( $h->{text} ) ) { $values{text} = $h->{text}; } elsif ( defined( $h->{message} ) ) { $values{text} = $h->{message}; } elsif ( defined( $h->{msg} ) ) { $values{text} = $h->{msg}; } else { $values{text} = join ' ', @$a; } $values{subtext} = ( defined( $h->{subtext} ) ? $h->{subtext} : undef ); $values{count} = ( defined( $h->{count} ) ? $h->{count} : undef ); return "parameter count is out of scope" unless ( !$values{count} || $values{count} =~ m/-?\d+$/ ); $values{percent} = ( defined( $h->{percent} ) ? $h->{percent} : undef ); return "parameter percent is out of scope" unless ( !$values{percent} || ( $values{percent} =~ m/\d+$/ && $values{percent} >= 0 && $values{percent} <= 100 ) ); return "Usage: $name glance [ title='' text='' subtext='' count= percent= ]" unless ( defined( $values{title} ) || ( defined( $values{text} ) && $values{text} ne "" ) || defined( $values{subtext} ) || defined( $values{count} ) || defined( $values{percent} ) ); } my $callback = ( defined( $attr{$name}{callbackUrl} ) && defined( $hash->{fhem}{infix} ) ? $attr{$name}{callbackUrl} : undef ); # check if we got a user or group key as device and use it as # user-key instead of hash->USER_KEY if ( $values{device} && $values{device} =~ /^(([A-Za-z0-9]{30}):)?([A-Za-z0-9,_-]*)(.*)$/ ) { $values{USER_KEY} = $2 if ( $2 ne "" ); $values{device} = $3; return $hash->{helper}{FAILED_USERKEYS}{ $values{USER_KEY} } if ( $values{USER_KEY} && defined( $hash->{helper}{FAILED_USERKEYS}{ $values{USER_KEY} } ) ); } # set timestamp if desired $values{timestamp} = int( time() ) if ( !$values{timestamp} && 1 == AttrVal( $hash->{NAME}, "timestamp", 0 ) ); # correct priority if ( defined( $values{priority} ) ) { $values{priority} = 2 if ( $values{priority} > 2 ); $values{priority} = -2 if ( $values{priority} < -2 ); # callback if ( $callback && $values{priority} > 1 ) { Log3 $name, 5, "Pushover $name: Adding emergency callback URL $callback"; $values{callback} = $callback; } } if ( $values{expire} ) { $values{cbNr} = round( time(), 0 ) + $values{expire}; my $cbReading = "cb_" . $values{cbNr}; until ( ReadingsVal( $name, $cbReading, "" ) eq "" ) { $values{cbNr}++; $cbReading = "cb_" . $values{cbNr}; } } if ( $values{url_title} && $values{action} && defined( $values{expire} ) ) { my $url; if ( !$callback || ( $values{action} !~ /^http[s]?:\/\/.*$/ && $values{action} =~ /^[\w-]+:\/\/.*$/ ) ) { $url = $values{action}; $values{expire} = undef; } else { $url = $callback . "?acknowledged=1&acknowledged_by=" . $hash->{USER_KEY} . "&FhemCallbackId=" . $values{cbNr}; } Log3 $name, 5, "Pushover $name: Adding supplementary URL '$values{url_title}' ($url) with " . "action '$values{action}' (expires after $values{expire} => " . "$values{cbNr})"; $values{url} = $url; } # generate body text my $body; my $multipart = 0; $multipart = 1 if ( $values{attachment} ); if ( defined( $values{message} ) ) { if ( $values{message} =~ /^\s*nohtml:\s*(.*)$/i ) { Log3 $name, 4, "Pushover $name: explicitly ignoring HTML tags in message"; $values{message} = $1; } elsif ( $values{message} =~ m/\<(\/|)[biu]\>|\<(\/|)font(.+)\>|\<(\/|)a(.*)\>|\/i ) { Log3 $name, 4, "Pushover $name: handling message with HTML content"; $body = Pushover_HttpForm( $body, $multipart, "html", "1" ); # replace \n by
but ignore \\n $values{message} =~ s/(?/g; } } if ( defined( $values{attachment} ) ) { my $path = "file://" . AttrVal( $name, "storage", AttrVal( "global", "modpath", "." ) ); $path .= "/" unless ( $path =~ /\/$/ ); $values{attachment} = $path . $values{attachment}; } $body = Pushover_HttpForm( $body, $multipart, \%values ); # cleanup callback readings keys %{ $hash->{READINGS} }; while ( my ( $key, $value ) = each %{ $hash->{READINGS} } ) { if ( $key =~ /^cb_(\d+)$/ ) { my $rTit = "cbTitle_" . $1; my $rMsg = "cbMsg_" . $1; my $rPrio = "cbPrio_" . $1; my $rAct = "cbAct_" . $1; my $rAck = "cbAck_" . $1; my $rAckAt = "cbAckAt_" . $1; my $rAckBy = "cbAckBy_" . $1; my $rCancelId = "cbCancelId_" . $1; my $rDev = "cbDev_" . $1; Log3 $name, 5, "Pushover $name: checking to clean up " . $hash->{NAME} . " $key: time=" . $1 . " ack=" . ReadingsVal( $name, $rAck, "-" ) . " curTime=" . int( time() ); if ( ReadingsVal( $name, $rAck, "0" ) eq "1" || $1 <= int( time() ) ) { delete $hash->{READINGS}{$key}; delete $hash->{READINGS}{$rTit}; delete $hash->{READINGS}{$rMsg}; delete $hash->{READINGS}{$rPrio}; delete $hash->{READINGS}{$rAck}; delete $hash->{READINGS}{$rDev}; delete $hash->{READINGS}{$rAct} if ( defined( $hash->{READINGS}{$rAct} ) ); delete $hash->{READINGS}{$rAckAt} if ( defined( $hash->{READINGS}{$rAckAt} ) ); delete $hash->{READINGS}{$rAckBy} if ( defined( $hash->{READINGS}{$rAckBy} ) ); delete $hash->{READINGS}{$rCancelId} if ( defined( $hash->{READINGS}{$rCancelId} ) ); Log3 $name, 4, "Pushover $name: cleaned up expired receipt " . $1; } } } return Pushover_SendCommand( $hash, "messages.json", $body, %values ) if ( $cmd eq "msg" ); return Pushover_SendCommand( $hash, "glances.json", $body, %values ) if ( $cmd eq "glance" ); } sub Pushover_CancelMessage ($$$$) { my ( $hash, $cmd, $cancelIds, $h ) = @_; my $name = $hash->{NAME}; my $success = 0; my $return; return "Unknown argument, choose one of cancel_id" if ( int(@$cancelIds) < 1 || $cancelIds[0] =~ /^(\?|help)$/i ); Log3 $name, 5, "Pushover $name: called function Pushover_CancelMessage()"; foreach my $string (@$cancelIds) { foreach my $cancelId ( split( ',', $string ) ) { foreach my $key ( keys %{ $hash->{READINGS} } ) { if ( $key =~ /^cbCancelId_(\d+)$/ && $hash->{READINGS}{$key}{VAL} eq $cancelId ) { $success = 1; my $receipt = $hash->{READINGS}{ "cb_" . $1 }{VAL}; $return .= " " if ($return); $return .= Pushover_SendCommand( $hash, "receipts/$receipt/cancel.json" ) if ($receipt); } } } } return "Invalid cancel_id" unless ($success); return $return; } sub Pushover_HttpForm ($$$;$) { my ( $ret, $multipart, $h, $v ) = @_; $h = { $h => $v } unless ( ref $h eq "HASH" ); my $boundary = "--msgsgmnt"; keys %$h; while ( my ( $n, $val ) = each %$h ) { next unless ( defined($val) ); $v = $val; # multipart/form-data if ($multipart) { $ret = "--$boundary" unless ( $ret && $ret ne "" ); if ( $multipart eq "2" || $v =~ /^file:\/\/(.*)/i ) { $v = $1 if ( defined($1) ); next unless ( $v =~ /(\w|[-.])+$/ ); my $fn = $0; my ( $err, @content ) = FileRead( { FileName => $v, ForceType => "file" } ); if ( defined($err) ) { Log 5, "Pushover_HttpForm: Unable to read file $v: $err"; next; } my $MIMEtype = filename2MIMEType($v); $v = join( $/, @content ); $ret .= "\r\nContent-Disposition: form-data; " . "name=\"$n\"; " . "filename=\"$fn\"" . "\r\nContent-Type: $MIMEtype"; } else { $ret .= "\r\nContent-Disposition: form-data; name=\"$n\""; $v =~ s/\\n/\r\n/g; } $ret .= "\r\n\r\n$v\r\n--$boundary"; } # application/x-www-form-urlencoded else { $ret = Pushover_HttpUri( $ret, $n, $v ); } } return $ret; } sub Pushover_HttpUri ($$;$) { my ( $uri, $h, $v ) = @_; $h = { $h => $v } unless ( ref $h eq "HASH" ); keys %$h; while ( my ( $n, $val ) = each %$h ) { $v = urlEncode($val); # replace any URL-encoded \n with their hex equivalent # but ignore \\n $v =~ s/(?

Pushover

    Pushover is a service to receive instant push notifications on your phone or tablet from a variety of sources.
    You need an account to use this module.
    For further information about the service see pushover.net.

    Installation of Perl module IO::Socket::SSL is mandatory to use this module (i.e. via 'cpan -i IO::Socket::SSL').
    It is recommended to install Perl-JSON to make use of advanced functions like supplementary URLs.

    Discuss the module here.


    Define
      define <name> Pushover <token> <user> [<infix>]

      You have to create an account to get the user key.
      And you have to create an application to get the API token.

      Attribute infix is optional to define FHEMWEB uri name for Pushover API callback function.
      Callback URL may be set using attribute callbackUrl (see below).
      Note: A uri name can only be used once within each FHEM instance!

      Example:
        define Pushover1 Pushover 01234 56789
        define Pushover1 Pushover 01234 56789 pushCallback1

    Set
      msg
        set <Pushover_device> msg <text> [<option1>=<value> <option2>="<value with space in it>" ...]

        The following options may be used to adjust message content and delivery behavior:

        message    - type: text - Your message text. Using this option takes precedence; non-option text content will be discarded.
        device     - type: text - Your user's device name to send the message directly to that device, rather than all of the user's devices (multiple devices may be separated by a comma). May also be set to a specific User or Group Key. To address a specific device for a specific User/Group, use User/Group Key first and add device name separated by colon.
        title      - type: text - Your message's title, otherwise your Pushover API app's name is used.
        action     - type: text - Either a FHEM command to run when user taps link or a supplementary URL to show with your message.
        url_title  - type: text - A title for your FHEM command or supplementary URL, otherwise just the URL is shown.
        priority   - type: integer - Send as -2 to generate no notification/alert, -1 to always send as a quiet notification, 1 to display as high-priority and bypass the user's quiet hours, or 2 to also require confirmation from the user.
        retry      - type: integer - Mandatory in combination with message priority >= 2.
        expire     - type: integer - Mandatory in combination with message priority >= 2.
        cancel_id  - type: text - Custom ID to immediate expire messages with priority >=2 and disable reoccuring notification.
        timestamp  - type: integer - A Unix timestamp of your message's date and time to display to the user, rather than the time your message is received by the Pushover servers. Takes precendence over attribute timestamp=1.
        sound      - type: text - The name of one of the sounds supported by device clients to override the user's default sound choice.
        attachment      - type: text - Path to an image file that should be attached to the message. The base path is relative to the FHEM directory and may be overwritten using the storage attribute.

        Examples:
          set Pushover1 msg My first Pushover message.
          set Pushover1 msg My second Pushover message.\nThis time with two lines.
          set Pushover1 msg "Another Pushover message in double quotes."
          set Pushover1 msg 'Another Pushover message in single quotes.'
          set Pushover1 msg message="Pushover message using explicit option for text content." This part of the text will be ignored.
          set Pushover1 msg This is a message with a title. title="This is a subject"
          set Pushover1 msg title="This is a subject, too!" This is another message with a title set at the beginning of the command.
          set Pushover1 msg This message has an attachment! attachment="demolog/pictures/p1.jpg"
          set Pushover1 msg title=Emergency priority=2 retry=30 expire=3600 Security issue in living room.
          set Pushover1 msg title=Link Have a look to this website: url_title="Open" action="http://fhem.de/" expire=3600
          set Pushover1 msg title=Hint expire=3600 This is a reminder to do something. Action will expire in 1h. url_title="Click here for action" action="set device something"
          set Pushover1 msg title=Emergency priority=2 retry=30 expire=3600 Security issue in living room. sound=siren url_title="Click here for action" action="set device something"



      msgCancel
        set <Pushover_device> msgCancel <ID>

        Prematurely stopps reoccuring confirmation request for messages with priority >= 2.

        Example:
          set Pushover1 msg title=Emergency priority=2 retry=30 expire=3600 Security Alarm in Living room. sound=siren cancel_id=SecurityAlarm
          set Pushover1 msgCancel SecurityAlarm


      msg (deprecated format)
        set <Pushover_device> msg [title] <msg> [<device> <priority> <sound> [<retry> <expire> [<url_title> <action>]]]

        Examples:
          set Pushover1 msg 'This is a text.'
          set Pushover1 msg 'Title' 'This is a text.'
          set Pushover1 msg 'Title' 'This is a text.' '' 0 ''
          set Pushover1 msg 'Emergency' 'Security issue in living room.' '' 2 'siren' 30 3600
          set Pushover1 msg 'Hint' 'This is a reminder to do something' '' 0 '' 0 3600 'Click here for action' 'set device something'
          set Pushover1 msg 'Emergency' 'Security issue in living room.' '' 2 'siren' 30 3600 'Click here for action' 'set device something'

        Notes:
        • For the first and the second example the corresponding default attributes for the missing arguments must be defined for the device (see attributes section)
        • If device is empty, the message will be sent to all devices.
        • If device has a User or Group Key, the message will be sent to this recipient instead. Should you wish to address a specific device here, add it at the end separated by colon.
        • If sound is empty, the default setting in the app will be used.
        • If priority is higher or equal 2, retry and expire must be defined.


      glance
        set <Pushover_device> glance [<text>] [<option1>=<value> <option2>="<value with space in it>" ...]

        Update Pushover's glances on Apple Watch.
        The following options may be used to adjust message content and delivery behavior:

        title    - type: text(100 characters) - A description of the data being shown, such as "Widgets Sold".
        text     - type: text(100 characters) - The main line of data, used on most screens. Using this option takes precedence; non-option text content will be discarded.
        subtext  - type: text(100 characters) - A second line of data.
        count    - type: integer(may be negative) - Shown on smaller screens; useful for simple counts.
        percent  - type: integer(0-100) - Shown on some screens as a progress bar/circle.
        device   - type: text - Your user's device name to send the message directly to that device, rather than all of the user's devices (multiple devices may be separated by a comma). May also be set to a specific User or Group Key. To address a specific device for a specific User/Group, use User/Group Key first and add device name separated by colon.


    Get
      N/A

    Attributes
    • do_not_notify
    • disabledForIntervals
    • readingFnAttributes
    • callbackUrl
      Set the callback URL to be used to acknowledge messages with emergency priority or supplementary URLs.
    • timestamp
      Send the unix timestamp with each message.
    • title
      Will be used as title if title is not specified as an argument.
    • device
      Will be used for the device name if device is not specified as an argument. If left blank, the message will be sent to all devices.
    • priority
      Will be used as priority value if priority is not specified as an argument. Valid values are -1 = silent / 0 = normal priority / 1 = high priority
    • expire
      When message priority is 2, this default value will be used for expire when not provided in the message. Needs to be 30 or higher.
    • retry
      When message priority is 2, this default value will be used for retry when not provided in the message.
    • sound
      Will be used as the default sound if sound argument is missing. If left blank the adjusted sound of the app will be used.
    • storage
      Will be used as the default path when sending attachments, otherwise global attribute modpath will be used.

    Generated events:
      N/A
=end html =begin html_DE

Pushover

    Pushover ist ein Dienst, um Benachrichtigungen von einer vielzahl von Quellen auf Deinem Smartphone oder Tablet zu empfangen.
    Du brauchst einen Account um dieses Modul zu verwenden.
    Für weitere Informationen über den Dienst besuche pushover.net.

    Die Installation des Perl Moduls IO::Socket::SSL ist Voraussetzung zur Nutzung dieses Moduls (z.B. via 'cpan -i IO::Socket::SSL').
    Es wird empfohlen Perl-JSON zu installieren, um erweiterte Funktion wie Supplementary URLs nutzen zu können.

    Diskutiere das Modul hier.


    Define
      define <name> Pushover <token> <user> [<infix>]

      Du musst einen Account erstellen, um den User Key zu bekommen.
      Und du musst eine Anwendung erstellen, um einen API APP_TOKEN zu bekommen.

      Das Attribut infix ist optional, um einen FHEMWEB uri Namen für die Pushover API Callback Funktion zu definieren.
      Die Callback URL Callback URL kann dann mit dem Attribut callbackUrl gesetzt werden (siehe unten).
      Hinweis: Eine infix uri can innerhalb einer FHEM Instanz nur einmal verwendet werden!

      Beispiel:
        define Pushover1 Pushover 01234 56789
        define Pushover1 Pushover 01234 56789 pushCallback1

    Set
      msg
        set <Pushover_device> msg <text> [<option1>=<value> <option2>="<value with space in it>" ...]

        Die folgenden Optionen können genutzt werden, um den Nachrichteninhalt und die Zustellung zu beeinflussen::

        message    - Typ: Text - Dein Nachrichtentext. Die Nutzung dieser Option hat Vorrang; Text außerhalb wird verworfen.
        device     - Typ: Text - Dein selbst vergebener Gerätename, um die Nachricht direkt an dieses Gerät zu senden anstatt an alle Geräte gleichzeitig (mehrere Geräte können mit Komma getrennt angegeben werden). Hier kann auch explizit ein User oder Group Key angegeben werden. Um gezielt ein Gerät einer/s speziellen User/Group anzusprechen, zuerst den User/Group Key angeben, gefolgt vom Gerätenamen und einem Doppelpunkt als Trennzeichen.
        title      - Typ: Text - Dein Nachrichten Titel, andernfalls wird der App Name wie in der Pushover API festgelegt verwendet.
        action     - Typ: Text - Entweder ein auszuführendes FHEM Kommando, wenn der Empfänger den Link anklickt oder eine supplementary URL, die mit der Nachricht zusammen angezeigt werden soll.
        url_title  - Typ: Text - Ein Titel für das FHEM Kommando oder die supplementary URL, andernfalls wird die URL direkt angezeigt.
        priority   - Typ: Integer - Sende mit -2, um keine/n Benachrichtigung/Alarm zu generieren. Sende mit -1, um immer eine lautlose Benachrichtigung zu senden. Sende mit 1, um die Nachricht mit hoher Priorität anzuzeigen und die Ruhezeiten des Empfängers zu umgehen. Oder sende mit 2, um zusätzlich eine Bestätigung des Empfängers anzufordern.
        retry      - Typ: Integer - Verpflichtend bei einer Nachrichten Priorität >= 2.
        expire     - Typ: Integer - Verpflichtend bei einer Nachrichten Priorität >= 2.
        cancel_id  - Typ: Text - Benutzerdefinierte ID, um Nachrichten mit einer Priorität >= 2 sofort ablaufen zu lassen und die wiederholte Benachrichtigung auszuschalten.
        timestamp  - Typ: Integer - Ein Unix Zeitstempfel mit Datum und Uhrzeit deiner Nachricht, die dem Empfänger statt der Uhrzeit des Einganges auf den Pushover Servern angezeigt wird. Hat Vorrang bei gesetztem Attribut timestamp=1.
        sound      - Typ: Text - Der Name eines vom Empfängergerät unterstützten Klangs, um den vom Empfänger ausgewählten Klang zu überschreiben.
        attachment      - Typ: Text - Pfad zu einer Bilddatei, welche an die Nachricht angehängt werden soll. Der Basispfad ist relativ zum FHEM Verzeichnis und kann über das storage Attribut überschrieben werden.

        Beispiele:
          set Pushover1 msg Meine erste Pushover Nachricht.
          set Pushover1 msg Meine zweite Pushover Nachricht.\nDiesmal mit zwei Zeilen.
          set Pushover1 msg "Eine andere Pushover Nachricht in doppelten Anfährungszeichen."
          set Pushover1 msg 'Eine andere Pushover Nachricht in einfachen Anfährungszeichen.'
          set Pushover1 msg message="Pushover Nachricht, die die explizite Nachrichten Option für den Textinhalt verwendet." Dieser Teil des Textes wird ignoriert.
          set Pushover1 msg Dies ist eine Nachricht mit einem Titel. title="Dies ist ein Betreff"
          set Pushover1 msg Diese Nachricht hat einen Anhang! attachment="demolog/pictures/p1.jpg"
          set Pushover1 msg title="Dies ist auch ein Betreff!" Dies ist eine weitere Nachricht mit einem Titel, der am Anfang des Kommandos gesetzt ist.
          set Pushover1 msg title=Notfall priority=2 retry=30 expire=3600 Sicherheits-Alarm im Wohnzimmer.
          set Pushover1 msg title=Link Schau dir mal diese Website an: url_title="Öffnen" action="http://fhem.de/" expire=3600
          set Pushover1 msg title=Hinweis expire=3600 Dies ist eine Erinnerung, um etwas zu tun. Der Link verliert in 1h seine Gültigkeit. url_title="Hier klicken, um den Befehl auszuführen" action="set device something"
          set Pushover1 msg title=Notfall priority=2 retry=30 expire=3600 Sicherheits-Alarm im Wohnzimmer. sound=siren url_title="Hier klicken, um den Befehl auszuführen" action="set device something"



      msgCancel
        set <Pushover_device> msgCancel <ID>

        Stoppt vorzeitig die wiederkehrende Aufforderung zur Bestätigung bei Nachrichten mit Priorität >= 2.

        Beispiel:
          set Pushover1 msg title=Notfall priority=2 retry=30 expire=3600 Sicherheits-Alarm im Wohnzimmer. sound=siren cancel_id=SicherheitsAlarm
          set Pushover1 msgCancel SicherheitsAlarm


      msg (veraltetes Format)
        set <Pushover_device> msg [title] <msg> [<device> <priority> <sound> [<retry> <expire> [<url_title> <action>]]]

        Beispiele:
          set Pushover1 msg 'Dies ist ein Text.'
          set Pushover1 msg 'Titel' 'Dies ist ein Text.'
          set Pushover1 msg 'Titel' 'Dies ist ein Text.' '' 0 ''
          set Pushover1 msg 'Notfall' 'Sicherheitsproblem im Wohnzimmer.' '' 2 'siren' 30 3600
          set Pushover1 msg 'Erinnerung' 'Dies ist eine Erinnerung an etwas' '' 0 '' 0 3600 'Hier klicken, um Aktion auszuführen' 'set device irgendwas'
          set Pushover1 msg 'Notfall' 'Sicherheitsproblem im Wohnzimmer.' '' 2 'siren' 30 3600 'Hier klicken, um Aktion auszuführen' 'set device something'

        Anmerkungen:
        • Bei der Verwendung der ersten beiden Beispiele müssen die entsprechenden Attribute als Ersatz für die fehlenden Parameter belegt sein (s. Attribute)
        • Wenn device leer ist, wird die Nachricht an alle Geräte geschickt.
        • Wenn device ein User oder Group Key ist, wird die Nachricht stattdessen hierhin verschickt. Möchte man trotzdem ein dediziertes Device angeben, trennt man den Namen mit einem Doppelpunkt ab.
        • Wenn sound leer ist, dann wird die Standardeinstellung in der App verwendet.
        • Wenn die Priorität höher oder gleich 2 ist müssen retry und expire definiert sein.


      glance
        set <Pushover_device> glance [<text>] [<option1>=<value> <option2>="<value with space in it>" ...]

        Aktualisiert die Pushover glances auf einer Apple Watch.
        Die folgenden Optionen können genutzt werden, um den Nachrichteninhalt und die Zustellung zu beeinflussen::

        title    - type: text(100 characters) - Eine Beschreibung der Daten, die angezeigt werden, beispielsweise "Verkaufte Dinge".
        text     - type: text(100 characters) - Textzeile, die in den meisten Ansichten verwendet wird. Die Nutzung dieser Option hat Vorrang; Text außerhalb wird verworfen.
        subtext  - type: text(100 characters) - Eine zweite Zeile mit Text.
        count    - type: integer(may be negative) - Wird auf kleineren Ansichten dargestellt; nützlich für einfache Zählerstände.
        percent  - type: integer(0-100) - Wird bei einigen Ansichten als Fortschrittsbalken/-kreis angezeigt.
        device   - Typ: Text - Dein selbst vergebener Gerätename, um die Nachricht direkt an dieses Gerät zu senden anstatt an alle Geräte gleichzeitig (mehrere Geräte können mit Komma getrennt angegeben werden). Hier kann auch explizit ein User oder Group Key angegeben werden. Um gezielt ein Gerät einer/s speziellen User/Group anzusprechen, zuerst den User/Group Key angeben, gefolgt vom Gerätenamen und einem Doppelpunkt als Trennzeichen.


    Get
      N/A

    Attributes
    • do_not_notify
    • disabledForIntervals
    • readingFnAttributes
    • callbackUrl
      Setzt die Callback URL, um Nachrichten mit Emergency Priorität zu bestätigen.
    • timestamp
      Sende den Unix-Zeitstempel mit jeder Nachricht.
    • title
      Wird beim Senden als Titel verwendet, sofern dieser nicht als Aufrufargument angegeben wurde.
    • device
      Wird beim Senden als Gerätename verwendet, sofern dieser nicht als Aufrufargument angegeben wurde. Kann auch generell entfallen, bzw. leer sein, dann wird an alle Geräte gesendet.
    • priority
      Wird beim Senden als Priorität verwendet, sofern diese nicht als Aufrufargument angegeben wurde. Zulässige Werte sind -1 = leise / 0 = normale Priorität / 1 = hohe Priorität
    • expire
      Wenn die Nachrichten Priorität 2 ist, wird dieser Wert als Standard für expire verwendet, falls dieser nicht in der Nachricht angegeben wurde. Muss 30 oder höher sein.
    • retry
      Wenn die Nachrichten Priorität 2 ist, wird dieser Wert als Standard für retry verwendet, falls dieser nicht in der Nachricht angegeben wurde.
    • sound
      Wird beim Senden als Titel verwendet, sofern dieser nicht als Aufrufargument angegeben wurde. Kann auch generell entfallen, dann wird der eingestellte Ton der App verwendet.
    • storage
      Wird als Standardpfad beim Versand von Anhängen verwendet, ansonsten wird das globale Attribut modpath benutzt.

    Generated events:
      N/A
=end html_DE =cut