# $Id$
##############################################################################
#
#     98_message.pm
#     Dynamic message and notification routing for FHEM
#
#     Copyright by Julian Pawlowski
#     e-mail: julian.pawlowski at gmail.com
#
#     This file is part of fhem.
#
#     Fhem is free software: you can redistribute it and/or modify
#     it under the terms of the GNU General Public License as published by
#     the Free Software Foundation, either version 2 of the License, or
#     (at your option) any later version.
#
#     Fhem is distributed in the hope that it will be useful,
#     but WITHOUT ANY WARRANTY; without even the implied warranty of
#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#     GNU General Public License for more details.
#
#     You should have received a copy of the GNU General Public License
#     along with fhem.  If not, see <http://www.gnu.org/licenses/>.
#
#
# Version: 1.0.0
#
# Major Version History:
#
# - 1.0.0 - 2015-09-23
# -- First release
#
##############################################################################

package main;
use strict;
use warnings;
use Time::HiRes qw(time);
use Data::Dumper;
use messageSchema;

no if $] >= 5.017011, warnings => 'experimental::smartmatch';

sub CommandMessage($$;$$);

########################################
sub message_Initialize($$) {
    my %hash = (
        Fn => "CommandMessage",
        Hlp =>
"[<type>] [<\@device>|<e-mail address>] [<priority>] [|<title>|] <message-text>",
    );
    $cmds{message} = \%hash;
}

########################################
sub CommandMessage($$;$$) {
    my ( $cl, $msg, $testMode ) = @_;
    my $return = "";

    # find existing messageConfig device or create a new instance
    my $globalDevName = "messageConfig";
    if (defined ($modules{msgConfig}{defptr})) {
        $globalDevName = $modules{msgConfig}{defptr}{NAME};
    } else {
      fhem "define $globalDevName messageConfig";
      $return .= "Global configuration device $globalDevName was created.\n\n";
    }

    if ( $msg eq "" || $msg =~ /^\?[\s\t]*$/ || $msg eq "help" ) {
        return
$return .
"Usage: message [<type>] [<\@device>|<e-mail address>] [<priority>] [|<title>|] <message>";
    }

    # default settings
    my $cmdSchema = messageSchema::get();
    my $settings = {
      'audio' => {
          'typeEscalation' => {
            'gwUnavailable' => 'text',
            'emergency' => 'text',
            'residentGone' => 'text',
            'residentAbsent' => 'text',
          },
          'title' => 'Announcement',
      },

      'light' => {
          'typeEscalation' => {
            'gwUnavailable' => 'audio',
            'emergency' => 'audio',
            'residentGone' => 'audio',
            'residentAbsent' => 'audio',
          },        
          'title' => 'Announcement',
      },

      'mail' => {
          'title' => 'System Message',
      },

      'push' => {
          'typeEscalation' => {
            'gwUnavailable' => 'mail',
            'emergency' => 'mail',
          },
          'title' => 'System Message',
      },

      'screen' => {
          'typeEscalation' => {
            'gwUnavailable' => 'light',
            'emergency' => 'light',
            'residentGone' => 'light',
            'residentAbsent' => 'light',
          },
          'title' => 'Info',
      },
    };

    ################################################################
    ### extract message details
    ###

    my $types      = "";
    my $recipients = "";
    my $priority   = "";
    my $title      = "-";
    my $advanced   = "";

    my $priorityCat = "";

    # check for message types
    if ( $msg =~
s/^[\s\t]*([a-z,]*!?(screen|light|audio|text|push|mail)[a-z,!|]*)[\s\t]+//
      )
    {
        $types = $1;
    }

    # check for given recipients
    if ( $msg =~
s/^[\s\t]*([!]?(([A-Za-z0-9%+._-])*@([%+a-z0-9A-Z.-]+))[\w,@.!|]*)[\s\t]+//
      )
    {
        $recipients = $1;
    }

    # check for given priority
    if ( $msg =~ s/^[\s\t]*([-+]{0,1}\d+[.\d]*)[\s\t]*// ) {
        $priority = $1;
    }

    # check for given message title
    if ( $msg =~
s/^[\s\t]*\|([\w\süöäß^°!"§$%&\/\\()<>=?´`"+\[\]#*@€]+)\|[\s\t]+//
      )
    {
        $title = $1;
    }

    # check for advanced options
    if ( $msg =~ s/[\s\t]*O(\[\{.*\}\])[\s\t]*$// )
    {
      # Use JSON module if possible
      eval 'use JSON qw( decode_json ); 1';
      if ( !$@ ) {
        $advanced = decode_json( Encode::encode_utf8($1) );
        Log3 $globalDevName, 5, "message: Advanced options\n" . Dumper($advanced);
      } else {
        Log3 $globalDevName, 3, "message: To use advanced options, please install Perl::JSON.";
      }
    }

    ################################################################
    ### command queue
    ###

    $types = "text"
      if ( $types eq "" );
    my $messageSent = 0;
    my $forwarded   = "";
    my %sentTypesPerDevice;
    my $sentCounter    = 0;
    my $messageID      = time();
    my $isTypeOr       = 1;
    my $isRecipientOr  = 1;
    my $hasTypeOr      = 0;
    my $hasRecipientOr = 0;
    $recipients = "\@".$globalDevName if ( $recipients eq "" );

    my @typesOr = split( /\|/, $types );
    $hasTypeOr = 1 if ( scalar( grep { defined $_ } @typesOr ) > 1 );
    Log3 $globalDevName, 5,
      "message: typeOr total is " . scalar( grep { defined $_ } @typesOr )
      if ( $testMode ne "1" );

    for (
        my $iTypesOr = 0 ;
        $iTypesOr < scalar( grep { defined $_ } @typesOr ) ;
        $iTypesOr++
      )
    {
        Log3 $globalDevName, 5,
          "message: start typeOr loop for type(s) $typesOr[$iTypesOr]"
          if ( $testMode ne "1" );

        my @type = split( /,/, $typesOr[$iTypesOr] );
        for ( my $i = 0 ; $i < scalar( grep { defined $_ } @type ) ; $i++ ) {
            Log3 $globalDevName, 5, "message: running loop for type $type[$i]"
              if ( $testMode ne "1" );
            last if ( !defined( $type[$i] ) );

            my $forceType = 0;
            if ( $type[$i] =~ s/(.*)![\s\t]*$// ) {
                $type[$i] = $1;
                $forceType = 1;
            }

            # check for correct type
            my @msgCmds =
              ( "screen", "light", "audio", "text", "push", "mail" );
            if ( !( $type[$i] ~~ @msgCmds ) ) {
                $return .= "Unknown message type $type[$i]\n";
                next;
            }

            ################################################################
            ### recipient loop
            ###

            my @recipientsOr = split( /\|/, $recipients );
            $hasRecipientOr = 1
              if ( scalar( grep { defined $_ } @recipientsOr ) > 1 );
            Log3 $globalDevName, 5,
              "message: recipientOr total is "
              . scalar( grep { defined $_ } @recipientsOr )
              if ( $testMode ne "1" );

            for (
                my $iRecipOr = 0 ;
                $iRecipOr < scalar( grep { defined $_ } @recipientsOr ) ;
                $iRecipOr++
              )
            {
                Log3 $globalDevName, 5,
"message: start recipientsOr loop for recipient(s) $recipientsOr[$iRecipOr]"
                  if ( $testMode ne "1" );

                my @recipient = split( /,/, $recipientsOr[$iRecipOr] );
                foreach my $device (@recipient) {

                    Log3 $globalDevName, 5, "message: running loop for device $device"
                      if ( $testMode ne "1" );

                    my $messageSentDev = 0;
                    my $gatewayDevs    = "";
                    my $forceDevice    = 0;

                    # for device type
                    my $deviceType = "device";
                    if ( $device =~
                        /^(([A-Za-z0-9%+._-])+[@]+([%+a-z0-9A-Z.-]*))$/ )
                    {
                        $gatewayDevs = $1;
                        $deviceType  = "email";
                    }
                    elsif ( $device =~ s/^@?(.*)![\s\t]*$// ) {
                        $device      = $1;
                        $forceDevice = 1;
                    }
                    elsif ( $device =~ s/^@(.*)// ) {
                        $device = $1;
                    }

                    # FATAL ERROR: device does not exist
                    if ( !defined( $defs{$device} )
                        && $deviceType eq "device" )
                    {
                        $return .= "Device $device does not exist\n";
                        Log3 $globalDevName, 5, "message $device: Device does not exist"
                          if ( $testMode ne "1" );

                        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;
                    }

                    my $typeUc      = ucfirst( $type[$i] );
                    my $catchall    = 0;
                    my $useLocation = 0;

                    my $logDevice;
                    $logDevice = $globalDevName;
                    $logDevice = $device
                      if (
                        # look for direct
                        AttrVal(
                            $device, "verbose",

                            #look for indirect
                            AttrVal(
                                AttrVal( $device, "msgRecipient$typeUc", "" ),
                                "verbose",

                                #look for indirect general
                                AttrVal(
                                    AttrVal( $device, "msgRecipient", "" ),
                                    "verbose",

                                    # no verbose found
                                    ""
                                )
                            )
                        ) ne ""
                      );

                    ################################################################
                    ### get target information from device location
                    ###

                    # search for location references
                    my @locationDevs;
                    @locationDevs = split(
                        /,/,

                        # look for direct
                        AttrVal(
                            $device, "msgLocationDevs",

                            #look for indirect
                            AttrVal(
                                AttrVal( $device, "msgRecipient$typeUc", "" ),
                                "msgLocationDevs",

                                # look for indirect general
                                AttrVal(
                                    AttrVal( $device, "msgRecipient", "" ),
                                    "msgLocationDevs",

                                    # look for global direct
                                    AttrVal(
                                        $globalDevName, "msgLocationDevs",

                                        #look for global indirect
                                        AttrVal(
                                            AttrVal(
                                                $globalDevName,
                                                "msgRecipient$typeUc", ""
                                            ),
                                            "msgLocationDevs",

                                            # look for global indirect general
                                            AttrVal(
                                                AttrVal(
                                                    $globalDevName, "msgRecipient",
                                                    ""
                                                ),
                                                "msgLocationDevs",

                                                # no locations defined
                                                ""
                                            )
                                        )
                                    )
                                )
                            )
                        )
                    );

                    if ( $deviceType eq "device" ) {

                        # get device location
                        my $deviceLocation =

                          # look for direct
                          ReadingsVal(
                            $device, "location",

                            # look for indirect
                            ReadingsVal(
                                AttrVal( $device, "msgRecipient$typeUc", "" ),
                                "location",

                                # look for indirect general
                                ReadingsVal(
                                    AttrVal( $device, "msgRecipient", "" ),
                                    "location",

                                    # no location found
                                    ""
                                )
                            )
                          );

                        my $locationDev = "";
                        if ( $deviceLocation ne "" ) {

                            # lookup matching location
                            foreach (@locationDevs) {
                                my $lName =
                                  AttrVal( $_, "msgLocationName", "" );
                                if ( $lName ne "" && $lName eq $deviceLocation )
                                {
                                    $locationDev = $_;
                                    last;
                                }
                            }

                            # look for gateway device
                            $gatewayDevs =

                              # look for direct
                              AttrVal(
                                $locationDev, "msgContact$typeUc",

                                # look for indirect
                                AttrVal(
                                    AttrVal(
                                        $locationDev, "msgRecipient$typeUc",
                                        ""
                                    ),
                                    "msgContact$typeUc",

                                    # look for indirect general
                                    AttrVal(
                                        AttrVal(
                                            $locationDev, "msgRecipient",
                                            ""
                                        ),
                                        "msgContact$typeUc",

                                        # no contact found
                                        ""
                                    )
                                )
                              );

                            # at least one of the location gateways needs to
                            # be available. Otherwise we fall back to
                            # non-location contacts
                            if ( $gatewayDevs ne "" ) {

                                foreach
                                  my $gatewayDevOr ( split /\|/, $gatewayDevs )
                                {

                                    foreach my $gatewayDev ( split /,/,
                                        $gatewayDevOr )
                                    {
                                        my $subRecipient = "";
                                        if ( $gatewayDev =~ s/:(.*)//)
                                        {
                                            $subRecipient = $1;
                                        }                                        

                                        if (   $type[$i] ne "mail"
                                            && !defined( $defs{$gatewayDev} )
                                            && $deviceType eq "device" )
                                        {
                                            $useLocation = 2
                                              if ( $useLocation == 0 );
                                        }
                                        elsif (
                                            $type[$i] ne "mail"
                                            && AttrVal( $gatewayDev, "disable",
                                                "0" ) eq "1"
                                          )
                                        {
                                            $useLocation = 2
                                              if ( $useLocation == 0 );
                                        }
                                        elsif (
                                            $type[$i] ne "mail"
                                            && (
                                                AttrVal(
                                                    $gatewayDev, "disable",
                                                    "0"
                                                ) eq "1"
                                                || ReadingsVal(
                                                    $gatewayDev, "power",
                                                    "on"
                                                ) eq "off"
                                                || ReadingsVal(
                                                    $gatewayDev, "presence",
                                                    "present"
                                                ) eq "absent"
                                                || ReadingsVal(
                                                    $gatewayDev, "presence",
                                                    "appeared"
                                                ) eq "disappeared"
                                                || ReadingsVal(
                                                    $gatewayDev, "state",
                                                    "present"
                                                ) eq "absent"
                                                || ReadingsVal(
                                                    $gatewayDev, "state",
                                                    "connected"
                                                ) eq "unauthorized"
                                                || ReadingsVal(
                                                    $gatewayDev, "state",
                                                    "connected"
                                                ) eq "disconnected"
                                                || ReadingsVal(
                                                    $gatewayDev, "state",
                                                    "reachable"
                                                ) eq "unreachable"
                                                || ReadingsVal(
                                                    $gatewayDev, "available",
                                                    "1"
                                                ) eq "0"
                                                || ReadingsVal(
                                                    $gatewayDev, "available",
                                                    "yes"
                                                ) eq "no"
                                                || ReadingsVal(
                                                    $gatewayDev, "reachable",
                                                    "1"
                                                ) eq "0"
                                                || ReadingsVal(
                                                    $gatewayDev, "reachable",
                                                    "yes"
                                                ) eq "no"
                                            )
                                          )
                                        {
                                            $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,
"message $device: Matching location definition found.";
                                }
                                else {
                                    $gatewayDevs = "";
                                }
                            }
                        }
                    }

                    ################################################################
                    ### given device name is already a gateway device itself
                    ###
                    
                    my $deviceType2 = defined($defs{$device}) ? $defs{$device}{TYPE} : "";

                    if (
                           $gatewayDevs eq ""
                        && $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}) )
                        )
                      )
                    {
                        Log3 $logDevice, 4,
"message $device: This recipient seems to be a gateway device itself. Still checking for any delegates ...";

                        $gatewayDevs =

                          # look for direct
                          AttrVal(
                            $device,
                            "msgContact$typeUc",

                            # look for indirect
                            AttrVal(
                                AttrVal( $device, "msgRecipient$typeUc", "" ),
                                "msgContact$typeUc",

                                # look for indirect general
                                AttrVal(
                                    AttrVal( $device, "msgRecipient", "" ),
                                    "msgContact$typeUc",

                                    # self
                                    $device
                                )
                            )
                          );

                    }

                    ################################################################
                    ### get target information from device
                    ###

                    elsif ( $deviceType eq "device" && $gatewayDevs eq "" ) {

                        # look for gateway device
                        $gatewayDevs =

                          # look for direct
                          AttrVal(
                            $device, "msgContact$typeUc",

                            #look for indirect
                            AttrVal(
                                AttrVal( $device, "msgRecipient$typeUc", "" ),
                                "msgContact$typeUc",

                                #look for indirect general
                                AttrVal(
                                    AttrVal( $device, "msgRecipient", "" ),
                                    "msgContact$typeUc",

                                    # no contact found
                                    ""
                                )
                            )
                          );

                        # fallback/catchall
                        if ( $gatewayDevs eq "" ) {
                            $catchall = 1
                              if ( $device ne $globalDevName );

                            Log3 $logDevice, 5,
"message $device:			(No $typeUc contact defined, trying global instead)"
                              if ( $catchall == 1 );

                            $gatewayDevs =

                              # look for direct
                              AttrVal(
                                $globalDevName, "msgContact$typeUc",

                                #look for indirect
                                AttrVal(
                                    AttrVal(
                                        $globalDevName, "msgRecipient$typeUc", ""
                                    ),
                                    "msgContact$typeUc",

                                    #look for indirect general
                                    AttrVal(
                                        AttrVal( $globalDevName, "msgRecipient", "" ),
                                        "msgContact$typeUc",

                                        # no contact found
                                        ""
                                    )
                                )
                              );
                        }
                    }

                    # Find priority if none was explicitly specified
                    my $loopPriority = $priority;
                    $loopPriority =

                      # look for direct
                      AttrVal(
                        $device, "msgPriority$typeUc",

                        #look for indirect
                        AttrVal(
                            AttrVal( $device, "msgRecipient$typeUc", "" ),
                            "msgPriority$typeUc",

                            #look for indirect general
                            AttrVal(
                                AttrVal( $device, "msgRecipient", "" ),
                                "msgPriority$typeUc",

                                # look for global direct
                                AttrVal(
                                    $globalDevName, "msgPriority$typeUc",

                                    #look for global indirect
                                    AttrVal(
                                        AttrVal(
                                            $globalDevName, "msgRecipient$typeUc",
                                            ""
                                        ),
                                        "msgPriority$typeUc",

                                        #look for global indirect general
                                        AttrVal(
                                            AttrVal(
                                                $globalDevName, "msgRecipient",
                                                ""
                                            ),
                                            "msgPriority$typeUc",

                                            # default
                                            0
                                        )
                                    )
                                )
                            )
                        )
                      ) if ( !$priority );

                    # check for available routes
                    #
                    my %routes;
                    $routes{screen} = 0;
                    $routes{light}  = 0;
                    $routes{audio}  = 0;
                    $routes{text}   = 0;
                    $routes{push}   = 0;
                    $routes{mail}   = 0;

                    if (
                        !defined($testMode)
                        || (   $testMode ne "1"
                            && $testMode ne "2" )
                      )
                    {
                        Log3 $logDevice, 5,
"message $device: Checking for available routes (triggered by type $type[$i])";

                        $routes{screen} = 1
                          if (
                            $deviceType eq "device"
                            && CommandMessage( "screen",
                                "screen \@$device $priority Routing Test", 1 )
                            eq "ROUTE_AVAILABLE"
                          );

                        $routes{light} = 1
                          if (
                            $deviceType eq "device"
                            && CommandMessage( "light",
                                "light \@$device $priority Routing Test", 1 )
                            eq "ROUTE_AVAILABLE"
                          );

                        $routes{audio} = 1
                          if (
                            $deviceType eq "device"
                            && CommandMessage( "audio",
                                "audio \@$device $priority Routing Test", 1 )
                            eq "ROUTE_AVAILABLE"
                          );

                        if (
                            $deviceType eq "device"
                            && CommandMessage( "push",
                                "push \@$device $priority Routing Test", 1 ) eq
                            "ROUTE_AVAILABLE"
                          )
                        {
                            $routes{push} = 1;
                            $routes{text} = 1;
                        }

                        if (
                            CommandMessage( "mail",
                                "mail \@$device $priority Routing Test", 1 ) eq
                            "ROUTE_AVAILABLE"
                          )
                        {
                            $routes{mail} = 1;
                            $routes{text} = 1;
                        }

                        Log3 $logDevice, 4,
                            "message $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 = 
                          # look for direct
                          AttrVal(
                              $device, "msgThPrioTextEmergency",

                              #look for indirect audio
                              AttrVal(
                                  AttrVal( $device, "msgRecipient$typeUc", "" ),
                                  "msgThPrioTextEmergency",

                                  #look for indirect general
                                  AttrVal(
                                      AttrVal( $device, "msgRecipient", "" ),
                                      "msgThPrioTextEmergency",

                                      # look for global direct
                                      AttrVal(
                                          $globalDevName, "msgThPrioTextEmergency",

                                          #look for global indirect type
                                          AttrVal(
                                              AttrVal(
                                                  $globalDevName,
                                                  "msgRecipient$typeUc", ""
                                              ),
                                              "msgThPrioTextEmergency",

                                              #look for global indirect general
                                              AttrVal(
                                                  AttrVal(
                                                      $globalDevName, "msgRecipient",
                                                      ""
                                                  ),
                                                  "msgThPrioTextEmergency",

                                                  # default
                                                  "2"
                                              )
                                          )
                                      )
                                  )
                              )
                          )
                      ;

                      # user selected low priority text threshold
                      my $prioThresTextNormal = 
                          # look for direct
                          AttrVal(
                              $device, "msgThPrioTextNormal",

                              #look for indirect audio
                              AttrVal(
                                  AttrVal( $device, "msgRecipient$typeUc", "" ),
                                  "msgThPrioTextNormal",

                                  #look for indirect general
                                  AttrVal(
                                      AttrVal( $device, "msgRecipient", "" ),
                                      "msgThPrioTextNormal",

                                      # look for global direct
                                      AttrVal(
                                          $globalDevName, "msgThPrioTextNormal",

                                          #look for global indirect type
                                          AttrVal(
                                              AttrVal(
                                                  $globalDevName,
                                                  "msgRecipient$typeUc", ""
                                              ),
                                              "msgThPrioTextNormal",

                                              #look for global indirect general
                                              AttrVal(
                                                  AttrVal(
                                                      $globalDevName, "msgRecipient",
                                                      ""
                                                  ),
                                                  "msgThPrioTextNormal",

                                                  # default
                                                  "-2"
                                              )
                                          )
                                      )
                                  )
                              )
                          )
                      ;

                     # Decide push and/or e-mail destination based on priorities
                        if (   $loopPriority >= $prioThresTextEmg
                            && $routes{push} == 1
                            && $routes{mail} == 1 )
                        {
                            Log3 $logDevice, 4,
"message $device: Text routing decision: push+mail(1)";
                            $forwarded .= ","
                              if ( $forwarded ne "" );
                            $forwarded .= "text>push+mail";
                            push @type, "push" if !( "push" ~~ @type );
                            push @type, "mail" if !( "mail" ~~ @type );
                        }
                        elsif ($loopPriority >= $prioThresTextEmg
                            && $routes{push} == 1
                            && $routes{mail} == 0 )
                        {
                            Log3 $logDevice, 4,
                              "message $device: Text routing decision: push(2)";
                            $forwarded .= ","
                              if ( $forwarded ne "" );
                            $forwarded .= "text>push";
                            push @type, "push" if !( "push" ~~ @type );
                        }
                        elsif ($loopPriority >= $prioThresTextEmg
                            && $routes{push} == 0
                            && $routes{mail} == 1 )
                        {
                            Log3 $logDevice, 4,
                              "message $device: Text routing decision: mail(3)";
                            $forwarded .= ","
                              if ( $forwarded ne "" );
                            $forwarded .= "text>mail";
                            push @type, "mail" if !( "mail" ~~ @type );
                        }
                        elsif ( $loopPriority >= $prioThresTextNormal && $routes{push} == 1 ) {
                            Log3 $logDevice, 4,
                              "message $device: Text routing decision: push(4)";
                            $forwarded .= ","
                              if ( $forwarded ne "" );
                            $forwarded .= "text>push";
                            push @type, "push" if !( "push" ~~ @type );
                        }
                        elsif ( $loopPriority >= $prioThresTextNormal && $routes{mail} == 1 ) {
                            Log3 $logDevice, 4,
                              "message $device: Text routing decision: mail(5)";
                            $forwarded .= ","
                              if ( $forwarded ne "" );
                            $forwarded .= "text>mail";
                            push @type, "mail" if !( "mail" ~~ @type );
                        }
                        elsif ( $routes{mail} == 1 ) {
                            Log3 $logDevice, 4,
                              "message $device: Text routing decision: mail(6)";
                            $forwarded .= ","
                              if ( $forwarded ne "" );
                            $forwarded .= "text>mail";
                            push @type, "mail" if !( "mail" ~~ @type );
                        }
                        elsif ( $routes{push} == 1 ) {
                            Log3 $logDevice, 4,
                              "message $device: Text routing decision: push(7)";
                            $forwarded .= ","
                              if ( $forwarded ne "" );
                            $forwarded .= "text>push";
                            push @type, "push" if !( "push" ~~ @type );
                        }

                        # FATAL ERROR: routing decision failed
                        else {
                            Log3 $logDevice, 4,
"message $device: Text routing FAILED - priority=$loopPriority push="
                              . $routes{push}
                              . " mail="
                              . $routes{mail};

                            $return .=
"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 )
                    {
                        $return .=
"ERROR: Could not find any $typeUc contact for device $device - set attributes: msgContact$typeUc | msgRecipient$typeUc | msgRecipient\n";
                    }

                    # FATAL ERROR: we could not find any targets at all
                    elsif ( $gatewayDevs eq "" ) {
                        $return .=
"ERROR: No global $typeUc contact defined. Please specify a destination device or set global attributes: msgContact$typeUc | msgRecipient$typeUc | msgRecipient\n";
                    }

                    #####################
                    # return if we are in routing target test mode
                    #
                    if ( defined($testMode) && $testMode eq "1" ) {
                        Log3 $logDevice, 5,
"message $device:		$type[$i] route check result: ROUTE_AVAILABLE"
                          if ( $return eq "" );
                        Log3 $logDevice, 5,
"message $device:		$type[$i] route check result: ROUTE_UNAVAILABLE"
                          if ( $return ne "" );
                        return "ROUTE_AVAILABLE"   if ( $return eq "" );
                        return "ROUTE_UNAVAILABLE" if ( $return ne "" );
                    }

                    # user selected audio-visual announcement state
                    my $annState = ReadingsVal(

                        # look for direct
                        AttrVal(
                            $device, "msgSwitcherDev",

                            #look for indirect audio
                            AttrVal(
                                AttrVal( $device, "msgRecipient$typeUc", "" ),
                                "msgSwitcherDev",

                                #look for indirect general
                                AttrVal(
                                    AttrVal( $device, "msgRecipient", "" ),
                                    "msgSwitcherDev",

                                    # look for global direct
                                    AttrVal(
                                        $globalDevName, "msgSwitcherDev",

                                        #look for global indirect type
                                        AttrVal(
                                            AttrVal(
                                                $globalDevName,
                                                "msgRecipient$typeUc", ""
                                            ),
                                            "msgSwitcherDev",

                                            #look for global indirect general
                                            AttrVal(
                                                AttrVal(
                                                    $globalDevName, "msgRecipient",
                                                    ""
                                                ),
                                                "msgSwitcherDev",

                                                # default
                                                ""
                                            )
                                        )
                                    )
                                )
                            )
                        ),
                        "state",
                        "long"
                    );

                    # user selected emergency priority audio threshold
                    my $prioThresAudioEmg = 
                        # look for direct
                        AttrVal(
                            $device, "msgThPrioAudioEmergency",

                            #look for indirect audio
                            AttrVal(
                                AttrVal( $device, "msgRecipient$typeUc", "" ),
                                "msgThPrioAudioEmergency",

                                #look for indirect general
                                AttrVal(
                                    AttrVal( $device, "msgRecipient", "" ),
                                    "msgThPrioAudioEmergency",

                                    # look for global direct
                                    AttrVal(
                                        $globalDevName, "msgThPrioAudioEmergency",

                                        #look for global indirect type
                                        AttrVal(
                                            AttrVal(
                                                $globalDevName,
                                                "msgRecipient$typeUc", ""
                                            ),
                                            "msgThPrioAudioEmergency",

                                            #look for global indirect general
                                            AttrVal(
                                                AttrVal(
                                                    $globalDevName, "msgRecipient",
                                                    ""
                                                ),
                                                "msgThPrioAudioEmergency",

                                                # default
                                                "2"
                                            )
                                        )
                                    )
                                )
                            )
                        )
                    ;

                    # user selected high priority audio threshold
                    my $prioThresAudioHigh = 
                        # look for direct
                        AttrVal(
                            $device, "msgThPrioAudioHigh",

                            #look for indirect audio
                            AttrVal(
                                AttrVal( $device, "msgRecipient$typeUc", "" ),
                                "msgThPrioAudioHigh",

                                #look for indirect general
                                AttrVal(
                                    AttrVal( $device, "msgRecipient", "" ),
                                    "msgThPrioAudioHigh",

                                    # look for global direct
                                    AttrVal(
                                        $globalDevName, "msgThPrioAudioHigh",

                                        #look for global indirect type
                                        AttrVal(
                                            AttrVal(
                                                $globalDevName,
                                                "msgRecipient$typeUc", ""
                                            ),
                                            "msgThPrioAudioHigh",

                                            #look for global indirect general
                                            AttrVal(
                                                AttrVal(
                                                    $globalDevName, "msgRecipient",
                                                    ""
                                                ),
                                                "msgThPrioAudioHigh",

                                                # default
                                                "1"
                                            )
                                        )
                                    )
                                )
                            )
                        )
                    ;

                    # user selected high priority threshold
                    my $prioThresHigh = 
                        # look for direct
                        AttrVal(
                            $device, "msgThPrioHigh",

                            #look for indirect audio
                            AttrVal(
                                AttrVal( $device, "msgRecipient$typeUc", "" ),
                                "msgThPrioHigh",

                                #look for indirect general
                                AttrVal(
                                    AttrVal( $device, "msgRecipient", "" ),
                                    "msgThPrioHigh",

                                    # look for global direct
                                    AttrVal(
                                        $globalDevName, "msgThPrioHigh",

                                        #look for global indirect type
                                        AttrVal(
                                            AttrVal(
                                                $globalDevName,
                                                "msgRecipient$typeUc", ""
                                            ),
                                            "msgThPrioHigh",

                                            #look for global indirect general
                                            AttrVal(
                                                AttrVal(
                                                    $globalDevName, "msgRecipient",
                                                    ""
                                                ),
                                                "msgThPrioHigh",

                                                # default
                                                "2"
                                            )
                                        )
                                    )
                                )
                            )
                        )
                    ;

                    # user selected normal priority threshold
                    my $prioThresNormal = 
                        # look for direct
                        AttrVal(
                            $device, "msgThPrioNormal",

                            #look for indirect audio
                            AttrVal(
                                AttrVal( $device, "msgRecipient$typeUc", "" ),
                                "msgThPrioNormal",

                                #look for indirect general
                                AttrVal(
                                    AttrVal( $device, "msgRecipient", "" ),
                                    "msgThPrioNormal",

                                    # look for global direct
                                    AttrVal(
                                        $globalDevName, "msgThPrioNormal",

                                        #look for global indirect type
                                        AttrVal(
                                            AttrVal(
                                                $globalDevName,
                                                "msgRecipient$typeUc", ""
                                            ),
                                            "msgThPrioNormal",

                                            #look for global indirect general
                                            AttrVal(
                                                AttrVal(
                                                    $globalDevName, "msgRecipient",
                                                    ""
                                                ),
                                                "msgThPrioNormal",

                                                # default
                                                "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
                    ###

                    # user selected emergency priority text threshold
                    my $prioThresGwEmg = 
                        # look for direct
                        AttrVal(
                            $device, "msgThPrioGwEmergency",

                            #look for indirect type
                            AttrVal(
                                AttrVal( $device, "msgRecipient$typeUc", "" ),
                                "msgThPrioGwEmergency",

                                #look for indirect general
                                AttrVal(
                                    AttrVal( $device, "msgRecipient", "" ),
                                    "msgThPrioGwEmergency",

                                    # look for global direct
                                    AttrVal(
                                        $globalDevName, "msgThPrioGwEmergency",

                                        #look for global indirect type
                                        AttrVal(
                                            AttrVal(
                                                $globalDevName,
                                                "msgRecipient$typeUc", ""
                                            ),
                                            "msgThPrioGwEmergency",

                                            #look for global indirect general
                                            AttrVal(
                                                AttrVal(
                                                    $globalDevName, "msgRecipient",
                                                    ""
                                                ),
                                                "msgThPrioGwEmergency",

                                                # default
                                                "2"
                                            )
                                        )
                                    )
                                )
                            )
                        )
                    ;

                    my %gatewaysStatus;

                    foreach my $gatewayDevOr ( split /\|/, $gatewayDevs ) {
                        foreach my $gatewayDev ( split /,/, $gatewayDevOr ) {

                            my $subRecipient = "";
                            if ( $gatewayDev =~ s/:(.*)//)
                            {
                                $subRecipient = $1;
                            }                                        

                            Log3 $logDevice, 5,
"message $device: Trying to send message via gateway $gatewayDev to recipient $subRecipient";

                            ##############
                           # check for gateway availability and set route status
                           #

                            my $routeStatus = "OK";
                            if (   $type[$i] ne "mail"
                                && !defined( $defs{$gatewayDev} )
                                && $deviceType eq "device" )
                            {
                                $routeStatus = "UNDEFINED";
                            }
                            elsif ( $type[$i] ne "mail"
                                && AttrVal( $gatewayDev, "disable", "0" ) eq
                                "1" )
                            {
                                $routeStatus = "DISABLED";
                            }
                            elsif (
                                $type[$i] ne "mail"
                                && (
                                ReadingsVal(
                                    $gatewayDev, "power",
                                    "on"
                                ) eq "off"
                                || ReadingsVal(
                                    $gatewayDev, "presence",
                                    "present"
                                ) eq "absent"
                                || ReadingsVal(
                                    $gatewayDev, "presence",
                                    "appeared"
                                ) eq "disappeared"
                                || ReadingsVal(
                                    $gatewayDev, "state",
                                    "present"
                                ) eq "absent"
                                || ReadingsVal(
                                    $gatewayDev, "state",
                                    "connected"
                                ) eq "unauthorized"
                                || ReadingsVal(
                                    $gatewayDev, "state",
                                    "connected"
                                ) eq "disconnected"
                                || ReadingsVal(
                                    $gatewayDev, "state",
                                    "reachable"
                                ) eq "unreachable"
                                || ReadingsVal(
                                    $gatewayDev, "available",
                                    "1"
                                ) eq "0"
                                || ReadingsVal(
                                    $gatewayDev, "available",
                                    "yes"
                                ) eq "no"
                                || ReadingsVal(
                                    $gatewayDev, "reachable",
                                    "1"
                                ) eq "0"
                                || ReadingsVal(
                                    $gatewayDev, "reachable",
                                    "yes"
                                ) eq "no"

                                )
                              )
                            {
                                $routeStatus = "UNAVAILABLE";
                            }
                            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";
                            }


                            my $gatewayType = $type[$i] eq "mail" ? "fhemMsgMail" : $defs{$gatewayDev}{TYPE};

                            my $defTitle = defined($settings->{ $type[$i] }{title}) ? $settings->{ $type[$i] }{title} : "System Message";
                            $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 "" );

                            # use title from device, global or internal default
                            my $loopTitle;
                            $loopTitle = $title if ( $title ne "-" );
                            $loopTitle =

                              # look for direct high
                              AttrVal(
                                $device, "msgTitle$typeUc$priorityCat",

                                # look for indirect high
                                AttrVal(
                                    AttrVal( $device, "msgRecipient$typeUc", "" ),
                                    "msgTitle$typeUc$priorityCat",

                                    #look for indirect general high
                                    AttrVal(
                                        AttrVal( $device, "msgRecipient", "" ),
                                        "msgTitle$typeUc$priorityCat",

                                        # look for global direct high
                                        AttrVal(
                                            $globalDevName, "msgTitle$typeUc$priorityCat",

                                            # look for global indirect high
                                            AttrVal(
                                                AttrVal(
                                                    $globalDevName, "msgRecipient$typeUc",
                                                    ""
                                                ),
                                                "msgTitle$typeUc$priorityCat",

                                                #look for global indirect general high
                                                AttrVal(
                                                    AttrVal(
                                                        $globalDevName, "msgRecipient",
                                                        ""
                                                    ),
                                                    "msgTitle$typeUc$priorityCat",

                                                    # default
                                                    $defTitle
                                                )
                                            )
                                        )
                                    )
                                )
                              ) if ( $title eq "-" );

                            if ( $type[$i] eq "mail" && $priorityCat ne "" ) {
                              $loopTitle = "[$priorityCat] $loopTitle";
                            }

                            my $loopMsg = $msg;
                            if ( $catchall == 1 ) {
                                $loopTitle = "Fw: $loopTitle";
                                if ( $type[$i] eq "mail" ) {
                                    $loopMsg .=
        "\n\n-- \nMail catched from device $device";
                                }
                                else {
                                    $loopMsg .= " ### (Catched from device $device)";
                                }
                            }

                            # correct message format
                            #
                            $loopMsg =~ s/\n/<br \/>/gi;
                            $loopMsg =~ s/((|(\d+)| )\|\w+\|( |))/\n\n/gi
                              if ( $type[$i] ne "audio" ); # Remove Sonos Speak commands


                            # 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",

                                # look for direct
                                AttrVal(
                                    $device, "msgCmd$typeUc$priorityCat",

                                    # look for indirect
                                    AttrVal(
                                        AttrVal(
                                            $device, "msgRecipient$typeUc",
                                            ""
                                        ),
                                        "msgCmd$typeUc$priorityCat",

                                        #look for indirect general
                                        AttrVal(
                                            AttrVal(
                                                $device, "msgRecipient", ""
                                            ),
                                            "msgCmd$typeUc$priorityCat",

                                            # look for global direct
                                            AttrVal(
                                                $globalDevName,
                                                "msgCmd$typeUc$priorityCat",

                                                # look for global indirect
                                                AttrVal(
                                                    AttrVal(
                                                        $globalDevName,
                                                        "msgRecipient$typeUc",
                                                        ""
                                                    ),
                                                    "msgCmd$typeUc$priorityCat",

                                               #look for global indirect general
                                                    AttrVal(
                                                        AttrVal(
                                                            $globalDevName,
                                                            "msgRecipient",
                                                            ""
                                                        ),
"msgCmd$typeUc$priorityCat",

                                                        # internal
                                                        $defCmd
                                                    )
                                                )
                                            )
                                        )
                                    )
                                )
                              );

                            if ($cmd eq "") {
                              Log3 $logDevice, 4, "$gatewayDev: Unknown command schema for gateway device type $gatewayType. Use manual definition by userattr msgCmd*";
                              $return .= "$gatewayDev: Unknown command schema for gateway device type $gatewayType. Use manual definition by userattr msgCmd*\n";
                              next;
                            }

                            $cmd =~ s/%DEVICE%/$gatewayDev/gi;
                            $cmd =~ s/%PRIORITY%/$loopPriority/gi;
                            $cmd =~ s/%TITLE%/$loopTitle/gi;
                            $cmd =~ s/%MSG%/$loopMsg/gi;

                            $cmd =~ s/%RECIPIENT%/$subRecipient/gi if ($subRecipient ne "");

                            # advanced options from message
                            if (ref($advanced) eq "ARRAY") {
                              for my $item (@$advanced) {
                                 for my $key (keys(%$item)) {
                                    my $val = $item->{$key};
                                    $cmd =~ s/%$key%/$val/gi;
                                 }
                              }
                            }

                            # advanced options 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;
                                 }
                              }

                            }
                            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;
                                 }
                              }

                            }

                            $sentCounter++;

                            if ( $routeStatus =~ /^OK\w*/ ) {
                                  
                                my $error = 0;

                                # run command
                                undef $@;
                                if ( $cmd =~ s/^[ \t]*\{|\}[ \t]*$//gi ) {
                                    $cmd =~ s/@\w+/\\$&/gi;
                                    Log3 $logDevice, 5,
"message $device: $type[$i] route command (Perl): $cmd";
                                    eval $cmd;
                                    if ( $@ ) {
                                      $error = 1;
                                      $return .= "$gatewayDev: $@\n";
                                    }
                                }
                                else {
                                    Log3 $logDevice, 5,
"message $device: $type[$i] route command (fhem): $cmd";
                                    fhem $cmd;
                                    if ( $@ ) {
                                      $error = 1;
                                      $return .= "$gatewayDev: $@\n";
                                    }
                                }

                                $routeStatus = "ERROR" if ($error == 1);

                                Log3 $logDevice, 3,
"message $device: ID=$messageID.$sentCounter TYPE=$type[$i] ROUTE=$gatewayDev RECIPIENT=$subRecipient STATUS=$routeStatus PRIORITY=$loopPriority($priorityCat) TITLE='$loopTitle' MSG='$msg'"
                                  if ( $priorityCat ne "" && $subRecipient ne "");
                                Log3 $logDevice, 3,
"message $device: ID=$messageID.$sentCounter TYPE=$type[$i] ROUTE=$gatewayDev RECIPIENT=$subRecipient STATUS=$routeStatus PRIORITY=$loopPriority TITLE='$loopTitle' MSG='$msg'"
                                  if ( $priorityCat eq "" && $subRecipient ne "");
                                  Log3 $logDevice, 3,
  "message $device: ID=$messageID.$sentCounter TYPE=$type[$i] ROUTE=$gatewayDev STATUS=$routeStatus PRIORITY=$loopPriority($priorityCat) TITLE='$loopTitle' MSG='$msg'"
                                    if ( $priorityCat ne "" && $subRecipient eq "");
                                  Log3 $logDevice, 3,
  "message $device: ID=$messageID.$sentCounter TYPE=$type[$i] ROUTE=$gatewayDev STATUS=$routeStatus PRIORITY=$loopPriority TITLE='$loopTitle' MSG='$msg'"
                                    if ( $priorityCat eq "" && $subRecipient eq "");

                                  $messageSent                 = 1 if ($error == 0);
                                  $messageSentDev              = 1 if ($error == 0);
                                  $gatewaysStatus{$gatewayDev} = $routeStatus;
                            }
                            elsif ($routeStatus eq "UNAVAILABLE"
                                || $routeStatus eq "UNDEFINED" )
                            {
                                Log3 $logDevice, 3,
"message $device: ID=$messageID.$sentCounter TYPE=$type[$i] ROUTE=$gatewayDev RECIPIENT=$subRecipient STATUS=$routeStatus PRIORITY=$loopPriority TITLE='$loopTitle' '$msg'" if ($subRecipient ne "");
                                Log3 $logDevice, 3,
"message $device: ID=$messageID.$sentCounter TYPE=$type[$i] ROUTE=$gatewayDev STATUS=$routeStatus PRIORITY=$loopPriority TITLE='$loopTitle' '$msg'" if ($subRecipient eq "");
                                $gatewaysStatus{$gatewayDev} = $routeStatus;
                            }
                            else {
                                Log3 $logDevice, 3,
"message $device: ID=$messageID.$sentCounter TYPE=$type[$i] ROUTE=$gatewayDev RECIPIENT=$subRecipient STATUS=$routeStatus PRIORITY=$loopPriority TITLE='$loopTitle' '$msg'" if ($subRecipient ne "");
                                Log3 $logDevice, 3,
"message $device: ID=$messageID.$sentCounter TYPE=$type[$i] ROUTE=$gatewayDev STATUS=$routeStatus PRIORITY=$loopPriority TITLE='$loopTitle' '$msg'" if ($subRecipient eq "");
                                $messageSent    = 2 if ( $messageSent != 1 );
                                $messageSentDev = 2 if ( $messageSentDev != 1 );
                                $gatewaysStatus{$gatewayDev} = $routeStatus;
                            }

                        }

                        last if ( $messageSentDev == 1 );
                    }

                    if ( $catchall == 0 ) {
                        if ( !defined( $sentTypesPerDevice{$device} ) ) {
                            $sentTypesPerDevice{$device} = "";
                        }
                        else {
                            $sentTypesPerDevice{$device} .= " ";
                        }

                        $sentTypesPerDevice{$device} .=
                          $type[$i] . ":" . $messageSentDev;
                    }
                    else {
                        if ( !defined( $sentTypesPerDevice{$device} ) ) {
                            $sentTypesPerDevice{$globalDevName} = "";
                        }
                        else {
                            $sentTypesPerDevice{$globalDevName} .= " ";
                        }

                        $sentTypesPerDevice{$globalDevName} .=
                          $type[$i] . ":" . $messageSentDev;
                    }

                    # update device readings
                    my $readingsDev = $defs{$device};
                    $readingsDev = $defs{$globalDevName} if ( $catchall == 1 );
                    readingsBeginUpdate($readingsDev);

                    readingsBulkUpdate( $readingsDev, "fhemMsg" . $typeUc,
                        $msg );
                    readingsBulkUpdate( $readingsDev,
                        "fhemMsg" . $typeUc . "Title", $title );
                    readingsBulkUpdate( $readingsDev,
                        "fhemMsg" . $typeUc . "Prio",
                        $loopPriority );

                    my $gwStates = "";

                    while ( ( my $gwName, my $gwState ) = each %gatewaysStatus )
                    {
                        $gwStates .= " " if $gwStates ne "";
                        $gwStates .= "$gwName:$gwState";
                    }
                    readingsBulkUpdate( $readingsDev,
                        "fhemMsg" . $typeUc . "Gw", $gwStates );
                    readingsBulkUpdate( $readingsDev,
                        "fhemMsg" . $typeUc . "State",
                        $messageSentDev );

                    ################################################################
                    ### Implicit forwards based on priority or presence
                    ###

                    # no implicit escalations for type mail
                    next if ( $type[$i] eq "mail" );

                    # Skip if typeOr is defined
                    # and this is not the last type entry
                    # TODO: bei mehreren gleichzeitigen Typen (and-Definition)?
                    if (   $messageSentDev != 1
                        && $hasTypeOr == 1
                        && $isTypeOr < scalar( grep { defined $_ } @typesOr ) )
                    {
                        Log3 $logDevice, 4,
"message $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;
                        }

                        next;
                    }

                    # Skip if recipientOr is defined
                    # and this is not the last device entry
                    # TODO: bei mehreren gleichzeitigen Empfängern
                    #       (and-Definition)?
                    if (   $messageSentDev != 1
                        && $hasRecipientOr == 1
                        && $isRecipientOr <
                        scalar( grep { defined $_ } @recipientsOr ) )
                    {
                        Log3 $logDevice, 4,
"message $device: Skipping implicit forward due to recipientOr definition";

                        next;
                    }

                    # priority forward thresholds
                    #

                    ### emergency
                    my $msgFwPrioEmergency =

                      # look for direct
                      AttrVal(
                        $device, "msgFwPrioEmergency$typeUc",

                        #look for indirect
                        AttrVal(
                            AttrVal( $device, "msgRecipient$typeUc", "" ),
                            "msgFwPrioEmergency$typeUc",

                            #look for indirect general
                            AttrVal(
                                AttrVal( $device, "msgRecipient", "" ),
                                "msgFwPrioEmergency$typeUc",

                                # default
                                2
                            )
                        )
                      );

                    ### absent
                    my $msgFwPrioAbsent =

                      # look for direct
                      AttrVal(
                        $device, "msgFwPrioAbsent$typeUc",

                        #look for indirect
                        AttrVal(
                            AttrVal( $device, "msgRecipient$typeUc", "" ),
                            "msgFwPrioAbsent$typeUc",

                            #look for indirect general
                            AttrVal(
                                AttrVal( $device, "msgRecipient", "" ),
                                "msgFwPrioAbsent$typeUc",

                                # default
                                0
                            )
                        )
                      );

                    ### gone
                    my $msgFwPrioGone =

                      # look for direct
                      AttrVal(
                        $device, "msgFwPrioGone$typeUc",

                        #look for indirect
                        AttrVal(
                            AttrVal( $device, "msgRecipient$typeUc", "" ),
                            "msgFwPrioGone$typeUc",

                            #look for indirect general
                            AttrVal(
                                AttrVal( $device, "msgRecipient", "" ),
                                "msgFwPrioGone$typeUc",

                                # default
                                1
                            )
                        )
                      );

                    Log3 $logDevice, 5,
"message $device: Implicit forwards: recipient presence=$residentDevPresence state=$residentDevState"
                      if ( $residentDevPresence ne ""
                        || $residentDevState ne "" );

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 (   $messageSentDev == 0
                        && $fw_gwUnavailable ne ""
                        && !( $fw_gwUnavailable ~~ @type )
                        && $routes{$fw_gwUnavailable} == 1 )
                    {
                        Log3 $logDevice, 4,
"message $device: Implicit forwards: No $type[$i] gateway device available for recipient $device ($gatewayDevs). Trying alternative message type "
                          . $fw_gwUnavailable;

                        push @type, $fw_gwUnavailable;
                        $forwarded .= "," . $type[$i] . ">" . $fw_gwUnavailable
                          if ( $forwarded ne "" );
                        $forwarded .= $type[$i] . ">" . $fw_gwUnavailable
                          if ( $forwarded eq "" );
                    }

                    # Forward message
                    # if emergency priority
                    if (   $loopPriority >= $msgFwPrioEmergency
                        && $fw_emergency ne ""
                        && !( $fw_emergency ~~ @type )
                        && $routes{$fw_emergency} == 1 )
                    {
                        Log3 $logDevice, 4,
"message $device: Implicit forwards: Escalating high priority $type[$i] message via "
                          . $fw_emergency;

                        push @type, $fw_emergency;
                        $forwarded .= "," . $type[$i] . ">" . $fw_emergency
                          if ( $forwarded ne "" );
                        $forwarded .= $type[$i] . ">" . $fw_emergency
                          if ( $forwarded eq "" );
                    }

                    # Forward message
                    # if high priority and residents are constantly not at home
                    if (   $residentDevPresence eq "absent"
                        && $loopPriority >= $msgFwPrioGone
                        && $fw_residentGone ne ""
                        && !( $fw_residentGone ~~ @type )
                        && $routes{$fw_residentGone} == 1 )
                    {
                        Log3 $logDevice, 4,
"message $device: Implicit forwards: Escalating high priority $type[$i] message via "
                          . $fw_residentGone;

                        push @type, $fw_residentGone;
                        $forwarded .= "," . $type[$i] . ">" . $fw_residentGone
                          if ( $forwarded ne "" );
                        $forwarded .= $type[$i] . ">" . $fw_residentGone
                          if ( $forwarded eq "" );
                    }

                    # Forward message
                    # if priority is normal or higher and residents
                    # are not at home but nearby
                    if (   $residentDevState eq "absent"
                        && $loopPriority >= $msgFwPrioAbsent
                        && $fw_residentAbsent ne ""
                        && !( $fw_residentAbsent ~~ @type )
                        && $routes{$fw_residentAbsent} == 1 )
                    {
                        Log3 $logDevice, 4,
"message $device: Implicit forwards: Escalating $type[$i] message via "
                          . $fw_residentAbsent
                          . " due to absence";

                        push @type, $fw_residentAbsent;
                        $forwarded .= "," . $type[$i] . ">" . $fw_residentAbsent
                          if ( $forwarded ne "" );
                        $forwarded .= $type[$i] . ">" . $fw_residentAbsent
                          if ( $forwarded eq "" );
                    }

                }

                last if ( $messageSent == 1 );

                $isRecipientOr++;
            }
        }

        last if ( $messageSent == 1 );

        $isTypeOr++;
    }

    # finalize device readings
    while ( ( my $device, my $types ) = each %sentTypesPerDevice ) {
        readingsBulkUpdate( $defs{$device}, "fhemMsgStateTypes", $types )
          if ( $forwarded eq "" );
        readingsBulkUpdate( $defs{$device}, "fhemMsgStateTypes",
            $types . " forwards:" . $forwarded )
          if ( $forwarded ne "" );
        readingsBulkUpdate( $defs{$device}, "fhemMsgState", $messageSent );
        readingsEndUpdate( $defs{$device}, 1 );
    }

    if ( $messageSent == 1 && $return ne "" ) {
        $return .= "However, message was still sent to some recipients!";
    }

    if ( $messageSent == 2 ) {
        $return .=
          "FATAL ERROR: Message NOT sent. No gateway device was available.";
    }

    return $return;
}

1;

=pod
=begin html

<a name="message"></a>
<h3>message</h3>
<ul>
  <code>message [&lt;type&gt;] [&lt;@device&gt;|&lt;e-mail address&gt;] [&lt;priority&gt;] [|&lt;title&gt;|] &lt;message&gt;</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="message"></a>
<h3>message</h3>
<ul>
  <code>message [&lt;type&gt;] [&lt;@device&gt;|&lt;e-mail address&gt;] [&lt;priority&gt;] [|&lt;title&gt;|] &lt;message&gt;</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
=cut