75_MSG.pm 110 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496
  1. ###############################################################################
  2. # $Id: 75_MSG.pm 15222 2017-10-10 06:47:28Z loredo $
  3. #
  4. #TODO
  5. # - be able to use type "default" to let read from attr
  6. # - forceType/forceDevice in user parameters
  7. # - implement default messages in RESIDENTS using msg command
  8. # - queue message until recipient is available again (e.g. when absent)
  9. # also see https://forum.fhem.de/index.php/topic,69683.0.html
  10. # - new msgType "queue"
  11. # - escalation to type "queue" when n/a
  12. # - automatically trigger to release queue messages by arriving at home
  13. # (ROOMMATE)
  14. # - allow some other ? to only reach people when they are at home
  15. # - if ROOMMATE is asleep, queue message for next day
  16. # (usefull escalate for screen with PostMe?)
  17. # - delivery options as attributes (like ! or ? to gateways, devices or types)
  18. # - all messages should be queued and then delivered so a timer may come back
  19. # and check the gateway device for successful delivery
  20. # - fix readings: store texts with \x{0000} notation
  21. # - end user documentation / commandref !!
  22. #
  23. package main;
  24. use strict;
  25. use warnings;
  26. use Data::Dumper;
  27. use Time::HiRes qw(time);
  28. use utf8;
  29. # initialize ##################################################################
  30. sub MSG_Initialize($$) {
  31. my %hash = (
  32. Fn => "CommandMsg",
  33. Hlp =>
  34. "[<type>] [<\@device>|<e-mail address>] [<priority>] [|<title>|] <message-text>",
  35. );
  36. $cmds{msg} = \%hash;
  37. require "$attr{global}{modpath}/FHEM/msgSchema.pm";
  38. }
  39. # regular Fn ##################################################################
  40. sub CommandMsg($$;$$);
  41. sub CommandMsg($$;$$) {
  42. my ( $cl, $msg, $testMode ) = @_;
  43. my $return = "";
  44. if ( $featurelevel >= 5.7 ) {
  45. my %dummy;
  46. my ( $err, @a ) = ReplaceSetMagic( \%dummy, 0, ($msg) );
  47. $msg = join( " ", @a )
  48. unless ($err);
  49. }
  50. # find existing msgConfig device or create a new instance
  51. my $globalDevName = "globalMsg";
  52. if ( defined( $modules{msgConfig}{defptr} ) ) {
  53. $globalDevName = $modules{msgConfig}{defptr}{NAME};
  54. }
  55. else {
  56. fhem "define $globalDevName msgConfig";
  57. $return .=
  58. "Global configuration device $globalDevName was created.\n\n";
  59. }
  60. if ( $msg eq "" || $msg =~ /^\?[\s\t ]*$/ || $msg eq "help" ) {
  61. return $return
  62. . "Usage: msg [<type>] [<\@device>|<e-mail address>] [<priority>] [|<title>|] <message>";
  63. }
  64. # default settings
  65. my $cmdSchema = msgSchema::get();
  66. my $settings = {
  67. audio => {
  68. typeEscalation => {
  69. gwUnavailable => 'text',
  70. emergency => 'text',
  71. residentGone => 'text',
  72. residentAbsent => 'text',
  73. },
  74. },
  75. light => {
  76. typeEscalation => {
  77. gwUnavailable => 'audio',
  78. emergency => 'audio',
  79. residentGone => 'audio',
  80. residentAbsent => 'audio',
  81. },
  82. },
  83. push => {
  84. typeEscalation => {
  85. gwUnavailable => 'mail',
  86. emergency => 'mail',
  87. },
  88. },
  89. screen => {
  90. typeEscalation => {
  91. gwUnavailable => 'light',
  92. emergency => 'light',
  93. residentGone => 'light',
  94. residentAbsent => 'light',
  95. },
  96. },
  97. };
  98. ################################################################
  99. ### extract message details
  100. ###
  101. my ( $msgA, $params ) = parseParams($msg, "[^\\S\\n]", " ");
  102. # only use output from parseParams when
  103. # parameters where found
  104. if ( ref($params) eq "HASH" && keys %$params ) {
  105. if ( scalar @$msgA > 0 ) {
  106. $msg = join( " ", @$msgA );
  107. }
  108. else {
  109. $msg = "";
  110. }
  111. }
  112. if ( defined( $params->{msgText} ) ) {
  113. Log3 $globalDevName, 5,
  114. "msg: Adding message text from given user parameters";
  115. $msg .= " " unless ( $msg eq "" );
  116. $msg .= $params->{msgText};
  117. delete $params->{msgText};
  118. }
  119. return $return
  120. . "Usage: msg [<type>] [<\@device>|<e-mail address>] [<priority>] [|<title>|] <message>"
  121. if ( $msg =~ m/^[\s\t\n ]*$/ );
  122. Log3 $globalDevName, 5, "msg: Extracted user parameters\n" . Dumper($params)
  123. if ( ref($params) eq "HASH" && keys %$params );
  124. my $types = "";
  125. my $recipients = "";
  126. my $priority = "";
  127. my $title = "-";
  128. my $priorityCat = "";
  129. # check for message types
  130. if ( $params->{msgType} ) {
  131. Log3 $globalDevName, 5, "msg: given types=$params->{msgType}";
  132. $types = $params->{msgType};
  133. $types =~ s/[\s\t ]+//g;
  134. delete $params->{msgType};
  135. }
  136. elsif ( $msg =~
  137. s/^[\s\t ]*([a-z,]*!?(screen|light|audio|text|push|mail|queue)[\w,!?&|]*)[\s\t ]+//i
  138. )
  139. {
  140. Log3 $globalDevName, 5, "msg: found types=$1"
  141. unless ( defined($testMode) && $testMode eq "1" );
  142. $types = $1;
  143. }
  144. # programatic exception:
  145. # e.g. recipients were given automatically from empty readings
  146. if (
  147. $msg =~ m/^[\s\t ]*([!]?(([A-Za-z0-9%+._-])*@([,\-:|]+)))[\s\t ]+/
  148. || ( $params->{msgRcpt}
  149. && $params->{msgRcpt} =~
  150. m/^[\s\t ]*([!]?(([A-Za-z0-9%+._-])*@([,\-:|]+)))[\s\t ]+/ )
  151. )
  152. {
  153. Log3 $globalDevName, 4,
  154. "msg: message won't be sent - recipient '$1' contains special"
  155. . " characters like ',-:|' or behind the @ character is simply"
  156. . " emptiness. This might be okay, e.g. if you are using something"
  157. . " like a reading from RESIDENTS/ROOMMATE/GUEST to address present"
  158. . " or absent residents and this list is simply empty at this time."
  159. . " ($msg)";
  160. return;
  161. }
  162. # check for given recipients
  163. if ( $params->{msgRcpt} ) {
  164. Log3 $globalDevName, 5, "msg: given recipient=$params->{msgRcpt}";
  165. $recipients = $params->{msgRcpt};
  166. $recipients =~ s/[\s\t ]+//g;
  167. delete $params->{msgRcpt};
  168. }
  169. elsif ( $msg =~
  170. s/^[\s\t ]*([!]?(([A-Za-z0-9%+._-])*@([%+a-z0-9A-Z.-]+))[\w,@.!?|:]*)[\s\t ]+//
  171. )
  172. {
  173. Log3 $globalDevName, 5, "msg: found recipient=$1"
  174. unless ( defined($testMode) && $testMode eq "1" );
  175. $recipients = $1;
  176. }
  177. # check for given priority
  178. if ( defined( $params->{msgPrio} ) ) {
  179. Log3 $globalDevName, 5, "msg: given priority=$params->{msgPrio}";
  180. $priority = $params->{msgPrio};
  181. $priority =~ s/[\s\t ]+//g;
  182. delete $params->{msgPrio};
  183. }
  184. elsif ( $msg =~ s/^[\s\t ]*([-+]{0,1}\d+[\.\d]*)[\s\t ]*// ) {
  185. Log3 $globalDevName, 5, "msg: found priority=$1"
  186. unless ( defined($testMode) && $testMode eq "1" );
  187. $priority = $1;
  188. }
  189. $priority = int($priority) if ( $priority =~ /^[-+]{0,1}\d+\.\d*$/ );
  190. return "Invalid priority $priority: Needs to be an integer value"
  191. unless ( $priority eq "" || $priority =~ /^[-+]{0,1}\d+$/ );
  192. # check for given message title
  193. if ( defined( $params->{msgTitle} ) ) {
  194. Log3 $globalDevName, 5, "msg: given title=$params->{msgTitle}";
  195. $title = $params->{msgTitle};
  196. $title =~ s/^[\s\t ]*\|(.*?)\|[\s\t ]*/$1/;
  197. delete $params->{msgTitle};
  198. }
  199. elsif ( $msg =~ s/^[\s\t ]*\|(.*?)\|[\s\t ]*// ) {
  200. Log3 $globalDevName, 5, "msg: found title=$1"
  201. unless ( defined($testMode) && $testMode eq "1" );
  202. $title = $1;
  203. }
  204. # check for user parameters (DEPRECATED / legacy compatibility only)
  205. if ( $msg =~ s/[\s\t ]*O(\[\{.*\}\])[\s\t ]*$// ) {
  206. Log3 $globalDevName, 5, "msg: found options=$1"
  207. unless ( defined($testMode) && $testMode eq "1" );
  208. # Use JSON module if possible
  209. eval {
  210. require JSON;
  211. import JSON qw( decode_json );
  212. };
  213. if ($@) {
  214. Log3 $globalDevName, 3,
  215. "msg: Error loading Perl::JSON. "
  216. . "Please switch to new syntax to use user parameters";
  217. }
  218. else {
  219. Log3 $globalDevName, 4,
  220. "msg: Please switch to new syntax to use user parameters";
  221. my $o;
  222. eval '$o = decode_json( Encode::encode_utf8($1) ); 1';
  223. if ($@) {
  224. Log3 $globalDevName, 5,
  225. "msg: Error decoding JSON for user parameters: $@";
  226. }
  227. elsif ( ref($o) eq "ARRAY" ) {
  228. for my $item (@$o) {
  229. next unless ( ref($item) eq "HASH" );
  230. for my $key ( keys(%$item) ) {
  231. next if ( ref( $item->{$key} ) );
  232. my $val = $item->{$key};
  233. $params->{$key} = $item->{$key}
  234. unless ( $params->{$key} );
  235. }
  236. }
  237. Log3 $globalDevName, 5,
  238. "msg: Decoded user parameters\n" . Dumper($params)
  239. if ($params);
  240. }
  241. }
  242. }
  243. $types = "default"
  244. if ( $types eq "" );
  245. my $msgSent = 0;
  246. my $forwarded = "";
  247. my %sentTypesPerDevice;
  248. my $sentCounter = 0;
  249. my $msgID = time();
  250. my $msgDateTime = TimeNow();
  251. my $isTypeOr = 1;
  252. my $isRecipientOr = 1;
  253. my $hasTypeOr = 0;
  254. my $hasRecipientOr = 0;
  255. my $softFail = 0;
  256. $recipients = "\@" . $globalDevName if ( $recipients eq "" );
  257. ################################################################
  258. ### recipient loop
  259. ###
  260. my @recipientsOr = split( /\|/, $recipients );
  261. Log3 $globalDevName, 6,
  262. "msg: recipientOr total is " . scalar( grep { defined $_ } @recipientsOr )
  263. unless ( defined($testMode) && $testMode eq "1" );
  264. for (
  265. my $iRecipOr = 0 ;
  266. $iRecipOr < scalar( grep { defined $_ } @recipientsOr ) ;
  267. $iRecipOr++
  268. )
  269. {
  270. Log3 $globalDevName, 6,
  271. "msg: "
  272. . "running loop recipientsOr for recipient(s) $recipientsOr[$iRecipOr]"
  273. unless ( defined($testMode) && $testMode eq "1" );
  274. my $loopReturn1 = "";
  275. $hasRecipientOr = 1
  276. if ( scalar( grep { defined $_ } @recipientsOr ) > 1 );
  277. my @recipient = split( /,/, $recipientsOr[$iRecipOr] );
  278. foreach my $device (@recipient) {
  279. Log3 $globalDevName, 6, "msg: running loop for device $device"
  280. unless ( defined($testMode) && $testMode eq "1" );
  281. my $loopReturn2 = "";
  282. ################################################################
  283. ### type loop
  284. ###
  285. my @typesOr = split( /\|/, $types );
  286. Log3 $globalDevName, 6,
  287. "msg: typeOr total is " . scalar( grep { defined $_ } @typesOr )
  288. unless ( defined($testMode) && $testMode eq "1" );
  289. for (
  290. my $iTypesOr = 0 ;
  291. $iTypesOr < scalar( grep { defined $_ } @typesOr ) ;
  292. $iTypesOr++
  293. )
  294. {
  295. Log3 $globalDevName, 6,
  296. "msg: running loop typeOr for type(s) $typesOr[$iTypesOr]"
  297. unless ( defined($testMode) && $testMode eq "1" );
  298. my $loopReturn3 = "";
  299. $hasTypeOr = 1
  300. if ( scalar( grep { defined $_ } @typesOr ) > 1 );
  301. my @type = split( /,/, lc( $typesOr[$iTypesOr] ) );
  302. for (
  303. my $i = 0 ;
  304. $i < scalar( grep { defined $_ } @type ) ;
  305. $i++
  306. )
  307. {
  308. Log3 $globalDevName, 6,
  309. "msg: running loop for type $type[$i]"
  310. unless ( defined($testMode) && $testMode eq "1" );
  311. last unless ( defined( $type[$i] ) );
  312. # check for correct type
  313. my @msgCmds = (
  314. "screen", "light", "audio", "text",
  315. "push", "mail", "queue", "default"
  316. );
  317. unless ( grep { $type[$i] =~ /^$_/i } @msgCmds ) {
  318. $loopReturn3 .= "Unknown message type $type[$i]\n";
  319. next;
  320. }
  321. ###
  322. ### /type loop
  323. ################################################################
  324. my @unavailabilityIndicators = (
  325. "0", "false",
  326. "absent", "disappeared",
  327. "unauthorized", "unavailable",
  328. "unreachable", "disconnected"
  329. );
  330. my $logDevice;
  331. $logDevice = $globalDevName;
  332. $logDevice = $device
  333. if (
  334. MSG_FindAttrVal( $device, "verbose", undef, undef ) );
  335. my $msgSentDev = 0;
  336. my $gatewayDevs = "";
  337. my $forceDevice = 0;
  338. my $forceQueue = 0;
  339. # for device type
  340. my $deviceType = "device";
  341. if ( $device =~
  342. m/^(([A-Za-z0-9%+._-])+@+([%+a-z0-9A-Z.-]*))$/ )
  343. {
  344. $gatewayDevs = $1;
  345. $deviceType = "email";
  346. }
  347. elsif ( $device =~
  348. m/^@?([A-Za-z\d_\.\-\/]+)([^A-Za-z\d_\.\-\/]+)[\s\t ]*$/
  349. )
  350. {
  351. $device = $1;
  352. foreach ( split( /(\S)/, $2 ) ) {
  353. $forceDevice = 1 if ( $_ eq "!" );
  354. $softFail = 1 if ( $_ eq "?" );
  355. $forceQueue = 1 if ( $_ eq "&" );
  356. }
  357. Log3 $logDevice, 5,
  358. "msg $device: forceDevice=$forceDevice (from device contact)"
  359. if ($forceDevice);
  360. Log3 $logDevice, 5,
  361. "msg $device: softFail=$softFail (from device contact)"
  362. if ($softFail);
  363. Log3 $logDevice, 5,
  364. "msg $device: forceQueue=$forceQueue (from device contact)"
  365. if ($forceQueue);
  366. }
  367. elsif ( $device =~ /^@(.*)/ ) {
  368. $device = $1;
  369. }
  370. # sub-recipient
  371. my $subRecipient = "";
  372. my $termRecipient = "";
  373. if ( $device =~
  374. m/^@?([A-Za-z0-9._]+):([A-Za-z0-9._\-\/@+]*):?([A-Za-z0-9._\-\/@+]*)$/
  375. )
  376. {
  377. $device = $1;
  378. $subRecipient = $2;
  379. $termRecipient = $3;
  380. }
  381. # FATAL ERROR: device does not exist
  382. if ( !IsDevice($device)
  383. && $deviceType eq "device" )
  384. {
  385. $loopReturn3 .= "Device $device does not exist\n"
  386. unless ($softFail);
  387. unless ( defined($testMode) && $testMode eq "1" ) {
  388. Log3 $logDevice, 2,
  389. "msg $device: Device does not exist"
  390. unless ($softFail);
  391. Log3 $logDevice, 5,
  392. "msg $device: Device does not exist"
  393. if ($softFail);
  394. }
  395. my $regex1 =
  396. "\\s*!?@?" . $device . "[,|]"; # at the beginning
  397. my $regex2 = "[,|]!?@?" . $device . "\\s*"; # at the end
  398. my $regex3 =
  399. ",!?@?" . $device . ","; # in the middle with comma
  400. my $regex4 =
  401. "[\|,]!?@?"
  402. . $device
  403. . "[\|,]"; # in the middle with pipe and/or comma
  404. $recipients =~ s/^$regex1//gi;
  405. $recipients =~ s/$regex2$/|/gi;
  406. $recipients =~ s/$regex3/,/gi;
  407. $recipients =~ s/$regex4/|/gi;
  408. next;
  409. }
  410. # Find custom types for devices
  411. if ( $type[$i] eq "default" ) {
  412. delete $typesOr[$iTypesOr]
  413. if ( $typesOr[$iTypesOr] eq "default" );
  414. delete $type[$i];
  415. Log3 $logDevice, 5,
  416. "msg $device: msgType lookup for $device:";
  417. my @t = split(
  418. m/\|/,
  419. MSG_FindAttrVal(
  420. $device, "msgType", undef, "text"
  421. )
  422. );
  423. $hasTypeOr = 1
  424. if ( scalar( grep { defined $_ } @t ) > 1 );
  425. foreach (@t) {
  426. Log3 $logDevice, 5,
  427. "msg $device: Adding to \@typesOr: $_";
  428. push @typesOr, $_;
  429. foreach ( split( /,/, lc($_) ) ) {
  430. Log3 $logDevice, 5,
  431. "msg $device: Adding to \@type: $_";
  432. push @type, $_;
  433. }
  434. }
  435. }
  436. my $forceType = 0;
  437. if ( $type[$i] =~
  438. m/^([A-Za-z\d_\.\-\/]+)([^A-Za-z\d_\.\-\/]+)[\s\t ]*$/ )
  439. {
  440. $type[$i] = $1;
  441. foreach ( split( /(\S)/, $2 ) ) {
  442. $forceType = 1 if ( $_ eq "!" );
  443. $softFail = 1 if ( $_ eq "?" );
  444. $forceQueue = 1 if ( $_ eq "&" );
  445. }
  446. Log3 $logDevice, 5,
  447. "msg $device: forceType=$forceType (from type)"
  448. if ($forceType);
  449. Log3 $logDevice, 5,
  450. "msg $device: softFail=$softFail (from type)"
  451. if ($softFail);
  452. Log3 $logDevice, 5,
  453. "msg $device: forceQueue=$forceQueue (from type)"
  454. if ($forceQueue);
  455. }
  456. # next type loop if device is an email address
  457. # and this is not the mail type loop run
  458. if ( $deviceType eq "email"
  459. && $type[$i] ne "mail"
  460. && $type[$i] ne "text" )
  461. {
  462. Log3 $logDevice, 5,
  463. "msg $device: "
  464. . "Skipping loop for device type 'email' with unmatched message type '"
  465. . $type[$i] . "'";
  466. next;
  467. }
  468. my $typeUc = ucfirst( $type[$i] );
  469. my $catchall = 0;
  470. my $useLocation = 0;
  471. ################################################################
  472. ### get target information from device location
  473. ###
  474. # search for location references
  475. my @locationDevs;
  476. @locationDevs = split(
  477. /,/,
  478. MSG_FindAttrVal(
  479. $device, "msgLocationDevs", $typeUc, ""
  480. )
  481. );
  482. if ( $deviceType eq "device" ) {
  483. # get device location
  484. my $deviceLocation =
  485. msgConfig_FindReadingsVal( $device, "location",
  486. $typeUc, "" );
  487. my $locationDev = "";
  488. if ( $deviceLocation ne "" && $deviceType eq "device" )
  489. {
  490. # lookup matching location
  491. foreach (@locationDevs) {
  492. if ( $featurelevel >= 5.7 ) {
  493. my %dummy;
  494. my ( $err, @a ) =
  495. ReplaceSetMagic( \%dummy, 0, ($_) );
  496. $_ = join( " ", @a )
  497. unless ($err);
  498. }
  499. my $lName =
  500. AttrVal( $_, "msgLocationName", "" );
  501. if ( $lName ne "" && $lName eq $deviceLocation )
  502. {
  503. $locationDev = $_;
  504. last;
  505. }
  506. }
  507. if ( $featurelevel >= 5.7 ) {
  508. my %dummy;
  509. my ( $err, @a ) =
  510. ReplaceSetMagic( \%dummy, 0, ($locationDev) );
  511. $locationDev = join( " ", @a )
  512. unless ($err);
  513. }
  514. # look for gateway device
  515. $gatewayDevs =
  516. MSG_FindAttrVal( $locationDev, "msgContact",
  517. $typeUc, "" );
  518. # at least one of the location gateways needs to
  519. # be available. Otherwise we fall back to
  520. # non-location contacts
  521. if ( $gatewayDevs ne "" ) {
  522. if ( $featurelevel >= 5.7 ) {
  523. my %dummy;
  524. my ( $err, @a ) =
  525. ReplaceSetMagic( \%dummy, 0,
  526. ($gatewayDevs) );
  527. $gatewayDevs = join( " ", @a )
  528. unless ($err);
  529. }
  530. foreach
  531. my $gatewayDevOr ( split /\|/, $gatewayDevs )
  532. {
  533. foreach my $gatewayDev ( split /,/,
  534. $gatewayDevOr )
  535. {
  536. my $tmpSubRecipient;
  537. if ( $gatewayDev =~ /:(.*)/ ) {
  538. $tmpSubRecipient = $1;
  539. }
  540. if ( $type[$i] ne "mail"
  541. && !IsDevice($gatewayDev)
  542. && $deviceType eq "device" )
  543. {
  544. $useLocation = 2
  545. if ( $useLocation == 0 );
  546. }
  547. elsif ( $type[$i] ne "mail"
  548. && IsDisabled($gatewayDev) )
  549. {
  550. $useLocation = 2
  551. if ( $useLocation == 0 );
  552. }
  553. elsif (
  554. $type[$i] ne "mail"
  555. && (
  556. (
  557. grep {
  558. ReadingsVal(
  559. $gatewayDev,
  560. "presence",
  561. "present"
  562. ) eq $_
  563. } @unavailabilityIndicators
  564. )
  565. || (
  566. grep {
  567. ReadingsVal(
  568. $gatewayDev,
  569. "state",
  570. "present"
  571. ) eq $_
  572. } @unavailabilityIndicators
  573. )
  574. || (
  575. defined(
  576. $defs{$gatewayDev}
  577. )
  578. && defined(
  579. $defs{$gatewayDev}
  580. {STATE}
  581. )
  582. && (
  583. grep {
  584. $defs{$gatewayDev}
  585. {STATE} eq $_
  586. } @unavailabilityIndicators
  587. )
  588. )
  589. || ReadingsVal(
  590. $gatewayDev, "available",
  591. "yes"
  592. ) =~ m/^(0|no|false)$/i
  593. || ReadingsVal(
  594. $gatewayDev, "reachable",
  595. "yes"
  596. ) =~ m/^(0|no|false)$/i
  597. )
  598. )
  599. {
  600. $useLocation = 2
  601. if ( $useLocation == 0 );
  602. }
  603. else {
  604. $useLocation = 1;
  605. }
  606. }
  607. }
  608. # use gatewayDevs from location only
  609. # if it has been confirmed to be available
  610. if ( $useLocation == 1 ) {
  611. Log3 $logDevice, 4, "msg $device: "
  612. . "Matching location definition found";
  613. }
  614. else {
  615. $gatewayDevs = "";
  616. }
  617. }
  618. }
  619. }
  620. ################################################################
  621. ### given device name is already a gateway device itself
  622. ###
  623. my $deviceType2 = GetType($device);
  624. if (
  625. $gatewayDevs eq ""
  626. && $deviceType eq "device"
  627. && $deviceType2 ne ""
  628. && (
  629. (
  630. $type[$i] eq "audio" && defined(
  631. $cmdSchema->{ $type[$i] }{$deviceType2}
  632. )
  633. )
  634. || (
  635. $type[$i] eq "light"
  636. && defined(
  637. $cmdSchema->{ $type[$i] }{$deviceType2}
  638. )
  639. )
  640. || (
  641. $type[$i] eq "push"
  642. && defined(
  643. $cmdSchema->{ $type[$i] }{$deviceType2}
  644. )
  645. )
  646. || (
  647. $type[$i] eq "screen"
  648. && defined(
  649. $cmdSchema->{ $type[$i] }{$deviceType2}
  650. )
  651. )
  652. || (
  653. $type[$i] eq "queue"
  654. && defined(
  655. $cmdSchema->{ $type[$i] }{$deviceType2}
  656. )
  657. )
  658. )
  659. )
  660. {
  661. Log3 $logDevice, 4,
  662. "msg $device: Recipient type $deviceType2 "
  663. . "is a gateway device itself for message type "
  664. . $type[$i]
  665. . ". Still checking for any delegates ..."
  666. unless ( defined($testMode) && $testMode eq "1" );
  667. $gatewayDevs =
  668. MSG_FindAttrVal( $device, "msgContact", $typeUc,
  669. $device );
  670. }
  671. ################################################################
  672. ### get target information from device
  673. ###
  674. elsif ( $deviceType eq "device" && $gatewayDevs eq "" ) {
  675. # look for gateway device
  676. $gatewayDevs =
  677. MSG_FindAttrVal( $device, "msgContact", $typeUc, "" );
  678. # fallback/catchall
  679. if ( $gatewayDevs eq "" ) {
  680. $catchall = 1
  681. if ( $device ne $globalDevName );
  682. Log3 $logDevice, 6,
  683. "msg $device: (No $typeUc contact defined, "
  684. . "trying global instead)"
  685. if ( $catchall == 1 );
  686. $gatewayDevs =
  687. MSG_FindAttrVal( $globalDevName, "msgContact",
  688. $typeUc, "" );
  689. }
  690. }
  691. # Find priority if none was explicitly specified
  692. my $loopPriority = $priority;
  693. $loopPriority =
  694. MSG_FindAttrVal( $device, "msgPriority$typeUc", $typeUc,
  695. MSG_FindAttrVal( $device, "msgPriority", $typeUc, 0 ) )
  696. if ( $priority eq "" );
  697. # check for available routes
  698. #
  699. my %routes;
  700. $routes{screen} = 0;
  701. $routes{light} = 0;
  702. $routes{audio} = 0;
  703. $routes{text} = 0;
  704. $routes{push} = 0;
  705. $routes{mail} = 0;
  706. $routes{queue} = 1;
  707. if (
  708. !defined($testMode)
  709. || ( $testMode ne "1"
  710. && $testMode ne "2" )
  711. )
  712. {
  713. Log3 $logDevice, 5,
  714. "msg $device: Checking for available routes "
  715. . "(triggered by type $type[$i])";
  716. $routes{screen} = 1
  717. if (
  718. $deviceType eq "device"
  719. && CommandMsg( "screen",
  720. "screen \@$device $priority Routing Test", 1 )
  721. eq "ROUTE_AVAILABLE"
  722. );
  723. $routes{light} = 1
  724. if (
  725. $deviceType eq "device"
  726. && CommandMsg( "light",
  727. "light \@$device $priority Routing Test", 1 )
  728. eq "ROUTE_AVAILABLE"
  729. );
  730. $routes{audio} = 1
  731. if (
  732. $deviceType eq "device"
  733. && CommandMsg( "audio",
  734. "audio \@$device $priority Routing Test", 1 )
  735. eq "ROUTE_AVAILABLE"
  736. );
  737. if (
  738. $deviceType eq "device"
  739. && CommandMsg( "push",
  740. "push \@$device $priority Routing Test", 1 ) eq
  741. "ROUTE_AVAILABLE"
  742. )
  743. {
  744. $routes{push} = 1;
  745. $routes{text} = 1;
  746. }
  747. if (
  748. CommandMsg( "mail",
  749. "mail \@$device $priority Routing Test", 1 ) eq
  750. "ROUTE_AVAILABLE"
  751. )
  752. {
  753. $routes{mail} = 1;
  754. $routes{text} = 1;
  755. }
  756. $routes{mail} = 1
  757. if ( $deviceType eq "email" );
  758. Log3 $logDevice, 4,
  759. "msg $device: Available routes: screen="
  760. . $routes{screen}
  761. . " light="
  762. . $routes{light}
  763. . " audio="
  764. . $routes{audio}
  765. . " text="
  766. . $routes{text}
  767. . " push="
  768. . $routes{push}
  769. . " mail="
  770. . $routes{mail};
  771. }
  772. ##################################################
  773. ### dynamic routing for text (->push, ->mail)
  774. ###
  775. if ( $type[$i] eq "text" ) {
  776. # user selected emergency priority text threshold
  777. my $prioThresTextEmg =
  778. MSG_FindAttrVal( $device, "msgThPrioTextEmergency",
  779. $typeUc, 2 );
  780. # user selected low priority text threshold
  781. my $prioThresTextNormal =
  782. MSG_FindAttrVal( $device, "msgThPrioTextNormal",
  783. $typeUc, -2 );
  784. # Decide push and/or e-mail destination based
  785. # on priorities
  786. if ( $loopPriority >= $prioThresTextEmg
  787. && $routes{push} == 1
  788. && $routes{mail} == 1 )
  789. {
  790. Log3 $logDevice, 4,
  791. "msg $device: "
  792. . "Text routing decision: push+mail(1)";
  793. $forwarded .= ","
  794. if ( $forwarded ne "" );
  795. $forwarded .= "text>push+mail";
  796. push @type, "push"
  797. unless grep { "push" eq $_ } @type;
  798. push @type, "mail"
  799. unless grep { "mail" eq $_ } @type;
  800. }
  801. elsif ($loopPriority >= $prioThresTextEmg
  802. && $routes{push} == 1
  803. && $routes{mail} == 0 )
  804. {
  805. Log3 $logDevice, 4,
  806. "msg $device: Text routing decision: push(2)";
  807. $forwarded .= ","
  808. if ( $forwarded ne "" );
  809. $forwarded .= "text>push";
  810. push @type, "push"
  811. unless grep { "push" eq $_ } @type;
  812. }
  813. elsif ($loopPriority >= $prioThresTextEmg
  814. && $routes{push} == 0
  815. && $routes{mail} == 1 )
  816. {
  817. Log3 $logDevice, 4,
  818. "msg $device: Text routing decision: mail(3)";
  819. $forwarded .= ","
  820. if ( $forwarded ne "" );
  821. $forwarded .= "text>mail";
  822. push @type, "mail"
  823. unless grep { "mail" eq $_ } @type;
  824. }
  825. elsif ($loopPriority >= $prioThresTextNormal
  826. && $routes{push} == 1 )
  827. {
  828. Log3 $logDevice, 4,
  829. "msg $device: Text routing decision: push(4)";
  830. $forwarded .= ","
  831. if ( $forwarded ne "" );
  832. $forwarded .= "text>push";
  833. push @type, "push"
  834. unless grep { "push" eq $_ } @type;
  835. }
  836. elsif ($loopPriority >= $prioThresTextNormal
  837. && $routes{mail} == 1 )
  838. {
  839. Log3 $logDevice, 4,
  840. "msg $device: Text routing decision: mail(5)";
  841. $forwarded .= ","
  842. if ( $forwarded ne "" );
  843. $forwarded .= "text>mail";
  844. push @type, "mail"
  845. unless grep { "mail" eq $_ } @type;
  846. }
  847. elsif ( $routes{mail} == 1 ) {
  848. Log3 $logDevice, 4,
  849. "msg $device: Text routing decision: mail(6)";
  850. $forwarded .= ","
  851. if ( $forwarded ne "" );
  852. $forwarded .= "text>mail";
  853. push @type, "mail"
  854. unless grep { "mail" eq $_ } @type;
  855. }
  856. elsif ( $routes{push} == 1 ) {
  857. Log3 $logDevice, 4,
  858. "msg $device: Text routing decision: push(7)";
  859. $forwarded .= ","
  860. if ( $forwarded ne "" );
  861. $forwarded .= "text>push";
  862. push @type, "push"
  863. unless grep { "push" eq $_ } @type;
  864. }
  865. # FATAL ERROR: routing decision failed
  866. else {
  867. Log3 $logDevice, 4,
  868. "msg $device: "
  869. . "Text routing FAILED - priority=$loopPriority push="
  870. . $routes{push}
  871. . " mail="
  872. . $routes{mail};
  873. $loopReturn3 .=
  874. "ERROR: Could not find any Push or Mail contact "
  875. . "for device $device - set attributes: msgContactPush "
  876. . "| msgContactMail | msgContactText | msgRecipientPush "
  877. . "| msgRecipientMail | msgRecipientText | msgRecipient\n";
  878. }
  879. next;
  880. }
  881. # FATAL ERROR: we could not find any targets for
  882. # user specified device...
  883. if ( $gatewayDevs eq ""
  884. && $device ne $globalDevName )
  885. {
  886. $loopReturn3 .=
  887. "ERROR: Could not find any $typeUc contact "
  888. . "for device $device - set attributes: msgContact$typeUc | msgRecipient$typeUc | msgRecipient\n"
  889. unless ( $type[$i] eq "queue" );
  890. }
  891. # FATAL ERROR: we could not find any targets at all
  892. elsif ( $gatewayDevs eq "" ) {
  893. $loopReturn3 .=
  894. "ERROR: Could not find any general $typeUc contact. "
  895. . "Please specify a destination device or set attributes in general msg configuration device $globalDevName : msgContact$typeUc | msgRecipient$typeUc | msgRecipient\n"
  896. unless ( $type[$i] eq "queue" );
  897. }
  898. # user selected audio-visual announcement state
  899. my $annState = ReadingsVal(
  900. MSG_FindAttrVal(
  901. $device, "msgSwitcherDev", $typeUc, ""
  902. ),
  903. "state", "long"
  904. );
  905. # user selected emergency priority audio threshold
  906. my $prioThresAudioEmg =
  907. MSG_FindAttrVal( $device, "msgThPrioAudioEmergency",
  908. $typeUc, 2 );
  909. # user selected high priority audio threshold
  910. my $prioThresAudioHigh =
  911. MSG_FindAttrVal( $device, "msgThPrioAudioHigh", $typeUc,
  912. 1 );
  913. # user selected high priority threshold
  914. my $prioThresHigh =
  915. MSG_FindAttrVal( $device, "msgThPrioHigh", $typeUc, 2 );
  916. # user selected normal priority threshold
  917. my $prioThresNormal =
  918. MSG_FindAttrVal( $device, "msgThPrioNormal", $typeUc, 0 );
  919. if ( $type[$i] eq "audio" ) {
  920. if ( $annState eq "long"
  921. || $forceType == 1
  922. || $forceDevice == 1
  923. || $loopPriority >= $prioThresAudioEmg )
  924. {
  925. $priorityCat = "";
  926. }
  927. elsif ( $loopPriority >= $prioThresAudioHigh ) {
  928. $priorityCat = "ShortPrio";
  929. }
  930. else {
  931. $priorityCat = "Short";
  932. }
  933. }
  934. else {
  935. if ( $loopPriority >= $prioThresHigh ) {
  936. $priorityCat = "High";
  937. }
  938. elsif ( $loopPriority >= $prioThresNormal ) {
  939. $priorityCat = "";
  940. }
  941. else {
  942. $priorityCat = "Low";
  943. }
  944. }
  945. # get resident presence information
  946. #
  947. my $residentDevState = "";
  948. my $residentDevPresence = "";
  949. # device
  950. if ( ReadingsVal( $device, "presence", "-" ) ne "-" ) {
  951. $residentDevState = ReadingsVal( $device, "state", "" );
  952. $residentDevPresence =
  953. ReadingsVal( $device, "presence", "" );
  954. }
  955. # device indirect
  956. if (
  957. (
  958. $residentDevState eq ""
  959. || $residentDevPresence eq ""
  960. )
  961. && ReadingsVal(
  962. AttrVal( $device, "msgRecipient$typeUc", "" ),
  963. "presence", "-" ) ne "-"
  964. )
  965. {
  966. $residentDevState =
  967. ReadingsVal(
  968. AttrVal( $device, "msgRecipient$typeUc", "" ),
  969. "state", "" )
  970. if ( $residentDevState eq "" );
  971. $residentDevPresence =
  972. ReadingsVal(
  973. AttrVal( $device, "msgRecipient$typeUc", "" ),
  974. "presence", "" )
  975. if ( $residentDevPresence eq "" );
  976. }
  977. # device indirect general
  978. if (
  979. (
  980. $residentDevState eq ""
  981. || $residentDevPresence eq ""
  982. )
  983. && ReadingsVal( AttrVal( $device, "msgRecipient", "" ),
  984. "presence", "-" ) ne "-"
  985. )
  986. {
  987. $residentDevState =
  988. ReadingsVal( AttrVal( $device, "msgRecipient", "" ),
  989. "state", "" )
  990. if ( $residentDevState eq "" );
  991. $residentDevPresence =
  992. ReadingsVal( AttrVal( $device, "msgRecipient", "" ),
  993. "presence", "" )
  994. if ( $residentDevPresence eq "" );
  995. }
  996. # device explicit
  997. if (
  998. (
  999. $residentDevState eq ""
  1000. || $residentDevPresence eq ""
  1001. )
  1002. && ReadingsVal(
  1003. AttrVal( $device, "msgResidentsDev", "" ),
  1004. "presence", "-" ) ne "-"
  1005. )
  1006. {
  1007. $residentDevState =
  1008. ReadingsVal(
  1009. AttrVal( $device, "msgResidentsDev", "" ),
  1010. "state", "" )
  1011. if ( $residentDevState eq "" );
  1012. $residentDevPresence =
  1013. ReadingsVal(
  1014. AttrVal( $device, "msgResidentsDev", "" ),
  1015. "presence", "" )
  1016. if ( $residentDevPresence eq "" );
  1017. }
  1018. # global indirect
  1019. if (
  1020. (
  1021. $residentDevState eq ""
  1022. || $residentDevPresence eq ""
  1023. )
  1024. && ReadingsVal(
  1025. AttrVal(
  1026. $globalDevName, "msgRecipient$typeUc", ""
  1027. ),
  1028. "presence",
  1029. "-"
  1030. ) ne "-"
  1031. )
  1032. {
  1033. $residentDevState = ReadingsVal(
  1034. AttrVal(
  1035. $globalDevName, "msgRecipient$typeUc", ""
  1036. ),
  1037. "state", ""
  1038. ) if ( $residentDevState eq "" );
  1039. $residentDevPresence = ReadingsVal(
  1040. AttrVal(
  1041. $globalDevName, "msgRecipient$typeUc", ""
  1042. ),
  1043. "presence",
  1044. ""
  1045. ) if ( $residentDevPresence eq "" );
  1046. }
  1047. # global indirect general
  1048. if (
  1049. (
  1050. $residentDevState eq ""
  1051. || $residentDevPresence eq ""
  1052. )
  1053. && ReadingsVal(
  1054. AttrVal( $globalDevName, "msgRecipient", "" ),
  1055. "presence", "-" ) ne "-"
  1056. )
  1057. {
  1058. $residentDevState =
  1059. ReadingsVal(
  1060. AttrVal( $globalDevName, "msgRecipient", "" ),
  1061. "state", "" )
  1062. if ( $residentDevState eq "" );
  1063. $residentDevPresence =
  1064. ReadingsVal(
  1065. AttrVal( $globalDevName, "msgRecipient", "" ),
  1066. "presence", "" )
  1067. if ( $residentDevPresence eq "" );
  1068. }
  1069. # global explicit
  1070. if (
  1071. (
  1072. $residentDevState eq ""
  1073. || $residentDevPresence eq ""
  1074. )
  1075. && ReadingsVal(
  1076. AttrVal( $globalDevName, "msgResidentsDev", "" ),
  1077. "presence", "-" ) ne "-"
  1078. )
  1079. {
  1080. $residentDevState =
  1081. ReadingsVal(
  1082. AttrVal( $globalDevName, "msgResidentsDev", "" ),
  1083. "state", "" )
  1084. if ( $residentDevState eq "" );
  1085. $residentDevPresence =
  1086. ReadingsVal(
  1087. AttrVal( $globalDevName, "msgResidentsDev", "" ),
  1088. "presence", "" )
  1089. if ( $residentDevPresence eq "" );
  1090. }
  1091. ################################################################
  1092. ### Send message
  1093. ###
  1094. my $queued = 0;
  1095. # user selected emergency priority text threshold
  1096. my $prioThresGwEmg =
  1097. MSG_FindAttrVal( $device, "msgThPrioGwEmergency",
  1098. $typeUc, 2 );
  1099. if ( $featurelevel >= 5.7 ) {
  1100. my %dummy;
  1101. my ( $err, @a ) =
  1102. ReplaceSetMagic( \%dummy, 0, ($gatewayDevs) );
  1103. $gatewayDevs = join( " ", @a )
  1104. unless ($err);
  1105. }
  1106. my %gatewaysStatus;
  1107. $gatewayDevs = $globalDevName if ( $type[$i] eq "queue" );
  1108. foreach my $gatewayDevOr ( split /\|/, $gatewayDevs ) {
  1109. foreach my $gatewayDev ( split /,/, $gatewayDevOr ) {
  1110. if ( $gatewayDev =~
  1111. m/^@?([A-Za-z0-9._]+):([A-Za-z0-9._\-\/@+]*):?([A-Za-z0-9._\-\/@+]*)$/
  1112. )
  1113. {
  1114. $gatewayDev = $1;
  1115. $subRecipient = $2 if ( $subRecipient eq "" );
  1116. $termRecipient = $3 if ( $termRecipient eq "" );
  1117. }
  1118. my $logMsg =
  1119. "msg $device: "
  1120. . "Trying to send message via gateway $gatewayDev";
  1121. $logMsg .= " to recipient $subRecipient"
  1122. if ( $subRecipient ne "" );
  1123. $logMsg .= ", terminal device $termRecipient"
  1124. if ( $termRecipient ne "" );
  1125. Log3 $logDevice, 5, $logMsg
  1126. unless ( defined($testMode) && $testMode eq "1" );
  1127. ##############
  1128. # check for gateway availability and set route status
  1129. #
  1130. my $routeStatus = "OK";
  1131. if ( $type[$i] eq "queue" ) {
  1132. $routeStatus = "OK_QUEUE";
  1133. }
  1134. elsif ($type[$i] ne "mail"
  1135. && !IsDevice($gatewayDev)
  1136. && $deviceType eq "device" )
  1137. {
  1138. $routeStatus = "UNDEFINED";
  1139. }
  1140. elsif ( $type[$i] ne "mail"
  1141. && IsDisabled($gatewayDev) )
  1142. {
  1143. $routeStatus = "DISABLED";
  1144. }
  1145. elsif (
  1146. $type[$i] ne "mail"
  1147. && (
  1148. (
  1149. grep {
  1150. ReadingsVal( $gatewayDev,
  1151. "presence", "present" ) eq $_
  1152. } @unavailabilityIndicators
  1153. )
  1154. || (
  1155. grep {
  1156. ReadingsVal( $gatewayDev, "state",
  1157. "present" ) eq $_
  1158. } @unavailabilityIndicators
  1159. )
  1160. || (
  1161. IsDevice($gatewayDev)
  1162. && defined( $defs{$gatewayDev}{STATE} )
  1163. && (
  1164. grep {
  1165. $defs{$gatewayDev}{STATE} eq $_
  1166. } @unavailabilityIndicators
  1167. )
  1168. )
  1169. || ReadingsVal( $gatewayDev, "available",
  1170. "yes" ) =~ m/^(0|no|false)$/i
  1171. || ReadingsVal( $gatewayDev, "reachable",
  1172. "yes" ) =~ m/^(0|no|false)$/i
  1173. )
  1174. )
  1175. {
  1176. $routeStatus = "UNAVAILABLE";
  1177. }
  1178. elsif (
  1179. $type[$i] ne "mail"
  1180. && (
  1181. ReadingsVal( $gatewayDev, "presence",
  1182. "present" ) =~
  1183. m/^(0|false|absent|disappeared|unauthorized|disconnected|unreachable)$/i
  1184. || ReadingsVal( $gatewayDev, "state",
  1185. "present" ) =~
  1186. m/^(absent|disappeared|unauthorized|disconnected|unreachable)$/i
  1187. || ( IsDevice($gatewayDev)
  1188. && defined( $defs{$gatewayDev}{STATE} )
  1189. && $defs{$gatewayDev}{STATE} =~
  1190. m/^(absent|disappeared|unauthorized|disconnected|unreachable)$/i
  1191. )
  1192. || ReadingsVal( $gatewayDev, "available",
  1193. "yes" ) =~ m/^(0|no|off|false)$/i
  1194. || ReadingsVal( $gatewayDev, "reachable",
  1195. "yes" ) =~ m/^(0|no|off|false)$/i
  1196. )
  1197. )
  1198. {
  1199. $routeStatus = "UNAVAILABLE";
  1200. }
  1201. elsif ( $type[$i] eq "screen"
  1202. && ReadingsVal( $gatewayDev, "power", "on" ) =~
  1203. m/^(0|off)$/i )
  1204. {
  1205. $routeStatus = "OFF";
  1206. }
  1207. elsif ($type[$i] eq "audio"
  1208. && $annState ne "long"
  1209. && $annState ne "short" )
  1210. {
  1211. $routeStatus = "USER_DISABLED";
  1212. }
  1213. elsif ( $type[$i] eq "light" && $annState eq "off" )
  1214. {
  1215. $routeStatus = "USER_DISABLED";
  1216. }
  1217. elsif ($type[$i] ne "push"
  1218. && $type[$i] ne "mail"
  1219. && $residentDevPresence eq "absent" )
  1220. {
  1221. $routeStatus = "USER_ABSENT";
  1222. }
  1223. elsif ($type[$i] ne "push"
  1224. && $type[$i] ne "mail"
  1225. && $residentDevState eq "asleep" )
  1226. {
  1227. $routeStatus = "USER_ASLEEP";
  1228. }
  1229. # enforce by user request
  1230. if (
  1231. (
  1232. $routeStatus eq "USER_DISABLED"
  1233. || $routeStatus eq "USER_ABSENT"
  1234. || $routeStatus eq "USER_ASLEEP"
  1235. )
  1236. && ( $forceType == 1 || $forceDevice == 1 )
  1237. )
  1238. {
  1239. $routeStatus = "OK_ENFORCED";
  1240. }
  1241. # enforce by priority
  1242. if (
  1243. (
  1244. $routeStatus eq "USER_DISABLED"
  1245. || $routeStatus eq "USER_ABSENT"
  1246. || $routeStatus eq "USER_ASLEEP"
  1247. )
  1248. && $loopPriority >= $prioThresGwEmg
  1249. )
  1250. {
  1251. $routeStatus = "OK_EMERGENCY";
  1252. }
  1253. # add location status
  1254. if ( $useLocation == 2 ) {
  1255. $routeStatus .= "+LOCATION-UNAVAILABLE";
  1256. }
  1257. elsif ( $useLocation == 1 ) {
  1258. $routeStatus .= "+LOCATION";
  1259. }
  1260. # # add to queue
  1261. # if (
  1262. # (
  1263. # $routeStatus eq "USER_DISABLED"
  1264. # || $routeStatus eq "USER_ABSENT"
  1265. # || $routeStatus eq "USER_ASLEEP"
  1266. # )
  1267. # && $routeStatus !~ /^OK/
  1268. # && !$softFail
  1269. # )
  1270. # {
  1271. # $routeStatus .= "+QUEUE";
  1272. # }
  1273. my $gatewayType =
  1274. $type[$i] eq "mail"
  1275. ? "fhemMsgMail"
  1276. : GetType( $gatewayDev, "UNDEFINED" );
  1277. my $defTitle = "";
  1278. $defTitle =
  1279. $cmdSchema->{ $type[$i] }{$gatewayType}
  1280. {defaultValues}{$priorityCat}{TITLE}
  1281. if (
  1282. defined(
  1283. $cmdSchema->{ $type[$i] }{$gatewayType}
  1284. {defaultValues}{$priorityCat}{TITLE}
  1285. )
  1286. && $priorityCat ne ""
  1287. );
  1288. $defTitle =
  1289. $cmdSchema->{ $type[$i] }{$gatewayType}
  1290. {defaultValues}{Normal}{TITLE}
  1291. if (
  1292. defined(
  1293. $cmdSchema->{ $type[$i] }{$gatewayType}
  1294. {defaultValues}{Normal}{TITLE}
  1295. )
  1296. && $priorityCat eq ""
  1297. );
  1298. Log3 $logDevice, 5,
  1299. "msg $device: "
  1300. . "Determined default title: $defTitle"
  1301. unless ( defined($testMode) && $testMode eq "1" );
  1302. # use title from device, global or internal default
  1303. my $loopTitle = $title;
  1304. $loopTitle = MSG_FindAttrVal(
  1305. $device,
  1306. "msgTitle$typeUc$priorityCat",
  1307. $typeUc,
  1308. MSG_FindAttrVal(
  1309. $device,
  1310. "msgTitle$typeUc",
  1311. $typeUc,
  1312. MSG_FindAttrVal(
  1313. $device, "msgTitle",
  1314. $typeUc, $defTitle
  1315. )
  1316. )
  1317. ) if ( $title eq "-" );
  1318. $loopTitle = ""
  1319. if ( $loopTitle eq "none"
  1320. || $loopTitle eq "-" );
  1321. my $loopMsg = $msg;
  1322. if ( $catchall == 1 && $type[$i] ne "queue" ) {
  1323. $loopTitle = "Fw: $loopTitle"
  1324. if ( $loopTitle ne ""
  1325. && $type[$i] !~ /^(audio|screen)$/ );
  1326. $loopMsg = "Forwarded Message: $loopMsg"
  1327. if ( $loopTitle eq "" );
  1328. if ( $type[$i] eq "mail" ) {
  1329. $loopMsg .=
  1330. "\n\n-- \nMail catched "
  1331. . "from device $device";
  1332. }
  1333. elsif ( $type[$i] !~ /^(audio|screen)$/ ) {
  1334. $loopMsg .=
  1335. " ### (Catched from device $device)";
  1336. }
  1337. }
  1338. my $loopMsgShrt =
  1339. defined( $params->{msgTextShrt} )
  1340. ? $params->{msgTextShrt}
  1341. : $msg;
  1342. # correct message format
  1343. #
  1344. # Remove Sonos Speak commands
  1345. $loopMsg =~ s/(\s*\|\w+\|\s*)/\\n\\n/gi
  1346. if ( $type[$i] ne "audio" );
  1347. $loopMsgShrt =~ s/(\s*\|\w+\|\s*)/\\n\\n/gi
  1348. if ( $type[$i] ne "audio" );
  1349. # Replace new line with HTML break
  1350. # for e-mails
  1351. $loopMsg =~ s/\n/<br \/>\n/gi
  1352. if ( $type[$i] eq "mail" );
  1353. $loopMsgShrt =~ s/\n/<br \/>\n/gi
  1354. if ( $type[$i] eq "mail" );
  1355. # use command from device, global or internal default
  1356. my $defCmd = "";
  1357. $defCmd =
  1358. $cmdSchema->{ $type[$i] }{$gatewayType}
  1359. {$priorityCat}
  1360. if (
  1361. defined(
  1362. $cmdSchema->{ $type[$i] }{$gatewayType}
  1363. {$priorityCat}
  1364. )
  1365. && $priorityCat ne ""
  1366. );
  1367. $defCmd =
  1368. $cmdSchema->{ $type[$i] }{$gatewayType}{Normal}
  1369. if (
  1370. defined(
  1371. $cmdSchema->{ $type[$i] }{$gatewayType}
  1372. {Normal}
  1373. )
  1374. && $priorityCat eq ""
  1375. );
  1376. my $cmd =
  1377. # gateway device
  1378. AttrVal(
  1379. $gatewayDev, "msgCmd$typeUc$priorityCat",
  1380. MSG_FindAttrVal(
  1381. $device, "msgCmd$typeUc$priorityCat",
  1382. $typeUc, $defCmd
  1383. )
  1384. );
  1385. if ( $cmd eq "" && $type[$i] ne "queue" ) {
  1386. Log3 $logDevice, 4,
  1387. "$gatewayDev: Unknown command schema "
  1388. . "for gateway device type $gatewayType. Use manual definition by userattr msgCmd*"
  1389. unless ( defined($testMode)
  1390. && $testMode eq "1" );
  1391. $loopReturn3 .=
  1392. "$gatewayDev: Unknown command schema "
  1393. . "for gateway device type $gatewayType. Use manual definition by userattr msgCmd*\n";
  1394. next;
  1395. }
  1396. # ReplaceSetMagic
  1397. #
  1398. my $replaceError;
  1399. if ( $featurelevel >= 5.7 ) {
  1400. my %dummy;
  1401. my ( $err, @a );
  1402. # TITLE
  1403. ( $err, @a ) =
  1404. ReplaceSetMagic( \%dummy, 0, ($loopTitle) );
  1405. $replaceError .=
  1406. "ReplaceSetMagic failed for TITLE: $err\n"
  1407. if ($err);
  1408. $loopTitle = join( " ", @a )
  1409. unless ($err);
  1410. # RECIPIENT
  1411. if ( $subRecipient ne "" ) {
  1412. ( $err, @a ) =
  1413. ReplaceSetMagic( \%dummy, 0,
  1414. ($subRecipient) );
  1415. $replaceError .=
  1416. "ReplaceSetMagic failed "
  1417. . "for RECIPIENT: $err\n"
  1418. if ($err);
  1419. $subRecipient = join( " ", @a )
  1420. unless ($err);
  1421. }
  1422. # TERMINAL
  1423. if ( $termRecipient ne "" ) {
  1424. ( $err, @a ) =
  1425. ReplaceSetMagic( \%dummy, 0,
  1426. ($termRecipient) );
  1427. $replaceError .=
  1428. "ReplaceSetMagic failed "
  1429. . "for TERMINAL: $err\n"
  1430. if ($err);
  1431. $termRecipient = join( " ", @a )
  1432. unless ($err);
  1433. }
  1434. }
  1435. $cmd =~ s/%PRIORITY%/$loopPriority/gi;
  1436. $cmd =~ s/%PRIOCAT%/$priorityCat/gi;
  1437. $cmd =~ s/%MSG%/$loopMsg/gi;
  1438. $cmd =~ s/%MSGSHRT%/$loopMsgShrt/gi;
  1439. $cmd =~ s/%MSGID%/$msgID.$sentCounter/gi;
  1440. $cmd =~ s/%TITLE%/$loopTitle/gi;
  1441. my $loopTitleShrt =
  1442. defined( $params->{msgTitleShrt} )
  1443. ? $params->{msgTitleShrt}
  1444. : MSG_FindAttrVal(
  1445. $device,
  1446. "msgTitleShrt$typeUc$priorityCat",
  1447. $typeUc,
  1448. MSG_FindAttrVal(
  1449. $device,
  1450. "msgTitleShrt$typeUc",
  1451. $typeUc,
  1452. MSG_FindAttrVal(
  1453. $device, "msgTitleShrt",
  1454. $typeUc, $loopTitle
  1455. )
  1456. )
  1457. );
  1458. $loopTitleShrt =
  1459. substr( $loopTitleShrt, 0, 37 ) . "..."
  1460. if ( length($loopTitleShrt) > 40 );
  1461. $cmd =~ s/%TITLESHRT%/$loopTitleShrt/gi;
  1462. $loopTitleShrt =~ s/ /_/;
  1463. $cmd =~ s/%TITLESHRT2%/$loopTitleShrt/gi;
  1464. $loopTitleShrt =~ s/^([\s\t ]*\w+).*/$1/g;
  1465. $loopTitleShrt =
  1466. substr( $loopTitleShrt, 0, 17 ) . "..."
  1467. if ( length($loopTitleShrt) > 20 );
  1468. $cmd =~ s/%TITLESHRT3%/$loopTitleShrt/gi;
  1469. my $deviceName = AttrVal(
  1470. $device,
  1471. AttrVal(
  1472. $device,
  1473. "rg_realname",
  1474. AttrVal( $device, "rr_realname", "group" )
  1475. ),
  1476. AttrVal( $device, "alias", $device )
  1477. );
  1478. my $deviceName2 = $deviceName;
  1479. $deviceName2 =~ s/ /_/;
  1480. $cmd =~ s/%SOURCE%/$device/gi;
  1481. $cmd =~ s/%SRCALIAS%/$deviceName/gi;
  1482. $cmd =~ s/%SRCALIAS2%/$deviceName2/gi;
  1483. my $gatewayDevName = AttrVal(
  1484. $gatewayDev,
  1485. AttrVal(
  1486. $gatewayDev,
  1487. "rg_realname",
  1488. AttrVal(
  1489. $gatewayDev, "rr_realname", "group"
  1490. )
  1491. ),
  1492. AttrVal( $gatewayDev, "alias", $gatewayDev )
  1493. );
  1494. my $gatewayDevName2 = $gatewayDevName;
  1495. $gatewayDevName2 =~ s/ /_/;
  1496. $cmd =~ s/%DEVICE%/$gatewayDev/gi;
  1497. $cmd =~ s/%DEVALIAS%/$gatewayDevName/gi;
  1498. $cmd =~ s/%DEVALIAS2%/$gatewayDevName2/gi;
  1499. my $loopMsgDateTime = $msgDateTime;
  1500. $loopMsgDateTime .= ".$sentCounter"
  1501. if ($sentCounter);
  1502. my $loopMsgDateTime2 = $loopMsgDateTime;
  1503. $loopMsgDateTime2 =~ s/ /_/;
  1504. $cmd =~ s/%MSGDATETIME%/$loopMsgDateTime/gi;
  1505. $cmd =~ s/%MSGDATETIME2%/$loopMsgDateTime2/gi;
  1506. my $subRecipientName =
  1507. $subRecipient eq ""
  1508. ? ""
  1509. : AttrVal(
  1510. $subRecipient,
  1511. AttrVal(
  1512. $subRecipient,
  1513. "rg_realname",
  1514. AttrVal(
  1515. $subRecipient, "rr_realname",
  1516. "group"
  1517. )
  1518. ),
  1519. AttrVal(
  1520. $subRecipient, "alias", $subRecipient
  1521. )
  1522. );
  1523. my $subRecipientName2 = $subRecipientName;
  1524. $subRecipientName2 =~ s/ /_/;
  1525. $cmd =~ s/%RECIPIENT%/$subRecipient/gi
  1526. if ( $subRecipient ne "" );
  1527. $cmd =~ s/%RCPTNAME%/$subRecipientName/gi
  1528. if ( $subRecipientName ne "" );
  1529. $cmd =~ s/%RCPTNAME2%/$subRecipientName2/gi
  1530. if ( $subRecipientName2 ne "" );
  1531. $cmd =~ s/%TERMINAL%/$termRecipient/gi
  1532. if ( $termRecipient ne "" );
  1533. my $paramsA;
  1534. unless ( defined($testMode) && $testMode eq "1" ) {
  1535. # user parameters from message
  1536. if ( ref($params) eq "HASH" ) {
  1537. for my $key ( keys %$params ) {
  1538. next if ( ref( $params->{$key} ) );
  1539. my $val = $params->{$key};
  1540. $cmd =~ s/%$key%/$val/gi;
  1541. $cmd =~ s/\$$key/$val/g;
  1542. Log3 $logDevice, 5,
  1543. "msg $device: User parameters: "
  1544. . "replacing %$key% and \$$key by '$val'";
  1545. }
  1546. }
  1547. # user parameters from attributes
  1548. my $paramsAttr1 =
  1549. AttrVal( $gatewayDev,
  1550. "msgParams$typeUc$priorityCat", undef );
  1551. my $paramsAttr2 =
  1552. AttrVal( $gatewayDev, "msgParams$typeUc",
  1553. undef );
  1554. my $paramsAttr3 =
  1555. AttrVal( $gatewayDev, "msgParams", undef );
  1556. my $paramsAttr4 =
  1557. MSG_FindAttrVal( $device,
  1558. "msgParams$typeUc$priorityCat",
  1559. $typeUc, undef );
  1560. my $paramsAttr5 =
  1561. MSG_FindAttrVal( $device, "msgParams$typeUc",
  1562. $typeUc, undef );
  1563. my $paramsAttr6 =
  1564. MSG_FindAttrVal( $device, "msgParams",
  1565. $typeUc, undef );
  1566. foreach (
  1567. $paramsAttr1, $paramsAttr2, $paramsAttr3,
  1568. $paramsAttr4, $paramsAttr5, $paramsAttr6
  1569. )
  1570. {
  1571. next unless ($_);
  1572. if ( $_ =~ m/^{.*}$/s
  1573. && $_ =~ m/=>/
  1574. && $_ !~ m/\$/ )
  1575. {
  1576. my $av = eval $_;
  1577. if ($@) {
  1578. Log3 $logDevice, 3,
  1579. "msg $device: "
  1580. . "ERROR while reading attribute msgParams";
  1581. }
  1582. else {
  1583. $paramsA = $av;
  1584. }
  1585. }
  1586. else {
  1587. my ( $a, $h ) = parseParams($_);
  1588. $paramsA = $h;
  1589. }
  1590. next unless ref($paramsA) eq "HASH";
  1591. if ( ref($paramsA) eq "HASH" ) {
  1592. for my $key ( keys %$paramsA ) {
  1593. next if ( ref( $params->{$key} ) );
  1594. my $val = $paramsA->{$key};
  1595. $cmd =~ s/%$key%/$val/gi;
  1596. $cmd =~ s/\$$key/$val/g;
  1597. Log3 $logDevice, 5,
  1598. "msg $device: "
  1599. . "msgParams: replacing %$key% and \$$key by '$val'";
  1600. }
  1601. }
  1602. }
  1603. # user parameters from command schema hash
  1604. if (
  1605. $priorityCat ne ""
  1606. && defined(
  1607. $cmdSchema->{ $type[$i] }{$gatewayType}
  1608. {defaultValues}{$priorityCat}
  1609. )
  1610. )
  1611. {
  1612. for my $item (
  1613. $cmdSchema->{ $type[$i] }{$gatewayType}
  1614. {defaultValues}{$priorityCat} )
  1615. {
  1616. for my $key ( keys(%$item) ) {
  1617. my $val = $item->{$key};
  1618. $cmd =~ s/%$key%/$val/gi;
  1619. $cmd =~ s/\$$key/$val/g;
  1620. Log3 $logDevice, 5,
  1621. "msg $device: "
  1622. . "msgSchema: replacing %$key% and \$$key by '$val'";
  1623. }
  1624. }
  1625. }
  1626. elsif (
  1627. $priorityCat eq ""
  1628. && defined(
  1629. $cmdSchema->{ $type[$i] }{$gatewayType}
  1630. {defaultValues}{Normal}
  1631. )
  1632. )
  1633. {
  1634. for my $item (
  1635. $cmdSchema->{ $type[$i] }{$gatewayType}
  1636. {defaultValues}{Normal} )
  1637. {
  1638. for my $key ( keys(%$item) ) {
  1639. my $val = $item->{$key};
  1640. $cmd =~ s/%$key%/$val/gi;
  1641. $cmd =~ s/\$$key/$val/g;
  1642. Log3 $logDevice, 5,
  1643. "msg $device: "
  1644. . "msgSchema: replacing %$key% and \$$key by '$val'";
  1645. }
  1646. }
  1647. }
  1648. }
  1649. $sentCounter++;
  1650. if ( $routeStatus =~ /^OK\w*/ ) {
  1651. my $error = 0;
  1652. unless ( defined($testMode)
  1653. && $testMode eq "1" )
  1654. {
  1655. # ReplaceSetMagic
  1656. #
  1657. if ( $featurelevel >= 5.7
  1658. && !$replaceError )
  1659. {
  1660. my %dummy;
  1661. my ( $err, @a ) =
  1662. ReplaceSetMagic( \%dummy, 0, ($cmd) );
  1663. $replaceError .=
  1664. "ReplaceSetMagic failed for CMD: "
  1665. . "$err\n"
  1666. if ($err);
  1667. $cmd = join( " ", @a )
  1668. unless ($err);
  1669. }
  1670. # add user parameters
  1671. # if gateway supports parseParams
  1672. my $gatewayDevType = GetType($gatewayDev);
  1673. if (
  1674. $gatewayDevType
  1675. && ref($params) eq "HASH"
  1676. && ( $modules{$gatewayDevType}
  1677. ->{parseParams}
  1678. || $modules{$gatewayDevType}
  1679. ->{msgParams}{parseParams}
  1680. || $modules{$gatewayDevType}
  1681. ->{'.msgParams'}{parseParams} )
  1682. )
  1683. {
  1684. Log3 $logDevice, 5,
  1685. "msg $device: "
  1686. . "parseParams support: Handing over user parameters to other device";
  1687. my ( $a, $h ) = parseParams($cmd);
  1688. keys %$params;
  1689. while ( ( my $key, my $value ) =
  1690. each %$params )
  1691. {
  1692. # Compatibility to legacy schema:
  1693. # lowercase after _
  1694. my $s = $gatewayDevType
  1695. . "[\_\/-]([A-Z0-9_-]+)";
  1696. $key =~ s/^$s$/\L$1/;
  1697. # remove gateway TYPE when
  1698. # used as prefix
  1699. $s = $gatewayDevType . "[_\/-]";
  1700. $key =~ s/^$s//;
  1701. $cmd .= " $key='$value'"
  1702. if ( !defined( $h->{$key} )
  1703. || $h->{$key} =~
  1704. m/^[\s\t\n ]*$/ );
  1705. }
  1706. keys %$paramsA;
  1707. while ( ( my $key, my $value ) =
  1708. each %$paramsA )
  1709. {
  1710. # Compatibility to legacy schema:
  1711. # lowercase after _
  1712. my $s = $gatewayDevType
  1713. . "[\_\/-]([A-Z0-9_-]+)";
  1714. $key =~ s/^$s$/\L$1/;
  1715. # remove gateway TYPE when
  1716. # used as prefix
  1717. $s = $gatewayDevType . "[_\/-]";
  1718. $key =~ s/^$s//;
  1719. $cmd .= " $key='$value'"
  1720. if ( !defined( $h->{$key} )
  1721. || $h->{$key} =~
  1722. m/^[\s\t\n ]*$/ );
  1723. }
  1724. }
  1725. # excplicitly queue message
  1726. if ( $routeStatus eq "OK_QUEUE" ) {
  1727. $queued = msgConfig_QueueAdd(
  1728. $msgA, $params,
  1729. $msgDateTime, $msgID,
  1730. $sentCounter, $type[$i],
  1731. $device, $subRecipient,
  1732. $termRecipient, $priority,
  1733. $loopTitle, $loopMsg
  1734. ) ? 1 : 0;
  1735. }
  1736. # run command
  1737. elsif ($replaceError) {
  1738. $error = 2;
  1739. $loopReturn3 .= $replaceError;
  1740. }
  1741. elsif ( $cmd =~ /^\s*\{.*\}\s*$/ ) {
  1742. Log3 $logDevice, 5,
  1743. "msg $device: "
  1744. . "$type[$i] route command (Perl): $cmd";
  1745. eval $cmd;
  1746. unless ( !$@ || $@ =~ m/^[\s\t\n ]*$/ )
  1747. {
  1748. $error = 1;
  1749. $loopReturn3 .= "$gatewayDev: $@\n";
  1750. }
  1751. }
  1752. else {
  1753. Log3 $logDevice, 5,
  1754. "msg $device: "
  1755. . "$type[$i] route command (fhem): $cmd";
  1756. my $ret = fhem $cmd, 1;
  1757. unless ( !$ret
  1758. || $ret =~ m/^[\s\t\n ]*$/ )
  1759. {
  1760. $error = 1;
  1761. $loopReturn3 .=
  1762. "$gatewayDev: $ret\n";
  1763. }
  1764. }
  1765. $routeStatus = "ERROR"
  1766. if ( $error == 1 );
  1767. $routeStatus = "ERROR_EVAL"
  1768. if ( $error == 2 );
  1769. Log3 $logDevice, 3,
  1770. "msg $device: "
  1771. . "ID=$msgID.$sentCounter "
  1772. . "TYPE=$type[$i] "
  1773. . "ROUTE=$gatewayDev "
  1774. . "RECIPIENT=$subRecipient "
  1775. . "STATUS=$routeStatus "
  1776. . "PRIORITY=$loopPriority($priorityCat) "
  1777. . "TITLE='$loopTitle' "
  1778. . "MSG='$loopMsg'"
  1779. if ( $priorityCat ne ""
  1780. && $subRecipient ne "" );
  1781. Log3 $logDevice, 3,
  1782. "msg $device: "
  1783. . "ID=$msgID.$sentCounter "
  1784. . "TYPE=$type[$i] "
  1785. . "ROUTE=$gatewayDev "
  1786. . "RECIPIENT=$subRecipient "
  1787. . "STATUS=$routeStatus "
  1788. . "PRIORITY=$loopPriority "
  1789. . "TITLE='$loopTitle' "
  1790. . "MSG='$loopMsg'"
  1791. if ( $priorityCat eq ""
  1792. && $subRecipient ne "" );
  1793. Log3 $logDevice, 3,
  1794. "msg $device: "
  1795. . "ID=$msgID.$sentCounter "
  1796. . "TYPE=$type[$i] "
  1797. . "ROUTE=$gatewayDev "
  1798. . "STATUS=$routeStatus "
  1799. . "PRIORITY=$loopPriority($priorityCat) "
  1800. . "TITLE='$loopTitle' "
  1801. . "MSG='$loopMsg'"
  1802. if ( $priorityCat ne ""
  1803. && $subRecipient eq "" );
  1804. Log3 $logDevice, 3,
  1805. "msg $device: "
  1806. . "ID=$msgID.$sentCounter "
  1807. . "TYPE=$type[$i] "
  1808. . "ROUTE=$gatewayDev "
  1809. . "STATUS=$routeStatus "
  1810. . "PRIORITY=$loopPriority "
  1811. . "TITLE='$loopTitle' "
  1812. . "MSG='$loopMsg'"
  1813. if ( $priorityCat eq ""
  1814. && $subRecipient eq "" );
  1815. }
  1816. $msgSent = 1 if ( $error == 0 );
  1817. $msgSentDev = 1 if ( $error == 0 );
  1818. if ( $subRecipient ne "" ) {
  1819. $gatewaysStatus{"$gatewayDev:$subRecipient"}
  1820. = $routeStatus
  1821. if ( $globalDevName ne $gatewayDev );
  1822. $gatewaysStatus{"$device:$subRecipient"} =
  1823. $routeStatus
  1824. if ( $globalDevName eq $gatewayDev );
  1825. }
  1826. else {
  1827. $gatewaysStatus{$gatewayDev} = $routeStatus
  1828. if ( $globalDevName ne $gatewayDev );
  1829. $gatewaysStatus{$device} = $routeStatus
  1830. if ( $globalDevName eq $gatewayDev );
  1831. }
  1832. }
  1833. elsif ( $routeStatus =~ /\+QUEUE/ || $forceQueue ) {
  1834. unless ( defined($testMode)
  1835. && $testMode eq "1" )
  1836. {
  1837. if ( !( grep { "queue" eq $_ } @type ) ) {
  1838. $queued = msgConfig_QueueAdd(
  1839. $msgA, $params,
  1840. $msgDateTime, $msgID,
  1841. $sentCounter, $type[$i],
  1842. $device, $subRecipient,
  1843. $termRecipient, $priority,
  1844. $loopTitle, $loopMsg
  1845. ) ? 1 : 0;
  1846. }
  1847. Log3 $logDevice, 3,
  1848. "msg $device: "
  1849. . "ID=$msgID.$sentCounter "
  1850. . "TYPE=$type[$i] "
  1851. . "ROUTE=$gatewayDev "
  1852. . "RECIPIENT=$subRecipient "
  1853. . "STATUS=$routeStatus "
  1854. . "PRIORITY=$loopPriority "
  1855. . "TITLE='$loopTitle' '$loopMsg'"
  1856. if ( $subRecipient ne "" );
  1857. Log3 $logDevice, 3,
  1858. "msg $device: "
  1859. . "ID=$msgID.$sentCounter "
  1860. . "TYPE=$type[$i] "
  1861. . "ROUTE=$gatewayDev "
  1862. . "STATUS=$routeStatus "
  1863. . "PRIORITY=$loopPriority "
  1864. . "TITLE='$loopTitle' '$loopMsg'"
  1865. if ( $subRecipient eq "" );
  1866. }
  1867. $msgSent = 3 if ( $msgSent != 1 );
  1868. $msgSentDev = 3 if ( $msgSentDev != 1 );
  1869. $gatewaysStatus{$gatewayDev} = $routeStatus
  1870. if ( $globalDevName ne $gatewayDev );
  1871. $gatewaysStatus{$device} = $routeStatus
  1872. if ( $globalDevName eq $gatewayDev );
  1873. }
  1874. elsif ($routeStatus eq "UNAVAILABLE"
  1875. || $routeStatus eq "UNDEFINED" )
  1876. {
  1877. unless ( defined($testMode)
  1878. && $testMode eq "1" )
  1879. {
  1880. Log3 $logDevice, 3,
  1881. "msg $device: "
  1882. . "ID=$msgID.$sentCounter "
  1883. . "TYPE=$type[$i] "
  1884. . "ROUTE=$gatewayDev "
  1885. . "RECIPIENT=$subRecipient "
  1886. . "STATUS=$routeStatus "
  1887. . "PRIORITY=$loopPriority "
  1888. . "TITLE='$loopTitle' '$loopMsg'"
  1889. if ( $subRecipient ne "" );
  1890. Log3 $logDevice, 3,
  1891. "msg $device: "
  1892. . "ID=$msgID.$sentCounter "
  1893. . "TYPE=$type[$i] "
  1894. . "ROUTE=$gatewayDev "
  1895. . "STATUS=$routeStatus "
  1896. . "PRIORITY=$loopPriority "
  1897. . "TITLE='$loopTitle' '$loopMsg'"
  1898. if ( $subRecipient eq "" );
  1899. }
  1900. $gatewaysStatus{$gatewayDev} = $routeStatus
  1901. if ( $globalDevName ne $gatewayDev );
  1902. $gatewaysStatus{$device} = $routeStatus
  1903. if ( $globalDevName eq $gatewayDev );
  1904. }
  1905. else {
  1906. unless ( defined($testMode)
  1907. && $testMode eq "1" )
  1908. {
  1909. Log3 $logDevice, 3,
  1910. "msg $device: "
  1911. . "ID=$msgID.$sentCounter "
  1912. . "TYPE=$type[$i] "
  1913. . "ROUTE=$gatewayDev "
  1914. . "RECIPIENT=$subRecipient "
  1915. . "STATUS=$routeStatus "
  1916. . "PRIORITY=$loopPriority "
  1917. . "TITLE='$loopTitle' '$loopMsg'"
  1918. if ( $subRecipient ne "" );
  1919. Log3 $logDevice, 3,
  1920. "msg $device: "
  1921. . "ID=$msgID.$sentCounter "
  1922. . "TYPE=$type[$i] "
  1923. . "ROUTE=$gatewayDev "
  1924. . "STATUS=$routeStatus "
  1925. . "PRIORITY=$loopPriority "
  1926. . "TITLE='$loopTitle' '$loopMsg'"
  1927. if ( $subRecipient eq "" );
  1928. }
  1929. $msgSent = 2 if ( $msgSent != 1 );
  1930. $msgSentDev = 2 if ( $msgSentDev != 1 );
  1931. $gatewaysStatus{$gatewayDev} = $routeStatus
  1932. if ( $globalDevName ne $gatewayDev );
  1933. $gatewaysStatus{$device} = $routeStatus
  1934. if ( $globalDevName eq $gatewayDev );
  1935. }
  1936. }
  1937. last if ( $msgSentDev == 1 || $msgSentDev == 3 );
  1938. }
  1939. #####################
  1940. # return if we are in routing target test mode
  1941. #
  1942. if ( defined($testMode) && $testMode eq "1" ) {
  1943. Log3 $logDevice, 5,
  1944. "msg $device: "
  1945. . "$type[$i] route check result: ROUTE_AVAILABLE"
  1946. if ( $loopReturn3 eq "" );
  1947. Log3 $logDevice, 5,
  1948. "msg $device: "
  1949. . "$type[$i] route check result: ROUTE_UNAVAILABLE"
  1950. if ( $loopReturn3 ne "" );
  1951. return "ROUTE_AVAILABLE" if ( $loopReturn3 eq "" );
  1952. return "ROUTE_UNAVAILABLE" if ( $loopReturn3 ne "" );
  1953. }
  1954. if ( $catchall == 0 ) {
  1955. if ( !defined( $sentTypesPerDevice{$device} ) ) {
  1956. $sentTypesPerDevice{$device} = "";
  1957. }
  1958. else {
  1959. $sentTypesPerDevice{$device} .= " ";
  1960. }
  1961. $sentTypesPerDevice{$device} .=
  1962. $type[$i] . ":" . $msgSentDev;
  1963. }
  1964. else {
  1965. if ( !defined( $sentTypesPerDevice{$device} ) ) {
  1966. $sentTypesPerDevice{$globalDevName} = "";
  1967. }
  1968. else {
  1969. $sentTypesPerDevice{$globalDevName} .= " ";
  1970. }
  1971. $sentTypesPerDevice{$globalDevName} .=
  1972. $type[$i] . ":" . $msgSentDev;
  1973. }
  1974. # update device readings
  1975. my $readingsDev = $defs{$device};
  1976. $readingsDev = $defs{$globalDevName}
  1977. if ( $catchall == 1 || $deviceType eq "email" );
  1978. readingsBeginUpdate($readingsDev);
  1979. readingsBulkUpdate( $readingsDev, "fhemMsg" . $typeUc,
  1980. $msg );
  1981. readingsBulkUpdate( $readingsDev,
  1982. "fhemMsg" . $typeUc . "Title", $title );
  1983. readingsBulkUpdate( $readingsDev,
  1984. "fhemMsg" . $typeUc . "Prio",
  1985. $loopPriority );
  1986. my $gwStates = "-";
  1987. keys %gatewaysStatus;
  1988. while ( ( my $gwName, my $gwState ) = each %gatewaysStatus )
  1989. {
  1990. $gwStates = "" if $gwStates eq "-";
  1991. $gwStates .= " " if $gwStates ne "-";
  1992. $gwStates .= "$gwName:$gwState";
  1993. }
  1994. readingsBulkUpdate( $readingsDev,
  1995. "fhemMsg" . $typeUc . "Gw", $gwStates );
  1996. readingsBulkUpdate( $readingsDev,
  1997. "fhemMsg" . $typeUc . "State", $msgSentDev );
  1998. # suppress errors when there are still alternatives
  1999. if ( $hasTypeOr == 1
  2000. && $isTypeOr < scalar( grep { defined $_ } @typesOr ) )
  2001. {
  2002. $loopReturn3 = "";
  2003. }
  2004. ################################################################
  2005. ### Implicit forwards based on priority or presence
  2006. ###
  2007. # Skip if typeOr is defined
  2008. # and this is not the last type entry
  2009. if ( $msgSentDev != 3
  2010. && $msgSentDev != 1
  2011. && $hasTypeOr == 1
  2012. && $isTypeOr < scalar( grep { defined $_ } @typesOr ) )
  2013. {
  2014. Log3 $logDevice, 4,
  2015. "msg $device: "
  2016. . "Skipping implicit forward due to typesOr definition";
  2017. # remove recipient from list to avoid
  2018. # other interaction when using recipientOr in parallel
  2019. if ( $hasRecipientOr == 1
  2020. && $isRecipientOr <
  2021. scalar( grep { defined $_ } @recipientsOr ) )
  2022. {
  2023. my $regex1 =
  2024. "\\s*!?@?" . $device . "[,|]"; # at the beginning
  2025. my $regex2 =
  2026. "[,|]!?@?" . $device . "\\s*"; # at the end
  2027. my $regex3 =
  2028. ",!?@?"
  2029. . $device
  2030. . ","; # in the middle with comma
  2031. my $regex4 =
  2032. "[\|,]!?@?"
  2033. . $device
  2034. . "[\|,]"; # in the middle with pipe and/or comma
  2035. $recipients =~ s/^$regex1//;
  2036. $recipients =~ s/$regex2$/|/gi;
  2037. $recipients =~ s/$regex3/,/gi;
  2038. $recipients =~ s/$regex4/|/gi;
  2039. }
  2040. }
  2041. # Skip if recipientOr is defined
  2042. # and this is not the last device entry
  2043. if ( $msgSentDev != 3
  2044. && $msgSentDev != 1
  2045. && $hasRecipientOr == 1
  2046. && $isRecipientOr <
  2047. scalar( grep { defined $_ } @recipientsOr ) )
  2048. {
  2049. Log3 $logDevice, 4,
  2050. "msg $device: "
  2051. . "Skipping implicit forward due to recipientOr definition";
  2052. }
  2053. # Skip if softFail
  2054. elsif ( $msgSentDev != 3 && $msgSentDev != 1 && $softFail )
  2055. {
  2056. Log3 $logDevice, 4,
  2057. "msg $device: Skipping implicit forward";
  2058. $loopReturn3 = "";
  2059. }
  2060. # priority forward thresholds
  2061. #
  2062. ### emergency
  2063. my $msgFwPrioEmergency =
  2064. MSG_FindAttrVal( $device, "msgFwPrioEmergency$typeUc",
  2065. $typeUc, 2 );
  2066. ### absent
  2067. my $msgFwPrioAbsent =
  2068. MSG_FindAttrVal( $device, "msgFwPrioAbsent$typeUc",
  2069. $typeUc, 0 );
  2070. ### gone
  2071. my $msgFwPrioGone =
  2072. MSG_FindAttrVal( $device, "msgFwPrioGone$typeUc",
  2073. $typeUc, 1 );
  2074. my $fw_gwUnavailable =
  2075. defined(
  2076. $settings->{ $type[$i] }{typeEscalation}{gwUnavailable}
  2077. )
  2078. ? $settings->{ $type[$i] }{typeEscalation}{gwUnavailable}
  2079. : "";
  2080. my $fw_emergency =
  2081. defined(
  2082. $settings->{ $type[$i] }{typeEscalation}{emergency} )
  2083. ? $settings->{ $type[$i] }{typeEscalation}{emergency}
  2084. : "";
  2085. my $fw_residentAbsent =
  2086. defined(
  2087. $settings->{ $type[$i] }{typeEscalation}{residentAbsent}
  2088. )
  2089. ? $settings->{ $type[$i] }{typeEscalation}{residentAbsent}
  2090. : "";
  2091. my $fw_residentGone =
  2092. defined(
  2093. $settings->{ $type[$i] }{typeEscalation}{residentGone} )
  2094. ? $settings->{ $type[$i] }{typeEscalation}{residentGone}
  2095. : "";
  2096. # Forward message
  2097. # if no gateway device for this type was available
  2098. if ( $msgSentDev == 0
  2099. && $fw_gwUnavailable ne ""
  2100. && !( grep { $fw_gwUnavailable eq $_ } @type )
  2101. && $routes{$fw_gwUnavailable} == 1 )
  2102. {
  2103. Log3 $logDevice, 4,
  2104. "msg $device: "
  2105. . "Implicit forwards: No $type[$i] gateway device available for recipient $device ($gatewayDevs). Trying alternative message type "
  2106. . $fw_gwUnavailable;
  2107. push @type, $fw_gwUnavailable;
  2108. $forwarded .= "," if ( $forwarded ne "" );
  2109. $forwarded .= $type[$i] . ">" . $fw_gwUnavailable;
  2110. }
  2111. # Forward message
  2112. # if emergency priority
  2113. if ( $loopPriority >= $msgFwPrioEmergency
  2114. && $fw_emergency ne ""
  2115. && !( grep { $fw_emergency eq $_ } @type )
  2116. && $routes{$fw_emergency} == 1 )
  2117. {
  2118. Log3 $logDevice, 4,
  2119. "msg $device: "
  2120. . "Implicit forwards: Escalating high priority $type[$i] message via "
  2121. . $fw_emergency;
  2122. push @type, $fw_emergency;
  2123. $forwarded .= "," if ( $forwarded ne "" );
  2124. $forwarded .= $type[$i] . ">" . $fw_emergency;
  2125. }
  2126. # Forward message
  2127. # if high priority and residents are
  2128. # constantly not at home
  2129. if ( $residentDevPresence eq "absent"
  2130. && $loopPriority >= $msgFwPrioGone
  2131. && $fw_residentGone ne ""
  2132. && !( grep { $fw_residentGone eq $_ } @type )
  2133. && $routes{$fw_residentGone} == 1 )
  2134. {
  2135. Log3 $logDevice, 4,
  2136. "msg $device: "
  2137. . "Implicit forwards: Escalating high priority $type[$i] message via "
  2138. . $fw_residentGone;
  2139. push @type, $fw_residentGone;
  2140. $forwarded .= "," if ( $forwarded ne "" );
  2141. $forwarded .= $type[$i] . ">" . $fw_residentGone;
  2142. }
  2143. # Forward message
  2144. # if priority is normal or higher and residents
  2145. # are not at home but nearby
  2146. if ( !$forceQueue
  2147. && $residentDevState eq "absent"
  2148. && $loopPriority >= $msgFwPrioAbsent
  2149. && $fw_residentAbsent ne ""
  2150. && !( grep { $fw_residentAbsent eq $_ } @type )
  2151. && $routes{$fw_residentAbsent} == 1 )
  2152. {
  2153. Log3 $logDevice, 4,
  2154. "msg $device: "
  2155. . "Implicit forwards: Escalating $type[$i] message via "
  2156. . $fw_residentAbsent
  2157. . " due to absence";
  2158. push @type, $fw_residentAbsent;
  2159. $forwarded .= "," if ( $forwarded ne "" );
  2160. $forwarded .= $type[$i] . ">" . $fw_residentAbsent;
  2161. }
  2162. }
  2163. $loopReturn2 .= $loopReturn3 unless ($softFail);
  2164. last if ( $msgSent == 1 );
  2165. $isTypeOr++;
  2166. }
  2167. $loopReturn1 .= $loopReturn2;
  2168. }
  2169. $return .= $loopReturn1;
  2170. last if ( $msgSent == 1 );
  2171. $isRecipientOr++;
  2172. }
  2173. # finalize device readings
  2174. keys %sentTypesPerDevice;
  2175. while ( ( my $device, my $types ) = each %sentTypesPerDevice ) {
  2176. $device = $globalDevName
  2177. if ( $device =~ /^(([A-Za-z0-9%+._-])+@+([%+a-z0-9A-Z.-]*))$/ );
  2178. readingsBulkUpdate( $defs{$device}, "fhemMsgStateTypes", $types )
  2179. if ( $forwarded eq "" );
  2180. readingsBulkUpdate( $defs{$device}, "fhemMsgStateTypes",
  2181. $types . " forwards:" . $forwarded )
  2182. if ( $forwarded ne "" );
  2183. readingsBulkUpdate( $defs{$device}, "fhemMsgState", $msgSent );
  2184. readingsEndUpdate( $defs{$device}, 1 );
  2185. }
  2186. $return .= "However, message was still sent to some recipients!"
  2187. if ( $msgSent == 1 && $return ne "" );
  2188. $return .=
  2189. "FATAL ERROR: Message NOT sent. No gateway device was available."
  2190. if ( !$softFail && $msgSent == 2 );
  2191. return $return;
  2192. }
  2193. 1;
  2194. =pod
  2195. =item command
  2196. =item summary dynamic routing of messages to FHEM devices and modules
  2197. =item summary_DE dynamisches Routing f&uuml;r Nachrichten an FHEM Ger&auml;te und Module
  2198. =begin html
  2199. <a name="MSG"></a>
  2200. <h3>msg</h3>
  2201. <ul>
  2202. <code>msg [&lt;type&gt;] [&lt;@device&gt;|&lt;e-mail address&gt;] [&lt;priority&gt;] [|&lt;title&gt;|] &lt;message&gt;</code>
  2203. <br>
  2204. <br>
  2205. No documentation here yet, sorry.<br>
  2206. <a href="http://forum.fhem.de/index.php/topic,39983.0.html">FHEM Forum</a>
  2207. </ul>
  2208. =end html
  2209. =begin html_DE
  2210. <a name="MSG"></a>
  2211. <h3>msg</h3>
  2212. <ul>
  2213. <code>msg [&lt;type&gt;] [&lt;@device&gt;|&lt;e-mail address&gt;] [&lt;priority&gt;] [|&lt;title&gt;|] &lt;message&gt;</code>
  2214. <br>
  2215. <br>
  2216. Bisher keine Dokumentation hier, sorry.<br>
  2217. <a href="http://forum.fhem.de/index.php/topic,39983.0.html">FHEM Forum</a>
  2218. </ul>
  2219. =end html_DE
  2220. =cut