RESIDENTStk.pm 135 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725
  1. ###############################################################################
  2. # $Id: RESIDENTStk.pm 14160 2017-05-01 19:43:40Z loredo $
  3. package main;
  4. use strict;
  5. use warnings;
  6. use Data::Dumper;
  7. use Time::Local;
  8. use Unit;
  9. our (@RESIDENTStk_attr);
  10. # module variables ############################################################
  11. @RESIDENTStk_attr = (
  12. "autoGoneAfter:0,12,16,24,26,28,30,36,48,60",
  13. "geofenceUUIDs",
  14. "lang:EN,DE",
  15. "locationHome",
  16. "locations",
  17. "locationUnderway",
  18. "locationWayhome",
  19. "moodDefault",
  20. "moods",
  21. "moodSleepy",
  22. "noDuration:0,1",
  23. "passPresenceTo",
  24. "presenceDevices",
  25. "realname:group,alias",
  26. "showAllStates:0,1",
  27. "wakeupDevice",
  28. );
  29. ## initialize #################################################################
  30. sub RESIDENTStk_Initialize() { }
  31. # regular Fn ##################################################################
  32. sub RESIDENTStk_InitializeDev($) {
  33. my ($hash) = @_;
  34. $hash = $defs{$hash} unless ( ref($hash) );
  35. my $NOTIFYDEV = "global";
  36. if ( $hash->{TYPE} eq "RESIDENTS" ) {
  37. $NOTIFYDEV .= "," . RESIDENTStk_findResidentSlaves($hash);
  38. }
  39. else {
  40. $NOTIFYDEV .= "," . RESIDENTStk_findDummySlaves($hash);
  41. }
  42. return 0
  43. if ( defined( $hash->{NOTIFYDEV} ) && $hash->{NOTIFYDEV} eq $NOTIFYDEV );
  44. $hash->{NOTIFYDEV} = $NOTIFYDEV;
  45. return undef;
  46. }
  47. sub RESIDENTStk_Define($$) {
  48. my ( $hash, $def ) = @_;
  49. my @a = split( "[ \t][ \t]*", $def );
  50. my $name = $hash->{NAME};
  51. my $name_attr;
  52. my $TYPE = GetType($name);
  53. my $prefix = RESIDENTStk_GetPrefixFromType($name);
  54. if ( $TYPE ne "RESIDENTS" && int(@a) < 2 ) {
  55. my $msg = "Wrong syntax: define <name> $TYPE [RESIDENTS-DEVICE-NAMES]";
  56. Log3 $name, 4, $msg;
  57. return $msg;
  58. }
  59. return "Invalid parameter for RESIDENTS-DEVICE-NAMES"
  60. unless ( $TYPE ne "RESIDENTS"
  61. || !defined( $a[2] )
  62. || $a[2] =~ /^[A-Za-z\d._]+(?:,[A-Za-z\d._]*)*$/ );
  63. $hash->{MOD_INIT} = 1;
  64. $hash->{NOTIFYDEV} = "global";
  65. delete $hash->{RESIDENTGROUPS} if ( $hash->{RESIDENTGROUPS} );
  66. $hash->{RESIDENTGROUPS} = $a[2] if ( defined( $a[2] ) );
  67. # set default settings on first define
  68. if ( $init_done && !defined( $hash->{OLDDEF} ) ) {
  69. RESIDENTStk_Attr( "init", $name, $prefix . "lang" );
  70. $attr{$name}{webCmd} = "state";
  71. if ( $TYPE eq "RESIDENTS" ) {
  72. $attr{$name}{icon} = "control_building_filled";
  73. $attr{$name}{room} = "Residents";
  74. }
  75. elsif ( $TYPE ne "RESIDENTS" ) {
  76. my $fname = $name;
  77. $fname =~ s/^$prefix//;
  78. $attr{$name}{group} = $fname if ( $prefix eq "rr_" );
  79. $attr{$name}{group} = "Guests" if ( $prefix eq "rg_" );
  80. $attr{$name}{alias} = "Status" if ( $prefix eq "rr_" );
  81. $attr{$name}{alias} = $fname if ( $prefix eq "rg_" );
  82. $attr{$name}{icon} = "people_sensor" if ( $prefix eq "rr_" );
  83. $attr{$name}{icon} = "scene_visit_guests" if ( $prefix eq "rg_" );
  84. $attr{$name}{rr_realname} = "group" if ( $prefix eq "rr_" );
  85. $attr{$name}{rg_realname} = "alias" if ( $prefix eq "rg_" );
  86. $attr{$name}{sortby} = "1";
  87. if ( $hash->{RESIDENTGROUPS} ) {
  88. my $firstRgr = $hash->{RESIDENTGROUPS};
  89. $firstRgr =~ s/,[\s\S]*$//gmi;
  90. $attr{$name}{room} = $attr{$firstRgr}{room}
  91. if ( $attr{$firstRgr} && $attr{$firstRgr}{room} );
  92. }
  93. }
  94. }
  95. # trigger for modified objects
  96. if ( $TYPE ne "RESIDENTS" ) {
  97. readingsBeginUpdate($hash);
  98. readingsBulkUpdateIfChanged( $hash, "state",
  99. ReadingsVal( $name, "state", "" ) );
  100. readingsEndUpdate( $hash, 1 );
  101. }
  102. # run timers
  103. InternalTimer(
  104. gettimeofday() + 15,
  105. "RESIDENTStk_StartInternalTimers",
  106. $hash, 0
  107. );
  108. return undef;
  109. }
  110. sub RESIDENTStk_Undefine($$) {
  111. my ( $hash, $name ) = @_;
  112. RESIDENTStk_StopInternalTimers($hash);
  113. return undef unless ($init_done);
  114. # delete child roommates
  115. if ( defined( $hash->{ROOMMATES} )
  116. && $hash->{ROOMMATES} ne "" )
  117. {
  118. my @registeredRoommates =
  119. split( /,/, $hash->{ROOMMATES} );
  120. foreach my $child (@registeredRoommates) {
  121. fhem( "delete " . $child );
  122. Log3 $name, 3, "RESIDENTS $name: deleted device $child";
  123. }
  124. }
  125. # delete child guests
  126. if ( defined( $hash->{GUESTS} )
  127. && $hash->{GUESTS} ne "" )
  128. {
  129. my @registeredGuests =
  130. split( /,/, $hash->{GUESTS} );
  131. foreach my $child (@registeredGuests) {
  132. fhem( "delete " . $child );
  133. Log3 $name, 3, "RESIDENTS $name: deleted device $child";
  134. }
  135. }
  136. return undef;
  137. }
  138. sub RESIDENTStk_Set($@);
  139. sub RESIDENTStk_Set($@) {
  140. my ( $hash, @a ) = @_;
  141. my $name = $hash->{NAME};
  142. my $TYPE = GetType($name);
  143. my $prefix = RESIDENTStk_GetPrefixFromType($name);
  144. my $state = ReadingsVal( $name, "state", "initialized" );
  145. my $presence = ReadingsVal( $name, "presence", "undefined" );
  146. my $mood = ReadingsVal( $name, "mood", "-" );
  147. my $location = ReadingsVal( $name, "location", "undefined" );
  148. my $roommates = ( $hash->{ROOMMATES} ? $hash->{ROOMMATES} : undef );
  149. my $guests = ( $hash->{GUESTS} ? $hash->{GUESTS} : undef );
  150. my $silent = 0;
  151. return undef if ( IsDisabled($name) );
  152. return "No Argument given" unless ( defined( $a[1] ) );
  153. # depending on current FHEMWEB instance's allowedCommands,
  154. # restrict set commands if there is "set-user" in it
  155. my $adminMode = 1;
  156. my $FWallowedCommands = 0;
  157. $FWallowedCommands = AttrVal( $FW_wname, "allowedCommands", 0 )
  158. if ( defined($FW_wname) );
  159. if ( $FWallowedCommands && $FWallowedCommands =~ m/\bset-user\b/ ) {
  160. $adminMode = 0;
  161. return "Forbidden command: set " . $a[1]
  162. if ( $TYPE ne "RESIDENTS" && lc( $a[1] ) eq "create" );
  163. return "Forbidden command: set "
  164. . $a[1]
  165. if (
  166. $TYPE eq "RESIDENTS"
  167. && ( lc( $a[1] ) eq "addroommate"
  168. || lc( $a[1] ) eq "addguest"
  169. || lc( $a[1] ) eq "removeroommate"
  170. || lc( $a[1] ) eq "removeguest"
  171. || lc( $a[1] ) eq "create" )
  172. );
  173. }
  174. # states
  175. my $states = (
  176. defined( $attr{$name}{ $prefix . 'states' } )
  177. ? $attr{$name}{ $prefix . 'states' }
  178. : (
  179. defined( $attr{$name}{ $prefix . 'showAllStates' } )
  180. && $attr{$name}{ $prefix . 'showAllStates' } == 1
  181. ? "home,gotosleep,asleep,awoken,absent"
  182. : "home,gotosleep,absent"
  183. )
  184. );
  185. $states .= ",gone" if ( $TYPE ne "GUEST" );
  186. $states .= ",none" if ( $TYPE eq "GUEST" );
  187. $states = $state . "," . $states
  188. if ( $state ne "initialized" && $states !~ /$state/ );
  189. $states =~ s/ /,/g;
  190. # moods
  191. my $moods = (
  192. defined( $attr{$name}{ $prefix . 'moods' } )
  193. ? $attr{$name}{ $prefix . 'moods' } . ",toggle"
  194. : "calm,relaxed,happy,excited,lonely,sad,bored,stressed,uncomfortable,sleepy,angry,toggle"
  195. );
  196. $moods = $mood . "," . $moods if ( $moods !~ /$mood/ );
  197. $moods =~ s/ /,/g;
  198. # locations
  199. my $locations = (
  200. defined( $attr{$name}{ $prefix . 'locations' } )
  201. ? $attr{$name}{ $prefix . 'locations' }
  202. : ""
  203. );
  204. if ( $locations !~ /$location/
  205. && $locations ne "" )
  206. {
  207. $locations = ":" . $location . "," . $locations;
  208. }
  209. elsif ( $locations ne "" ) {
  210. $locations = ":" . $locations;
  211. }
  212. $locations =~ s/ /,/g;
  213. my $usage = "Unknown argument " . $a[1] . ", choose one of state:$states";
  214. if ( $TYPE ne "RESIDENTS" ) {
  215. $usage .= " mood:$moods";
  216. $usage .= " location$locations";
  217. if ($adminMode) {
  218. $usage .= " create:wakeuptimer";
  219. $usage .= ",locationMap"
  220. if ( ReadingsVal( $name, "locationLat", "-" ) ne "-"
  221. && ReadingsVal( $name, "locationLong", "-" ) ne "-" );
  222. }
  223. }
  224. else {
  225. if ($adminMode) {
  226. $usage .= " addRoommate addGuest";
  227. $usage .= " removeRoommate:" . $roommates if ($roommates);
  228. $usage .= " removeGuest:" . $guests if ($guests);
  229. $usage .= " create:wakeuptimer";
  230. }
  231. }
  232. # silentSet
  233. if ( $a[1] eq "silentSet" ) {
  234. $silent = 1;
  235. my $first = shift @a;
  236. $a[0] = $first;
  237. }
  238. # states
  239. if ( $a[1] eq "state"
  240. || $a[1] eq "home"
  241. || $a[1] eq "gotosleep"
  242. || $a[1] eq "asleep"
  243. || $a[1] eq "awoken"
  244. || $a[1] eq "absent"
  245. || $a[1] eq "none"
  246. || $a[1] eq "gone" )
  247. {
  248. my $newstate;
  249. # if not direct
  250. if (
  251. $a[1] eq "state"
  252. && defined( $a[2] )
  253. && ( $a[2] eq "home"
  254. || $a[2] eq "gotosleep"
  255. || $a[2] eq "asleep"
  256. || $a[2] eq "awoken"
  257. || $a[2] eq "absent"
  258. || $a[2] eq "none"
  259. || $a[2] eq "gone" )
  260. )
  261. {
  262. $newstate = $a[2];
  263. }
  264. elsif ( defined( $a[2] ) ) {
  265. return
  266. "Invalid 2nd argument, choose one of home gotosleep asleep awoken absent gone ";
  267. }
  268. else {
  269. $newstate = $a[1];
  270. }
  271. $newstate = "none" if ( $newstate eq "gone" && $TYPE eq "GUEST" );
  272. $newstate = "gone" if ( $newstate eq "none" && $TYPE ne "GUEST" );
  273. Log3 $name, 2, "$TYPE set $name " . $newstate if ( !$silent );
  274. # RESIDENTS only
  275. if ( $TYPE eq "RESIDENTS" ) {
  276. $presence = "absent";
  277. # loop through every roommate
  278. if ($roommates) {
  279. my @registeredRoommates =
  280. split( /,/, $roommates );
  281. foreach my $roommate (@registeredRoommates) {
  282. fhem "set $roommate silentSet state $newstate"
  283. if ( ReadingsVal( $roommate, "state", "initialized" ) ne
  284. $newstate );
  285. }
  286. }
  287. # loop through every guest
  288. if ($guests) {
  289. $newstate = "none" if ( $newstate eq "gone" );
  290. my @registeredGuests =
  291. split( /,/, $guests );
  292. foreach my $guest (@registeredGuests) {
  293. fhem "set $guest silentSet state $newstate"
  294. if ( ReadingsVal( $guest, "state", "initialized" ) ne
  295. $newstate );
  296. }
  297. }
  298. }
  299. # ROOMMATE+GUEST: if state changed
  300. elsif ( $state ne $newstate ) {
  301. readingsBeginUpdate($hash);
  302. readingsBulkUpdate( $hash, "lastState", $state );
  303. readingsBulkUpdate( $hash, "state", $newstate );
  304. my $datetime = TimeNow();
  305. # reset mood
  306. my $mood_default =
  307. ( defined( $attr{$name}{ $prefix . 'moodDefault' } ) )
  308. ? $attr{$name}{ $prefix . 'moodDefault' }
  309. : "calm";
  310. my $mood_sleepy =
  311. ( defined( $attr{$name}{ $prefix . 'moodSleepy' } ) )
  312. ? $attr{$name}{ $prefix . 'moodSleepy' }
  313. : "sleepy";
  314. if (
  315. $mood ne "-"
  316. && ( $newstate eq "gone"
  317. || $newstate eq "none"
  318. || $newstate eq "absent"
  319. || $newstate eq "asleep" )
  320. )
  321. {
  322. Log3 $name, 4,
  323. "$TYPE $name: implicit mood change caused by state "
  324. . $newstate;
  325. RESIDENTStk_Set( $hash, $name, "silentSet", "mood", "-" );
  326. }
  327. elsif ( $mood ne $mood_sleepy
  328. && ( $newstate eq "gotosleep" || $newstate eq "awoken" ) )
  329. {
  330. Log3 $name, 4,
  331. "$TYPE $name: implicit mood change caused by state "
  332. . $newstate;
  333. RESIDENTStk_Set( $hash, $name, "silentSet", "mood",
  334. $mood_sleepy );
  335. }
  336. elsif ( ( $mood eq "-" || $mood eq $mood_sleepy )
  337. && $newstate eq "home" )
  338. {
  339. Log3 $name, 4,
  340. "$TYPE $name: implicit mood change caused by state "
  341. . $newstate;
  342. RESIDENTStk_Set( $hash, $name, "silentSet", "mood",
  343. $mood_default );
  344. }
  345. # if state is asleep, start sleep timer
  346. readingsBulkUpdate( $hash, "lastSleep", $datetime )
  347. if ( $newstate eq "asleep" );
  348. # if prior state was asleep, update sleep statistics
  349. if ( $state eq "asleep"
  350. && ReadingsVal( $name, "lastSleep", "" ) ne "" )
  351. {
  352. readingsBulkUpdate( $hash, "lastAwake", $datetime );
  353. readingsBulkUpdate(
  354. $hash,
  355. "lastDurSleep",
  356. RESIDENTStk_TimeDiff(
  357. $datetime, ReadingsVal( $name, "lastSleep", "" )
  358. )
  359. );
  360. readingsBulkUpdate(
  361. $hash,
  362. "lastDurSleep_cr",
  363. RESIDENTStk_TimeDiff(
  364. $datetime, ReadingsVal( $name, "lastSleep", "" ),
  365. "min"
  366. )
  367. );
  368. }
  369. # calculate presence state
  370. my $newpresence =
  371. ( $newstate ne "none"
  372. && $newstate ne "gone"
  373. && $newstate ne "absent" )
  374. ? "present"
  375. : "absent";
  376. # stop any running wakeup-timers in case state changed
  377. my $wakeupState = ReadingsVal( $name, "wakeup", 0 );
  378. if ( $wakeupState > 0 ) {
  379. my $wakeupDeviceList =
  380. AttrVal( $name, $prefix . 'wakeupDevice', 0 );
  381. for my $wakeupDevice ( split /,/, $wakeupDeviceList ) {
  382. next if !$wakeupDevice;
  383. if ( IsDevice( $wakeupDevice, "dummy" ) ) {
  384. # forced-stop only if resident is not present anymore
  385. if ( $newpresence eq "present" ) {
  386. Log3 $name, 4,
  387. "$TYPE $name: ending wakeup-timer $wakeupDevice";
  388. fhem "set $wakeupDevice:FILTER=running!=0 end";
  389. }
  390. else {
  391. Log3 $name, 4,
  392. "$TYPE $name: stopping wakeup-timer $wakeupDevice";
  393. fhem "set $wakeupDevice:FILTER=running!=0 stop";
  394. }
  395. }
  396. }
  397. }
  398. # if presence changed
  399. if ( $newpresence ne $presence ) {
  400. readingsBulkUpdate( $hash, "presence", $newpresence );
  401. # update location
  402. my @location_home =
  403. split( ' ',
  404. AttrVal( $name, $prefix . 'locationHome', "home" ) );
  405. my @location_underway =
  406. split(
  407. ' ',
  408. AttrVal( $name, $prefix . 'locationUnderway', "underway" )
  409. );
  410. my @location_wayhome =
  411. split( ' ',
  412. AttrVal( $name, $prefix . 'locationWayhome', "wayhome" ) );
  413. my $searchstring = quotemeta($location);
  414. if ( !$silent && $newpresence eq "present" ) {
  415. if ( !grep( m/^$searchstring$/, @location_home )
  416. && $location ne $location_home[0] )
  417. {
  418. Log3 $name, 4,
  419. "$TYPE $name: implicit location change caused by state "
  420. . $newstate;
  421. RESIDENTStk_Set( $hash, $name, "silentSet", "location",
  422. $location_home[0] );
  423. }
  424. }
  425. else {
  426. if ( !$silent
  427. && !grep( m/^$searchstring$/, @location_underway )
  428. && $location ne $location_underway[0] )
  429. {
  430. Log3 $name, 4,
  431. "$TYPE $name: implicit location change caused by state "
  432. . $newstate;
  433. RESIDENTStk_Set( $hash, $name, "silentSet", "location",
  434. $location_underway[0] );
  435. }
  436. }
  437. # reset wayhome
  438. readingsBulkUpdateIfChanged( $hash, "wayhome", "0" );
  439. # update statistics
  440. if ( $newpresence eq "present" ) {
  441. readingsBulkUpdate( $hash, "lastArrival", $datetime );
  442. # absence duration
  443. if ( ReadingsVal( $name, "lastDeparture", "-" ) ne "-" ) {
  444. readingsBulkUpdate(
  445. $hash,
  446. "lastDurAbsence",
  447. RESIDENTStk_TimeDiff(
  448. $datetime,
  449. ReadingsVal( $name, "lastDeparture", "-" )
  450. )
  451. );
  452. readingsBulkUpdate(
  453. $hash,
  454. "lastDurAbsence_cr",
  455. RESIDENTStk_TimeDiff(
  456. $datetime,
  457. ReadingsVal( $name, "lastDeparture", "-" ),
  458. "min"
  459. )
  460. );
  461. }
  462. }
  463. else {
  464. readingsBulkUpdate( $hash, "lastDeparture", $datetime );
  465. # presence duration
  466. if ( ReadingsVal( $name, "lastArrival", "-" ) ne "-" ) {
  467. readingsBulkUpdate(
  468. $hash,
  469. "lastDurPresence",
  470. RESIDENTStk_TimeDiff(
  471. $datetime,
  472. ReadingsVal( $name, "lastArrival", "-" )
  473. )
  474. );
  475. readingsBulkUpdate(
  476. $hash,
  477. "lastDurPresence_cr",
  478. RESIDENTStk_TimeDiff(
  479. $datetime,
  480. ReadingsVal( $name, "lastArrival", "-" ), "min"
  481. )
  482. );
  483. }
  484. }
  485. # adjust linked objects
  486. if ( defined( $attr{$name}{ $prefix . 'passPresenceTo' } )
  487. && $attr{$name}{ $prefix . 'passPresenceTo' } ne "" )
  488. {
  489. my @linkedObjects =
  490. split( ' ', $attr{$name}{ $prefix . 'passPresenceTo' } );
  491. foreach my $object (@linkedObjects) {
  492. if ( IsDevice( $object, "ROOMMATE|GUEST" )
  493. && $defs{$object} ne $name
  494. && ReadingsVal( $object, "state", "" ) ne "gone"
  495. && ReadingsVal( $object, "state", "" ) ne "none" )
  496. {
  497. fhem("set $object $newstate");
  498. }
  499. }
  500. }
  501. }
  502. # clear readings if guest is gone
  503. if ( $newstate eq "none" ) {
  504. readingsBulkUpdate( $hash, "lastArrival", "-" )
  505. if ( ReadingsVal( $name, "lastArrival", "-" ) ne "-" );
  506. readingsBulkUpdate( $hash, "lastAwake", "-" )
  507. if ( ReadingsVal( $name, "lastAwake", "-" ) ne "-" );
  508. readingsBulkUpdate( $hash, "lastDurAbsence", "-" )
  509. if ( ReadingsVal( $name, "lastDurAbsence", "-" ) ne "-" );
  510. readingsBulkUpdate( $hash, "lastDurSleep", "-" )
  511. if ( ReadingsVal( $name, "lastDurSleep", "-" ) ne "-" );
  512. readingsBulkUpdate( $hash, "lastLocation", "-" )
  513. if ( ReadingsVal( $name, "lastLocation", "-" ) ne "-" );
  514. readingsBulkUpdate( $hash, "lastSleep", "-" )
  515. if ( ReadingsVal( $name, "lastSleep", "-" ) ne "-" );
  516. readingsBulkUpdate( $hash, "lastMood", "-" )
  517. if ( ReadingsVal( $name, "lastMood", "-" ) ne "-" );
  518. readingsBulkUpdate( $hash, "location", "-" )
  519. if ( ReadingsVal( $name, "location", "-" ) ne "-" );
  520. readingsBulkUpdate( $hash, "mood", "-" )
  521. if ( ReadingsVal( $name, "mood", "-" ) ne "-" );
  522. }
  523. # calculate duration timers
  524. RESIDENTStk_DurationTimer( $hash, $silent );
  525. readingsEndUpdate( $hash, 1 );
  526. # enable or disable AutoGone timer
  527. if ( $newstate eq "absent" ) {
  528. RESIDENTStk_AutoGone($hash);
  529. }
  530. elsif ( $state eq "absent" ) {
  531. delete $hash->{AUTOGONE} if ( $hash->{AUTOGONE} );
  532. RESIDENTStk_RemoveInternalTimer( "AutoGone", $hash );
  533. }
  534. }
  535. }
  536. # RESIDENTS only: addRoommate
  537. elsif ( $TYPE eq "RESIDENTS" && lc( $a[1] ) eq "addroommate" ) {
  538. Log3 $name, 2, "$TYPE set $name " . $a[1] . " " . $a[2]
  539. if ( defined( $a[2] ) );
  540. my $rr_name;
  541. my $rr_name_attr;
  542. if ( $a[2] ne "" ) {
  543. $rr_name = "rr_" unless ( $a[2] =~ /^rr_/ );
  544. $rr_name .= $a[2];
  545. # define roommate
  546. fhem( "define " . $rr_name . " ROOMMATE " . $name )
  547. unless ( IsDevice($rr_name) );
  548. if ( IsDevice($rr_name) ) {
  549. return "Can't create, device $rr_name already existing."
  550. unless ( IsDevice( $rr_name, "ROOMMATE" ) );
  551. my $lang =
  552. $a[3]
  553. ? uc( $a[3] )
  554. : AttrVal( $rr_name, "rr_lang",
  555. AttrVal( $name, "rgr_lang", undef ) );
  556. fhem( "attr " . $rr_name . " rr_lang " . $lang )
  557. if ($lang);
  558. $attr{$rr_name}{comment} = "Auto-created by $name"
  559. unless ( defined( $attr{$rr_name}{comment} )
  560. && $attr{$rr_name}{comment} eq "Auto-created by $name" );
  561. fhem "sleep 1;set $rr_name silentSet state home";
  562. Log3 $name, 3, "$TYPE $name: created new device $rr_name";
  563. }
  564. }
  565. else {
  566. return "No Argument given, choose one of name ";
  567. }
  568. }
  569. # RESIDENTS only: removeRoommate
  570. elsif ( $TYPE eq "RESIDENTS" && lc( $a[1] ) eq "removeroommate" ) {
  571. Log3 $name, 2, "$TYPE set $name " . $a[1] . " " . $a[2]
  572. if ( defined( $a[2] ) );
  573. if ( $a[2] ne "" ) {
  574. my $rr_name = $a[2];
  575. # delete roommate
  576. if ( IsDevice($rr_name) ) {
  577. Log3 $name, 3, "$TYPE $name: deleted device $rr_name"
  578. if fhem( "delete " . $rr_name );
  579. }
  580. }
  581. else {
  582. return "No Argument given, choose one of name ";
  583. }
  584. }
  585. # RESIDENTS only: addGuest
  586. elsif ( $TYPE eq "RESIDENTS" && lc( $a[1] ) eq "addguest" ) {
  587. Log3 $name, 2, "$TYPE set $name " . $a[1] . " " . $a[2]
  588. if ( defined( $a[2] ) );
  589. my $rg_name;
  590. my $rg_name_attr;
  591. if ( $a[2] ne "" ) {
  592. $rg_name = "rg_" unless ( $a[2] =~ /^rg_/ );
  593. $rg_name .= $a[2];
  594. # define guest
  595. fhem( "define " . $rg_name . " GUEST " . $name )
  596. unless ( IsDevice($rg_name) );
  597. if ( IsDevice($rg_name) ) {
  598. return "Can't create, device $rg_name already existing."
  599. unless ( IsDevice( $rg_name, "GUEST" ) );
  600. my $lang =
  601. $a[3]
  602. ? uc( $a[3] )
  603. : AttrVal( $rg_name, "rg_lang",
  604. AttrVal( $name, "rgr_lang", undef ) );
  605. fhem( "attr " . $rg_name . " rg_lang " . $lang )
  606. if ($lang);
  607. $attr{$rg_name}{comment} = "Auto-created by $name"
  608. unless ( defined( $attr{$rg_name}{comment} )
  609. && $attr{$rg_name}{comment} eq "Auto-created by $name" );
  610. fhem "sleep 1;set $rg_name silentSet state home";
  611. Log3 $name, 3, "$TYPE $name: created new device $rg_name";
  612. }
  613. }
  614. else {
  615. return "No Argument given, choose one of name ";
  616. }
  617. }
  618. # RESIDENTS only: removeGuest
  619. elsif ( $TYPE eq "RESIDENTS" && lc( $a[1] ) eq "removeguest" ) {
  620. Log3 $name, 2, "$TYPE set $name " . $a[1] . " " . $a[2]
  621. if ( defined( $a[2] ) );
  622. if ( $a[2] ne "" ) {
  623. my $rg_name = $a[2];
  624. # delete guest
  625. if ( IsDevice($rg_name) ) {
  626. Log3 $name, 3, "$TYPE $name: deleted device $rg_name"
  627. if fhem( "delete " . $rg_name );
  628. }
  629. }
  630. else {
  631. return "No Argument given, choose one of name ";
  632. }
  633. }
  634. # mood
  635. elsif ( $TYPE ne "RESIDENTS" && $a[1] eq "mood" ) {
  636. if ( defined( $a[2] ) && $a[2] ne "" ) {
  637. Log3 $name, 2, "$TYPE set $name mood " . $a[2] if ( !$silent );
  638. readingsBeginUpdate($hash) if ( !$silent );
  639. if ( $a[2] eq "toggle"
  640. && ReadingsVal( $name, "lastMood", "" ) ne "" )
  641. {
  642. readingsBulkUpdate( $hash, "mood",
  643. ReadingsVal( $name, "lastMood", "" ) );
  644. readingsBulkUpdate( $hash, "lastMood", $mood );
  645. }
  646. elsif ( $mood ne $a[2] ) {
  647. readingsBulkUpdate( $hash, "lastMood", $mood )
  648. if ( $mood ne "-" );
  649. readingsBulkUpdate( $hash, "mood", $a[2] );
  650. }
  651. readingsEndUpdate( $hash, 1 ) if ( !$silent );
  652. }
  653. else {
  654. return "Invalid 2nd argument, choose one of mood toggle";
  655. }
  656. }
  657. # location
  658. elsif ( $TYPE ne "RESIDENTS" && $a[1] eq "location" ) {
  659. if ( defined( $a[2] ) && $a[2] ne "" ) {
  660. Log3 $name, 2, "$TYPE set $name location " . $a[2]
  661. if ( !$silent );
  662. if ( $location ne $a[2] ) {
  663. my $searchstring;
  664. readingsBeginUpdate($hash) if ( !$silent );
  665. # read attributes
  666. my @location_home =
  667. split( ' ',
  668. AttrVal( $name, $prefix . 'locationHome', "home" ) );
  669. my @location_underway =
  670. split(
  671. ' ',
  672. AttrVal( $name, $prefix . 'locationUnderway', "underway" )
  673. );
  674. my @location_wayhome =
  675. split( ' ',
  676. AttrVal( $name, $prefix . 'locationWayhome', "wayhome" ) );
  677. $searchstring = quotemeta($location);
  678. readingsBulkUpdate( $hash, "lastLocation", $location )
  679. if ( $location ne "wayhome"
  680. && !grep( m/^$searchstring$/, @location_underway ) );
  681. readingsBulkUpdate( $hash, "location", $a[2] )
  682. if ( $a[2] ne "wayhome" );
  683. # wayhome detection
  684. $searchstring = quotemeta($location);
  685. if (
  686. (
  687. $a[2] eq "wayhome"
  688. || grep( m/^$searchstring$/, @location_wayhome )
  689. )
  690. && ( $presence eq "absent" )
  691. )
  692. {
  693. Log3 $name, 3,
  694. "$TYPE $name: on way back home from $location";
  695. readingsBulkUpdateIfChanged( $hash, "wayhome", "1" );
  696. }
  697. readingsEndUpdate( $hash, 1 ) if ( !$silent );
  698. # auto-updates
  699. $searchstring = quotemeta( $a[2] );
  700. if (
  701. (
  702. $a[2] eq "home"
  703. || grep( m/^$searchstring$/, @location_home )
  704. )
  705. && $state ne "home"
  706. && $state ne "gotosleep"
  707. && $state ne "asleep"
  708. && $state ne "awoken"
  709. && $state ne "initialized"
  710. )
  711. {
  712. Log3 $name, 4,
  713. "$TYPE $name: implicit state change caused by location "
  714. . $a[2];
  715. RESIDENTStk_Set( $hash, $name, "silentSet", "state",
  716. "home" );
  717. }
  718. elsif (
  719. (
  720. $a[2] eq "underway"
  721. || grep( m/^$searchstring$/, @location_underway )
  722. )
  723. && $state ne "gone"
  724. && $state ne "none"
  725. && $state ne "absent"
  726. && $state ne "initialized"
  727. )
  728. {
  729. Log3 $name, 4,
  730. "$TYPE $name: implicit state change caused by location "
  731. . $a[2];
  732. RESIDENTStk_Set( $hash, $name, "silentSet", "state",
  733. "absent" );
  734. }
  735. }
  736. }
  737. else {
  738. return "Invalid 2nd argument, choose one of location ";
  739. }
  740. }
  741. # create
  742. elsif ( lc( $a[1] ) eq "create" ) {
  743. if ( $TYPE eq "RESIDENTS"
  744. && ( !defined( $a[2] ) || lc( $a[2] ) ne "wakeuptimer" ) )
  745. {
  746. return "Invalid 2nd argument, choose one of wakeuptimer ";
  747. }
  748. elsif ( !defined( $a[2] ) || $a[2] !~ /^(wakeuptimer|locationMap)$/i ) {
  749. return
  750. "Invalid 2nd argument, choose one of wakeuptimer locationMap ";
  751. }
  752. elsif ( lc( $a[2] ) eq "wakeuptimer" ) {
  753. my $i = "1";
  754. my $wakeuptimerName = $name . "_wakeuptimer" . $i;
  755. my $created = 0;
  756. until ($created) {
  757. if ( IsDevice($wakeuptimerName) ) {
  758. $i++;
  759. $wakeuptimerName = $name . "_wakeuptimer" . $i;
  760. }
  761. else {
  762. my $sortby = AttrVal( $name, "sortby", -1 );
  763. $sortby++;
  764. # create new dummy device
  765. fhem "define $wakeuptimerName dummy";
  766. fhem "attr $wakeuptimerName alias Wake-up Timer $i";
  767. fhem
  768. "attr $wakeuptimerName comment Auto-created by $TYPE module for use with RESIDENTS Toolkit";
  769. fhem
  770. "attr $wakeuptimerName devStateIcon OFF:general_aus\@red:reset running:general_an\@green:stop .*:general_an\@orange:nextRun%20OFF";
  771. fhem "attr $wakeuptimerName group " . $attr{$name}{group}
  772. if ( defined( $attr{$name}{group} ) );
  773. fhem "attr $wakeuptimerName icon time_timer";
  774. fhem "attr $wakeuptimerName room " . $attr{$name}{room}
  775. if ( defined( $attr{$name}{room} ) );
  776. fhem
  777. "attr $wakeuptimerName setList nextRun:OFF,00:00,00:15,00:30,00:45,01:00,01:15,01:30,01:45,02:00,02:15,02:30,02:45,03:00,03:15,03:30,03:45,04:00,04:15,04:30,04:45,05:00,05:15,05:30,05:45,06:00,06:15,06:30,06:45,07:00,07:15,07:30,07:45,08:00,08:15,08:30,08:45,09:00,09:15,09:30,09:45,10:00,10:15,10:30,10:45,11:00,11:15,11:30,11:45,12:00,12:15,12:30,12:45,13:00,13:15,13:30,13:45,14:00,14:15,14:30,14:45,15:00,15:15,15:30,15:45,16:00,16:15,16:30,16:45,17:00,17:15,17:30,17:45,18:00,18:15,18:30,18:45,19:00,19:15,19:30,19:45,20:00,20:15,20:30,20:45,21:00,21:15,21:30,21:45,22:00,22:15,22:30,22:45,23:00,23:15,23:30,23:45 reset:noArg trigger:noArg start:noArg stop:noArg end:noArg wakeupOffset:slider,0,1,120 wakeupDefaultTime:OFF,00:00,00:15,00:30,00:45,01:00,01:15,01:30,01:45,02:00,02:15,02:30,02:45,03:00,03:15,03:30,03:45,04:00,04:15,04:30,04:45,05:00,05:15,05:30,05:45,06:00,06:15,06:30,06:45,07:00,07:15,07:30,07:45,08:00,08:15,08:30,08:45,09:00,09:15,09:30,09:45,10:00,10:15,10:30,10:45,11:00,11:15,11:30,11:45,12:00,12:15,12:30,12:45,13:00,13:15,13:30,13:45,14:00,14:15,14:30,14:45,15:00,15:15,15:30,15:45,16:00,16:15,16:30,16:45,17:00,17:15,17:30,17:45,18:00,18:15,18:30,18:45,19:00,19:15,19:30,19:45,20:00,20:15,20:30,20:45,21:00,21:15,21:30,21:45,22:00,22:15,22:30,22:45,23:00,23:15,23:30,23:45 wakeupResetdays:multiple-strict,0,1,2,3,4,5,6 wakeupDays:multiple-strict,0,1,2,3,4,5,6 wakeupHolidays:,andHoliday,orHoliday,andNoHoliday,orNoHoliday wakeupEnforced:0,1,2,3";
  778. fhem "attr $wakeuptimerName userattr wakeupUserdevice";
  779. fhem "attr $wakeuptimerName sortby " . $sortby
  780. if ($sortby);
  781. fhem "attr $wakeuptimerName wakeupUserdevice $name";
  782. fhem "attr $wakeuptimerName webCmd nextRun";
  783. # register slave device
  784. my $wakeupDevice =
  785. AttrVal( $name, $prefix . 'wakeupDevice', 0 );
  786. if ( !$wakeupDevice ) {
  787. fhem "attr $name $prefix"
  788. . "wakeupDevice $wakeuptimerName";
  789. }
  790. elsif ( $wakeupDevice !~ /(.*,?)($wakeuptimerName)(.*,?)/ )
  791. {
  792. fhem "attr $name $prefix"
  793. . "wakeupDevice "
  794. . $wakeupDevice
  795. . ",$wakeuptimerName";
  796. }
  797. # trigger first update
  798. fhem "sleep 1;set $wakeuptimerName nextRun OFF";
  799. $created = 1;
  800. }
  801. }
  802. return
  803. "Dummy $wakeuptimerName and other pending devices created and pre-configured.\nYou may edit Macro_$wakeuptimerName to define your wake-up actions\nand at_$wakeuptimerName for optional at-device adjustments.";
  804. }
  805. elsif ( $TYPE ne "RESIDENTS" && lc( $a[2] ) eq "locationmap" ) {
  806. my $locationmapName = $name . "_map";
  807. if ( IsDevice($locationmapName) ) {
  808. return
  809. "Device $locationmapName existing already, delete it first to have it re-created.";
  810. }
  811. else {
  812. my $sortby = AttrVal( $name, "sortby", -1 );
  813. $sortby++;
  814. # create new weblink device
  815. fhem "define $locationmapName weblink htmlCode {
  816. '<ul style=\"width: 400px;; overflow: hidden;; height: 300px;;\">
  817. <iframe name=\"$locationmapName\" src=\"https://www.google.com/maps/embed/v1/place?key=AIzaSyB66DvcpbXJ5eWgIkzxpUN2s_9l3_6fegM&q='
  818. .ReadingsVal('$name','locationLat','')
  819. .','
  820. .ReadingsVal('$name','locationLong','')
  821. .'&zoom=13\" width=\"480\" height=\"480\" frameborder=\"0\" style=\"border:0;; margin-top: -165px;; margin-left: -135px;;\">
  822. </iframe>
  823. </ul>'
  824. }";
  825. fhem "attr $locationmapName alias Current Location";
  826. fhem
  827. "attr $locationmapName comment Auto-created by $TYPE module";
  828. fhem "attr $locationmapName group " . $attr{$name}{group}
  829. if ( defined( $attr{$name}{group} ) );
  830. fhem "attr $locationmapName room " . $attr{$name}{room}
  831. if ( defined( $attr{$name}{room} ) );
  832. }
  833. return "Weblink device $locationmapName was created.";
  834. }
  835. }
  836. # return usage hint
  837. else {
  838. return $usage;
  839. }
  840. return undef;
  841. }
  842. sub RESIDENTStk_Attr(@) {
  843. my ( $cmd, $name, $attribute, $value ) = @_;
  844. my $hash = $defs{$name};
  845. my $prefix = RESIDENTStk_GetPrefixFromType($name);
  846. if ( $attribute eq $prefix . "wakeupDevice"
  847. || $attribute eq $prefix . "presenceDevices" )
  848. {
  849. return "Value for $attribute has invalid format"
  850. unless ( $cmd eq "del"
  851. || $value =~
  852. m/^([a-zA-Z\d._]+(:[A-Za-z\d_\.\-\/]+)?,?)([a-zA-Z\d._]+(:[A-Za-z\d_\.\-\/]+)?,?)*$/
  853. );
  854. }
  855. elsif ( !$init_done ) {
  856. return undef;
  857. }
  858. elsif ( $attribute eq "disable" ) {
  859. if ( $value and $value == 1 ) {
  860. $hash->{STATE} = "disabled";
  861. RESIDENTStk_StopInternalTimers($hash);
  862. }
  863. elsif ( $cmd eq "del" or !$value ) {
  864. evalStateFormat($hash);
  865. RESIDENTStk_StartInternalTimers( $hash, 1 );
  866. }
  867. }
  868. elsif ( $prefix ne "rgr_" && $attribute eq $prefix . "autoGoneAfter" ) {
  869. if ($value) {
  870. RESIDENTStk_AutoGone($hash);
  871. }
  872. elsif ( !$value ) {
  873. delete $hash->{AUTOGONE} if ( $hash->{AUTOGONE} );
  874. RESIDENTStk_RemoveInternalTimer( "AutoGone", $hash );
  875. }
  876. }
  877. elsif ( $attribute eq $prefix . "noDuration" ) {
  878. if ($value) {
  879. delete $hash->{DURATIONTIMER} if ( $hash->{DURATIONTIMER} );
  880. RESIDENTStk_RemoveInternalTimer( "DurationTimer", $hash );
  881. }
  882. elsif ( !$value ) {
  883. RESIDENTStk_DurationTimer($hash);
  884. }
  885. }
  886. elsif ( $attribute eq $prefix . "lang" ) {
  887. my $lang =
  888. $cmd eq "set" ? uc($value) : AttrVal( "global", "language", "EN" );
  889. # for initial define, ensure fallback to EN
  890. $lang = "EN"
  891. if ( $cmd eq "init" && $lang !~ /^EN|DE$/i );
  892. if ( $lang eq "DE" ) {
  893. if ( $prefix eq "rgr_" ) {
  894. $attr{$name}{alias} = "Bewohner"
  895. if ( !defined( $attr{$name}{alias} )
  896. || $attr{$name}{alias} eq "Residents" );
  897. $attr{$name}{group} = "Zuhause Status"
  898. if ( !defined( $attr{$name}{group} )
  899. || $attr{$name}{group} eq "Home State" );
  900. }
  901. $attr{$name}{devStateIcon} =
  902. '.*zuhause:user_available:absent '
  903. . '.*anwesend:user_available:absent .*abwesend:user_away:home .*verreist:user_ext_away:home .*bettfertig:scene_toilet:asleep .*schlaeft:scene_sleeping:awoken .*schläft:scene_sleeping:awoken .*aufgestanden:scene_sleeping_alternat:home .*:user_unknown:home';
  904. $attr{$name}{eventMap} =
  905. "home:zuhause absent:abwesend gone:verreist "
  906. . "gotosleep:bettfertig asleep:schläft awoken:aufgestanden";
  907. $attr{$name}{widgetOverride} =
  908. "state:zuhause,bettfertig,schläft,"
  909. . "aufgestanden,abwesend,verreist";
  910. }
  911. elsif ( $lang eq "EN" ) {
  912. if ( $prefix eq "rgr_" ) {
  913. $attr{$name}{alias} = "Residents"
  914. if ( !defined( $attr{$name}{alias} )
  915. || $attr{$name}{alias} eq "Bewohner" );
  916. $attr{$name}{group} = "Home State"
  917. if ( !defined( $attr{$name}{group} )
  918. || $attr{$name}{group} eq "Zuhause Status" );
  919. }
  920. $attr{$name}{devStateIcon} =
  921. '.*home:user_available:absent .*absent:user_away:home '
  922. . '.*gone:user_ext_away:home .*gotosleep:scene_toilet:asleep .*asleep:scene_sleeping:awoken .*awoken:scene_sleeping_alternat:home .*:user_unknown:home';
  923. delete $attr{$name}{eventMap}
  924. if ( defined( $attr{$name}{eventMap} ) );
  925. delete $attr{$name}{widgetOverride}
  926. if ( defined( $attr{$name}{widgetOverride} ) );
  927. }
  928. else {
  929. return "Unsupported language $lang";
  930. }
  931. evalStateFormat($hash);
  932. }
  933. return undef;
  934. }
  935. sub RESIDENTStk_Notify($$) {
  936. my ( $hash, $dev ) = @_;
  937. my $name = $hash->{NAME};
  938. my $TYPE = GetType($name);
  939. my $prefix = RESIDENTStk_GetPrefixFromType($name);
  940. my $devName = $dev->{NAME};
  941. my $devPrefix = RESIDENTStk_GetPrefixFromType($devName);
  942. my $devType = GetType($devName);
  943. if ( $devName eq "global" ) {
  944. my $events = deviceEvents( $dev, 1 );
  945. return "" unless ($events);
  946. foreach ( @{$events} ) {
  947. next if ( $_ =~ m/^[A-Za-z\d_-]+:/ );
  948. # module and device initialization
  949. if ( $_ =~ m/^INITIALIZED|REREADCFG$/ ) {
  950. if ( !defined( &{'DoInitDev'} ) ) {
  951. if ( $_ eq "REREADCFG" ) {
  952. delete $modules{$TYPE}{READY};
  953. delete $modules{$TYPE}{INIT};
  954. }
  955. RESIDENTStk_DoInitDev(
  956. devspec2array("TYPE=$TYPE:FILTER=MOD_INIT=.+") );
  957. }
  958. }
  959. # if any of our monitored devices was modified,
  960. # recalculate monitoring status
  961. elsif ( $_ =~
  962. m/^(DEFINED|MODIFIED|RENAMED|DELETED)\s+([A-Za-z\d_-]+)$/ )
  963. {
  964. if ( defined( &{'DoInitDev'} ) ) {
  965. # DELETED would normally be handled by fhem.pl and imply
  966. # DoModuleTrigger instead of DoInitDev to update module
  967. # init state
  968. next if ( $_ =~ /^DELETED/ );
  969. DoInitDev($name);
  970. }
  971. else {
  972. # for DELETED, we normally would want to use
  973. # DoModuleTrigger() but we miss the deleted
  974. # device's TYPE at this state :-(
  975. RESIDENTStk_DoInitDev($name);
  976. }
  977. }
  978. # only process attribute events
  979. next
  980. unless ( $_ =~
  981. m/^((?:DELETE)?ATTR)\s+([A-Za-z\d._]+)\s+([A-Za-z\d_\.\-\/]+)(?:\s+(.*)\s*)?$/
  982. );
  983. my $cmd = $1;
  984. my $d = $2;
  985. my $attr = $3;
  986. my $val = $4;
  987. my $type = GetType($d);
  988. # filter attributes to be processed
  989. next
  990. unless ( $attr eq $prefix . "wakeupDevice"
  991. || $attr eq $prefix . "presenceDevices"
  992. || $attr eq $prefix . "wakeupResetSwitcher" );
  993. # when attributes of RESIDENTS, ROOMMATE or GUEST were changed
  994. if ( $d eq $name ) {
  995. if ( defined( &{'DoInitDev'} ) ) {
  996. DoInitDev($name)
  997. if ( $TYPE ne "dummy" );
  998. }
  999. else {
  1000. RESIDENTStk_DoInitDev($name)
  1001. if ( $TYPE ne "dummy" );
  1002. }
  1003. return "";
  1004. }
  1005. # when slave attributes where changed
  1006. elsif ( $hash->{NOTIFYDEV} ) {
  1007. my @ndlarr = devspec2array( $hash->{NOTIFYDEV} );
  1008. return "" unless ( grep( $_ eq $d, @ndlarr ) );
  1009. # wakeupResetSwitcher
  1010. if ( $attr eq "wakeupResetSwitcher" ) {
  1011. if ( $cmd eq "ATTR" ) {
  1012. if ( !IsDevice($val) ) {
  1013. my $alias = AttrVal( $d, "alias", undef );
  1014. my $group = AttrVal( $d, "group", undef );
  1015. my $room = AttrVal( $d, "room", undef );
  1016. fhem "define $val dummy";
  1017. $attr{$val}{comment} =
  1018. "Auto-created by RESIDENTS Toolkit:"
  1019. . " easy switch between on/off for auto time reset of wake-up timer $d";
  1020. if ($alias) {
  1021. $attr{$val}{alias} = "$alias Reset";
  1022. }
  1023. else {
  1024. $attr{$val}{alias} = "Wake-up Timer Reset";
  1025. }
  1026. $attr{$val}{devStateIcon} =
  1027. "auto:time_automatic:off "
  1028. . "off:time_manual_mode:auto";
  1029. $attr{$val}{group} = "$group"
  1030. if ($group);
  1031. $attr{$val}{icon} = "refresh";
  1032. $attr{$val}{room} = "$room"
  1033. if ($room);
  1034. $attr{$val}{setList} = "state:auto,off";
  1035. $attr{$val}{webCmd} = "state";
  1036. fhem "set $val auto";
  1037. Log3 $d, 3,
  1038. "RESIDENTStk $d: "
  1039. . "new slave dummy device $val created";
  1040. }
  1041. }
  1042. if ( defined( &{'DoInitDev'} ) ) {
  1043. DoInitDev($name);
  1044. }
  1045. else {
  1046. RESIDENTStk_DoInitDev($name);
  1047. }
  1048. }
  1049. }
  1050. }
  1051. return "";
  1052. }
  1053. return "" if ( IsDisabled($name) or IsDisabled($devName) );
  1054. # process events from ROOMMATE or GUEST devices
  1055. # only when they hit RESIDENTS devices
  1056. if ( $TYPE eq "RESIDENTS" && $devType =~ /^ROOMMATE|GUEST$/ ) {
  1057. my $events = deviceEvents( $dev, 1 );
  1058. return "" unless ($events);
  1059. readingsBeginUpdate($hash);
  1060. foreach my $event ( @{$events} ) {
  1061. next unless ( defined($event) );
  1062. # state changed
  1063. if ( $event !~ /^[a-zA-Z\d._]+:/
  1064. || $event =~ /^state:/
  1065. || $event =~ /^wayhome:/
  1066. || $event =~ /^wakeup:/ )
  1067. {
  1068. RESIDENTS_UpdateReadings($hash);
  1069. }
  1070. # activity
  1071. if ( $event !~ /^[a-zA-Z\d._]+:/ || $event =~ /^state:/ ) {
  1072. # get user realname
  1073. my $realname =
  1074. AttrVal( $devName,
  1075. AttrVal( $devName, $devPrefix . "realname", "group" ),
  1076. $devName );
  1077. # update statistics
  1078. readingsBulkUpdate( $hash, "lastActivity",
  1079. ReadingsVal( $devName, "state", $event ) );
  1080. readingsBulkUpdate( $hash, "lastActivityBy", $realname );
  1081. readingsBulkUpdate( $hash, "lastActivityByDev", $devName );
  1082. }
  1083. }
  1084. readingsEndUpdate( $hash, 1 );
  1085. return "";
  1086. }
  1087. delete $dev->{CHANGEDWITHSTATE};
  1088. my $events = deviceEvents( $dev, 1 );
  1089. return "" unless ($events);
  1090. my @registeredWakeupdevs =
  1091. split( ',', AttrVal( $name, $prefix . "wakeupDevice", "" ) );
  1092. my @presenceDevices =
  1093. split( ',', AttrVal( $name, $prefix . "presenceDevices", "" ) );
  1094. foreach my $event ( @{$events} ) {
  1095. next unless ( defined($event) );
  1096. my $found = 0;
  1097. # process wakeup devices
  1098. if (@registeredWakeupdevs) {
  1099. # if this is an event of a registered wakeup device
  1100. if ( grep { m/^$devName$/ } @registeredWakeupdevs ) {
  1101. RESIDENTStk_wakeupSet( $devName, $event );
  1102. next;
  1103. }
  1104. # process sub-child events: *_wakeupDevice
  1105. foreach my $wakeupDev (@registeredWakeupdevs) {
  1106. # if this is an event of a registered sub dummy device
  1107. # of one of our wakeup devices
  1108. if ( AttrVal( $wakeupDev, "wakeupResetSwitcher", "" ) eq
  1109. $devName
  1110. && IsDevice( $devName, "dummy" ) )
  1111. {
  1112. RESIDENTStk_wakeupSet( $wakeupDev, $event )
  1113. unless ( $event =~ /^(?:state:\s*)?off$/i );
  1114. $found = 1;
  1115. last;
  1116. }
  1117. }
  1118. next if ($found);
  1119. }
  1120. # process PRESENCE
  1121. if ( @presenceDevices
  1122. && ( grep { /^$devName(:[A-Za-z\d_\.\-\/]+)?$/ } @presenceDevices )
  1123. && $event =~ /^(?:([A-Za-z\d_\.\-\/]+): )?(.+)$/ )
  1124. {
  1125. my $reading = $1;
  1126. my $value = $2;
  1127. # early exit if unexpected event value
  1128. next
  1129. unless ( $value =~
  1130. m/^0|false|absent|disappeared|unavailable|unreachable|disconnected|1|true|present|appeared|available|reachable|connected$/i
  1131. );
  1132. my $counter = {
  1133. absent => 0,
  1134. present => 0,
  1135. };
  1136. for (@presenceDevices) {
  1137. my $r = "presence";
  1138. my $d = $_;
  1139. if ( $d =~ m/^([a-zA-Z\d._]+):(?:([A-Za-z\d_\.\-\/]*))?$/ ) {
  1140. $d = $1;
  1141. $r = $2 if ( $2 && $2 ne "" );
  1142. }
  1143. my $presenceState =
  1144. ReadingsVal( $d, $r, ReadingsVal( $d, "state", "" ) );
  1145. # ignore device if it has unexpected state
  1146. next
  1147. unless ( $presenceState =~
  1148. m/^(0|false|absent|disappeared|unavailable|unreachable|disconnected)|(1|true|present|appeared|available|reachable|connected|)$/i
  1149. );
  1150. $counter->{absent}++ if ( defined($1) );
  1151. $counter->{present}++ if ( defined($2) );
  1152. }
  1153. if ( $counter->{absent} && !$counter->{present} ) {
  1154. Log3 $name, 4,
  1155. "$TYPE $name: " . "Syncing status with $devName = absent";
  1156. fhem "set $name:FILTER=presence=present absent";
  1157. }
  1158. elsif ( $counter->{present} ) {
  1159. Log3 $name, 4,
  1160. "$TYPE $name: " . "Syncing status with $devName = present";
  1161. fhem "set $name:FILTER=presence=absent home";
  1162. }
  1163. }
  1164. }
  1165. return "";
  1166. }
  1167. # module Fn ####################################################################
  1168. sub RESIDENTStk_AutoGone($;$) {
  1169. my ( $mHash, @a ) = @_;
  1170. my $hash = ( $mHash->{HASH} ) ? $mHash->{HASH} : $mHash;
  1171. my $name = $hash->{NAME};
  1172. my $TYPE = GetType($name);
  1173. my $prefix = RESIDENTStk_GetPrefixFromType($name);
  1174. my $autoGoneAfter = AttrVal(
  1175. $hash->{NAME},
  1176. $prefix . "autoGoneAfter",
  1177. ( $prefix eq "rr_" ? 36 : 16 )
  1178. );
  1179. delete $hash->{AUTOGONE} if ( $hash->{AUTOGONE} );
  1180. RESIDENTStk_RemoveInternalTimer( "AutoGone", $hash );
  1181. return if ( IsDisabled($name) || !$autoGoneAfter );
  1182. if ( ReadingsVal( $name, "state", "home" ) eq "absent" ) {
  1183. my ( $date, $time, $y, $m, $d,
  1184. $hour, $min, $sec, $timestamp, $timeDiff );
  1185. my $timestampNow = gettimeofday();
  1186. ( $date, $time ) = split( ' ', $hash->{READINGS}{state}{TIME} );
  1187. ( $y, $m, $d ) = split( '-', $date );
  1188. ( $hour, $min, $sec ) = split( ':', $time );
  1189. $m -= 01;
  1190. $timestamp = timelocal( $sec, $min, $hour, $d, $m, $y );
  1191. $timeDiff = $timestampNow - $timestamp;
  1192. if ( $timeDiff >= $autoGoneAfter * 3600 ) {
  1193. Log3 $name, 3,
  1194. "$TYPE $name: AutoGone timer changed state to 'gone'";
  1195. RESIDENTStk_Set( $hash, $name, "silentSet", "state", "gone" );
  1196. }
  1197. else {
  1198. my $runtime = $timestamp + $autoGoneAfter * 3600;
  1199. $hash->{AUTOGONE} = $runtime;
  1200. Log3 $name, 4, "$TYPE $name: AutoGone timer scheduled: $runtime";
  1201. RESIDENTStk_InternalTimer( "AutoGone", $runtime,
  1202. "RESIDENTStk_AutoGone", $hash, 1 );
  1203. }
  1204. }
  1205. return undef;
  1206. }
  1207. sub RESIDENTStk_DurationTimer($;$) {
  1208. my ( $mHash, @a ) = @_;
  1209. my $hash = ( $mHash->{HASH} ) ? $mHash->{HASH} : $mHash;
  1210. my $name = $hash->{NAME};
  1211. my $TYPE = GetType($name);
  1212. my $prefix = RESIDENTStk_GetPrefixFromType($name);
  1213. my $silent = ( defined( $a[0] ) && $a[0] eq "1" ) ? 1 : 0;
  1214. my $timestampNow = gettimeofday();
  1215. my $diff;
  1216. my $durPresence = "0";
  1217. my $durAbsence = "0";
  1218. my $durSleep = "0";
  1219. my $noDuration = AttrVal( $name, $prefix . "noDuration", 0 );
  1220. delete $hash->{DURATIONTIMER} if ( $hash->{DURATIONTIMER} );
  1221. RESIDENTStk_RemoveInternalTimer( "DurationTimer", $hash );
  1222. return if ( IsDisabled($name) || $noDuration );
  1223. # presence timer
  1224. if ( ReadingsVal( $name, "presence", "absent" ) eq "present"
  1225. && ReadingsVal( $name, "lastArrival", "-" ) ne "-" )
  1226. {
  1227. $durPresence =
  1228. $timestampNow -
  1229. time_str2num( ReadingsVal( $name, "lastArrival", "" ) );
  1230. }
  1231. # absence timer
  1232. if ( ReadingsVal( $name, "presence", "present" ) eq "absent"
  1233. && ReadingsVal( $name, "lastDeparture", "-" ) ne "-" )
  1234. {
  1235. $durAbsence =
  1236. $timestampNow -
  1237. time_str2num( ReadingsVal( $name, "lastDeparture", "" ) );
  1238. }
  1239. # sleep timer
  1240. if ( ReadingsVal( $name, "state", "home" ) eq "asleep"
  1241. && ReadingsVal( $name, "lastSleep", "-" ) ne "-" )
  1242. {
  1243. $durSleep =
  1244. $timestampNow - time_str2num( ReadingsVal( $name, "lastSleep", "" ) );
  1245. }
  1246. my $durPresence_hr =
  1247. ( $durPresence > 0 )
  1248. ? UConv::s2hms($durPresence)
  1249. : "00:00:00";
  1250. my $durPresence_cr =
  1251. ( $durPresence > 60 ) ? int( $durPresence / 60 + 0.5 ) : 0;
  1252. my $durAbsence_hr =
  1253. ( $durAbsence > 0 ) ? UConv::s2hms($durAbsence) : "00:00:00";
  1254. my $durAbsence_cr =
  1255. ( $durAbsence > 60 ) ? int( $durAbsence / 60 + 0.5 ) : 0;
  1256. my $durSleep_hr = ( $durSleep > 0 ) ? UConv::s2hms($durSleep) : "00:00:00";
  1257. my $durSleep_cr = ( $durSleep > 60 ) ? int( $durSleep / 60 + 0.5 ) : 0;
  1258. readingsBeginUpdate($hash) if ( !$silent );
  1259. readingsBulkUpdateIfChanged( $hash, "durTimerPresence_cr",
  1260. $durPresence_cr );
  1261. readingsBulkUpdateIfChanged( $hash, "durTimerPresence", $durPresence_hr );
  1262. readingsBulkUpdateIfChanged( $hash, "durTimerAbsence_cr", $durAbsence_cr );
  1263. readingsBulkUpdateIfChanged( $hash, "durTimerAbsence", $durAbsence_hr );
  1264. readingsBulkUpdateIfChanged( $hash, "durTimerSleep_cr", $durSleep_cr );
  1265. readingsBulkUpdateIfChanged( $hash, "durTimerSleep", $durSleep_hr );
  1266. readingsEndUpdate( $hash, 1 ) if ( !$silent );
  1267. $hash->{DURATIONTIMER} = $timestampNow + 60;
  1268. RESIDENTStk_InternalTimer( "DurationTimer", $hash->{DURATIONTIMER},
  1269. "RESIDENTStk_DurationTimer", $hash, 1 );
  1270. return undef;
  1271. }
  1272. sub RESIDENTStk_SetLocation($$$;$$$$$$) {
  1273. my ( $name, $location, $trigger, $id, $time,
  1274. $lat, $long, $address, $device ) = @_;
  1275. my $hash = $defs{$name};
  1276. my $TYPE = GetType($name);
  1277. my $prefix = RESIDENTStk_GetPrefixFromType($name);
  1278. my $state = ReadingsVal( $name, "state", "initialized" );
  1279. my $presence = ReadingsVal( $name, "presence", "present" );
  1280. my $currLocation = ReadingsVal( $name, "location", "-" );
  1281. my $currWayhome = ReadingsVal( $name, "wayhome", "0" );
  1282. my $currLat = ReadingsVal( $name, "locationLat", "-" );
  1283. my $currLong = ReadingsVal( $name, "locationLong", "-" );
  1284. my $currAddr = ReadingsVal( $name, "locationAddr", "" );
  1285. $id = "-" if ( !$id || $id eq "" );
  1286. $lat = "-" if ( !$lat || $lat eq "" );
  1287. $long = "-" if ( !$long || $long eq "" );
  1288. $address = "" if ( !$address );
  1289. $time = "" if ( !$time );
  1290. $device = "" if ( !$device );
  1291. Log3 $name, 5,
  1292. "$TYPE $name: received location information: id=$id name=$location trig=$trigger date=$time lat=$lat long=$long address:$address device=$device";
  1293. my $searchstring;
  1294. readingsBeginUpdate($hash);
  1295. # read attributes
  1296. my @location_home =
  1297. split( ' ', AttrVal( $name, $prefix . "locationHome", "home" ) );
  1298. my @location_underway =
  1299. split( ' ', AttrVal( $name, $prefix . "locationUnderway", "underway" ) );
  1300. my @location_wayhome =
  1301. split( ' ', AttrVal( $name, $prefix . "locationWayhome", "wayhome" ) );
  1302. $searchstring = quotemeta($location);
  1303. # update locationPresence
  1304. readingsBulkUpdate( $hash, "locationPresence", "present" )
  1305. if ( $trigger == 1 );
  1306. readingsBulkUpdate( $hash, "locationPresence", "absent" )
  1307. if ( $trigger == 0 );
  1308. # check for implicit state change
  1309. #
  1310. my $stateChange = 0;
  1311. # home
  1312. if ( $location eq "home" || grep( m/^$searchstring$/, @location_home ) ) {
  1313. Log3 $name, 5, "$TYPE $name: received signal from home location";
  1314. # home
  1315. if ( $state ne "home"
  1316. && $state ne "gotosleep"
  1317. && $state ne "asleep"
  1318. && $state ne "awoken"
  1319. && $trigger eq "1" )
  1320. {
  1321. $stateChange = 1;
  1322. }
  1323. # absent
  1324. elsif ($state ne "gone"
  1325. && $state ne "none"
  1326. && $state ne "absent"
  1327. && $trigger eq "0" )
  1328. {
  1329. $stateChange = 2;
  1330. }
  1331. }
  1332. # underway
  1333. elsif ($location eq "underway"
  1334. || $location eq "wayhome"
  1335. || grep( m/^$searchstring$/, @location_underway )
  1336. || grep( m/^$searchstring$/, @location_wayhome ) )
  1337. {
  1338. Log3 $name, 5, "$TYPE $name: received signal from underway location";
  1339. # absent
  1340. $stateChange = 2
  1341. if ( $state ne "gone"
  1342. && $state ne "none"
  1343. && $state ne "absent" );
  1344. }
  1345. # wayhome
  1346. if (
  1347. $location eq "wayhome"
  1348. || ( grep( m/^$searchstring$/, @location_wayhome )
  1349. && $trigger eq "0" )
  1350. )
  1351. {
  1352. Log3 $name, 5, "$TYPE $name: wayhome signal received";
  1353. # wayhome=true
  1354. if (
  1355. (
  1356. ( $location eq "wayhome" && $trigger eq "1" )
  1357. || ( $location ne "wayhome" && $trigger eq "0" )
  1358. )
  1359. && ReadingsVal( $name, "wayhome", "0" ) ne "1"
  1360. )
  1361. {
  1362. Log3 $name, 3, "$TYPE $name: on way back home from $location";
  1363. readingsBulkUpdate( $hash, "wayhome", "1" );
  1364. }
  1365. # wayhome=false
  1366. elsif ($location eq "wayhome"
  1367. && $trigger eq "0"
  1368. && ReadingsVal( $name, "wayhome", "0" ) ne "0" )
  1369. {
  1370. Log3 $name, 3,
  1371. "$TYPE $name: seems not to be on way back home anymore";
  1372. readingsBulkUpdate( $hash, "wayhome", "0" );
  1373. }
  1374. }
  1375. # activate wayhome tracing when reaching another location while wayhome=1
  1376. elsif ( $stateChange == 0 && $trigger == 1 && $currWayhome == 1 ) {
  1377. Log3 $name, 3,
  1378. "$TYPE $name: seems to stay at $location before coming home";
  1379. readingsBulkUpdate( $hash, "wayhome", "2" );
  1380. }
  1381. # revert wayhome during active wayhome tracing
  1382. elsif ( $stateChange == 0 && $trigger == 0 && $currWayhome == 2 ) {
  1383. Log3 $name, 3, "$TYPE $name: finally on way back home from $location";
  1384. readingsBulkUpdate( $hash, "wayhome", "1" );
  1385. }
  1386. my $currLongDiff = 0;
  1387. my $currLatDiff = 0;
  1388. $currLongDiff =
  1389. maxNum( ReadingsVal( $name, "lastLocationLong", 0 ), $currLong ) -
  1390. minNum( ReadingsVal( $name, "lastLocationLong", 0 ), $currLong )
  1391. if ( $currLong ne "-" );
  1392. $currLatDiff =
  1393. maxNum( ReadingsVal( $name, "lastLocationLat", 0 ), $currLat ) -
  1394. minNum( ReadingsVal( $name, "lastLocationLat", 0 ), $currLat )
  1395. if ( $currLat ne "-" );
  1396. if (
  1397. $trigger == 1
  1398. && ( $stateChange > 0
  1399. || ReadingsVal( $name, "lastLocation", "-" ) ne $currLocation
  1400. || $currLongDiff > 0.00002
  1401. || $currLatDiff > 0.00002 )
  1402. )
  1403. {
  1404. Log3 $name, 5, "$TYPE $name: archiving last known location";
  1405. readingsBulkUpdate( $hash, "lastLocationLat", $currLat );
  1406. readingsBulkUpdate( $hash, "lastLocationLong", $currLong );
  1407. readingsBulkUpdate( $hash, "lastLocationAddr", $currAddr )
  1408. if ( $currAddr ne "" );
  1409. readingsBulkUpdate( $hash, "lastLocation", $currLocation );
  1410. }
  1411. readingsBulkUpdate( $hash, "locationLat", $lat );
  1412. readingsBulkUpdate( $hash, "locationLong", $long );
  1413. if ( $address ne "" ) {
  1414. readingsBulkUpdate( $hash, "locationAddr", $address );
  1415. }
  1416. elsif ( $currAddr ne "" ) {
  1417. readingsBulkUpdate( $hash, "locationAddr", "-" );
  1418. }
  1419. readingsBulkUpdate( $hash, "location", $location );
  1420. readingsEndUpdate( $hash, 1 );
  1421. # trigger state change
  1422. if ( $stateChange > 0 ) {
  1423. Log3 $name, 4,
  1424. "$TYPE $name: implicit state change caused by location " . $location;
  1425. RESIDENTStk_Set( $hash, $name, "silentSet", "state", "home" )
  1426. if $stateChange == 1;
  1427. RESIDENTStk_Set( $hash, $name, "silentSet", "state", "absent" )
  1428. if $stateChange == 2;
  1429. }
  1430. }
  1431. sub RESIDENTStk_wakeupSet($$) {
  1432. my ( $NAME, $n ) = @_;
  1433. my ( $a, $h ) = parseParams($n);
  1434. my $cmd = shift @$a;
  1435. my $VALUE = join( " ", @$a );
  1436. $cmd =~ s/^state:\s*(.*)$/$1/;
  1437. return if ( $cmd =~ /^[A-Za-z]+:/ );
  1438. # filter non-registered events
  1439. if ( $cmd !~
  1440. m/^((?:next[rR]un)?\s*(off|OFF|([\+\-])?(([0-9]{2}):([0-9]{2})|([1-9]+[0-9]*)))?|trigger|start|stop|end|reset|auto|wakeupResetdays|wakeupDays|wakeupHolidays|wakeupEnforced|wakeupDefaultTime|wakeupOffset)$/i
  1441. )
  1442. {
  1443. Log3 $NAME, 6,
  1444. "RESIDENTStk $NAME: "
  1445. . "received unspecified event '$cmd' - nothing to do";
  1446. return;
  1447. }
  1448. my $wakeupMacro = AttrVal( $NAME, "wakeupMacro", 0 );
  1449. my $wakeupDefaultTime =
  1450. ReadingsVal( $NAME, "wakeupDefaultTime",
  1451. AttrVal( $NAME, "wakeupDefaultTime", 0 ) );
  1452. my $wakeupAtdevice = AttrVal( $NAME, "wakeupAtdevice", 0 );
  1453. my $wakeupUserdevice = AttrVal( $NAME, "wakeupUserdevice", 0 );
  1454. my $wakeupDays =
  1455. ReadingsVal( $NAME, "wakeupDays", AttrVal( $NAME, "wakeupDays", "" ) );
  1456. my $wakeupHolidays =
  1457. ReadingsVal( $NAME, "wakeupHolidays",
  1458. AttrVal( $NAME, "wakeupHolidays", "" ) );
  1459. my $wakeupResetdays =
  1460. ReadingsVal( $NAME, "wakeupResetdays",
  1461. AttrVal( $NAME, "wakeupResetdays", "" ) );
  1462. my $wakeupOffset =
  1463. ReadingsVal( $NAME, "wakeupOffset", AttrVal( $NAME, "wakeupOffset", 0 ) );
  1464. my $wakeupEnforced =
  1465. ReadingsVal( $NAME, "wakeupEnforced",
  1466. AttrVal( $NAME, "wakeupEnforced", 0 ) );
  1467. my $wakeupResetSwitcher = AttrVal( $NAME, "wakeupResetSwitcher", 0 );
  1468. my $holidayDevice = AttrVal( "global", "holiday2we", 0 );
  1469. my $room = AttrVal( $NAME, "room", 0 );
  1470. my $userattr = AttrVal( $NAME, "userattr", 0 );
  1471. my $lastRun = ReadingsVal( $NAME, "lastRun", "07:00" );
  1472. my $nextRun = ReadingsVal( $NAME, "nextRun", "07:00" );
  1473. my $running = ReadingsVal( $NAME, "running", 0 );
  1474. my $wakeupUserdeviceState = ReadingsVal( $wakeupUserdevice, "state", 0 );
  1475. my $atName = "at_" . $NAME;
  1476. my $wdNameGotosleep = "wd_" . $wakeupUserdevice . "_gotosleep";
  1477. my $wdNameAsleep = "wd_" . $wakeupUserdevice . "_asleep";
  1478. my $wdNameAwoken = "wd_" . $wakeupUserdevice . "_awoken";
  1479. my $macroName = "Macro_" . $NAME;
  1480. my $macroNameGotosleep = "Macro_" . $wakeupUserdevice . "_gotosleep";
  1481. my $macroNameAsleep = "Macro_" . $wakeupUserdevice . "_asleep";
  1482. my $macroNameAwoken = "Macro_" . $wakeupUserdevice . "_awoken";
  1483. my $TYPE = GetType($wakeupUserdevice);
  1484. my $prefix = RESIDENTStk_GetPrefixFromType($wakeupUserdevice);
  1485. my $wakeupUserdeviceRealname = "Bewohner";
  1486. if ( $TYPE eq "RESIDENTS" ) {
  1487. $wakeupUserdeviceRealname = AttrVal(
  1488. AttrVal( $NAME, "wakeupUserdevice", "" ),
  1489. AttrVal(
  1490. AttrVal( $NAME, "wakeupUserdevice", "" ),
  1491. $prefix . "realname", "alias"
  1492. ),
  1493. $wakeupUserdeviceRealname
  1494. );
  1495. }
  1496. else {
  1497. $wakeupUserdeviceRealname = AttrVal(
  1498. AttrVal( $NAME, "wakeupUserdevice", "" ),
  1499. AttrVal(
  1500. AttrVal( $NAME, "wakeupUserdevice", "" ),
  1501. $prefix . "realname", "group"
  1502. ),
  1503. $wakeupUserdeviceRealname
  1504. );
  1505. }
  1506. # check for required userattr attribute
  1507. my $userattributes =
  1508. "wakeupOffset:slider,0,1,120 wakeupDefaultTime:OFF,00:00,00:15,00:30,00:45,01:00,01:15,01:30,01:45,02:00,02:15,02:30,02:45,03:00,03:15,03:30,03:45,04:00,04:15,04:30,04:45,05:00,05:15,05:30,05:45,06:00,06:15,06:30,06:45,07:00,07:15,07:30,07:45,08:00,08:15,08:30,08:45,09:00,09:15,09:30,09:45,10:00,10:15,10:30,10:45,11:00,11:15,11:30,11:45,12:00,12:15,12:30,12:45,13:00,13:15,13:30,13:45,14:00,14:15,14:30,14:45,15:00,15:15,15:30,15:45,16:00,16:15,16:30,16:45,17:00,17:15,17:30,17:45,18:00,18:15,18:30,18:45,19:00,19:15,19:30,19:45,20:00,20:15,20:30,20:45,21:00,21:15,21:30,21:45,22:00,22:15,22:30,22:45,23:00,23:15,23:30,23:45 wakeupMacro wakeupUserdevice wakeupAtdevice wakeupResetSwitcher wakeupResetdays:multiple-strict,0,1,2,3,4,5,6 wakeupDays:multiple-strict,0,1,2,3,4,5,6 wakeupHolidays:andHoliday,orHoliday,andNoHoliday,orNoHoliday wakeupEnforced:0,1,2,3 wakeupWaitPeriod:slider,0,1,360";
  1509. if ( !$userattr || $userattr ne $userattributes ) {
  1510. Log3 $NAME, 4,
  1511. "RESIDENTStk $NAME: "
  1512. . "adjusting dummy device for required attribute userattr";
  1513. fhem "attr $NAME userattr $userattributes";
  1514. }
  1515. # check for required userdevice attribute
  1516. if ( !$wakeupUserdevice ) {
  1517. Log3 $NAME, 3,
  1518. "RESIDENTStk $NAME: "
  1519. . "WARNING - set attribute wakeupUserdevice before running wakeup function!";
  1520. }
  1521. elsif ( !IsDevice($wakeupUserdevice) ) {
  1522. Log3 $NAME, 3,
  1523. "RESIDENTStk $NAME: "
  1524. . "WARNING - user device $wakeupUserdevice does not exist!";
  1525. }
  1526. elsif ( !IsDevice( $wakeupUserdevice, "RESIDENTS|ROOMMATE|GUEST" ) ) {
  1527. Log3 $NAME, 3,
  1528. "RESIDENTStk $NAME: "
  1529. . "WARNING - defined user device '$wakeupUserdevice' is not a RESIDENTS, ROOMMATE or GUEST device!";
  1530. }
  1531. # check for required wakeupMacro attribute
  1532. if ( !$wakeupMacro ) {
  1533. Log3 $NAME, 4,
  1534. "RESIDENTStk $NAME: "
  1535. . "adjusting dummy device for required attribute wakeupMacro";
  1536. fhem "attr $NAME wakeupMacro $macroName";
  1537. $wakeupMacro = $macroName;
  1538. }
  1539. if ( !IsDevice($wakeupMacro) ) {
  1540. my $wakeUpMacroTemplate = "{\
  1541. ##=============================================================================\
  1542. ## This is an example wake-up program running within a period of 30 minutes:\
  1543. ## - drive shutters upwards slowly\
  1544. ## - light up a HUE bulb from 2000K to 5600K\
  1545. ## - have some voice notifications via SONOS\
  1546. ## - have some wake-up chill music via SONOS during program run\
  1547. ##\
  1548. ## Actual FHEM commands are commented out by default as they would need\
  1549. ## to be adapted to your configuration.\
  1550. ##\
  1551. ## Available wake-up variables:\
  1552. ## 1. \$EVTPART0 -> start or stop\
  1553. ## 2. \$EVTPART1 -> target wake-up time\
  1554. ## 3. \$EVTPART2 -> wake-up begin time considering wakeupOffset attribute\
  1555. ## 4. \$EVTPART3 -> enforced wakeup yes=1,no=0 from wakeupEnforced attribute\
  1556. ## 5. \$EVTPART4 -> device name of the user which called this macro\
  1557. ## 6. \$EVTPART5 -> current state of user\
  1558. ##=============================================================================\
  1559. \
  1560. ##-----------------------------------------------------------------------------\
  1561. ## DELETE TEMP. AT-COMMANDS POTENTIALLY CREATED EARLIER BY THIS SCRIPT\
  1562. ## Executed for start to cleanup in case this wake-up automation is re-started.\
  1563. ## Executed for stop to cleanup in case the user ends this automation earlier.\
  1564. ##\
  1565. fhem \"delete atTmp_.*_\".\$NAME.\":FILTER=TYPE=at\";;\
  1566. \
  1567. ##-----------------------------------------------------------------------------\
  1568. ## BEGIN WAKE-UP PROGRAM\
  1569. ## Run first automation commands and create temp. at-devices for lagging actions.\
  1570. ##\
  1571. if (\$EVTPART0 eq \"start\") {\
  1572. Log3 \$NAME, 3, \"\$NAME: Wake-up program started for \$EVTPART4 with target time \$EVTPART1. Current state: \$EVTPART5\";;\
  1573. \
  1574. # fhem \"set BR_FloorLamp:FILTER=onoff=0 pct 1 : ct 2000 : transitiontime 0;; set BR_FloorLamp:FILTER=pct=1 pct 90 : ct 5600 : transitiontime 17700\";;\
  1575. \
  1576. # fhem \"define atTmp_1_\$NAME at +00:10:00 set BR_Shutter:FILTER=pct<20 pct 20\";;\
  1577. # fhem \"define atTmp_2_\$NAME at +00:20:00 set BR_Shutter:FILTER=pct<40 pct 40\";;\
  1578. # fhem \"define atTmp_4_\$NAME at +00:30:00 msg audio \\\@Sonos_Bedroom |Hint| Es ist \".\$EVTPART1.\" Uhr, Zeit zum aufstehen!;;;; set BR_FloorLamp:FILTER=pct<100 pct 100 60;;;; sleep 10;;;; set BR_Shutter:FILTER=pct<60 pct 60;;;; set Sonos_Bedroom:FILTER=Volume<10 Volume 10 10\";;\
  1579. \
  1580. # if wake-up should be enforced\
  1581. if (\$EVTPART3) {\
  1582. Log3 \$NAME, 3, \"\$NAME: planning enforced wake-up\";;\
  1583. # fhem \"define atTmp_3_\$NAME at +00:25:00 set Sonos_Bedroom:FILTER=Volume>4 Volume 4;;;; sleep 0.5;;;; set Sonos_Bedroom:FILTER=Shuffle=0 Shuffle 1;;;; sleep 0.5;;;; set Sonos_Bedroom StartFavourite Morning%20Sounds\";;\
  1584. # fhem \"define atTmp_4_\$NAME at +00:26:00 set Sonos_Bedroom:FILTER=Volume<5 Volume 5\";;\
  1585. # fhem \"define atTmp_5_\$NAME at +00:27:00 set Sonos_Bedroom:FILTER=Volume<6 Volume 6\";;\
  1586. # fhem \"define atTmp_6_\$NAME at +00:28:00 set Sonos_Bedroom:FILTER=Volume<7 Volume 7\";;\
  1587. # fhem \"define atTmp_7_\$NAME at +00:29:00 set Sonos_Bedroom:FILTER=Volume<8 Volume 8\";;\
  1588. }\
  1589. }\
  1590. \
  1591. ##-----------------------------------------------------------------------------\
  1592. ## END WAKE-UP PROGRAM (OPTIONAL)\
  1593. ## Put some post wake-up tasks here like reminders after the actual wake-up period.\
  1594. ##\
  1595. ## Note: Will only be run when program ends normally after minutes specified in wakeupOffset.\
  1596. ## If stop was user-forced by sending explicit set-command 'stop', this is not executed\
  1597. ## assuming the user does not want any further automation activities.\
  1598. ##\
  1599. if (\$EVTPART0 eq \"stop\") {\
  1600. Log3 \$NAME, 3, \"\$NAME: Wake-up program ended for \$EVTPART4 with target time \$EVTPART1. Current state: \$EVTPART5\";;\
  1601. \
  1602. # if wake-up should be enforced, auto-change user state from 'asleep' to 'awoken'\
  1603. # after a small additional nap to kick you out of bed if user did not confirm to be awake :-)\
  1604. # An additional notify for user state 'awoken' may take further actions\
  1605. # and change to state 'home' afterwards.\
  1606. if (\$EVTPART3) {\
  1607. fhem \"define atTmp_9_\$NAME at +00:05:00 set \$EVTPART4:FILTER=state=asleep awoken\";;\
  1608. \
  1609. # Without enforced wake-up, be jentle and just set user state to 'home' after some\
  1610. # additional long nap time\
  1611. } else {\
  1612. fhem \"define atTmp_9_\$NAME at +01:30:00 set \$EVTPART4:FILTER=state=asleep home\";;\
  1613. }\
  1614. }\
  1615. \
  1616. }\
  1617. ";
  1618. Log3 $NAME, 3,
  1619. "RESIDENTStk $NAME: "
  1620. . "new notify macro device $wakeupMacro created";
  1621. fhem "define $wakeupMacro notify $wakeupMacro $wakeUpMacroTemplate";
  1622. fhem
  1623. "attr $wakeupMacro comment Macro auto-created by RESIDENTS Toolkit";
  1624. fhem "attr $wakeupMacro room $room"
  1625. if ($room);
  1626. }
  1627. elsif ( !IsDevice( $wakeupMacro, "notify" ) ) {
  1628. Log3 $NAME, 3,
  1629. "RESIDENTStk $NAME: "
  1630. . "WARNING - defined macro device '$wakeupMacro' is not a notify device!";
  1631. }
  1632. # check for required wakeupAtdevice attribute
  1633. if ( !$wakeupAtdevice ) {
  1634. Log3 $NAME, 4,
  1635. "RESIDENTStk $NAME: "
  1636. . "adjusting dummy device for required attribute wakeupAtdevice";
  1637. fhem "attr $NAME wakeupAtdevice $atName";
  1638. $wakeupAtdevice = $atName;
  1639. }
  1640. if ( !IsDevice($wakeupAtdevice) ) {
  1641. Log3 $NAME, 3,
  1642. "RESIDENTStk $NAME: " . "new at-device $wakeupAtdevice created";
  1643. fhem "define $wakeupAtdevice "
  1644. . "at *{RESIDENTStk_wakeupGetBegin(\"$NAME\",\"$wakeupAtdevice\")} set $NAME trigger";
  1645. fhem "attr $wakeupAtdevice "
  1646. . "comment Auto-created by RESIDENTS Toolkit: trigger wake-up timer at specific time";
  1647. fhem "attr $wakeupAtdevice computeAfterInit 1";
  1648. fhem "attr $wakeupAtdevice room $room"
  1649. if ($room);
  1650. ########
  1651. # (re)create other notify and watchdog templates
  1652. # for ROOMMATE or GUEST devices
  1653. # macro: gotosleep
  1654. if ( !IsDevice( $wakeupUserdevice, "RESIDENTS" )
  1655. && !IsDevice($macroNameGotosleep) )
  1656. {
  1657. my $templateGotosleep = "{\
  1658. ##=============================================================================\
  1659. ## This is an example macro when gettin' ready for bed.\
  1660. ##\
  1661. ## Actual FHEM commands are commented out by default as they would need\
  1662. ## to be adapted to your configuration.\
  1663. ##=============================================================================\
  1664. \
  1665. ##-----------------------------------------------------------------------------\
  1666. ## LIGHT SCENE\
  1667. ##\
  1668. \
  1669. ## Dim up floor light\
  1670. #fhem \"set FL_Light:FILTER=pct=0 pct 20\";;\
  1671. \
  1672. ## Dim down bright ceilling light in bedroom\
  1673. #fhem \"set BR_Light:FILTER=pct!=0 pct 0 5\";;\
  1674. \
  1675. ## Dim up HUE floor lamp with very low color temperature\
  1676. #fhem \"set BR_FloorLamp ct 2000 : pct 80 : transitiontime 30\";;\
  1677. \
  1678. \
  1679. ##-----------------------------------------------------------------------------\
  1680. ## ENVIRONMENT SCENE\
  1681. ##\
  1682. \
  1683. ## Turn down shutter to 28%\
  1684. #fhem \"set BR_Shutter:FILTER=pct>28 pct 28\";;\
  1685. \
  1686. \
  1687. ##-----------------------------------------------------------------------------\
  1688. ## PLAY CHILLOUT MUSIC\
  1689. ## via SONOS at Bedroom and Bathroom\
  1690. ##\
  1691. \
  1692. ## Stop playback bedroom's Sonos device might be involved in\
  1693. #fhem \"set Sonos_Bedroom:transportState=PLAYING stop;;\";;\
  1694. \
  1695. ## Make Bedroom's and Bathroom's Sonos devices a single device\
  1696. ## and do not touch other Sonos devices (this is why we use RemoveMember!)\
  1697. #fhem \"sleep 0.5;; set Sonos_Bedroom RemoveMember Sonos_Bedroom\";;\
  1698. #fhem \"sleep 1.0;; set Sonos_Bathroom RemoveMember Sonos_Bathroom\";;\
  1699. \
  1700. ## Group Bedroom's and Bathroom's Sonos devices with Bedroom as master\
  1701. #fhem \"sleep 2.0;; set Sonos_Bedroom AddMember Sonos_Bathroom;; set Sonos_Bedroom:FILTER=Shuffle!=1 Shuffle 1;; set Sonos_Bedroom:FILTER=Volume!=12,Sonos_Bathroom:FILTER=Volume!=12 Volume 12\";;\
  1702. \
  1703. ## Start music from playlist\
  1704. #fhem \"sleep 3.0;; set Sonos_Bedroom StartFavourite Evening%%20Chill\";;\
  1705. \
  1706. return;;\
  1707. }";
  1708. Log3 $NAME, 3,
  1709. "RESIDENTStk $NAME: "
  1710. . "new notify macro device $macroNameGotosleep created";
  1711. fhem "define $macroNameGotosleep "
  1712. . "notify $macroNameGotosleep $templateGotosleep";
  1713. fhem "attr $macroNameGotosleep "
  1714. . "comment Auto-created by RESIDENTS Toolkit: FHEM commands to run when gettin' ready for bed";
  1715. fhem "attr $macroNameGotosleep room $room"
  1716. if ($room);
  1717. }
  1718. # wd: gotosleep
  1719. if ( !IsDevice($wdNameGotosleep) ) {
  1720. Log3 $NAME, 3,
  1721. "RESIDENTStk $NAME: "
  1722. . "new watchdog device $wdNameGotosleep created";
  1723. fhem "define $wdNameGotosleep "
  1724. . "watchdog $wakeupUserdevice:(gotosleep|bettfertig) 00:00:04 $wakeupUserdevice:(home|anwesend|zuhause|absent|abwesend|gone|verreist|asleep|schlaeft|schläft|awoken|aufgestanden) trigger $macroNameGotosleep";
  1725. fhem "attr $wdNameGotosleep autoRestart 1";
  1726. fhem "attr $wdNameGotosleep "
  1727. . "comment Auto-created by RESIDENTS Toolkit: trigger macro after going to state gotosleep";
  1728. fhem "attr $wdNameGotosleep room $room"
  1729. if ($room);
  1730. }
  1731. # macro: asleep
  1732. if ( !IsDevice( $wakeupUserdevice, "RESIDENTS" )
  1733. && !IsDevice($macroNameAsleep) )
  1734. {
  1735. my $templateAsleep = "{\
  1736. ##=============================================================================\
  1737. ## This is an example macro when jumpin' into bed and start to sleep.\
  1738. ##\
  1739. ## Actual FHEM commands are commented out by default as they would need\
  1740. ## to be adapted to your configuration.\
  1741. ##=============================================================================\
  1742. \
  1743. ##-----------------------------------------------------------------------------\
  1744. ## LIGHT SCENE\
  1745. ##\
  1746. \
  1747. ## In 15 seconds, turn off all lights in Bedroom using a structure\
  1748. #fhem \"sleep 15;; set g_BR_Lights [FILTER=state!=off] off\";;\
  1749. \
  1750. \
  1751. ##-----------------------------------------------------------------------------\
  1752. ## ENVIRONMENT SCENE\
  1753. ##\
  1754. \
  1755. ## In 12 seconds, close shutter if window is closed\
  1756. #if (ReadingsVal(\"BR_Window\",\"state\",0) eq \"closed\") {\
  1757. # fhem \"sleep 12;; set BR_Shutter:FILTER=pct>0 close\";;\
  1758. \
  1759. ## In 12 seconds, if window is not closed just make sure shutter is at least\
  1760. ## at 28% to allow some ventilation\
  1761. #} else {\
  1762. # fhem \"sleep 12;; set BR_Shutter:FILTER=pct>28 pct 28\";;\
  1763. #}\
  1764. \
  1765. \
  1766. ##-----------------------------------------------------------------------------\
  1767. ## PLAY WAKE-UP ANNOUNCEMENT\
  1768. ## via SONOS at Bedroom and stop playback elsewhere\
  1769. ##\
  1770. \
  1771. #my \$nextWakeup = ReadingsVal(\"$wakeupUserdevice\",\"nextWakeup\",\"none\");;
  1772. #my \$text = \"|Hint| $wakeupUserdeviceRealname, es ist kein Wecker gestellt. Du könntest verschlafen! Trotzdem eine gute Nacht.\";;
  1773. #if (\$nextWakeup ne \"OFF\") {
  1774. # \$text = \"|Hint| $wakeupUserdeviceRealname, dein Wecker ist auf \$nextWakeup Uhr gestellt. Gute Nacht und schlaf gut.\";;
  1775. #}
  1776. #if (\$nextWakeup ne \"none\") {
  1777. # fhem \"set Sonos_Bedroom RemoveMember Sonos_Bedroom;; sleep 0.5;; msg audio \\\@Sonos_Bedroom \$text\";;\
  1778. #}
  1779. \
  1780. return;;\
  1781. }";
  1782. Log3 $NAME, 3,
  1783. "RESIDENTStk $NAME: "
  1784. . "new notify macro device $macroNameAsleep created";
  1785. fhem
  1786. "define $macroNameAsleep notify $macroNameAsleep $templateAsleep";
  1787. fhem "attr $macroNameAsleep "
  1788. . "comment Auto-created by RESIDENTS Toolkit: FHEM commands to run when jumpin' into bed and start to sleep";
  1789. fhem "attr $macroNameAsleep room $room"
  1790. if ($room);
  1791. }
  1792. # wd: asleep
  1793. if ( !IsDevice($wdNameAsleep) ) {
  1794. Log3 $NAME, 3,
  1795. "RESIDENTStk $NAME: "
  1796. . "new watchdog device $wdNameAsleep created";
  1797. fhem "define $wdNameAsleep "
  1798. . "watchdog $wakeupUserdevice:(asleep|schlaeft|schläft) 00:00:04 $wakeupUserdevice:(home|anwesend|zuhause|absent|abwesend|gone|verreist|gotosleep|bettfertig|awoken|aufgestanden) trigger $macroNameAsleep";
  1799. fhem "attr $wdNameAsleep autoRestart 1";
  1800. fhem "attr $wdNameAsleep "
  1801. . "comment Auto-created by RESIDENTS Toolkit: trigger macro after going to state asleep";
  1802. fhem "attr $wdNameAsleep room $room"
  1803. if ($room);
  1804. }
  1805. # macro: awoken
  1806. if ( !IsDevice( $wakeupUserdevice, "RESIDENTS" )
  1807. && !IsDevice($macroNameAwoken) )
  1808. {
  1809. my $templateAwoken = "{\
  1810. ##=============================================================================\
  1811. ## This is an example macro after confirming to be awake.\
  1812. ##\
  1813. ## Actual FHEM commands are commented out by default as they would need\
  1814. ## to be adapted to your configuration.\
  1815. ##=============================================================================\
  1816. \
  1817. ##-----------------------------------------------------------------------------\
  1818. ## LIGHT SCENE\
  1819. ##\
  1820. \
  1821. ## Dim up HUE floor lamp to maximum with cold color temperature\
  1822. #fhem \"set BR_FloorLamp:FILTER=pct<100 pct 100 : ct 6500 : transitiontime 30\";;\
  1823. \
  1824. \
  1825. ##-----------------------------------------------------------------------------\
  1826. ## ENVIRONMENT SCENE\
  1827. ##\
  1828. \
  1829. ## In 22 seconds, turn up shutter at least until 60%\
  1830. #fhem \"sleep 22;; set BR_Shutter:FILTER=pct<60 60\";;\
  1831. \
  1832. \
  1833. ##-----------------------------------------------------------------------------\
  1834. ## RAMP-UP ALL MORNING STUFF\
  1835. ##\
  1836. \
  1837. ## Play morning announcement via SONOS at Bedroom\
  1838. #fhem \"set Sonos_Bedroom Stop;; msg audio \\\@Sonos_Bedroom |Hint| Guten Morgen, $wakeupUserdeviceRealname.\";;\
  1839. \
  1840. ## In 10 seconds, start webradio playback in Bedroom\
  1841. #fhem \"sleep 10;; set Sonos_Bedroom StartRadio /Charivari/;; sleep 2;; set Sonos_Bedroom Volume 15\";;\
  1842. \
  1843. ## Make webradio stream available at Bathroom and\
  1844. ## Kitchen 5 seonds after it started\
  1845. #fhem \"set Sonos_Bathroom,Sonos_Kitchen Volume 15;; sleep 15;; set Sonos_Bedroom AddMember Sonos_Bathroom;; set Sonos_Bedroom AddMember Sonos_Kitchen\";;\
  1846. \
  1847. ## change user state to home after 60 seconds\
  1848. fhem \"sleep 60;; set $wakeupUserdevice:FILTER=state!=home home\";;\
  1849. \
  1850. return;;\
  1851. }";
  1852. Log3 $NAME, 3,
  1853. "RESIDENTStk $NAME: "
  1854. . "new notify macro device $macroNameAwoken created";
  1855. fhem
  1856. "define $macroNameAwoken notify $macroNameAwoken $templateAwoken";
  1857. fhem "attr $macroNameAwoken "
  1858. . "comment Auto-created by RESIDENTS Toolkit: FHEM commands to run after confirming to be awake";
  1859. fhem "attr $macroNameAwoken room $room"
  1860. if ($room);
  1861. }
  1862. # wd: awoken
  1863. if ( !IsDevice($wdNameAwoken) ) {
  1864. Log3 $NAME, 3,
  1865. "RESIDENTStk $NAME: "
  1866. . "new watchdog device $wdNameAwoken created";
  1867. fhem "define $wdNameAwoken "
  1868. . "watchdog $wakeupUserdevice:(awoken|aufgestanden) 00:00:04 $wakeupUserdevice:(home|anwesend|zuhause|absent|abwesend|gone|verreist|gotosleep|bettfertig|asleep|schlaeft|schläft) trigger $macroNameAwoken";
  1869. fhem "attr $wdNameAwoken autoRestart 1";
  1870. fhem "attr $wdNameAwoken "
  1871. . "comment Auto-created by RESIDENTS Toolkit: trigger macro after going to state awoken";
  1872. fhem "attr $wdNameAwoken room $room"
  1873. if ($room);
  1874. }
  1875. ########
  1876. # (re)create other notify and watchdog templates
  1877. # for RESIDENT devices
  1878. #
  1879. my $RESIDENTGROUPS;
  1880. if ( IsDevice( $wakeupUserdevice, "RESIDENTS" ) ) {
  1881. $RESIDENTGROUPS = $wakeupUserdevice;
  1882. }
  1883. elsif ( IsDevice($wakeupUserdevice)
  1884. && defined( $defs{$wakeupUserdevice}{RESIDENTGROUPS} ) )
  1885. {
  1886. $RESIDENTGROUPS = $defs{$wakeupUserdevice}{RESIDENTGROUPS};
  1887. }
  1888. for my $deviceName ( split /,/, $RESIDENTGROUPS ) {
  1889. my $macroRNameGotosleep = "Macro_" . $deviceName . "_gotosleep";
  1890. my $macroRNameAsleep = "Macro_" . $deviceName . "_asleep";
  1891. my $macroRNameAwoken = "Macro_" . $deviceName . "_awoken";
  1892. my $wdRNameGotosleep = "wd_" . $deviceName . "_gotosleep";
  1893. my $wdRNameAsleep = "wd_" . $deviceName . "_asleep";
  1894. my $wdRNameAwoken = "wd_" . $deviceName . "_awoken";
  1895. # macro: gotosleep
  1896. if ( !IsDevice($macroRNameGotosleep) ) {
  1897. my $templateGotosleep = "{\
  1898. ##=============================================================================\
  1899. ## This is an example macro when all residents are gettin' ready for bed.\
  1900. ##\
  1901. ## Actual FHEM commands are commented out by default as they would need\
  1902. ## to be adapted to your configuration.\
  1903. ##=============================================================================\
  1904. \
  1905. ##-----------------------------------------------------------------------------\
  1906. ## HOUSE MODE\
  1907. ## Enforce evening mode if we are still in day mode\
  1908. ##\
  1909. \
  1910. #fhem \"set HouseMode:FILTER=state=day evening\";;\
  1911. \
  1912. \
  1913. ##-----------------------------------------------------------------------------\
  1914. ## LIGHT SCENE\
  1915. ##\
  1916. \
  1917. ## In 10 seconds, turn off lights in unused rooms using structures\
  1918. #fhem \"sleep 10;; set g_LR_Lights,g_KT_Lights [FILTER=state!=off] off\";;\
  1919. \
  1920. \
  1921. ##-----------------------------------------------------------------------------\
  1922. ## ENVIRONMENT SCENE\
  1923. ##\
  1924. \
  1925. ## Turn off all media devices in the Living Room\
  1926. #fhem \"set g_HSE_Media [FILTER=state!=off] off\";;\
  1927. \
  1928. return;;\
  1929. }";
  1930. Log3 $NAME, 3,
  1931. "RESIDENTStk $NAME: "
  1932. . "new notify macro device $macroRNameGotosleep created";
  1933. fhem "define $macroRNameGotosleep "
  1934. . "notify $macroRNameGotosleep $templateGotosleep";
  1935. fhem "attr $macroRNameGotosleep "
  1936. . "comment Auto-created by RESIDENTS Toolkit: FHEM commands to run when all residents are gettin' ready for bed";
  1937. fhem "attr $macroRNameGotosleep room $room"
  1938. if ($room);
  1939. }
  1940. # wd: gotosleep
  1941. if ( !IsDevice($wdRNameGotosleep) ) {
  1942. Log3 $NAME, 3,
  1943. "RESIDENTStk $NAME: "
  1944. . "new watchdog device $wdRNameGotosleep created";
  1945. fhem "define $wdRNameGotosleep "
  1946. . "watchdog $deviceName:(gotosleep|bettfertig) 00:00:03 $deviceName:(home|anwesend|zuhause|absent|abwesend|gone|verreist|asleep|schlaeft|schläft|awoken|aufgestanden) trigger $macroRNameGotosleep";
  1947. fhem "attr $wdRNameGotosleep autoRestart 1";
  1948. fhem "attr $wdRNameGotosleep "
  1949. . "comment Auto-created by RESIDENTS Toolkit: trigger macro after going to state gotosleep";
  1950. fhem "attr $wdRNameGotosleep room $room"
  1951. if ($room);
  1952. }
  1953. # macro: asleep
  1954. if ( !IsDevice($macroRNameAsleep) ) {
  1955. my $templateAsleep = "{\
  1956. ##=============================================================================\
  1957. ## This is an example macro when all residents are in their beds.\
  1958. ##\
  1959. ## Actual FHEM commands are commented out by default as they would need\
  1960. ## to be adapted to your configuration.\
  1961. ##=============================================================================\
  1962. \
  1963. ##-----------------------------------------------------------------------------\
  1964. ## HOUSE MODE\
  1965. ## Enforce night mode if we are still in evening mode\
  1966. ##\
  1967. \
  1968. #fhem \"set HouseMode:FILTER=state=evening night\";;\
  1969. \
  1970. \
  1971. ##-----------------------------------------------------------------------------\
  1972. ## LIGHT SCENE\
  1973. ##\
  1974. \
  1975. ## In 20 seconds, turn off all lights in the house using structures\
  1976. #fhem \"sleep 20;; set g_HSE_Lights [FILTER=state!=off] off\";;\
  1977. \
  1978. \
  1979. ##-----------------------------------------------------------------------------\
  1980. ## ENVIRONMENT SCENE\
  1981. ##\
  1982. \
  1983. ## Stop playback at SONOS devices in shared rooms, e.g. Bathroom\
  1984. #fhem \"set Sonos_Bathroom:FILTER=transportState=PLAYING Stop\";;\
  1985. \
  1986. return;;\
  1987. }";
  1988. Log3 $NAME, 3,
  1989. "RESIDENTStk $NAME: "
  1990. . "new notify macro device $macroRNameAsleep created";
  1991. fhem "define $macroRNameAsleep "
  1992. . "notify $macroRNameAsleep $templateAsleep";
  1993. fhem "attr $macroRNameAsleep "
  1994. . "comment Auto-created by RESIDENTS Toolkit: FHEM commands to run when all residents are in their beds";
  1995. fhem "attr $macroRNameAsleep room $room"
  1996. if ($room);
  1997. }
  1998. # wd: asleep
  1999. if ( !IsDevice($wdRNameAsleep) ) {
  2000. Log3 $NAME, 3,
  2001. "RESIDENTStk $NAME: "
  2002. . "new watchdog device $wdNameAsleep created";
  2003. fhem "define $wdRNameAsleep "
  2004. . "watchdog $deviceName:(asleep|schlaeft|schläft) 00:00:03 $deviceName:(home|anwesend|zuhause|absent|abwesend|gone|verreist|gotosleep|bettfertig|awoken|aufgestanden) trigger $macroRNameAsleep";
  2005. fhem "attr $wdRNameAsleep autoRestart 1";
  2006. fhem "attr $wdRNameAsleep "
  2007. . "comment Auto-created by RESIDENTS Toolkit: trigger macro after going to state asleep";
  2008. fhem "attr $wdRNameAsleep room $room"
  2009. if ($room);
  2010. }
  2011. # macro: awoken
  2012. if ( !IsDevice($macroRNameAwoken) ) {
  2013. my $templateAwoken = "{\
  2014. ##=============================================================================\
  2015. ## This is an example macro when the first resident has confirmed to be awake\
  2016. ##\
  2017. ## Actual FHEM commands are commented out by default as they would need\
  2018. ## to be adapted to your configuration.\
  2019. ##=============================================================================\
  2020. \
  2021. ##-----------------------------------------------------------------------------\
  2022. ## HOUSE MODE\
  2023. ## Enforce morning mode if we are still in night mode\
  2024. ##\
  2025. \
  2026. #fhem \"set HouseMode:FILTER=state=night morning\";;\
  2027. \
  2028. \
  2029. ##-----------------------------------------------------------------------------\
  2030. ## LIGHT SCENE\
  2031. ##\
  2032. \
  2033. ## Turn on lights in the Kitchen already but set a timer to turn it off again\
  2034. #fhem \"set KT_CounterLight on-for-timer 6300\";;\
  2035. \
  2036. \
  2037. ##-----------------------------------------------------------------------------\
  2038. ## PREPARATIONS\
  2039. ##\
  2040. \
  2041. ## In 90 minutes, switch House Mode to 'day' and\
  2042. ## play voice announcement via SONOS\
  2043. #unless (IsDevice(\"atTmpHouseMode_day\")) {\
  2044. # fhem \"define atTmpHouseMode_day at +01:30:00 {if (ReadingsVal(\\\"HouseMode\\\", \\\"state\\\", 0) ne \\\"day\\\") {fhem \\\"msg audio \\\@Sonos_Kitchen Tagesmodus wird etabliert.;;;; sleep 10;;;; set HouseMode day\\\"}}\";;\
  2045. #}\
  2046. \
  2047. return;;\
  2048. }";
  2049. Log3 $NAME, 3,
  2050. "RESIDENTStk $NAME: "
  2051. . "new notify macro device $macroRNameAwoken created";
  2052. fhem "define $macroRNameAwoken "
  2053. . "notify $macroRNameAwoken $templateAwoken";
  2054. fhem "attr $macroRNameAwoken "
  2055. . "comment Auto-created by RESIDENTS Toolkit: FHEM commands to run after first resident confirmed to be awake";
  2056. fhem "attr $macroRNameAwoken room $room"
  2057. if ($room);
  2058. }
  2059. # wd: awoken
  2060. if ( !IsDevice($wdRNameAwoken) ) {
  2061. Log3 $NAME, 3,
  2062. "RESIDENTStk $NAME: "
  2063. . "new watchdog device $wdNameAwoken created";
  2064. fhem "define $wdRNameAwoken "
  2065. . "watchdog $deviceName:(awoken|aufgestanden) 00:00:04 $deviceName:(home|anwesend|zuhause|absent|abwesend|gone|verreist|gotosleep|bettfertig|asleep|schlaeft|schläft) trigger $macroRNameAwoken";
  2066. fhem "attr $wdRNameAwoken autoRestart 1";
  2067. fhem "attr $wdRNameAwoken "
  2068. . "comment Auto-created by RESIDENTS Toolkit: trigger macro after going to state awoken";
  2069. fhem "attr $wdRNameAwoken room $room"
  2070. if ($room);
  2071. }
  2072. }
  2073. }
  2074. elsif ( !IsDevice( $wakeupAtdevice, "at" ) ) {
  2075. Log3 $NAME, 3,
  2076. "RESIDENTStk $NAME: "
  2077. . "WARNING - defined at-device '$wakeupAtdevice' is not an at-device!";
  2078. }
  2079. elsif ( AttrVal( $wakeupAtdevice, "computeAfterInit", 0 ) ne "1" ) {
  2080. Log3 $NAME, 3,
  2081. "RESIDENTStk $NAME: "
  2082. . "Correcting '$wakeupAtdevice' attribute computeAfterInit required for correct recalculation after reboot";
  2083. fhem "attr $wakeupAtdevice computeAfterInit 1";
  2084. }
  2085. # verify holiday2we attribute
  2086. if ( $wakeupHolidays ne "" ) {
  2087. if ( !$holidayDevice ) {
  2088. Log3 $NAME, 3,
  2089. "RESIDENTStk $NAME: "
  2090. . "ERROR - wakeupHolidays set in this alarm clock but global attribute holiday2we not set!";
  2091. return "ERROR: "
  2092. . "wakeupHolidays set in this alarm clock but global attribute holiday2we not set!";
  2093. }
  2094. elsif ( !IsDevice($holidayDevice) ) {
  2095. Log3 $NAME, 3,
  2096. "RESIDENTStk $NAME: "
  2097. . "ERROR - global attribute holiday2we has reference to non-existing device $holidayDevice";
  2098. return "ERROR: "
  2099. . "global attribute holiday2we has reference to non-existing device $holidayDevice";
  2100. }
  2101. elsif ( !IsDevice( $holidayDevice, "holiday" ) ) {
  2102. Log3 $NAME, 3,
  2103. "RESIDENTStk $NAME: "
  2104. . "ERROR - global attribute holiday2we seems to have invalid device reference - $holidayDevice is not of type 'holiday'";
  2105. return "ERROR: "
  2106. . "global attribute holiday2we seems to have invalid device reference - $holidayDevice is not of type 'holiday'";
  2107. }
  2108. }
  2109. # start
  2110. #
  2111. if ( $cmd eq "start" ) {
  2112. RESIDENTStk_wakeupRun( $NAME, 1 );
  2113. }
  2114. # trigger
  2115. #
  2116. elsif ( $cmd eq "trigger" ) {
  2117. RESIDENTStk_wakeupRun($NAME);
  2118. }
  2119. # stop | end
  2120. #
  2121. elsif ( $cmd eq "stop" || $cmd eq "end" ) {
  2122. if ($running) {
  2123. Log3 $NAME, 4, "RESIDENTStk $NAME: " . "stopping wake-up program";
  2124. readingsSingleUpdate( $defs{$NAME}, "running", "0", 1 );
  2125. }
  2126. fhem "set $NAME nextRun $nextRun";
  2127. return unless ($running);
  2128. # trigger macro again so it may clean up it's stuff.
  2129. # use $EVTPART1 to check
  2130. if ( !$wakeupMacro ) {
  2131. Log3 $NAME, 2,
  2132. "RESIDENTStk $NAME: " . "missing attribute wakeupMacro";
  2133. }
  2134. elsif ( !IsDevice($wakeupMacro) ) {
  2135. Log3 $NAME, 2,
  2136. "RESIDENTStk $NAME: "
  2137. . "notify macro $wakeupMacro not found - no wakeup actions defined!";
  2138. }
  2139. elsif ( !IsDevice( $wakeupMacro, "notify" ) ) {
  2140. Log3 $NAME, 2,
  2141. "RESIDENTStk $NAME: "
  2142. . "device $wakeupMacro is not of type notify";
  2143. }
  2144. else {
  2145. # conditional enforced wake-up:
  2146. # only if actual wake-up time is
  2147. # earlier than wakeupDefaultTime
  2148. if ( $wakeupEnforced == 3
  2149. && $wakeupDefaultTime
  2150. && UConv::hms2s($wakeupDefaultTime) > UConv::hms2s($lastRun) )
  2151. {
  2152. Log3 $NAME, 4,
  2153. "RESIDENTStk $NAME: "
  2154. . "Enforcing wake-up because wake-up time is earlier than normal (wakeupDefaultTime=$wakeupDefaultTime > lastRun=$lastRun)";
  2155. $wakeupEnforced = 1;
  2156. }
  2157. # conditional enforced wake-up:
  2158. # only if actual wake-up time is not wakeupDefaultTime
  2159. elsif ($wakeupEnforced == 2
  2160. && $wakeupDefaultTime
  2161. && $wakeupDefaultTime ne $lastRun )
  2162. {
  2163. Log3 $NAME, 4,
  2164. "RESIDENTStk $NAME: "
  2165. . "Enforcing wake-up because wake-up is different from normal (wakeupDefaultTime=$wakeupDefaultTime =! lastRun=$lastRun)";
  2166. $wakeupEnforced = 1;
  2167. }
  2168. elsif ( $wakeupEnforced > 1 ) {
  2169. $wakeupEnforced = 0;
  2170. }
  2171. if ( $VALUE ne "" || $cmd eq "end" ) {
  2172. Log3 $NAME, 4,
  2173. "RESIDENTStk $NAME: "
  2174. . "trigger $wakeupMacro stop $lastRun $wakeupOffset $wakeupEnforced $wakeupUserdevice $wakeupUserdeviceState";
  2175. fhem "trigger $wakeupMacro "
  2176. . "stop $lastRun $wakeupOffset $wakeupEnforced $wakeupUserdevice $wakeupUserdeviceState";
  2177. }
  2178. else {
  2179. Log3 $NAME, 4,
  2180. "RESIDENTStk $NAME: "
  2181. . "trigger $wakeupMacro forced-stop $lastRun $wakeupOffset $wakeupEnforced $wakeupUserdevice $wakeupUserdeviceState";
  2182. fhem "trigger $wakeupMacro "
  2183. . "forced-stop $lastRun $wakeupOffset $wakeupEnforced $wakeupUserdevice $wakeupUserdeviceState";
  2184. fhem "set $wakeupUserdevice:FILTER=state=asleep awoken";
  2185. }
  2186. my $wakeupStopAtdevice = $wakeupAtdevice . "_stop";
  2187. fhem "delete $wakeupStopAtdevice"
  2188. if ( IsDevice($wakeupStopAtdevice) );
  2189. }
  2190. readingsSingleUpdate( $defs{$wakeupUserdevice}, "wakeup", "0", 1 );
  2191. return;
  2192. }
  2193. # auto or reset
  2194. #
  2195. elsif ( $cmd eq "auto" || $cmd eq "reset" ) {
  2196. my $resetTime = ReadingsVal( $NAME, "lastRun", 0 );
  2197. if ($wakeupDefaultTime) {
  2198. $resetTime = $wakeupDefaultTime;
  2199. }
  2200. if ( $resetTime
  2201. && !( $cmd eq "auto" && lc($resetTime) eq "off" ) )
  2202. {
  2203. fhem "set $NAME:FILTER=state!=$resetTime nextRun $resetTime";
  2204. }
  2205. elsif ( $cmd eq "reset" ) {
  2206. Log3 $NAME, 4,
  2207. "RESIDENTStk $NAME: "
  2208. . "no default value specified in attribute wakeupDefaultTime, just keeping setting OFF";
  2209. fhem "set $NAME:FILTER=state!=OFF nextRun OFF";
  2210. }
  2211. return;
  2212. }
  2213. # wakeup attributes
  2214. #
  2215. elsif ( $cmd =~
  2216. m/^(wakeupResetdays|wakeupDays|wakeupHolidays|wakeupEnforced|wakeupDefaultTime|wakeupOffset)$/
  2217. )
  2218. {
  2219. Log3 $NAME, 4, "RESIDENTStk $NAME: " . "setting $1 to '$VALUE'";
  2220. readingsBeginUpdate( $defs{$NAME} );
  2221. readingsBulkUpdate( $defs{$NAME}, $1, $VALUE );
  2222. readingsEndUpdate( $defs{$NAME}, 1 );
  2223. fhem "set $NAME nextRun $nextRun";
  2224. return;
  2225. }
  2226. # set new wakeup value
  2227. elsif ( IsDevice( $wakeupAtdevice, "at" )
  2228. && $cmd =~
  2229. m/^(?:nextRun)?\s*(OFF|([\+\-])?(([0-9]{2}):([0-9]{2})|([1-9]+[0-9]*)))?$/i
  2230. )
  2231. {
  2232. $VALUE = $1 if ( $1 && $1 ne "" );
  2233. $VALUE =
  2234. RESIDENTStk_TimeSum( ReadingsVal( $NAME, "nextRun", 0 ), $VALUE )
  2235. if ($2);
  2236. # Update wakeuptimer device
  2237. #
  2238. readingsBeginUpdate( $defs{$NAME} );
  2239. if ( ReadingsVal( $NAME, "nextRun", 0 ) ne $VALUE ) {
  2240. Log3 $NAME, 4, "RESIDENTStk $NAME: " . "New wake-up time: $VALUE";
  2241. readingsBulkUpdate( $defs{$NAME}, "nextRun", $VALUE );
  2242. # Update at-device
  2243. fhem "set $wakeupAtdevice "
  2244. . "modifyTimeSpec {RESIDENTStk_wakeupGetBegin(\"$NAME\",\"$wakeupAtdevice\")}";
  2245. }
  2246. if ( ReadingsVal( $NAME, "state", 0 ) ne $VALUE
  2247. && !$running )
  2248. {
  2249. readingsBulkUpdate( $defs{$NAME}, "state", $VALUE );
  2250. }
  2251. elsif ( ReadingsVal( $NAME, "state", 0 ) ne "running"
  2252. && $running )
  2253. {
  2254. readingsBulkUpdate( $defs{$NAME}, "state", "running" );
  2255. }
  2256. readingsEndUpdate( $defs{$NAME}, 1 );
  2257. # Update user device
  2258. #
  2259. readingsBeginUpdate( $defs{$wakeupUserdevice} );
  2260. my ( $nextWakeupDev, $nextWakeup ) =
  2261. RESIDENTStk_wakeupGetNext($wakeupUserdevice);
  2262. if ( !$nextWakeupDev || !$nextWakeup ) {
  2263. $nextWakeupDev = "";
  2264. $nextWakeup = "OFF";
  2265. }
  2266. readingsBulkUpdateIfChanged( $defs{$wakeupUserdevice},
  2267. "nextWakeupDev", $nextWakeupDev );
  2268. readingsBulkUpdateIfChanged( $defs{$wakeupUserdevice},
  2269. "nextWakeup", $nextWakeup );
  2270. readingsEndUpdate( $defs{$wakeupUserdevice}, 1 );
  2271. }
  2272. return undef;
  2273. }
  2274. sub RESIDENTStk_wakeupGetBegin($;$) {
  2275. my ( $NAME, $wakeupAtdevice ) = @_;
  2276. unless ( IsDevice($NAME) ) {
  2277. Log3 $NAME, 3,
  2278. "RESIDENTStk $NAME: "
  2279. . "Run function RESIDENTStk_wakeupGetBegin() for non-existing device!";
  2280. return "$NAME: Non-existing device";
  2281. }
  2282. my $nextRun = ReadingsVal( $NAME, "nextRun", 0 );
  2283. my $wakeupDefaultTime =
  2284. ReadingsVal( $NAME, "wakeupDefaultTime",
  2285. AttrVal( $NAME, "wakeupDefaultTime", 0 ) );
  2286. my $wakeupOffset =
  2287. ReadingsVal( $NAME, "wakeupOffset", AttrVal( $NAME, "wakeupOffset", 0 ) );
  2288. my $wakeupInitTime = (
  2289. $wakeupDefaultTime && lc($wakeupDefaultTime) ne "off"
  2290. ? $wakeupDefaultTime
  2291. : "05:00"
  2292. );
  2293. my $wakeupTime;
  2294. if ($wakeupAtdevice) {
  2295. Log3 $NAME, 4,
  2296. "RESIDENTStk $NAME: "
  2297. . "Wakeuptime recalculation triggered by at-device $wakeupAtdevice";
  2298. }
  2299. # just give any valuable return to at-device
  2300. # if wakeuptimer device does not exit anymore
  2301. # and run self-destruction to clean up
  2302. if ( !IsDevice($NAME) ) {
  2303. Log3 $NAME, 3,
  2304. "RESIDENTStk $NAME: "
  2305. . "this wake-up timer device does not exist anymore";
  2306. my $atName = "at_" . $NAME;
  2307. if ( IsDevice( $wakeupAtdevice, "at" ) ) {
  2308. Log3 $NAME, 3,
  2309. "RESIDENTStk $NAME: "
  2310. . "Cleaning up at-device $wakeupAtdevice (self-destruction)";
  2311. fhem "sleep 1; delete $wakeupAtdevice";
  2312. }
  2313. elsif ( IsDevice( $atName, "at" ) ) {
  2314. Log3 $NAME, 3,
  2315. "RESIDENTStk $NAME: " . "Cleaning up at-device $atName";
  2316. fhem "sleep 1; delete $atName";
  2317. }
  2318. else {
  2319. Log3 $NAME, 3,
  2320. "RESIDENTStk $NAME: "
  2321. . "Could not automatically clean up at-device, please perform manual cleanup.";
  2322. }
  2323. return $wakeupInitTime;
  2324. }
  2325. # use nextRun value if not OFF
  2326. if ( $nextRun && lc($nextRun) ne "off" ) {
  2327. $wakeupTime = $nextRun;
  2328. Log3 $NAME, 4, "RESIDENTStk $NAME: " . "wakeupGetBegin source: nextRun";
  2329. }
  2330. # use wakeupDefaultTime if present and not OFF
  2331. elsif ( $wakeupDefaultTime
  2332. && lc($wakeupDefaultTime) ne "off" )
  2333. {
  2334. $wakeupTime = $wakeupDefaultTime;
  2335. Log3 $NAME, 4,
  2336. "RESIDENTStk $NAME: " . "wakeupGetBegin source: wakeupDefaultTime";
  2337. }
  2338. # Use a default value to ensure auto-reset at least once a day
  2339. else {
  2340. $wakeupTime = $wakeupInitTime;
  2341. Log3 $NAME, 4,
  2342. "RESIDENTStk $NAME: " . "wakeupGetBegin source: defaultValue";
  2343. }
  2344. # Recalculate new wake-up value
  2345. my $seconds = UConv::hms2s($wakeupTime) - $wakeupOffset * 60;
  2346. if ( $seconds < 0 ) { $seconds = 86400 + $seconds }
  2347. Log3 $NAME, 4,
  2348. "RESIDENTStk $NAME: "
  2349. . "wakeupGetBegin result: $wakeupTime = $seconds s - $wakeupOffset m = "
  2350. . UConv::s2hms($seconds);
  2351. return UConv::s2hms($seconds);
  2352. }
  2353. sub RESIDENTStk_wakeupRun($;$) {
  2354. my ( $NAME, $forceRun ) = @_;
  2355. unless ( IsDevice($NAME) ) {
  2356. Log3 $NAME, 3,
  2357. "RESIDENTStk $NAME: "
  2358. . "Run function RESIDENTStk_wakeupRun() for non-existing device!";
  2359. return "$NAME: Non-existing device";
  2360. }
  2361. my $wakeupMacro = AttrVal( $NAME, "wakeupMacro", 0 );
  2362. my $wakeupDefaultTime =
  2363. ReadingsVal( $NAME, "wakeupDefaultTime",
  2364. AttrVal( $NAME, "wakeupDefaultTime", 0 ) );
  2365. my $wakeupAtdevice = AttrVal( $NAME, "wakeupAtdevice", 0 );
  2366. my $wakeupUserdevice = AttrVal( $NAME, "wakeupUserdevice", 0 );
  2367. my $wakeupDays =
  2368. ReadingsVal( $NAME, "wakeupDays", AttrVal( $NAME, "wakeupDays", "" ) );
  2369. my $wakeupHolidays =
  2370. ReadingsVal( $NAME, "wakeupHolidays",
  2371. AttrVal( $NAME, "wakeupHolidays", "" ) );
  2372. my $wakeupResetdays =
  2373. ReadingsVal( $NAME, "wakeupResetdays",
  2374. AttrVal( $NAME, "wakeupResetdays", "" ) );
  2375. my $wakeupOffset =
  2376. ReadingsVal( $NAME, "wakeupOffset", AttrVal( $NAME, "wakeupOffset", 0 ) );
  2377. my $wakeupEnforced =
  2378. ReadingsVal( $NAME, "wakeupEnforced",
  2379. AttrVal( $NAME, "wakeupEnforced", 0 ) );
  2380. my $wakeupResetSwitcher = AttrVal( $NAME, "wakeupResetSwitcher", 0 );
  2381. my $wakeupWaitPeriod = AttrVal( $NAME, "wakeupWaitPeriod", 360 );
  2382. my $holidayDevice = AttrVal( "global", "holiday2we", 0 );
  2383. my $lastRun = ReadingsVal( $NAME, "lastRun", "06:00" );
  2384. my $nextRun = ReadingsVal( $NAME, "nextRun", "06:00" );
  2385. my $wakeupUserdeviceState = ReadingsVal( $wakeupUserdevice, "state", 0 );
  2386. my $wakeupUserdeviceWakeup = ReadingsVal( $wakeupUserdevice, "wakeup", 0 );
  2387. my $room = AttrVal( $NAME, "room", 0 );
  2388. my $running = 0;
  2389. my $preventRun = 0;
  2390. my $holidayToday = 0;
  2391. if ( $wakeupHolidays ne ""
  2392. && IsDevice( $holidayDevice, "holiday" ) )
  2393. {
  2394. $holidayToday = 1
  2395. unless ( ReadingsVal( $holidayDevice, "state", "none" ) eq "none" );
  2396. }
  2397. else {
  2398. $wakeupHolidays = "";
  2399. }
  2400. my ( $sec, $min, $hour, $mday, $mon, $year, $today, $yday, $isdst ) =
  2401. localtime( time() + $wakeupOffset * 60 );
  2402. $year += 1900;
  2403. $mon++;
  2404. $mon = "0" . $mon if ( $mon < 10 );
  2405. $mday = "0" . $mday if ( $mday < 10 );
  2406. $hour = "0" . $hour if ( $hour < 10 );
  2407. $min = "0" . $min if ( $min < 10 );
  2408. $sec = "0" . $sec if ( $sec < 10 );
  2409. my $nowRun = $hour . ":" . $min;
  2410. my $nowRunSec =
  2411. time_str2num( $year . "-"
  2412. . $mon . "-"
  2413. . $mday . " "
  2414. . $hour . ":"
  2415. . $min . ":"
  2416. . $sec );
  2417. if ( $nextRun ne $nowRun ) {
  2418. $lastRun = $nowRun;
  2419. Log3 $NAME, 4, "RESIDENTStk $NAME: " . "lastRun != nextRun = $lastRun";
  2420. }
  2421. else {
  2422. $lastRun = $nextRun;
  2423. Log3 $NAME, 4, "RESIDENTStk $NAME: " . "lastRun = nextRun = $lastRun";
  2424. }
  2425. my @days = ($today);
  2426. @days = split /,/, $wakeupDays
  2427. if ( $wakeupDays ne "" );
  2428. my %days = map { $_ => 1 } @days;
  2429. my @rdays = ($today);
  2430. @rdays = split /,/, $wakeupResetdays
  2431. if ( $wakeupResetdays ne "" );
  2432. my %rdays = map { $_ => 1 } @rdays;
  2433. if ( IsDisabled($NAME) ) {
  2434. Log3 $NAME, 4,
  2435. "RESIDENTStk $NAME: "
  2436. . "wakeupDevice disabled - not triggering wake-up program";
  2437. }
  2438. elsif ( IsDisabled($wakeupUserdevice) ) {
  2439. Log3 $NAME, 4,
  2440. "RESIDENTStk $NAME: "
  2441. . "wakeupUserdevice disabled - not triggering wake-up program";
  2442. }
  2443. elsif ( !$wakeupUserdevice ) {
  2444. return "$NAME: missing attribute wakeupUserdevice";
  2445. }
  2446. elsif ( !IsDevice($wakeupUserdevice) ) {
  2447. return "$NAME: Non existing wakeupUserdevice $wakeupUserdevice";
  2448. }
  2449. elsif ( !IsDevice( $wakeupUserdevice, "RESIDENTS|ROOMMATE|GUEST" ) ) {
  2450. return "$NAME: "
  2451. . "wakeupUserdevice $wakeupUserdevice is not of type RESIDENTS, ROOMMATE or GUEST";
  2452. }
  2453. elsif ( IsDevice( $wakeupUserdevice, "GUEST" )
  2454. && $wakeupUserdeviceState eq "none" )
  2455. {
  2456. Log3 $NAME, 4,
  2457. "RESIDENTStk $NAME: "
  2458. . "GUEST device $wakeupUserdevice has status value 'none' so let's disable this wake-up timer";
  2459. fhem "set $NAME nextRun OFF";
  2460. return;
  2461. }
  2462. elsif ( IsDisabled($wakeupUserdevice) ) {
  2463. Log3 $NAME, 4,
  2464. "RESIDENTStk $NAME: "
  2465. . "wakeupUserdevice disabled - not triggering wake-up program";
  2466. }
  2467. elsif ( lc($nextRun) eq "off" && !$forceRun ) {
  2468. Log3 $NAME, 4,
  2469. "RESIDENTStk $NAME: "
  2470. . "wakeup timer set to OFF - not triggering wake-up program";
  2471. }
  2472. elsif (
  2473. !$forceRun
  2474. && !$days{$today}
  2475. && ( $wakeupHolidays eq ""
  2476. || $wakeupHolidays eq "andHoliday"
  2477. || $wakeupHolidays eq "andNoHoliday" )
  2478. )
  2479. {
  2480. Log3 $NAME, 4,
  2481. "RESIDENTStk $NAME: "
  2482. . "weekday restriction in use - not triggering wake-up program this time";
  2483. }
  2484. elsif (
  2485. !$forceRun
  2486. # && !$days{$today}
  2487. # && (
  2488. # ( $wakeupHolidays eq "andHoliday" && !$holidayToday )
  2489. # || ( $wakeupHolidays eq "andNoHoliday"
  2490. # && $holidayToday )
  2491. # || ( $wakeupHolidays eq "orHoliday" && !$holidayToday )
  2492. # || ( $wakeupHolidays eq "orNoHoliday"
  2493. # && $holidayToday )
  2494. # )
  2495. && (
  2496. (
  2497. $days{$today}
  2498. && ( ( $wakeupHolidays eq "andHoliday" && !$holidayToday )
  2499. || ( $wakeupHolidays eq "andNoHoliday" && $holidayToday ) )
  2500. )
  2501. || (
  2502. !$days{$today}
  2503. && ( ( $wakeupHolidays eq "orHoliday" && !$holidayToday )
  2504. || ( $wakeupHolidays eq "orNoHoliday" && $holidayToday ) )
  2505. )
  2506. )
  2507. )
  2508. {
  2509. Log3 $NAME, 4,
  2510. "RESIDENTStk $NAME: "
  2511. . "holiday restriction $wakeupHolidays in use - not triggering wake-up program this time";
  2512. }
  2513. elsif ($wakeupUserdeviceState eq "absent"
  2514. || $wakeupUserdeviceState eq "gone"
  2515. || $wakeupUserdeviceState eq "gotosleep"
  2516. || $wakeupUserdeviceState eq "awoken" )
  2517. {
  2518. Log3 $NAME, 4,
  2519. "RESIDENTStk $NAME: "
  2520. . "we should not start any wake-up program for resident device $wakeupUserdevice being in state '"
  2521. . $wakeupUserdeviceState
  2522. . "' - not triggering wake-up program this time";
  2523. }
  2524. # general conditions to trigger program fulfilled
  2525. else {
  2526. my $expLastWakeup = time_str2num(
  2527. ReadingsTimestamp(
  2528. $wakeupUserdevice, "lastWakeup", "1970-01-01 00:00:00"
  2529. )
  2530. ) - 1 +
  2531. $wakeupOffset * 60 +
  2532. $wakeupWaitPeriod * 60;
  2533. my $expLastAwake = time_str2num(
  2534. ReadingsTimestamp(
  2535. $wakeupUserdevice, "lastAwake", "1970-01-01 00:00:00"
  2536. )
  2537. ) - 1 +
  2538. $wakeupWaitPeriod * 60;
  2539. if ( !$wakeupMacro ) {
  2540. return "$NAME: missing attribute wakeupMacro";
  2541. }
  2542. elsif ( !IsDevice($wakeupMacro) ) {
  2543. return "$NAME: "
  2544. . "notify macro $wakeupMacro not found - no wakeup actions defined!";
  2545. }
  2546. elsif ( !IsDevice( $wakeupMacro, "notify" ) ) {
  2547. return "$NAME: device $wakeupMacro is not of type notify";
  2548. }
  2549. elsif ($wakeupUserdeviceWakeup) {
  2550. Log3 $NAME, 3,
  2551. "RESIDENTStk $NAME: "
  2552. . "Another wake-up program is already being executed for device $wakeupUserdevice, won't trigger $wakeupMacro";
  2553. }
  2554. elsif ( $expLastWakeup > $nowRunSec && !$forceRun ) {
  2555. Log3 $NAME, 3,
  2556. "RESIDENTStk $NAME: "
  2557. . "won't trigger wake-up program due to non-expired wakeupWaitPeriod threshold since lastWakeup (expLastWakeup=$expLastWakeup > nowRunSec=$nowRunSec)";
  2558. }
  2559. elsif ( $expLastAwake > $nowRunSec && !$forceRun ) {
  2560. Log3 $NAME, 3,
  2561. "RESIDENTStk $NAME: "
  2562. . "won't trigger wake-up program due to non-expired wakeupWaitPeriod threshold since lastAwake (expLastAwake=$expLastAwake > nowRunSec=$nowRunSec)";
  2563. }
  2564. else {
  2565. # conditional enforced wake-up:
  2566. # only if actual wake-up time is
  2567. # earlier than wakeupDefaultTime
  2568. if ( $wakeupEnforced == 3
  2569. && $wakeupDefaultTime
  2570. && UConv::hms2s($wakeupDefaultTime) > UConv::hms2s($lastRun) )
  2571. {
  2572. Log3 $NAME, 4,
  2573. "RESIDENTStk $NAME: "
  2574. . "Enforcing wake-up because wake-up time is earlier than normal (wakeupDefaultTime=$wakeupDefaultTime > lastRun=$lastRun)";
  2575. $wakeupEnforced = 1;
  2576. }
  2577. # conditional enforced wake-up:
  2578. # only if actual wake-up time is not wakeupDefaultTime
  2579. elsif ($wakeupEnforced == 2
  2580. && $wakeupDefaultTime
  2581. && $wakeupDefaultTime ne $lastRun )
  2582. {
  2583. Log3 $NAME, 4,
  2584. "RESIDENTStk $NAME: "
  2585. . "Enforcing wake-up because wake-up is different from normal (wakeupDefaultTime=$wakeupDefaultTime =! lastRun=$lastRun)";
  2586. $wakeupEnforced = 1;
  2587. }
  2588. elsif ( $wakeupEnforced > 1 ) {
  2589. $wakeupEnforced = 0;
  2590. }
  2591. Log3 $NAME, 4,
  2592. "RESIDENTStk $NAME: " . "trigger $wakeupMacro (running=1)";
  2593. fhem "trigger $wakeupMacro "
  2594. . "start $lastRun $wakeupOffset $wakeupEnforced $wakeupUserdevice $wakeupUserdeviceState";
  2595. # Update user device with last wakeup details
  2596. #
  2597. readingsBeginUpdate( $defs{$wakeupUserdevice} );
  2598. readingsBulkUpdate( $defs{$wakeupUserdevice},
  2599. "lastWakeup", $lastRun );
  2600. readingsBulkUpdate( $defs{$wakeupUserdevice},
  2601. "lastWakeupDev", $NAME );
  2602. readingsBulkUpdate( $defs{$wakeupUserdevice}, "wakeup", "1" );
  2603. readingsEndUpdate( $defs{$wakeupUserdevice}, 1 );
  2604. readingsSingleUpdate( $defs{$wakeupUserdevice}, "wakeup", "0", 1 )
  2605. unless ($wakeupOffset);
  2606. readingsSingleUpdate( $defs{$NAME}, "lastRun", $lastRun, 1 );
  2607. if ($wakeupOffset) {
  2608. my $wakeupStopAtdevice = $wakeupAtdevice . "_stop";
  2609. fhem "delete $wakeupStopAtdevice"
  2610. if ( IsDevice($wakeupStopAtdevice) );
  2611. Log3 $NAME, 4,
  2612. "RESIDENTStk $NAME: "
  2613. . "created at-device $wakeupStopAtdevice to stop wake-up program in $wakeupOffset minutes";
  2614. fhem "define $wakeupStopAtdevice at +"
  2615. . UConv::s2hms( $wakeupOffset * 60 + 1 )
  2616. . " set $NAME:FILTER=running=1 stop triggerpost";
  2617. fhem "attr $wakeupStopAtdevice "
  2618. . "comment Auto-created by RESIDENTS Toolkit: temp. at-device to stop wake-up program of timer $NAME when wake-up time is reached";
  2619. $running = 1;
  2620. }
  2621. }
  2622. }
  2623. if ( $running && $wakeupOffset ) {
  2624. readingsBeginUpdate( $defs{$NAME} );
  2625. readingsBulkUpdate( $defs{$NAME}, "running", "1" );
  2626. readingsBulkUpdate( $defs{$NAME}, "state", "running" );
  2627. readingsEndUpdate( $defs{$NAME}, 1 );
  2628. }
  2629. # Update user device with next wakeup details
  2630. #
  2631. readingsBeginUpdate( $defs{$wakeupUserdevice} );
  2632. my ( $nextWakeupDev, $nextWakeup ) =
  2633. RESIDENTStk_wakeupGetNext( $wakeupUserdevice, $NAME );
  2634. if ( !$nextWakeupDev || !$nextWakeup ) {
  2635. $nextWakeupDev = "";
  2636. $nextWakeup = "OFF";
  2637. }
  2638. readingsBulkUpdateIfChanged( $defs{$wakeupUserdevice},
  2639. "nextWakeupDev", $nextWakeupDev );
  2640. readingsBulkUpdateIfChanged( $defs{$wakeupUserdevice},
  2641. "nextWakeup", $nextWakeup );
  2642. readingsEndUpdate( $defs{$wakeupUserdevice}, 1 );
  2643. my $doReset = 1;
  2644. if ( IsDevice( $wakeupResetSwitcher, "dummy" )
  2645. && ReadingsVal( $wakeupResetSwitcher, "state", 0 ) eq "off" )
  2646. {
  2647. $doReset = 0;
  2648. }
  2649. if ( $wakeupDefaultTime && $rdays{$today} && $doReset ) {
  2650. Log3 $NAME, 4,
  2651. "RESIDENTStk $NAME: " . "Resetting based on wakeupDefaultTime";
  2652. fhem "set $NAME:FILTER=state!=$wakeupDefaultTime "
  2653. . "nextRun $wakeupDefaultTime";
  2654. }
  2655. elsif ( !$running ) {
  2656. readingsBeginUpdate( $defs{$NAME} );
  2657. readingsBulkUpdateIfChanged( $defs{$NAME}, "state", $nextRun );
  2658. readingsEndUpdate( $defs{$NAME}, 1 );
  2659. }
  2660. return undef;
  2661. }
  2662. sub RESIDENTStk_wakeupGetNext($;$) {
  2663. my ( $name, $wakeupDeviceRunning ) = @_;
  2664. my $wakeupDeviceAttrName = "";
  2665. $wakeupDeviceAttrName = "rgr_wakeupDevice"
  2666. if ( defined( $attr{$name}{"rgr_wakeupDevice"} ) );
  2667. $wakeupDeviceAttrName = "rr_wakeupDevice"
  2668. if ( defined( $attr{$name}{"rr_wakeupDevice"} ) );
  2669. $wakeupDeviceAttrName = "rg_wakeupDevice"
  2670. if ( defined( $attr{$name}{"rg_wakeupDevice"} ) );
  2671. my $wakeupDeviceList = AttrVal( $name, $wakeupDeviceAttrName, 0 );
  2672. my ( $sec, $min, $hour, $mday, $mon, $year, $today, $yday, $isdst ) =
  2673. localtime(time);
  2674. $hour = "0" . $hour if ( $hour < 10 );
  2675. $min = "0" . $min if ( $min < 10 );
  2676. my $tomorrow = $today + 1;
  2677. $tomorrow = 0 if ( $tomorrow == 7 );
  2678. my $secNow = UConv::hms2s( $hour . ":" . $min ) + $sec;
  2679. my $definitiveNextToday;
  2680. my $definitiveNextTomorrow;
  2681. my $definitiveNextTodayDev;
  2682. my $definitiveNextTomorrowDev;
  2683. my $holidayDevice = AttrVal( "global", "holiday2we", 0 );
  2684. # check for each registered wake-up device
  2685. for my $wakeupDevice ( split /,/, $wakeupDeviceList ) {
  2686. if ( !IsDevice($wakeupDevice) ) {
  2687. Log3 $name, 4,
  2688. "RESIDENTStk $name: "
  2689. . "00 - ignoring reference to non-existing wakeupDevice $wakeupDevice";
  2690. my $wakeupDeviceListNew = $wakeupDeviceList;
  2691. $wakeupDeviceListNew =~ s/,$wakeupDevice,/,/g;
  2692. $wakeupDeviceListNew =~ s/$wakeupDevice,//g;
  2693. $wakeupDeviceListNew =~ s/,$wakeupDevice//g;
  2694. if ( $wakeupDeviceListNew ne $wakeupDeviceList ) {
  2695. Log3 $name, 3,
  2696. "RESIDENTStk $name: "
  2697. . "reference to non-existing wakeupDevice '$wakeupDevice' was removed";
  2698. fhem "attr $name $wakeupDeviceAttrName $wakeupDeviceListNew";
  2699. }
  2700. next;
  2701. }
  2702. my $wakeupAtdevice = AttrVal( $wakeupDevice, "wakeupAtdevice", undef );
  2703. my $wakeupOffset =
  2704. ReadingsVal( $wakeupDevice, "wakeupOffset",
  2705. AttrVal( $wakeupDevice, "wakeupOffset", 0 ) );
  2706. my $wakeupAtNTM = (
  2707. IsDevice($wakeupAtdevice)
  2708. && defined( $defs{$wakeupAtdevice}{NTM} )
  2709. ? substr( $defs{$wakeupAtdevice}{NTM}, 0, -3 )
  2710. : undef
  2711. );
  2712. my $wakeupDays =
  2713. ReadingsVal( $wakeupDevice, "wakeupDays",
  2714. AttrVal( $wakeupDevice, "wakeupDays", "" ) );
  2715. my $wakeupHolidays =
  2716. ReadingsVal( $wakeupDevice, "wakeupHolidays",
  2717. AttrVal( $wakeupDevice, "wakeupHolidays", "" ) );
  2718. my $holidayToday = 0;
  2719. my $holidayTomorrow = 0;
  2720. my $nextRunSrc;
  2721. my $nextRun = ReadingsVal( $wakeupDevice, "nextRun", undef );
  2722. my $ltoday = $today;
  2723. my $ltomorrow = $tomorrow;
  2724. if ( IsDisabled($wakeupDevice)
  2725. || !$nextRun
  2726. || lc($nextRun) eq "off"
  2727. || $nextRun !~ /^([0-9]{2}:[0-9]{2})$/ )
  2728. {
  2729. Log3 $name, 4,
  2730. "RESIDENTStk $name: "
  2731. . "00 - ignoring disabled wakeupDevice $wakeupDevice";
  2732. next;
  2733. }
  2734. Log3 $name, 4,
  2735. "RESIDENTStk $name: "
  2736. . "00 - checking for next wake-up candidate $wakeupDevice";
  2737. # get holiday status for today and tomorrow
  2738. if ( $wakeupHolidays eq "" ) {
  2739. Log3 $name, 4,
  2740. "RESIDENTStk $wakeupDevice: 01 - Not considering any holidays";
  2741. }
  2742. elsif ( IsDevice( $holidayDevice, "holiday" ) ) {
  2743. $holidayToday = 1
  2744. unless (
  2745. ReadingsVal( $holidayDevice, "state", "none" ) eq "none" );
  2746. $holidayTomorrow = 1
  2747. unless (
  2748. ReadingsVal( $holidayDevice, "tomorrow", "none" ) eq "none" );
  2749. Log3 $name, 4,
  2750. "RESIDENTStk $wakeupDevice: "
  2751. . "01 - Holidays to be considered ($wakeupHolidays) - holidayToday=$holidayToday holidayTomorrow=$holidayTomorrow";
  2752. }
  2753. # set day scope for today
  2754. my @days = ($ltoday);
  2755. @days = split /,/, $wakeupDays
  2756. if ( $wakeupDays ne "" );
  2757. my %days = map { $_ => 1 } @days;
  2758. # set day scope for tomorrow
  2759. my @daysTomorrow = ($ltomorrow);
  2760. @daysTomorrow = split /,/, $wakeupDays
  2761. if ( $wakeupDays ne "" );
  2762. my %daysTomorrow = map { $_ => 1 } @daysTomorrow;
  2763. Log3 $name, 4,
  2764. "RESIDENTStk $wakeupDevice: "
  2765. . "02 - possible candidate found - weekdayToday=$ltoday weekdayTomorrow=$ltomorrow";
  2766. my $nextRunSec;
  2767. my $nextRunSecTarget;
  2768. # Use direct information from at-device if possible
  2769. if ( $wakeupAtNTM
  2770. && $wakeupAtNTM =~ /^([0-9]{2}:[0-9]{2})$/ )
  2771. {
  2772. $nextRunSrc = "at";
  2773. $nextRunSec = UConv::hms2s($wakeupAtNTM);
  2774. $nextRunSecTarget = $nextRunSec + $wakeupOffset * 60;
  2775. if ( $wakeupOffset && $nextRunSecTarget >= 86400 ) {
  2776. $nextRunSecTarget -= 86400;
  2777. $ltoday++;
  2778. $ltoday = $ltoday - 7
  2779. if ( $ltoday > 6 );
  2780. $ltomorrow++;
  2781. $ltomorrow = $ltomorrow - 7
  2782. if ( $ltomorrow > 6 );
  2783. }
  2784. Log3 $name, 4,
  2785. "RESIDENTStk $wakeupDevice: "
  2786. . "03 - considering at-device value wakeupAtNTM=$wakeupAtNTM wakeupOffset=$wakeupOffset nextRunSec=$nextRunSec nextRunSecTarget=$nextRunSecTarget";
  2787. }
  2788. else {
  2789. $nextRunSrc = "dummy";
  2790. $nextRunSecTarget = UConv::hms2s($nextRun);
  2791. $nextRunSec = $nextRunSecTarget - $wakeupOffset * 60;
  2792. if ( $wakeupOffset && $nextRunSec < 0 ) {
  2793. $nextRunSec += 86400;
  2794. $ltoday--;
  2795. $ltoday = $ltoday + 7
  2796. if ( $ltoday < 0 );
  2797. $ltomorrow--;
  2798. $ltomorrow = $ltomorrow + 7
  2799. if ( $ltomorrow < 0 );
  2800. }
  2801. Log3 $name, 4,
  2802. "RESIDENTStk $wakeupDevice: "
  2803. . "03 - considering dummy-device value nextRun=$nextRun wakeupOffset=$wakeupOffset nextRunSec=$nextRunSec nextRunSecTarget=$nextRunSecTarget (wakeupAtNTM=$wakeupAtNTM)";
  2804. }
  2805. # still running today
  2806. if ( $nextRunSec > $secNow ) {
  2807. Log3 $name, 4,
  2808. "RESIDENTStk $wakeupDevice: "
  2809. . "04 - this is a candidate for today - weekdayToday=$ltoday";
  2810. # if today is in scope
  2811. if ( $days{$ltoday} ) {
  2812. # if we need to consider holidays in addition
  2813. if (
  2814. ( $wakeupHolidays eq "andHoliday" && !$holidayToday )
  2815. || ( $wakeupHolidays eq "andNoHoliday"
  2816. && $holidayToday )
  2817. )
  2818. {
  2819. Log3 $name, 4,
  2820. "RESIDENTStk $wakeupDevice: "
  2821. . "05 - no run today due to holiday based on combined weekday and holiday decision";
  2822. next;
  2823. }
  2824. # easy if there is no holiday dependency
  2825. elsif ( !$definitiveNextToday
  2826. || $nextRunSec < $definitiveNextToday )
  2827. {
  2828. Log3 $name, 4,
  2829. "RESIDENTStk $wakeupDevice: "
  2830. . "05 - until now, will be NEXT WAKE-UP RUN today based on weekday decision";
  2831. $definitiveNextToday = $nextRunSec;
  2832. $definitiveNextTodayDev = $wakeupDevice;
  2833. }
  2834. }
  2835. elsif ($wakeupHolidays eq ""
  2836. || $wakeupHolidays eq "andHoliday"
  2837. || $wakeupHolidays eq "andNoHoliday" )
  2838. {
  2839. Log3 $name, 4,
  2840. "RESIDENTStk $wakeupDevice: "
  2841. . "05 - won't be running today anymore based on weekday decision";
  2842. next;
  2843. }
  2844. # if we need to consider holidays in parallel to weekdays
  2845. elsif (( $wakeupHolidays eq "orHoliday" && !$holidayToday )
  2846. || ( $wakeupHolidays eq "orNoHoliday" && $holidayToday ) )
  2847. {
  2848. Log3 $name, 4,
  2849. "RESIDENTStk $wakeupDevice: "
  2850. . "06 - won't be running today based on holiday decision";
  2851. next;
  2852. }
  2853. # easy if there is no holiday dependency
  2854. elsif ( !$definitiveNextToday
  2855. || $nextRunSec < $definitiveNextToday )
  2856. {
  2857. Log3 $name, 4,
  2858. "RESIDENTStk $wakeupDevice: "
  2859. . "06 - until now, will be NEXT WAKE-UP RUN today based on holiday decision";
  2860. $definitiveNextToday = $nextRunSec;
  2861. $definitiveNextTodayDev = $wakeupDevice;
  2862. }
  2863. }
  2864. # running later
  2865. else {
  2866. Log3 $name, 4,
  2867. "RESIDENTStk $wakeupDevice: "
  2868. . "04 - this is a candidate for tomorrow or later - weekdayTomorrow=$ltomorrow";
  2869. # if tomorrow is in scope
  2870. if ( $daysTomorrow{$ltomorrow} ) {
  2871. # if we need to consider holidays in addition
  2872. if (
  2873. ( $wakeupHolidays eq "andHoliday" && !$holidayTomorrow )
  2874. || ( $wakeupHolidays eq "andNoHoliday"
  2875. && $holidayTomorrow )
  2876. )
  2877. {
  2878. Log3 $name, 4,
  2879. "RESIDENTStk $wakeupDevice: "
  2880. . "05 - no run tomorrow due to holiday based on combined weekday and holiday decision";
  2881. next;
  2882. }
  2883. # easy if there is no holiday dependency
  2884. elsif ( !$definitiveNextTomorrow
  2885. || $nextRunSec < $definitiveNextTomorrow )
  2886. {
  2887. Log3 $name, 4,
  2888. "RESIDENTStk $wakeupDevice: "
  2889. . "05 - until now, will be NEXT WAKE-UP RUN tomorrow based on weekday decision";
  2890. $definitiveNextTomorrow = $nextRunSec;
  2891. $definitiveNextTomorrowDev = $wakeupDevice;
  2892. }
  2893. }
  2894. elsif ($wakeupHolidays eq ""
  2895. || $wakeupHolidays eq "andHoliday"
  2896. || $wakeupHolidays eq "andNoHoliday" )
  2897. {
  2898. Log3 $name, 4,
  2899. "RESIDENTStk $wakeupDevice: "
  2900. . "05 - won't be running tomorrow based on weekday decision";
  2901. next;
  2902. }
  2903. # if we need to consider holidays in parallel to weekdays
  2904. elsif (
  2905. ( $wakeupHolidays eq "orHoliday" && !$holidayTomorrow )
  2906. || ( $wakeupHolidays eq "orNoHoliday"
  2907. && $holidayTomorrow )
  2908. )
  2909. {
  2910. Log3 $name, 4,
  2911. "RESIDENTStk $wakeupDevice: "
  2912. . "06 - won't be running tomorrow based on holiday decision";
  2913. next;
  2914. }
  2915. # easy if there is no holiday dependency
  2916. elsif ( !$definitiveNextTomorrow
  2917. || $nextRunSec < $definitiveNextTomorrow )
  2918. {
  2919. Log3 $name, 4,
  2920. "RESIDENTStk $wakeupDevice: "
  2921. . "06 - until now, will be NEXT WAKE-UP RUN tomorrow based on holiday decision";
  2922. $definitiveNextTomorrow = $nextRunSec;
  2923. $definitiveNextTomorrowDev = $wakeupDevice;
  2924. }
  2925. }
  2926. if ($wakeupOffset) {
  2927. # add Offset
  2928. $definitiveNextToday += $wakeupOffset * 60
  2929. if ( defined($definitiveNextToday) );
  2930. $definitiveNextTomorrow += $wakeupOffset * 60
  2931. if ( defined($definitiveNextTomorrow) );
  2932. # correct change over midnight
  2933. if ( defined($definitiveNextToday) ) {
  2934. if ( $definitiveNextToday >= 86400 ) {
  2935. $definitiveNextToday -= 86400;
  2936. }
  2937. elsif ( $definitiveNextToday < 0 ) {
  2938. $definitiveNextToday += 86400;
  2939. }
  2940. }
  2941. if ( defined($definitiveNextTomorrow) ) {
  2942. if ( $definitiveNextTomorrow >= 86400 ) {
  2943. $definitiveNextTomorrow -= 86400;
  2944. }
  2945. elsif ( $definitiveNextTomorrow < 0 ) {
  2946. $definitiveNextTomorrow += 86400;
  2947. }
  2948. }
  2949. }
  2950. }
  2951. if ( defined($definitiveNextTodayDev)
  2952. && defined($definitiveNextToday) )
  2953. {
  2954. Log3 $name, 4,
  2955. "RESIDENTStk $name: 07 - next wake-up result: today at "
  2956. . UConv::s2hms($definitiveNextToday)
  2957. . ", wakeupDevice="
  2958. . $definitiveNextTodayDev;
  2959. return ( $definitiveNextTodayDev,
  2960. substr( UConv::s2hms($definitiveNextToday), 0, -3 ) );
  2961. }
  2962. elsif (defined($definitiveNextTomorrowDev)
  2963. && defined($definitiveNextTomorrow) )
  2964. {
  2965. Log3 $name, 4,
  2966. "RESIDENTStk $name: 07 - next wake-up result: tomorrow at "
  2967. . UConv::s2hms($definitiveNextTomorrow)
  2968. . ", wakeupDevice="
  2969. . $definitiveNextTomorrowDev;
  2970. return ( $definitiveNextTomorrowDev,
  2971. substr( UConv::s2hms($definitiveNextTomorrow), 0, -3 ) );
  2972. }
  2973. return ( undef, undef );
  2974. }
  2975. sub RESIDENTStk_TimeSum($$) {
  2976. my ( $val1, $val2 ) = @_;
  2977. my ( $timestamp1, $timestamp2, $math );
  2978. if ( $val1 !~ /^([0-9]{2}):([0-9]{2})$/ ) {
  2979. return $val1;
  2980. }
  2981. else {
  2982. $timestamp1 = UConv::hms2s($val1);
  2983. }
  2984. if ( $val2 =~ /^([\+\-])([0-9]{2}):([0-9]{2})$/ ) {
  2985. $math = $1;
  2986. $timestamp2 = UConv::hms2s("$2:$3");
  2987. }
  2988. elsif ( $val2 =~ /^([\+\-])([0-9]*)$/ ) {
  2989. $math = $1;
  2990. $timestamp2 = $2 * 60;
  2991. }
  2992. else {
  2993. return $val1;
  2994. }
  2995. if ( $math eq "-" ) {
  2996. return substr( UConv::s2hms( $timestamp1 - $timestamp2 ), 0, -3 );
  2997. }
  2998. else {
  2999. return substr( UConv::s2hms( $timestamp1 + $timestamp2 ), 0, -3 );
  3000. }
  3001. }
  3002. sub RESIDENTStk_TimeDiff ($$;$) {
  3003. my ( $datetimeNow, $datetimeOld, $format ) = @_;
  3004. if ( $datetimeNow eq "" || $datetimeOld eq "" ) {
  3005. $datetimeNow = "1970-01-01 00:00:00";
  3006. $datetimeOld = "1970-01-01 00:00:00";
  3007. }
  3008. my $timestampNow = time_str2num($datetimeNow);
  3009. my $timestampOld = time_str2num($datetimeOld);
  3010. my $timeDiff = $timestampNow - $timestampOld;
  3011. # return seconds
  3012. return round( $timeDiff, 0 )
  3013. if ( defined($format) && $format eq "sec" );
  3014. # return minutes
  3015. return round( $timeDiff / 60, 0 )
  3016. if ( defined($format) && $format eq "min" );
  3017. # return human readable format
  3018. return UConv::s2hms( round( $timeDiff, 0 ) );
  3019. }
  3020. sub RESIDENTStk_InternalTimer($$$$$) {
  3021. my ( $modifier, $tim, $callback, $hash, $waitIfInitNotDone ) = @_;
  3022. my $mHash;
  3023. if ( $modifier eq "" ) {
  3024. $mHash = $hash;
  3025. }
  3026. else {
  3027. my $timerName = $hash->{NAME} . "_" . $modifier;
  3028. if ( exists( $hash->{TIMER}{$timerName} ) ) {
  3029. $mHash = $hash->{TIMER}{$timerName};
  3030. }
  3031. else {
  3032. $mHash = {
  3033. HASH => $hash,
  3034. NAME => $hash->{NAME} . "_" . $modifier,
  3035. MODIFIER => $modifier
  3036. };
  3037. $hash->{TIMER}{$timerName} = $mHash;
  3038. }
  3039. }
  3040. InternalTimer( $tim, $callback, $mHash, $waitIfInitNotDone );
  3041. }
  3042. sub RESIDENTStk_RemoveInternalTimer($$) {
  3043. my ( $modifier, $hash ) = @_;
  3044. my $timerName = $hash->{NAME} . "_" . $modifier;
  3045. if ( $modifier eq "" ) {
  3046. RemoveInternalTimer($hash);
  3047. }
  3048. else {
  3049. my $mHash = $hash->{TIMER}{$timerName};
  3050. if ( defined($mHash) ) {
  3051. delete $hash->{TIMER}{$timerName};
  3052. RemoveInternalTimer($mHash);
  3053. }
  3054. }
  3055. }
  3056. sub RESIDENTStk_StartInternalTimers($$) {
  3057. my ($hash) = @_;
  3058. RESIDENTStk_AutoGone($hash);
  3059. RESIDENTStk_DurationTimer($hash);
  3060. }
  3061. sub RESIDENTStk_StopInternalTimers($) {
  3062. my ($hash) = @_;
  3063. delete $hash->{AUTOGONE} if ( $hash->{AUTOGONE} );
  3064. delete $hash->{DURATIONTIMER} if ( $hash->{DURATIONTIMER} );
  3065. RESIDENTStk_RemoveInternalTimer( "AutoGone", $hash );
  3066. RESIDENTStk_RemoveInternalTimer( "DurationTimer", $hash );
  3067. }
  3068. sub RESIDENTStk_findResidentSlaves($;$) {
  3069. my ( $hash, $ret ) = @_;
  3070. my @slaves;
  3071. my @ROOMMATES;
  3072. foreach ( devspec2array("TYPE=ROOMMATE") ) {
  3073. next
  3074. unless (
  3075. defined( $defs{$_}{RESIDENTGROUPS} )
  3076. && grep { $hash->{NAME} eq $_ }
  3077. split( /,/, $defs{$_}{RESIDENTGROUPS} )
  3078. );
  3079. push @ROOMMATES, $_;
  3080. }
  3081. my @GUESTS;
  3082. foreach ( devspec2array("TYPE=GUEST") ) {
  3083. next
  3084. unless (
  3085. defined( $defs{$_}{RESIDENTGROUPS} )
  3086. && grep { $hash->{NAME} eq $_ }
  3087. split( /,/, $defs{$_}{RESIDENTGROUPS} )
  3088. );
  3089. push @GUESTS, $_;
  3090. }
  3091. if ( scalar @ROOMMATES ) {
  3092. $hash->{ROOMMATES} = join( ",", @ROOMMATES );
  3093. }
  3094. elsif ( $hash->{ROOMMATES} ) {
  3095. delete $hash->{ROOMMATES};
  3096. }
  3097. if ( scalar @GUESTS ) {
  3098. $hash->{GUESTS} = join( ",", @GUESTS );
  3099. }
  3100. elsif ( $hash->{GUESTS} ) {
  3101. delete $hash->{GUESTS};
  3102. }
  3103. if ( $hash->{ROOMMATES} ) {
  3104. $ret .= "," if ($ret);
  3105. $ret .= $hash->{ROOMMATES};
  3106. }
  3107. if ( $hash->{GUESTS} ) {
  3108. $ret .= "," if ($ret);
  3109. $ret .= $hash->{GUESTS};
  3110. }
  3111. return RESIDENTStk_findDummySlaves( $hash, $ret );
  3112. }
  3113. sub RESIDENTStk_findDummySlaves($;$);
  3114. sub RESIDENTStk_findDummySlaves($;$) {
  3115. my ( $hash, $ret ) = @_;
  3116. my $prefix = RESIDENTStk_GetPrefixFromType( $hash->{NAME} );
  3117. my $wakeupDevice =
  3118. AttrVal( $hash->{NAME}, $prefix . "wakeupDevice", undef );
  3119. my $presenceDevices =
  3120. AttrVal( $hash->{NAME}, $prefix . "presenceDevices", undef );
  3121. $ret = "" unless ($ret);
  3122. # add r_*wakeupDevice
  3123. if ($wakeupDevice) {
  3124. $ret .= "," if ( $ret ne "" );
  3125. $ret .= $wakeupDevice;
  3126. # wakeupResetSwitcher
  3127. foreach ( split( ',', $wakeupDevice ) ) {
  3128. my $rsw = AttrVal( $_, "wakeupResetSwitcher", "" );
  3129. if ( $rsw =~ /^[a-zA-Z\d._]+$/ ) {
  3130. $ret .= "," if ( $ret ne "" );
  3131. $ret .= $rsw;
  3132. }
  3133. }
  3134. }
  3135. # add r_*presenceDevices
  3136. if ($presenceDevices) {
  3137. foreach ($presenceDevices) {
  3138. my $d = $_;
  3139. $d =~ s/:.*$//g;
  3140. $ret .= "," if ( $ret ne "" );
  3141. $ret .= $d;
  3142. }
  3143. }
  3144. return $ret;
  3145. }
  3146. sub RESIDENTStk_GetPrefixFromType($) {
  3147. my ($name) = @_;
  3148. return $modules{ $defs{$name}{TYPE} }{AttrPrefix}
  3149. if ( $defs{$name}
  3150. && $defs{$name}{TYPE}
  3151. && $modules{ $defs{$name}{TYPE} }
  3152. && $modules{ $defs{$name}{TYPE} }{AttrPrefix} );
  3153. return "";
  3154. }
  3155. sub RESIDENTStk_DoModuleTrigger($$@) {
  3156. my ( $hash, $newState, $noreplace, $TYPE ) = @_;
  3157. $hash = $defs{$hash} unless ( ref($hash) );
  3158. $TYPE = $hash->{TYPE} unless ( defined($TYPE) );
  3159. return ""
  3160. unless ( defined($TYPE)
  3161. && defined( $modules{$TYPE} )
  3162. && defined($newState)
  3163. && $newState =~
  3164. m/^([A-Za-z\d._]+)(?:\s+([A-Za-z\d._]+)(?:\s+(.*))?)?$/ );
  3165. $noreplace = 1 unless ( defined($noreplace) );
  3166. my $e = $1;
  3167. my $d = $2;
  3168. return "DoModuleTrigger() can only handle module related events"
  3169. if ( ( $hash->{NAME} && $hash->{NAME} eq "global" )
  3170. || $d eq "global" );
  3171. return DoTrigger( "global", "$TYPE:$newState", $noreplace )
  3172. unless ( $e =~ /^INITIALIZED|INITIALIZING|MODIFIED|DELETED$/ );
  3173. return "$e: missing device name"
  3174. if ( !defined($d) || $d eq "" );
  3175. my $dret;
  3176. my @rets;
  3177. $hash = $defs{$d};
  3178. if ( !$hash || !ref($hash) ) {
  3179. $dret = "device was deleted"
  3180. unless ( $e eq "DELETED" );
  3181. $e = "DELETED";
  3182. }
  3183. elsif ($e eq "INITIALIZING"
  3184. || ( $hash->{READY} && $hash->{MOD_INIT} )
  3185. || ( $modules{$TYPE}{READY} && $hash->{MOD_INIT} ) )
  3186. {
  3187. $dret = "device initialization started";
  3188. Log 4, "$TYPE $d: $dret";
  3189. $hash->{MOD_INIT} = 1;
  3190. $e = "INITIALIZING";
  3191. }
  3192. elsif ( $hash->{MOD_INIT} ) {
  3193. $dret = "pending device initialization";
  3194. Log 4, "$TYPE $d: $dret";
  3195. }
  3196. elsif ( !$hash->{READY} ) {
  3197. $e = "INITIALIZED";
  3198. $hash->{READY} = 1;
  3199. Log 4, "$TYPE $d: device initialization completed";
  3200. }
  3201. else {
  3202. $e = "MODIFIED";
  3203. }
  3204. my $ret = DoTrigger( "global", "$TYPE:$e $d", $noreplace )
  3205. if ( $e ne "DELETED"
  3206. && ( !$hash->{MOD_INIT} || $e eq "INITIALIZING" ) );
  3207. push @rets, $ret if ( defined($ret) && $ret ne "" );
  3208. ####
  3209. ## track module readiness
  3210. my $initcnt = scalar devspec2array("TYPE=$TYPE:FILTER=MOD_INIT=.+");
  3211. # no more devices of that module
  3212. if ( scalar devspec2array("TYPE=$TYPE") == 0 ) {
  3213. delete $modules{$TYPE}{READY};
  3214. delete $modules{$TYPE}{INIT};
  3215. Log 4, "$TYPE: module is no more in use";
  3216. my $ret = DoTrigger( "global", "$TYPE:DELETED", $noreplace );
  3217. push @rets, $ret if ( defined($ret) && $ret ne "" );
  3218. }
  3219. # all devices completed initialization after bootup
  3220. elsif ( !$modules{$TYPE}{READY} && !$initcnt ) {
  3221. $modules{$TYPE}{READY} = 1;
  3222. delete $modules{$TYPE}{INIT};
  3223. Log 4, "$TYPE: module initialization completed";
  3224. my $ret = DoTrigger( "global", "$TYPE:INITIALIZED", $noreplace );
  3225. push @rets, $ret if ( defined($ret) && $ret ne "" );
  3226. }
  3227. # pending devices during bootup
  3228. elsif ( !$modules{$TYPE}{READY} && !$modules{$TYPE}{INIT} && $initcnt ) {
  3229. $modules{$TYPE}{INIT} = 1;
  3230. }
  3231. # pending devices during runtime
  3232. elsif ( !$modules{$TYPE}{INIT} && $initcnt ) {
  3233. $modules{$TYPE}{INIT} = 1;
  3234. Log 4, "$TYPE: module re-initialization in progress";
  3235. my $ret = DoTrigger( "global", "$TYPE:INITIALIZING", $noreplace );
  3236. push @rets, $ret if ( defined($ret) && $ret ne "" );
  3237. }
  3238. # device constellation changed during runtime
  3239. # with pending devices
  3240. elsif ($modules{$TYPE}{READY}
  3241. && $modules{$TYPE}{INIT}
  3242. && ( !$initcnt || $e eq "DELETED" ) )
  3243. {
  3244. delete $modules{$TYPE}{INIT};
  3245. Log 4, "$TYPE: module re-initialization completed";
  3246. my $ret = DoTrigger( "global", "$TYPE:INITIALIZED", $noreplace );
  3247. push @rets, $ret if ( defined($ret) && $ret ne "" );
  3248. }
  3249. # device constellation changed during runtime
  3250. elsif ( $modules{$TYPE}{READY} && ( !$initcnt || $e eq "DELETED" ) ) {
  3251. Log 4, "$TYPE: module constellation updated";
  3252. my $ret = DoTrigger( "global", "$TYPE:MODIFIED", $noreplace );
  3253. push @rets, $ret if ( defined($ret) && $ret ne "" );
  3254. }
  3255. unshift @rets, " $d: $dret" if ($dret);
  3256. return join( "\n", @rets );
  3257. }
  3258. sub RESIDENTStk_DoInitDev(@) {
  3259. my (@devices) = @_;
  3260. my @rets;
  3261. foreach my $d (@devices) {
  3262. $d = $d->{NAME} if ( ref($d) eq "HASH" );
  3263. next unless ( $defs{$d} );
  3264. $defs{$d}{MOD_INIT} = 1;
  3265. my $ret = CallFn( $d, "InitDevFn", $defs{$d} );
  3266. if ( defined($ret) && $ret eq "0" ) {
  3267. delete $defs{$d}{MOD_INIT};
  3268. next;
  3269. }
  3270. if ( defined($ret) && $ret ne "" ) {
  3271. $defs{$d}{MOD_INIT} = 2;
  3272. $modules{ $defs{$d}{TYPE} }{INIT} = 2;
  3273. push @rets, $ret;
  3274. }
  3275. else {
  3276. delete $defs{$d}{MOD_INIT};
  3277. }
  3278. $ret = RESIDENTStk_DoModuleTrigger( $defs{$d}, "INITIALIZED $d" );
  3279. push @rets, $ret if ( defined($ret) && $ret ne "" );
  3280. }
  3281. return join( "\n", @rets );
  3282. }
  3283. 1;