############################################################################### # $Id: 75_MSG.pm 19215 2019-04-18 11:55:19Z loredo $ # #TODO # - be able to use type "default" to let read from attr # - forceType/forceDevice in user parameters # - implement default messages in RESIDENTS using msg command # - queue message until recipient is available again (e.g. when absent) # also see https://forum.fhem.de/index.php/topic,69683.0.html # - new msgType "queue" # - escalation to type "queue" when n/a # - automatically trigger to release queue messages by arriving at home # (ROOMMATE) # - allow some other ? to only reach people when they are at home # - if ROOMMATE is asleep, queue message for next day # (usefull escalate for screen with PostMe?) # - delivery options as attributes (like ! or ? to gateways, devices or types) # - all messages should be queued and then delivered so a timer may come back # and check the gateway device for successful delivery # - fix readings: store texts with \x{0000} notation # - end user documentation / commandref !! # package main; use strict; use warnings; use Data::Dumper; use Time::HiRes qw(time); use Encode; use utf8; # initialize ################################################################## sub MSG_Initialize($$) { my %hash = ( Fn => "CommandMsg", Hlp => "[] [<\@device>|] [] [||] <message-text>", ); $cmds{msg} = \%hash; require "$attr{global}{modpath}/FHEM/msgSchema.pm"; } # regular Fn ################################################################## sub CommandMsg($$;$$); sub CommandMsg($$;$$) { my ( $cl, $msg, $testMode ) = @_; my $return = ""; if ( $featurelevel >= 5.7 ) { my %dummy; my ( $err, @a ) = ReplaceSetMagic( \%dummy, 0, ($msg) ); $msg = join( " ", @a ) unless ($err); } # find existing msgConfig device or create a new instance my $globalDevName = "globalMsg"; if ( defined( $modules{msgConfig}{defptr} ) ) { $globalDevName = $modules{msgConfig}{defptr}{NAME}; } else { fhem "define $globalDevName msgConfig"; $return .= "Global configuration device $globalDevName was created.\n\n"; } if ( $msg eq "" || $msg =~ /^\?[\s\t ]*$/ || $msg eq "help" ) { return $return . "Usage: msg [<type>] [<\@device>|<e-mail address>] [<priority>] [|<title>|] <message>"; } # default settings my $cmdSchema = msgSchema::get(); my $settings = { audio => { typeEscalation => { gwUnavailable => 'text', emergency => 'text', residentGone => 'text', residentAbsent => 'text', }, }, light => { typeEscalation => { gwUnavailable => 'audio', emergency => 'audio', residentGone => 'audio', residentAbsent => 'audio', }, }, push => { typeEscalation => { gwUnavailable => 'mail', emergency => 'mail', }, }, screen => { typeEscalation => { gwUnavailable => 'light', emergency => 'light', residentGone => 'light', residentAbsent => 'light', }, }, }; ################################################################ ### extract message details ### my ( $msgA, $params ) = parseParams($msg, "[^\\S\\n]", " "); # only use output from parseParams when # parameters where found if ( ref($params) eq "HASH" && keys %$params ) { if ( scalar @$msgA > 0 ) { $msg = join( " ", @$msgA ); } else { $msg = ""; } } if ( defined( $params->{msgText} ) ) { Log3 $globalDevName, 5, "msg: Adding message text from given user parameters"; $msg .= " " unless ( $msg eq "" ); $msg .= $params->{msgText}; delete $params->{msgText}; } return $return . "Usage: msg [<type>] [<\@device>|<e-mail address>] [<priority>] [|<title>|] <message>" if ( $msg =~ m/^[\s\t\n ]*$/ ); Log3 $globalDevName, 5, "msg: Extracted user parameters\n" . Dumper($params) if ( ref($params) eq "HASH" && keys %$params ); my $types = ""; my $recipients = ""; my $priority = ""; my $title = "-"; my $priorityCat = ""; # check for message types if ( $params->{msgType} ) { Log3 $globalDevName, 5, "msg: given types=$params->{msgType}"; $types = $params->{msgType}; $types =~ s/[\s\t ]+//g; delete $params->{msgType}; } elsif ( $msg =~ s/^[\s\t ]*([a-z,]*!?(screen|light|audio|text|push|mail|queue)[\w,!?&|]*)[\s\t ]+//i ) { Log3 $globalDevName, 5, "msg: found types=$1" unless ( defined($testMode) && $testMode eq "1" ); $types = $1; } # programatic exception: # e.g. recipients were given automatically from empty readings if ( $msg =~ m/^[\s\t ]*([!]?(([A-Za-z0-9%+._-])*@([,\-:|]+)))[\s\t ]+/ || ( $params->{msgRcpt} && $params->{msgRcpt} =~ m/^[\s\t ]*([!]?(([A-Za-z0-9%+._-])*@([,\-:|]+)))[\s\t ]+/ ) ) { Log3 $globalDevName, 4, "msg: message won't be sent - recipient '$1' contains special" . " characters like ',-:|' or behind the @ character is simply" . " emptiness. This might be okay, e.g. if you are using something" . " like a reading from RESIDENTS/ROOMMATE/GUEST to address present" . " or absent residents and this list is simply empty at this time." . " ($msg)"; return; } # check for given recipients if ( $params->{msgRcpt} ) { Log3 $globalDevName, 5, "msg: given recipient=$params->{msgRcpt}"; $recipients = $params->{msgRcpt}; $recipients =~ s/[\s\t ]+//g; delete $params->{msgRcpt}; } elsif ( $msg =~ s/^[\s\t ]*([!]?(([A-Za-z0-9%+._-])*@([%+a-z0-9A-Z.-]+))[\w,@.!?|:]*)[\s\t ]+// ) { Log3 $globalDevName, 5, "msg: found recipient=$1" unless ( defined($testMode) && $testMode eq "1" ); $recipients = $1; } # check for given priority if ( defined( $params->{msgPrio} ) ) { Log3 $globalDevName, 5, "msg: given priority=$params->{msgPrio}"; $priority = $params->{msgPrio}; $priority =~ s/[\s\t ]+//g; delete $params->{msgPrio}; } elsif ( $msg =~ s/^[\s\t ]*([-+]{0,1}\d+[\.\d]*)[\s\t ]*// ) { Log3 $globalDevName, 5, "msg: found priority=$1" unless ( defined($testMode) && $testMode eq "1" ); $priority = $1; } $priority = int($priority) if ( $priority =~ /^[-+]{0,1}\d+\.\d*$/ ); return "Invalid priority $priority: Needs to be an integer value" unless ( $priority eq "" || $priority =~ /^[-+]{0,1}\d+$/ ); # check for given message title if ( defined( $params->{msgTitle} ) ) { Log3 $globalDevName, 5, "msg: given title=$params->{msgTitle}"; $title = $params->{msgTitle}; $title =~ s/^[\s\t ]*\|(.*?)\|[\s\t ]*/$1/; delete $params->{msgTitle}; } elsif ( $msg =~ s/^[\s\t ]*\|(.*?)\|[\s\t ]*// ) { Log3 $globalDevName, 5, "msg: found title=$1" unless ( defined($testMode) && $testMode eq "1" ); $title = $1; } # check for user parameters (DEPRECATED / legacy compatibility only) if ( $msg =~ s/[\s\t ]*O(\[\{.*\}\])[\s\t ]*$// ) { Log3 $globalDevName, 5, "msg: found options=$1" unless ( defined($testMode) && $testMode eq "1" ); # Use JSON module if possible eval { require JSON::PP; import JSON::PP qw( decode_json ); }; if ($@) { Log3 $globalDevName, 3, "msg: Error loading JSON::PP. " . "Please switch to new syntax to use user parameters"; } else { Log3 $globalDevName, 4, "msg: Please switch to new syntax to use user parameters"; my $o; eval '$o = decode_json( encode_utf8($1) ); 1'; if ($@) { Log3 $globalDevName, 5, "msg: Error decoding JSON for user parameters: $@"; } elsif ( ref($o) eq "ARRAY" ) { for my $item (@$o) { next unless ( ref($item) eq "HASH" ); for my $key ( keys(%$item) ) { next if ( ref( $item->{$key} ) ); my $val = $item->{$key}; $params->{$key} = $item->{$key} unless ( $params->{$key} ); } } Log3 $globalDevName, 5, "msg: Decoded user parameters\n" . Dumper($params) if ($params); } } } $types = "default" if ( $types eq "" ); my $msgSent = 0; my $forwarded = ""; my %sentTypesPerDevice; my $sentCounter = 0; my $msgID = time(); my $msgDateTime = TimeNow(); my $isTypeOr = 1; my $isRecipientOr = 1; my $hasTypeOr = 0; my $hasRecipientOr = 0; my $softFail = 0; $recipients = "\@" . $globalDevName if ( $recipients eq "" ); ################################################################ ### recipient loop ### my @recipientsOr = split( /\|/, $recipients ); Log3 $globalDevName, 6, "msg: recipientOr total is " . scalar( grep { defined $_ } @recipientsOr ) unless ( defined($testMode) && $testMode eq "1" ); for ( my $iRecipOr = 0 ; $iRecipOr < scalar( grep { defined $_ } @recipientsOr ) ; $iRecipOr++ ) { Log3 $globalDevName, 6, "msg: " . "running loop recipientsOr for recipient(s) $recipientsOr[$iRecipOr]" unless ( defined($testMode) && $testMode eq "1" ); my $loopReturn1 = ""; $hasRecipientOr = 1 if ( scalar( grep { defined $_ } @recipientsOr ) > 1 ); my @recipient = split( /,/, $recipientsOr[$iRecipOr] ); foreach my $device (@recipient) { Log3 $globalDevName, 6, "msg: running loop for device $device" unless ( defined($testMode) && $testMode eq "1" ); my $loopReturn2 = ""; ################################################################ ### type loop ### my @typesOr = split( /\|/, $types ); Log3 $globalDevName, 6, "msg: typeOr total is " . scalar( grep { defined $_ } @typesOr ) unless ( defined($testMode) && $testMode eq "1" ); for ( my $iTypesOr = 0 ; $iTypesOr < scalar( grep { defined $_ } @typesOr ) ; $iTypesOr++ ) { Log3 $globalDevName, 6, "msg: running loop typeOr for type(s) $typesOr[$iTypesOr]" unless ( defined($testMode) && $testMode eq "1" ); my $loopReturn3 = ""; $hasTypeOr = 1 if ( scalar( grep { defined $_ } @typesOr ) > 1 ); my @type = split( /,/, lc( $typesOr[$iTypesOr] ) ); for ( my $i = 0 ; $i < scalar( grep { defined $_ } @type ) ; $i++ ) { Log3 $globalDevName, 6, "msg: running loop for type $type[$i]" unless ( defined($testMode) && $testMode eq "1" ); last unless ( defined( $type[$i] ) ); # check for correct type my @msgCmds = ( "screen", "light", "audio", "text", "push", "mail", "queue", "default" ); unless ( grep { $type[$i] =~ /^$_/i } @msgCmds ) { $loopReturn3 .= "Unknown message type $type[$i]\n"; next; } ### ### /type loop ################################################################ my @unavailabilityIndicators = ( "0", "false", "absent", "disappeared", "unauthorized", "unavailable", "unreachable", "disconnected" ); my $logDevice; $logDevice = $globalDevName; $logDevice = $device if ( MSG_FindAttrVal( $device, "verbose", undef, undef ) ); my $msgSentDev = 0; my $gatewayDevs = ""; my $forceDevice = 0; my $forceQueue = 0; # for device type my $deviceType = "device"; if ( $device =~ m/^(([A-Za-z0-9%+._-])+@+([%+a-z0-9A-Z.-]*))$/ ) { $gatewayDevs = $1; $deviceType = "email"; } elsif ( $device =~ m/^@?([A-Za-z\d_\.\-\/]+)([^A-Za-z\d_\.\-\/]+)[\s\t ]*$/ ) { $device = $1; foreach ( split( /(\S)/, $2 ) ) { $forceDevice = 1 if ( $_ eq "!" ); $softFail = 1 if ( $_ eq "?" ); $forceQueue = 1 if ( $_ eq "&" ); } Log3 $logDevice, 5, "msg $device: forceDevice=$forceDevice (from device contact)" if ($forceDevice); Log3 $logDevice, 5, "msg $device: softFail=$softFail (from device contact)" if ($softFail); Log3 $logDevice, 5, "msg $device: forceQueue=$forceQueue (from device contact)" if ($forceQueue); } elsif ( $device =~ /^@(.*)/ ) { $device = $1; } # sub-recipient my $subRecipient = ""; my $termRecipient = ""; if ( $device =~ m/^@?([A-Za-z0-9._]+):([A-Za-z0-9._\-\/@+]*):?([A-Za-z0-9._\-\/@+]*)$/ ) { $device = $1; $subRecipient = $2; $termRecipient = $3; } # FATAL ERROR: device does not exist if ( !IsDevice($device) && $deviceType eq "device" ) { $loopReturn3 .= "Device $device does not exist\n" unless ($softFail); unless ( defined($testMode) && $testMode eq "1" ) { Log3 $logDevice, 2, "msg $device: Device does not exist" unless ($softFail); Log3 $logDevice, 5, "msg $device: Device does not exist" if ($softFail); } my $regex1 = "\\s*!?@?" . $device . "[,|]"; # at the beginning my $regex2 = "[,|]!?@?" . $device . "\\s*"; # at the end my $regex3 = ",!?@?" . $device . ","; # in the middle with comma my $regex4 = "[\|,]!?@?" . $device . "[\|,]"; # in the middle with pipe and/or comma $recipients =~ s/^$regex1//gi; $recipients =~ s/$regex2$/|/gi; $recipients =~ s/$regex3/,/gi; $recipients =~ s/$regex4/|/gi; next; } # Find custom types for devices if ( $type[$i] eq "default" ) { delete $typesOr[$iTypesOr] if ( $typesOr[$iTypesOr] eq "default" ); delete $type[$i]; Log3 $logDevice, 5, "msg $device: msgType lookup for $device:"; my @t = split( m/\|/, MSG_FindAttrVal( $device, "msgType", undef, "text" ) ); $hasTypeOr = 1 if ( scalar( grep { defined $_ } @t ) > 1 ); foreach (@t) { Log3 $logDevice, 5, "msg $device: Adding to \@typesOr: $_"; push @typesOr, $_; foreach ( split( /,/, lc($_) ) ) { Log3 $logDevice, 5, "msg $device: Adding to \@type: $_"; push @type, $_; } } } my $forceType = 0; if ( $type[$i] =~ m/^([A-Za-z\d_\.\-\/]+)([^A-Za-z\d_\.\-\/]+)[\s\t ]*$/ ) { $type[$i] = $1; foreach ( split( /(\S)/, $2 ) ) { $forceType = 1 if ( $_ eq "!" ); $softFail = 1 if ( $_ eq "?" ); $forceQueue = 1 if ( $_ eq "&" ); } Log3 $logDevice, 5, "msg $device: forceType=$forceType (from type)" if ($forceType); Log3 $logDevice, 5, "msg $device: softFail=$softFail (from type)" if ($softFail); Log3 $logDevice, 5, "msg $device: forceQueue=$forceQueue (from type)" if ($forceQueue); } # next type loop if device is an email address # and this is not the mail type loop run if ( $deviceType eq "email" && $type[$i] ne "mail" && $type[$i] ne "text" ) { Log3 $logDevice, 5, "msg $device: " . "Skipping loop for device type 'email' with unmatched message type '" . $type[$i] . "'"; next; } my $typeUc = ucfirst( $type[$i] ); my $catchall = 0; my $useLocation = 0; ################################################################ ### get target information from device location ### # search for location references my @locationDevs; @locationDevs = split( /,/, MSG_FindAttrVal( $device, "msgLocationDevs", $typeUc, "" ) ); if ( $deviceType eq "device" ) { # get device location my $deviceLocation = msgConfig_FindReadingsVal( $device, "location", $typeUc, "" ); my $locationDev = ""; if ( $deviceLocation ne "" && $deviceType eq "device" ) { # lookup matching location foreach (@locationDevs) { if ( $featurelevel >= 5.7 ) { my %dummy; my ( $err, @a ) = ReplaceSetMagic( \%dummy, 0, ($_) ); $_ = join( " ", @a ) unless ($err); } my $lName = AttrVal( $_, "msgLocationName", "" ); if ( $lName ne "" && $lName eq $deviceLocation ) { $locationDev = $_; last; } } if ( $featurelevel >= 5.7 ) { my %dummy; my ( $err, @a ) = ReplaceSetMagic( \%dummy, 0, ($locationDev) ); $locationDev = join( " ", @a ) unless ($err); } # look for gateway device $gatewayDevs = MSG_FindAttrVal( $locationDev, "msgContact", $typeUc, "" ); # at least one of the location gateways needs to # be available. Otherwise we fall back to # non-location contacts if ( $gatewayDevs ne "" ) { if ( $featurelevel >= 5.7 ) { my %dummy; my ( $err, @a ) = ReplaceSetMagic( \%dummy, 0, ($gatewayDevs) ); $gatewayDevs = join( " ", @a ) unless ($err); } foreach my $gatewayDevOr ( split /\|/, $gatewayDevs ) { foreach my $gatewayDev ( split /,/, $gatewayDevOr ) { my $tmpSubRecipient; if ( $gatewayDev =~ /:(.*)/ ) { $tmpSubRecipient = $1; } if ( $type[$i] ne "mail" && !IsDevice($gatewayDev) && $deviceType eq "device" ) { $useLocation = 2 if ( $useLocation == 0 ); } elsif ( $type[$i] ne "mail" && IsDisabled($gatewayDev) ) { $useLocation = 2 if ( $useLocation == 0 ); } elsif ( $type[$i] ne "mail" && ( ( grep { ReadingsVal( $gatewayDev, "presence", "present" ) eq $_ } @unavailabilityIndicators ) || ( grep { ReadingsVal( $gatewayDev, "state", "present" ) eq $_ } @unavailabilityIndicators ) || ( defined( $defs{$gatewayDev} ) && defined( $defs{$gatewayDev} {STATE} ) && ( grep { $defs{$gatewayDev} {STATE} eq $_ } @unavailabilityIndicators ) ) || ReadingsVal( $gatewayDev, "available", "yes" ) =~ m/^(0|no|false)$/i || ReadingsVal( $gatewayDev, "reachable", "yes" ) =~ m/^(0|no|false)$/i ) ) { $useLocation = 2 if ( $useLocation == 0 ); } else { $useLocation = 1; } } } # use gatewayDevs from location only # if it has been confirmed to be available if ( $useLocation == 1 ) { Log3 $logDevice, 4, "msg $device: " . "Matching location definition found"; } else { $gatewayDevs = ""; } } } } ################################################################ ### given device name is already a gateway device itself ### my $deviceType2 = GetType($device); if ( $gatewayDevs eq "" && $deviceType eq "device" && $deviceType2 ne "" && ( ( $type[$i] eq "audio" && defined( $cmdSchema->{ $type[$i] }{$deviceType2} ) ) || ( $type[$i] eq "light" && defined( $cmdSchema->{ $type[$i] }{$deviceType2} ) ) || ( $type[$i] eq "push" && defined( $cmdSchema->{ $type[$i] }{$deviceType2} ) ) || ( $type[$i] eq "screen" && defined( $cmdSchema->{ $type[$i] }{$deviceType2} ) ) || ( $type[$i] eq "queue" && defined( $cmdSchema->{ $type[$i] }{$deviceType2} ) ) ) ) { Log3 $logDevice, 4, "msg $device: Recipient type $deviceType2 " . "is a gateway device itself for message type " . $type[$i] . ". Still checking for any delegates ..." unless ( defined($testMode) && $testMode eq "1" ); $gatewayDevs = MSG_FindAttrVal( $device, "msgContact", $typeUc, $device ); } ################################################################ ### get target information from device ### elsif ( $deviceType eq "device" && $gatewayDevs eq "" ) { # look for gateway device $gatewayDevs = MSG_FindAttrVal( $device, "msgContact", $typeUc, "" ); # fallback/catchall if ( $gatewayDevs eq "" ) { $catchall = 1 if ( $device ne $globalDevName ); Log3 $logDevice, 6, "msg $device: (No $typeUc contact defined, " . "trying global instead)" if ( $catchall == 1 ); $gatewayDevs = MSG_FindAttrVal( $globalDevName, "msgContact", $typeUc, "" ); } } # Find priority if none was explicitly specified my $loopPriority = $priority; $loopPriority = MSG_FindAttrVal( $device, "msgPriority$typeUc", $typeUc, MSG_FindAttrVal( $device, "msgPriority", $typeUc, 0 ) ) if ( $priority eq "" ); # check for available routes # my %routes; $routes{screen} = 0; $routes{light} = 0; $routes{audio} = 0; $routes{text} = 0; $routes{push} = 0; $routes{mail} = 0; $routes{queue} = 1; if ( !defined($testMode) || ( $testMode ne "1" && $testMode ne "2" ) ) { Log3 $logDevice, 5, "msg $device: Checking for available routes " . "(triggered by type $type[$i])"; $routes{screen} = 1 if ( $deviceType eq "device" && CommandMsg( "screen", "screen \@$device $priority Routing Test", 1 ) eq "ROUTE_AVAILABLE" ); $routes{light} = 1 if ( $deviceType eq "device" && CommandMsg( "light", "light \@$device $priority Routing Test", 1 ) eq "ROUTE_AVAILABLE" ); $routes{audio} = 1 if ( $deviceType eq "device" && CommandMsg( "audio", "audio \@$device $priority Routing Test", 1 ) eq "ROUTE_AVAILABLE" ); if ( $deviceType eq "device" && CommandMsg( "push", "push \@$device $priority Routing Test", 1 ) eq "ROUTE_AVAILABLE" ) { $routes{push} = 1; $routes{text} = 1; } if ( CommandMsg( "mail", "mail \@$device $priority Routing Test", 1 ) eq "ROUTE_AVAILABLE" ) { $routes{mail} = 1; $routes{text} = 1; } $routes{mail} = 1 if ( $deviceType eq "email" ); Log3 $logDevice, 4, "msg $device: Available routes: screen=" . $routes{screen} . " light=" . $routes{light} . " audio=" . $routes{audio} . " text=" . $routes{text} . " push=" . $routes{push} . " mail=" . $routes{mail}; } ################################################## ### dynamic routing for text (->push, ->mail) ### if ( $type[$i] eq "text" ) { # user selected emergency priority text threshold my $prioThresTextEmg = MSG_FindAttrVal( $device, "msgThPrioTextEmergency", $typeUc, 2 ); # user selected low priority text threshold my $prioThresTextNormal = MSG_FindAttrVal( $device, "msgThPrioTextNormal", $typeUc, -2 ); # Decide push and/or e-mail destination based # on priorities if ( $loopPriority >= $prioThresTextEmg && $routes{push} == 1 && $routes{mail} == 1 ) { Log3 $logDevice, 4, "msg $device: " . "Text routing decision: push+mail(1)"; $forwarded .= "," if ( $forwarded ne "" ); $forwarded .= "text>push+mail"; push @type, "push" unless grep { "push" eq $_ } @type; push @type, "mail" unless grep { "mail" eq $_ } @type; } elsif ($loopPriority >= $prioThresTextEmg && $routes{push} == 1 && $routes{mail} == 0 ) { Log3 $logDevice, 4, "msg $device: Text routing decision: push(2)"; $forwarded .= "," if ( $forwarded ne "" ); $forwarded .= "text>push"; push @type, "push" unless grep { "push" eq $_ } @type; } elsif ($loopPriority >= $prioThresTextEmg && $routes{push} == 0 && $routes{mail} == 1 ) { Log3 $logDevice, 4, "msg $device: Text routing decision: mail(3)"; $forwarded .= "," if ( $forwarded ne "" ); $forwarded .= "text>mail"; push @type, "mail" unless grep { "mail" eq $_ } @type; } elsif ($loopPriority >= $prioThresTextNormal && $routes{push} == 1 ) { Log3 $logDevice, 4, "msg $device: Text routing decision: push(4)"; $forwarded .= "," if ( $forwarded ne "" ); $forwarded .= "text>push"; push @type, "push" unless grep { "push" eq $_ } @type; } elsif ($loopPriority >= $prioThresTextNormal && $routes{mail} == 1 ) { Log3 $logDevice, 4, "msg $device: Text routing decision: mail(5)"; $forwarded .= "," if ( $forwarded ne "" ); $forwarded .= "text>mail"; push @type, "mail" unless grep { "mail" eq $_ } @type; } elsif ( $routes{mail} == 1 ) { Log3 $logDevice, 4, "msg $device: Text routing decision: mail(6)"; $forwarded .= "," if ( $forwarded ne "" ); $forwarded .= "text>mail"; push @type, "mail" unless grep { "mail" eq $_ } @type; } elsif ( $routes{push} == 1 ) { Log3 $logDevice, 4, "msg $device: Text routing decision: push(7)"; $forwarded .= "," if ( $forwarded ne "" ); $forwarded .= "text>push"; push @type, "push" unless grep { "push" eq $_ } @type; } # FATAL ERROR: routing decision failed else { Log3 $logDevice, 4, "msg $device: " . "Text routing FAILED - priority=$loopPriority push=" . $routes{push} . " mail=" . $routes{mail}; $loopReturn3 .= "ERROR: Could not find any Push or Mail contact " . "for device $device - set attributes: msgContactPush " . "| msgContactMail | msgContactText | msgRecipientPush " . "| msgRecipientMail | msgRecipientText | msgRecipient\n"; } next; } # FATAL ERROR: we could not find any targets for # user specified device... if ( $gatewayDevs eq "" && $device ne $globalDevName ) { $loopReturn3 .= "ERROR: Could not find any $typeUc contact " . "for device $device - set attributes: msgContact$typeUc | msgRecipient$typeUc | msgRecipient\n" unless ( $type[$i] eq "queue" ); } # FATAL ERROR: we could not find any targets at all elsif ( $gatewayDevs eq "" ) { $loopReturn3 .= "ERROR: Could not find any general $typeUc contact. " . "Please specify a destination device or set attributes in general msg configuration device $globalDevName : msgContact$typeUc | msgRecipient$typeUc | msgRecipient\n" unless ( $type[$i] eq "queue" ); } # user selected audio-visual announcement state my $annState = ReadingsVal( MSG_FindAttrVal( $device, "msgSwitcherDev", $typeUc, "" ), "state", "long" ); # user selected emergency priority audio threshold my $prioThresAudioEmg = MSG_FindAttrVal( $device, "msgThPrioAudioEmergency", $typeUc, 2 ); # user selected high priority audio threshold my $prioThresAudioHigh = MSG_FindAttrVal( $device, "msgThPrioAudioHigh", $typeUc, 1 ); # user selected high priority threshold my $prioThresHigh = MSG_FindAttrVal( $device, "msgThPrioHigh", $typeUc, 2 ); # user selected normal priority threshold my $prioThresNormal = MSG_FindAttrVal( $device, "msgThPrioNormal", $typeUc, 0 ); if ( $type[$i] eq "audio" ) { if ( $annState eq "long" || $forceType == 1 || $forceDevice == 1 || $loopPriority >= $prioThresAudioEmg ) { $priorityCat = ""; } elsif ( $loopPriority >= $prioThresAudioHigh ) { $priorityCat = "ShortPrio"; } else { $priorityCat = "Short"; } } else { if ( $loopPriority >= $prioThresHigh ) { $priorityCat = "High"; } elsif ( $loopPriority >= $prioThresNormal ) { $priorityCat = ""; } else { $priorityCat = "Low"; } } # get resident presence information # my $residentDevState = ""; my $residentDevPresence = ""; # device if ( ReadingsVal( $device, "presence", "-" ) ne "-" ) { $residentDevState = ReadingsVal( $device, "state", "" ); $residentDevPresence = ReadingsVal( $device, "presence", "" ); } # device indirect if ( ( $residentDevState eq "" || $residentDevPresence eq "" ) && ReadingsVal( AttrVal( $device, "msgRecipient$typeUc", "" ), "presence", "-" ) ne "-" ) { $residentDevState = ReadingsVal( AttrVal( $device, "msgRecipient$typeUc", "" ), "state", "" ) if ( $residentDevState eq "" ); $residentDevPresence = ReadingsVal( AttrVal( $device, "msgRecipient$typeUc", "" ), "presence", "" ) if ( $residentDevPresence eq "" ); } # device indirect general if ( ( $residentDevState eq "" || $residentDevPresence eq "" ) && ReadingsVal( AttrVal( $device, "msgRecipient", "" ), "presence", "-" ) ne "-" ) { $residentDevState = ReadingsVal( AttrVal( $device, "msgRecipient", "" ), "state", "" ) if ( $residentDevState eq "" ); $residentDevPresence = ReadingsVal( AttrVal( $device, "msgRecipient", "" ), "presence", "" ) if ( $residentDevPresence eq "" ); } # device explicit if ( ( $residentDevState eq "" || $residentDevPresence eq "" ) && ReadingsVal( AttrVal( $device, "msgResidentsDev", "" ), "presence", "-" ) ne "-" ) { $residentDevState = ReadingsVal( AttrVal( $device, "msgResidentsDev", "" ), "state", "" ) if ( $residentDevState eq "" ); $residentDevPresence = ReadingsVal( AttrVal( $device, "msgResidentsDev", "" ), "presence", "" ) if ( $residentDevPresence eq "" ); } # global indirect if ( ( $residentDevState eq "" || $residentDevPresence eq "" ) && ReadingsVal( AttrVal( $globalDevName, "msgRecipient$typeUc", "" ), "presence", "-" ) ne "-" ) { $residentDevState = ReadingsVal( AttrVal( $globalDevName, "msgRecipient$typeUc", "" ), "state", "" ) if ( $residentDevState eq "" ); $residentDevPresence = ReadingsVal( AttrVal( $globalDevName, "msgRecipient$typeUc", "" ), "presence", "" ) if ( $residentDevPresence eq "" ); } # global indirect general if ( ( $residentDevState eq "" || $residentDevPresence eq "" ) && ReadingsVal( AttrVal( $globalDevName, "msgRecipient", "" ), "presence", "-" ) ne "-" ) { $residentDevState = ReadingsVal( AttrVal( $globalDevName, "msgRecipient", "" ), "state", "" ) if ( $residentDevState eq "" ); $residentDevPresence = ReadingsVal( AttrVal( $globalDevName, "msgRecipient", "" ), "presence", "" ) if ( $residentDevPresence eq "" ); } # global explicit if ( ( $residentDevState eq "" || $residentDevPresence eq "" ) && ReadingsVal( AttrVal( $globalDevName, "msgResidentsDev", "" ), "presence", "-" ) ne "-" ) { $residentDevState = ReadingsVal( AttrVal( $globalDevName, "msgResidentsDev", "" ), "state", "" ) if ( $residentDevState eq "" ); $residentDevPresence = ReadingsVal( AttrVal( $globalDevName, "msgResidentsDev", "" ), "presence", "" ) if ( $residentDevPresence eq "" ); } ################################################################ ### Send message ### my $queued = 0; # user selected emergency priority text threshold my $prioThresGwEmg = MSG_FindAttrVal( $device, "msgThPrioGwEmergency", $typeUc, 2 ); if ( $featurelevel >= 5.7 ) { my %dummy; my ( $err, @a ) = ReplaceSetMagic( \%dummy, 0, ($gatewayDevs) ); $gatewayDevs = join( " ", @a ) unless ($err); } my %gatewaysStatus; $gatewayDevs = $globalDevName if ( $type[$i] eq "queue" ); foreach my $gatewayDevOr ( split /\|/, $gatewayDevs ) { foreach my $gatewayDev ( split /,/, $gatewayDevOr ) { if ( $gatewayDev =~ m/^@?([A-Za-z0-9._]+):([A-Za-z0-9._\-\/@+]*):?([A-Za-z0-9._\-\/@+]*)$/ ) { $gatewayDev = $1; $subRecipient = $2 if ( $subRecipient eq "" ); $termRecipient = $3 if ( $termRecipient eq "" ); } my $logMsg = "msg $device: " . "Trying to send message via gateway $gatewayDev"; $logMsg .= " to recipient $subRecipient" if ( $subRecipient ne "" ); $logMsg .= ", terminal device $termRecipient" if ( $termRecipient ne "" ); Log3 $logDevice, 5, $logMsg unless ( defined($testMode) && $testMode eq "1" ); ############## # check for gateway availability and set route status # my $routeStatus = "OK"; if ( $type[$i] eq "queue" ) { $routeStatus = "OK_QUEUE"; } elsif ($type[$i] ne "mail" && !IsDevice($gatewayDev) && $deviceType eq "device" ) { $routeStatus = "UNDEFINED"; } elsif ( $type[$i] ne "mail" && IsDisabled($gatewayDev) ) { $routeStatus = "DISABLED"; } elsif ( $type[$i] ne "mail" && ( ( grep { ReadingsVal( $gatewayDev, "presence", "present" ) eq $_ } @unavailabilityIndicators ) || ( grep { ReadingsVal( $gatewayDev, "state", "present" ) eq $_ } @unavailabilityIndicators ) || ( IsDevice($gatewayDev) && defined( $defs{$gatewayDev}{STATE} ) && ( grep { $defs{$gatewayDev}{STATE} eq $_ } @unavailabilityIndicators ) ) || ReadingsVal( $gatewayDev, "available", "yes" ) =~ m/^(0|no|false)$/i || ReadingsVal( $gatewayDev, "reachable", "yes" ) =~ m/^(0|no|false)$/i ) ) { $routeStatus = "UNAVAILABLE"; } elsif ( $type[$i] ne "mail" && ( ReadingsVal( $gatewayDev, "presence", "present" ) =~ m/^(0|false|absent|disappeared|unauthorized|disconnected|unreachable)$/i || ReadingsVal( $gatewayDev, "state", "present" ) =~ m/^(absent|disappeared|unauthorized|disconnected|unreachable)$/i || ( IsDevice($gatewayDev) && defined( $defs{$gatewayDev}{STATE} ) && $defs{$gatewayDev}{STATE} =~ m/^(absent|disappeared|unauthorized|disconnected|unreachable)$/i ) || ReadingsVal( $gatewayDev, "available", "yes" ) =~ m/^(0|no|off|false)$/i || ReadingsVal( $gatewayDev, "reachable", "yes" ) =~ m/^(0|no|off|false)$/i ) ) { $routeStatus = "UNAVAILABLE"; } elsif ( $type[$i] eq "screen" && ReadingsVal( $gatewayDev, "power", "on" ) =~ m/^(0|off)$/i ) { $routeStatus = "OFF"; } elsif ($type[$i] eq "audio" && $annState ne "long" && $annState ne "short" ) { $routeStatus = "USER_DISABLED"; } elsif ( $type[$i] eq "light" && $annState eq "off" ) { $routeStatus = "USER_DISABLED"; } elsif ($type[$i] ne "push" && $type[$i] ne "mail" && $residentDevPresence eq "absent" ) { $routeStatus = "USER_ABSENT"; } elsif ($type[$i] ne "push" && $type[$i] ne "mail" && $residentDevState eq "asleep" ) { $routeStatus = "USER_ASLEEP"; } # enforce by user request if ( ( $routeStatus eq "USER_DISABLED" || $routeStatus eq "USER_ABSENT" || $routeStatus eq "USER_ASLEEP" ) && ( $forceType == 1 || $forceDevice == 1 ) ) { $routeStatus = "OK_ENFORCED"; } # enforce by priority if ( ( $routeStatus eq "USER_DISABLED" || $routeStatus eq "USER_ABSENT" || $routeStatus eq "USER_ASLEEP" ) && $loopPriority >= $prioThresGwEmg ) { $routeStatus = "OK_EMERGENCY"; } # add location status if ( $useLocation == 2 ) { $routeStatus .= "+LOCATION-UNAVAILABLE"; } elsif ( $useLocation == 1 ) { $routeStatus .= "+LOCATION"; } # # add to queue # if ( # ( # $routeStatus eq "USER_DISABLED" # || $routeStatus eq "USER_ABSENT" # || $routeStatus eq "USER_ASLEEP" # ) # && $routeStatus !~ /^OK/ # && !$softFail # ) # { # $routeStatus .= "+QUEUE"; # } my $gatewayType = $type[$i] eq "mail" ? "fhemMsgMail" : GetType( $gatewayDev, "UNDEFINED" ); my $defTitle = ""; $defTitle = $cmdSchema->{ $type[$i] }{$gatewayType} {defaultValues}{$priorityCat}{TITLE} if ( defined( $cmdSchema->{ $type[$i] }{$gatewayType} {defaultValues}{$priorityCat}{TITLE} ) && $priorityCat ne "" ); $defTitle = $cmdSchema->{ $type[$i] }{$gatewayType} {defaultValues}{Normal}{TITLE} if ( defined( $cmdSchema->{ $type[$i] }{$gatewayType} {defaultValues}{Normal}{TITLE} ) && $priorityCat eq "" ); Log3 $logDevice, 5, "msg $device: " . "Determined default title: $defTitle" unless ( defined($testMode) && $testMode eq "1" ); # use title from device, global or internal default my $loopTitle = $title; $loopTitle = MSG_FindAttrVal( $device, "msgTitle$typeUc$priorityCat", $typeUc, MSG_FindAttrVal( $device, "msgTitle$typeUc", $typeUc, MSG_FindAttrVal( $device, "msgTitle", $typeUc, $defTitle ) ) ) if ( $title eq "-" ); $loopTitle = "" if ( $loopTitle eq "none" || $loopTitle eq "-" ); my $loopMsg = $msg; if ( $catchall == 1 && $type[$i] ne "queue" ) { $loopTitle = "Fw: $loopTitle" if ( $loopTitle ne "" && $type[$i] !~ /^(audio|screen)$/ ); $loopMsg = "Forwarded Message: $loopMsg" if ( $loopTitle eq "" ); if ( $type[$i] eq "mail" ) { $loopMsg .= "\n\n-- \nMail catched " . "from device $device"; } elsif ( $type[$i] !~ /^(audio|screen)$/ ) { $loopMsg .= " ### (Catched from device $device)"; } } my $loopMsgShrt = defined( $params->{msgTextShrt} ) ? $params->{msgTextShrt} : $msg; # correct message format # # Remove Sonos Speak commands $loopMsg =~ s/(\s*\|\w+\|\s*)/\\n\\n/gi if ( $type[$i] ne "audio" ); $loopMsgShrt =~ s/(\s*\|\w+\|\s*)/\\n\\n/gi if ( $type[$i] ne "audio" ); # Replace new line with HTML break # for e-mails $loopMsg =~ s/\n/<br \/>\n/gi if ( $type[$i] eq "mail" ); $loopMsgShrt =~ s/\n/<br \/>\n/gi if ( $type[$i] eq "mail" ); # use command from device, global or internal default my $defCmd = ""; $defCmd = $cmdSchema->{ $type[$i] }{$gatewayType} {$priorityCat} if ( defined( $cmdSchema->{ $type[$i] }{$gatewayType} {$priorityCat} ) && $priorityCat ne "" ); $defCmd = $cmdSchema->{ $type[$i] }{$gatewayType}{Normal} if ( defined( $cmdSchema->{ $type[$i] }{$gatewayType} {Normal} ) && $priorityCat eq "" ); my $cmd = # gateway device AttrVal( $gatewayDev, "msgCmd$typeUc$priorityCat", MSG_FindAttrVal( $device, "msgCmd$typeUc$priorityCat", $typeUc, $defCmd ) ); if ( $cmd eq "" && $type[$i] ne "queue" ) { Log3 $logDevice, 4, "$gatewayDev: Unknown command schema " . "for gateway device type $gatewayType. Use manual definition by userattr msgCmd*" unless ( defined($testMode) && $testMode eq "1" ); $loopReturn3 .= "$gatewayDev: Unknown command schema " . "for gateway device type $gatewayType. Use manual definition by userattr msgCmd*\n"; next; } # ReplaceSetMagic # my $replaceError; if ( $featurelevel >= 5.7 ) { my %dummy; my ( $err, @a ); # TITLE ( $err, @a ) = ReplaceSetMagic( \%dummy, 0, ($loopTitle) ); $replaceError .= "ReplaceSetMagic failed for TITLE: $err\n" if ($err); $loopTitle = join( " ", @a ) unless ($err); # RECIPIENT if ( $subRecipient ne "" ) { ( $err, @a ) = ReplaceSetMagic( \%dummy, 0, ($subRecipient) ); $replaceError .= "ReplaceSetMagic failed " . "for RECIPIENT: $err\n" if ($err); $subRecipient = join( " ", @a ) unless ($err); } # TERMINAL if ( $termRecipient ne "" ) { ( $err, @a ) = ReplaceSetMagic( \%dummy, 0, ($termRecipient) ); $replaceError .= "ReplaceSetMagic failed " . "for TERMINAL: $err\n" if ($err); $termRecipient = join( " ", @a ) unless ($err); } } $cmd =~ s/%PRIORITY%/$loopPriority/gi; $cmd =~ s/%PRIOCAT%/$priorityCat/gi; $cmd =~ s/%MSG%/$loopMsg/gi; $cmd =~ s/%MSGSHRT%/$loopMsgShrt/gi; $cmd =~ s/%MSGID%/$msgID.$sentCounter/gi; $cmd =~ s/%TITLE%/$loopTitle/gi; my $loopTitleShrt = defined( $params->{msgTitleShrt} ) ? $params->{msgTitleShrt} : MSG_FindAttrVal( $device, "msgTitleShrt$typeUc$priorityCat", $typeUc, MSG_FindAttrVal( $device, "msgTitleShrt$typeUc", $typeUc, MSG_FindAttrVal( $device, "msgTitleShrt", $typeUc, $loopTitle ) ) ); $loopTitleShrt = substr( $loopTitleShrt, 0, 37 ) . "..." if ( length($loopTitleShrt) > 40 ); $cmd =~ s/%TITLESHRT%/$loopTitleShrt/gi; $loopTitleShrt =~ s/ /_/; $cmd =~ s/%TITLESHRT2%/$loopTitleShrt/gi; $loopTitleShrt =~ s/^([\s\t ]*\w+).*/$1/g; $loopTitleShrt = substr( $loopTitleShrt, 0, 17 ) . "..." if ( length($loopTitleShrt) > 20 ); $cmd =~ s/%TITLESHRT3%/$loopTitleShrt/gi; my $deviceName = AttrVal( $device, AttrVal( $device, "rg_realname", AttrVal( $device, "rr_realname", "group" ) ), AttrVal( $device, "alias", $device ) ); my $deviceName2 = $deviceName; $deviceName2 =~ s/ /_/; $cmd =~ s/%SOURCE%/$device/gi; $cmd =~ s/%SRCALIAS%/$deviceName/gi; $cmd =~ s/%SRCALIAS2%/$deviceName2/gi; my $gatewayDevName = AttrVal( $gatewayDev, AttrVal( $gatewayDev, "rg_realname", AttrVal( $gatewayDev, "rr_realname", "group" ) ), AttrVal( $gatewayDev, "alias", $gatewayDev ) ); my $gatewayDevName2 = $gatewayDevName; $gatewayDevName2 =~ s/ /_/; $cmd =~ s/%DEVICE%/$gatewayDev/gi; $cmd =~ s/%DEVALIAS%/$gatewayDevName/gi; $cmd =~ s/%DEVALIAS2%/$gatewayDevName2/gi; my $loopMsgDateTime = $msgDateTime; $loopMsgDateTime .= ".$sentCounter" if ($sentCounter); my $loopMsgDateTime2 = $loopMsgDateTime; $loopMsgDateTime2 =~ s/ /_/; $cmd =~ s/%MSGDATETIME%/$loopMsgDateTime/gi; $cmd =~ s/%MSGDATETIME2%/$loopMsgDateTime2/gi; my $subRecipientName = $subRecipient eq "" ? "" : AttrVal( $subRecipient, AttrVal( $subRecipient, "rg_realname", AttrVal( $subRecipient, "rr_realname", "group" ) ), AttrVal( $subRecipient, "alias", $subRecipient ) ); my $subRecipientName2 = $subRecipientName; $subRecipientName2 =~ s/ /_/; $cmd =~ s/%RECIPIENT%/$subRecipient/gi if ( $subRecipient ne "" ); $cmd =~ s/%RCPTNAME%/$subRecipientName/gi if ( $subRecipientName ne "" ); $cmd =~ s/%RCPTNAME2%/$subRecipientName2/gi if ( $subRecipientName2 ne "" ); $cmd =~ s/%TERMINAL%/$termRecipient/gi if ( $termRecipient ne "" ); my $paramsA; unless ( defined($testMode) && $testMode eq "1" ) { # user parameters from message if ( ref($params) eq "HASH" ) { for my $key ( keys %$params ) { next if ( ref( $params->{$key} ) ); my $val = $params->{$key}; $cmd =~ s/%$key%/$val/gi; $cmd =~ s/\$$key/$val/g; Log3 $logDevice, 5, "msg $device: User parameters: " . "replacing %$key% and \$$key by '$val'"; } } # user parameters from attributes my $paramsAttr1 = AttrVal( $gatewayDev, "msgParams$typeUc$priorityCat", undef ); my $paramsAttr2 = AttrVal( $gatewayDev, "msgParams$typeUc", undef ); my $paramsAttr3 = AttrVal( $gatewayDev, "msgParams", undef ); my $paramsAttr4 = MSG_FindAttrVal( $device, "msgParams$typeUc$priorityCat", $typeUc, undef ); my $paramsAttr5 = MSG_FindAttrVal( $device, "msgParams$typeUc", $typeUc, undef ); my $paramsAttr6 = MSG_FindAttrVal( $device, "msgParams", $typeUc, undef ); foreach ( $paramsAttr1, $paramsAttr2, $paramsAttr3, $paramsAttr4, $paramsAttr5, $paramsAttr6 ) { next unless ($_); if ( $_ =~ m/^{.*}$/s && $_ =~ m/=>/ && $_ !~ m/\$/ ) { my $av = eval $_; if ($@) { Log3 $logDevice, 3, "msg $device: " . "ERROR while reading attribute msgParams"; } else { $paramsA = $av; } } else { my ( $a, $h ) = parseParams($_); $paramsA = $h; } next unless ref($paramsA) eq "HASH"; if ( ref($paramsA) eq "HASH" ) { for my $key ( keys %$paramsA ) { next if ( ref( $params->{$key} ) ); my $val = $paramsA->{$key}; $cmd =~ s/%$key%/$val/gi; $cmd =~ s/\$$key/$val/g; Log3 $logDevice, 5, "msg $device: " . "msgParams: replacing %$key% and \$$key by '$val'"; } } } # user parameters from command schema hash if ( $priorityCat ne "" && defined( $cmdSchema->{ $type[$i] }{$gatewayType} {defaultValues}{$priorityCat} ) ) { for my $item ( $cmdSchema->{ $type[$i] }{$gatewayType} {defaultValues}{$priorityCat} ) { for my $key ( keys(%$item) ) { my $val = $item->{$key}; $cmd =~ s/%$key%/$val/gi; $cmd =~ s/\$$key/$val/g; Log3 $logDevice, 5, "msg $device: " . "msgSchema: replacing %$key% and \$$key by '$val'"; } } } elsif ( $priorityCat eq "" && defined( $cmdSchema->{ $type[$i] }{$gatewayType} {defaultValues}{Normal} ) ) { for my $item ( $cmdSchema->{ $type[$i] }{$gatewayType} {defaultValues}{Normal} ) { for my $key ( keys(%$item) ) { my $val = $item->{$key}; $cmd =~ s/%$key%/$val/gi; $cmd =~ s/\$$key/$val/g; Log3 $logDevice, 5, "msg $device: " . "msgSchema: replacing %$key% and \$$key by '$val'"; } } } } $sentCounter++; if ( $routeStatus =~ /^OK\w*/ ) { my $error = 0; unless ( defined($testMode) && $testMode eq "1" ) { # ReplaceSetMagic # if ( $featurelevel >= 5.7 && !$replaceError ) { my %dummy; my ( $err, @a ) = ReplaceSetMagic( \%dummy, 0, ($cmd) ); $replaceError .= "ReplaceSetMagic failed for CMD: " . "$err\n" if ($err); $cmd = join( " ", @a ) unless ($err); } # add user parameters # if gateway supports parseParams my $gatewayDevType = GetType($gatewayDev); if ( $gatewayDevType && ref($params) eq "HASH" && ( $modules{$gatewayDevType} ->{parseParams} || $modules{$gatewayDevType} ->{msgParams}{parseParams} || $modules{$gatewayDevType} ->{'.msgParams'}{parseParams} ) ) { Log3 $logDevice, 5, "msg $device: " . "parseParams support: Handing over user parameters to other device"; my ( $a, $h ) = parseParams($cmd); keys %$params; while ( ( my $key, my $value ) = each %$params ) { # Compatibility to legacy schema: # lowercase after _ my $s = $gatewayDevType . "[\_\/-]([A-Z0-9_-]+)"; $key =~ s/^$s$/\L$1/; # remove gateway TYPE when # used as prefix $s = $gatewayDevType . "[_\/-]"; $key =~ s/^$s//; $cmd .= " $key='$value'" if ( !defined( $h->{$key} ) || $h->{$key} =~ m/^[\s\t\n ]*$/ ); } keys %$paramsA; while ( ( my $key, my $value ) = each %$paramsA ) { # Compatibility to legacy schema: # lowercase after _ my $s = $gatewayDevType . "[\_\/-]([A-Z0-9_-]+)"; $key =~ s/^$s$/\L$1/; # remove gateway TYPE when # used as prefix $s = $gatewayDevType . "[_\/-]"; $key =~ s/^$s//; $cmd .= " $key='$value'" if ( !defined( $h->{$key} ) || $h->{$key} =~ m/^[\s\t\n ]*$/ ); } } # excplicitly queue message if ( $routeStatus eq "OK_QUEUE" ) { $queued = msgConfig_QueueAdd( $msgA, $params, $msgDateTime, $msgID, $sentCounter, $type[$i], $device, $subRecipient, $termRecipient, $priority, $loopTitle, $loopMsg ) ? 1 : 0; } # run command elsif ($replaceError) { $error = 2; $loopReturn3 .= $replaceError; } elsif ( $cmd =~ /^\s*\{.*\}\s*$/ ) { Log3 $logDevice, 5, "msg $device: " . "$type[$i] route command (Perl): $cmd"; #eval $cmd; my $ret = AnalyzePerlCommand(undef, $cmd); unless ( !$ret || $ret =~ m/^[\s\t\n ]*$/ ) { $error = 1; $loopReturn3 .= "$gatewayDev: $ret\n"; } } else { Log3 $logDevice, 5, "msg $device: " . "$type[$i] route command (fhem): $cmd"; my $ret = AnalyzeCommandChain(undef,$cmd); unless ( !$ret || $ret =~ m/^[\s\t\n ]*$/ ) { $error = 1; $loopReturn3 .= "$gatewayDev: $ret\n"; } } $routeStatus = "ERROR" if ( $error == 1 ); $routeStatus = "ERROR_EVAL" if ( $error == 2 ); Log3 $logDevice, 3, "msg $device: " . "ID=$msgID.$sentCounter " . "TYPE=$type[$i] " . "ROUTE=$gatewayDev " . "RECIPIENT=$subRecipient " . "STATUS=$routeStatus " . "PRIORITY=$loopPriority($priorityCat) " . "TITLE='$loopTitle' " . "MSG='$loopMsg'" if ( $priorityCat ne "" && $subRecipient ne "" ); Log3 $logDevice, 3, "msg $device: " . "ID=$msgID.$sentCounter " . "TYPE=$type[$i] " . "ROUTE=$gatewayDev " . "RECIPIENT=$subRecipient " . "STATUS=$routeStatus " . "PRIORITY=$loopPriority " . "TITLE='$loopTitle' " . "MSG='$loopMsg'" if ( $priorityCat eq "" && $subRecipient ne "" ); Log3 $logDevice, 3, "msg $device: " . "ID=$msgID.$sentCounter " . "TYPE=$type[$i] " . "ROUTE=$gatewayDev " . "STATUS=$routeStatus " . "PRIORITY=$loopPriority($priorityCat) " . "TITLE='$loopTitle' " . "MSG='$loopMsg'" if ( $priorityCat ne "" && $subRecipient eq "" ); Log3 $logDevice, 3, "msg $device: " . "ID=$msgID.$sentCounter " . "TYPE=$type[$i] " . "ROUTE=$gatewayDev " . "STATUS=$routeStatus " . "PRIORITY=$loopPriority " . "TITLE='$loopTitle' " . "MSG='$loopMsg'" if ( $priorityCat eq "" && $subRecipient eq "" ); } $msgSent = 1 if ( $error == 0 ); $msgSentDev = 1 if ( $error == 0 ); if ( $subRecipient ne "" ) { $gatewaysStatus{"$gatewayDev:$subRecipient"} = $routeStatus if ( $globalDevName ne $gatewayDev ); $gatewaysStatus{"$device:$subRecipient"} = $routeStatus if ( $globalDevName eq $gatewayDev ); } else { $gatewaysStatus{$gatewayDev} = $routeStatus if ( $globalDevName ne $gatewayDev ); $gatewaysStatus{$device} = $routeStatus if ( $globalDevName eq $gatewayDev ); } } elsif ( $routeStatus =~ /\+QUEUE/ || $forceQueue ) { unless ( defined($testMode) && $testMode eq "1" ) { if ( !( grep { "queue" eq $_ } @type ) ) { $queued = msgConfig_QueueAdd( $msgA, $params, $msgDateTime, $msgID, $sentCounter, $type[$i], $device, $subRecipient, $termRecipient, $priority, $loopTitle, $loopMsg ) ? 1 : 0; } Log3 $logDevice, 3, "msg $device: " . "ID=$msgID.$sentCounter " . "TYPE=$type[$i] " . "ROUTE=$gatewayDev " . "RECIPIENT=$subRecipient " . "STATUS=$routeStatus " . "PRIORITY=$loopPriority " . "TITLE='$loopTitle' '$loopMsg'" if ( $subRecipient ne "" ); Log3 $logDevice, 3, "msg $device: " . "ID=$msgID.$sentCounter " . "TYPE=$type[$i] " . "ROUTE=$gatewayDev " . "STATUS=$routeStatus " . "PRIORITY=$loopPriority " . "TITLE='$loopTitle' '$loopMsg'" if ( $subRecipient eq "" ); } $msgSent = 3 if ( $msgSent != 1 ); $msgSentDev = 3 if ( $msgSentDev != 1 ); $gatewaysStatus{$gatewayDev} = $routeStatus if ( $globalDevName ne $gatewayDev ); $gatewaysStatus{$device} = $routeStatus if ( $globalDevName eq $gatewayDev ); } elsif ($routeStatus eq "UNAVAILABLE" || $routeStatus eq "UNDEFINED" ) { unless ( defined($testMode) && $testMode eq "1" ) { Log3 $logDevice, 3, "msg $device: " . "ID=$msgID.$sentCounter " . "TYPE=$type[$i] " . "ROUTE=$gatewayDev " . "RECIPIENT=$subRecipient " . "STATUS=$routeStatus " . "PRIORITY=$loopPriority " . "TITLE='$loopTitle' '$loopMsg'" if ( $subRecipient ne "" ); Log3 $logDevice, 3, "msg $device: " . "ID=$msgID.$sentCounter " . "TYPE=$type[$i] " . "ROUTE=$gatewayDev " . "STATUS=$routeStatus " . "PRIORITY=$loopPriority " . "TITLE='$loopTitle' '$loopMsg'" if ( $subRecipient eq "" ); } $gatewaysStatus{$gatewayDev} = $routeStatus if ( $globalDevName ne $gatewayDev ); $gatewaysStatus{$device} = $routeStatus if ( $globalDevName eq $gatewayDev ); } else { unless ( defined($testMode) && $testMode eq "1" ) { Log3 $logDevice, 3, "msg $device: " . "ID=$msgID.$sentCounter " . "TYPE=$type[$i] " . "ROUTE=$gatewayDev " . "RECIPIENT=$subRecipient " . "STATUS=$routeStatus " . "PRIORITY=$loopPriority " . "TITLE='$loopTitle' '$loopMsg'" if ( $subRecipient ne "" ); Log3 $logDevice, 3, "msg $device: " . "ID=$msgID.$sentCounter " . "TYPE=$type[$i] " . "ROUTE=$gatewayDev " . "STATUS=$routeStatus " . "PRIORITY=$loopPriority " . "TITLE='$loopTitle' '$loopMsg'" if ( $subRecipient eq "" ); } $msgSent = 2 if ( $msgSent != 1 ); $msgSentDev = 2 if ( $msgSentDev != 1 ); $gatewaysStatus{$gatewayDev} = $routeStatus if ( $globalDevName ne $gatewayDev ); $gatewaysStatus{$device} = $routeStatus if ( $globalDevName eq $gatewayDev ); } } last if ( $msgSentDev == 1 || $msgSentDev == 3 ); } ##################### # return if we are in routing target test mode # if ( defined($testMode) && $testMode eq "1" ) { Log3 $logDevice, 5, "msg $device: " . "$type[$i] route check result: ROUTE_AVAILABLE" if ( $loopReturn3 eq "" ); Log3 $logDevice, 5, "msg $device: " . "$type[$i] route check result: ROUTE_UNAVAILABLE" if ( $loopReturn3 ne "" ); return "ROUTE_AVAILABLE" if ( $loopReturn3 eq "" ); return "ROUTE_UNAVAILABLE" if ( $loopReturn3 ne "" ); } if ( $catchall == 0 ) { if ( !defined( $sentTypesPerDevice{$device} ) ) { $sentTypesPerDevice{$device} = ""; } else { $sentTypesPerDevice{$device} .= " "; } $sentTypesPerDevice{$device} .= $type[$i] . ":" . $msgSentDev; } else { if ( !defined( $sentTypesPerDevice{$device} ) ) { $sentTypesPerDevice{$globalDevName} = ""; } else { $sentTypesPerDevice{$globalDevName} .= " "; } $sentTypesPerDevice{$globalDevName} .= $type[$i] . ":" . $msgSentDev; } # update device readings my $readingsDev = $defs{$device}; $readingsDev = $defs{$globalDevName} if ( $catchall == 1 || $deviceType eq "email" ); readingsBeginUpdate($readingsDev); readingsBulkUpdate( $readingsDev, "fhemMsg" . $typeUc, $msg ); readingsBulkUpdate( $readingsDev, "fhemMsg" . $typeUc . "Title", $title ); readingsBulkUpdate( $readingsDev, "fhemMsg" . $typeUc . "Prio", $loopPriority ); my $gwStates = "-"; keys %gatewaysStatus; while ( ( my $gwName, my $gwState ) = each %gatewaysStatus ) { $gwStates = "" if $gwStates eq "-"; $gwStates .= " " if $gwStates ne "-"; $gwStates .= "$gwName:$gwState"; } readingsBulkUpdate( $readingsDev, "fhemMsg" . $typeUc . "Gw", $gwStates ); readingsBulkUpdate( $readingsDev, "fhemMsg" . $typeUc . "State", $msgSentDev ); # suppress errors when there are still alternatives if ( $hasTypeOr == 1 && $isTypeOr < scalar( grep { defined $_ } @typesOr ) ) { $loopReturn3 = ""; } ################################################################ ### Implicit forwards based on priority or presence ### # Skip if typeOr is defined # and this is not the last type entry if ( $msgSentDev != 3 && $msgSentDev != 1 && $hasTypeOr == 1 && $isTypeOr < scalar( grep { defined $_ } @typesOr ) ) { Log3 $logDevice, 4, "msg $device: " . "Skipping implicit forward due to typesOr definition"; # remove recipient from list to avoid # other interaction when using recipientOr in parallel if ( $hasRecipientOr == 1 && $isRecipientOr < scalar( grep { defined $_ } @recipientsOr ) ) { my $regex1 = "\\s*!?@?" . $device . "[,|]"; # at the beginning my $regex2 = "[,|]!?@?" . $device . "\\s*"; # at the end my $regex3 = ",!?@?" . $device . ","; # in the middle with comma my $regex4 = "[\|,]!?@?" . $device . "[\|,]"; # in the middle with pipe and/or comma $recipients =~ s/^$regex1//; $recipients =~ s/$regex2$/|/gi; $recipients =~ s/$regex3/,/gi; $recipients =~ s/$regex4/|/gi; } } # Skip if recipientOr is defined # and this is not the last device entry if ( $msgSentDev != 3 && $msgSentDev != 1 && $hasRecipientOr == 1 && $isRecipientOr < scalar( grep { defined $_ } @recipientsOr ) ) { Log3 $logDevice, 4, "msg $device: " . "Skipping implicit forward due to recipientOr definition"; } # Skip if softFail elsif ( $msgSentDev != 3 && $msgSentDev != 1 && $softFail ) { Log3 $logDevice, 4, "msg $device: Skipping implicit forward"; $loopReturn3 = ""; } # priority forward thresholds # ### emergency my $msgFwPrioEmergency = MSG_FindAttrVal( $device, "msgFwPrioEmergency$typeUc", $typeUc, 2 ); ### absent my $msgFwPrioAbsent = MSG_FindAttrVal( $device, "msgFwPrioAbsent$typeUc", $typeUc, 0 ); ### gone my $msgFwPrioGone = MSG_FindAttrVal( $device, "msgFwPrioGone$typeUc", $typeUc, 1 ); my $fw_gwUnavailable = defined( $settings->{ $type[$i] }{typeEscalation}{gwUnavailable} ) ? $settings->{ $type[$i] }{typeEscalation}{gwUnavailable} : ""; my $fw_emergency = defined( $settings->{ $type[$i] }{typeEscalation}{emergency} ) ? $settings->{ $type[$i] }{typeEscalation}{emergency} : ""; my $fw_residentAbsent = defined( $settings->{ $type[$i] }{typeEscalation}{residentAbsent} ) ? $settings->{ $type[$i] }{typeEscalation}{residentAbsent} : ""; my $fw_residentGone = defined( $settings->{ $type[$i] }{typeEscalation}{residentGone} ) ? $settings->{ $type[$i] }{typeEscalation}{residentGone} : ""; # Forward message # if no gateway device for this type was available if ( $msgSentDev == 0 && $fw_gwUnavailable ne "" && !( grep { $fw_gwUnavailable eq $_ } @type ) && $routes{$fw_gwUnavailable} == 1 ) { Log3 $logDevice, 4, "msg $device: " . "Implicit forwards: No $type[$i] gateway device available for recipient $device ($gatewayDevs). Trying alternative message type " . $fw_gwUnavailable; push @type, $fw_gwUnavailable; $forwarded .= "," if ( $forwarded ne "" ); $forwarded .= $type[$i] . ">" . $fw_gwUnavailable; } # Forward message # if emergency priority if ( $loopPriority >= $msgFwPrioEmergency && $fw_emergency ne "" && !( grep { $fw_emergency eq $_ } @type ) && $routes{$fw_emergency} == 1 ) { Log3 $logDevice, 4, "msg $device: " . "Implicit forwards: Escalating high priority $type[$i] message via " . $fw_emergency; push @type, $fw_emergency; $forwarded .= "," if ( $forwarded ne "" ); $forwarded .= $type[$i] . ">" . $fw_emergency; } # Forward message # if high priority and residents are # constantly not at home if ( $residentDevPresence eq "absent" && $loopPriority >= $msgFwPrioGone && $fw_residentGone ne "" && !( grep { $fw_residentGone eq $_ } @type ) && $routes{$fw_residentGone} == 1 ) { Log3 $logDevice, 4, "msg $device: " . "Implicit forwards: Escalating high priority $type[$i] message via " . $fw_residentGone; push @type, $fw_residentGone; $forwarded .= "," if ( $forwarded ne "" ); $forwarded .= $type[$i] . ">" . $fw_residentGone; } # Forward message # if priority is normal or higher and residents # are not at home but nearby if ( !$forceQueue && $residentDevState eq "absent" && $loopPriority >= $msgFwPrioAbsent && $fw_residentAbsent ne "" && !( grep { $fw_residentAbsent eq $_ } @type ) && $routes{$fw_residentAbsent} == 1 ) { Log3 $logDevice, 4, "msg $device: " . "Implicit forwards: Escalating $type[$i] message via " . $fw_residentAbsent . " due to absence"; push @type, $fw_residentAbsent; $forwarded .= "," if ( $forwarded ne "" ); $forwarded .= $type[$i] . ">" . $fw_residentAbsent; } } $loopReturn2 .= $loopReturn3 unless ($softFail); last if ( $msgSent == 1 ); $isTypeOr++; } $loopReturn1 .= $loopReturn2; } $return .= $loopReturn1; last if ( $msgSent == 1 ); $isRecipientOr++; } # finalize device readings keys %sentTypesPerDevice; while ( ( my $device, my $types ) = each %sentTypesPerDevice ) { $device = $globalDevName if ( $device =~ /^(([A-Za-z0-9%+._-])+@+([%+a-z0-9A-Z.-]*))$/ ); readingsBulkUpdate( $defs{$device}, "fhemMsgStateTypes", $types ) if ( $forwarded eq "" ); readingsBulkUpdate( $defs{$device}, "fhemMsgStateTypes", $types . " forwards:" . $forwarded ) if ( $forwarded ne "" ); readingsBulkUpdate( $defs{$device}, "fhemMsgState", $msgSent ); readingsEndUpdate( $defs{$device}, 1 ); } $return .= "However, message was still sent to some recipients!" if ( $msgSent == 1 && $return ne "" ); $return .= "FATAL ERROR: Message NOT sent. No gateway device was available." if ( !$softFail && $msgSent == 2 ); return $return; } 1; =pod =item command =item summary dynamic routing of messages to FHEM devices and modules =item summary_DE dynamisches Routing für Nachrichten an FHEM Geräte und Module =begin html <a name="MSG"></a> <h3>msg</h3> <ul> <code>msg [<type>] [<@device>|<e-mail address>] [<priority>] [|<title>|] <message></code> <br> <br> No documentation here yet, sorry.<br> <a href="http://forum.fhem.de/index.php/topic,39983.0.html">FHEM Forum</a> </ul> =end html =begin html_DE <a name="MSG"></a> <h3>msg</h3> <ul> <code>msg [<type>] [<@device>|<e-mail address>] [<priority>] [|<title>|] <message></code> <br> <br> Bisher keine Dokumentation hier, sorry.<br> <a href="http://forum.fhem.de/index.php/topic,39983.0.html">FHEM Forum</a> </ul> =end html_DE =for :application/json;q=META.json 75_MSG.pm { "author": [ "Julian Pawlowski <julian.pawlowski@gmail.com>" ], "x_fhem_maintainer": [ "loredo" ], "x_fhem_maintainer_github": [ "jpawlowski" ], "keywords": [ "audio router", "email", "messaging", "messenger", "push" ] } =end :application/json;q=META.json =cut