HOMESTATEtk.pm 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315
  1. ###############################################################################
  2. # $Id: HOMESTATEtk.pm 14339 2017-05-21 15:31:50Z loredo $
  3. package main;
  4. use strict;
  5. use warnings;
  6. use Data::Dumper;
  7. use Time::Local;
  8. require RESIDENTStk;
  9. # module variables ############################################################
  10. my %stateSecurity = (
  11. en => [ 'unlocked', 'locked', 'protected', 'secured', 'guarded' ],
  12. de =>
  13. [ 'unverriegelt', 'verriegelt', 'geschützt', 'gesichert', 'überwacht' ],
  14. icons => [
  15. 'status_open@yellow', 'status_standby@yellow@green',
  16. 'status_night@green', 'status_locked@green',
  17. 'building_security@green'
  18. ],
  19. );
  20. my %stateOnoff = (
  21. en => [ 'off', 'on' ],
  22. de => [ 'aus', 'an' ],
  23. );
  24. my %readingsMap = (
  25. date_long => 'calTod',
  26. date_short => 'calTodS',
  27. daytime_long => 'daytime',
  28. daytimeStage => 'daytimeStage',
  29. daytimeStageLn => 'calTodDaytimeStageLn',
  30. daytimeT => 'calTodDaytimeT',
  31. day_desc => 'calTodDesc',
  32. # dstchange => 'calTodDSTchng',
  33. dst_long => 'calTodDST',
  34. isholiday => 'calTodHoliday',
  35. isly => 'calTodLeapyear',
  36. iswe => 'calTodTodWeekend',
  37. mday => 'calTodMonthday',
  38. mdayrem => 'calTodMonthdayRem',
  39. monISO => 'calTodMonthN',
  40. mon_long => 'calTodMonth',
  41. mon_short => 'calTodMonthS',
  42. rday_long => 'calTodRel',
  43. seasonAstroChng => 'calTodSAstroChng',
  44. seasonAstro_long => 'calTodSAstro',
  45. seasonMeteoChng => 'calTodSMeteoChng',
  46. seasonMeteo_long => 'calTodSMeteo',
  47. seasonPheno_long => 'calTodSPheno',
  48. sunrise => 'calTodSunrise',
  49. sunset => 'calTodSunset',
  50. daytimeStages => 'daytimeStages',
  51. wdaynISO => 'calTodWeekdayN',
  52. wday_long => 'calTodWeekday',
  53. wday_short => 'calTodWeekdayS',
  54. weekISO => 'calTodWeek',
  55. yday => 'calTodYearday',
  56. ydayrem => 'calTodYeardayRem',
  57. year => 'calTodYear',
  58. );
  59. my %readingsMap_tom = (
  60. date_long => 'calTom',
  61. date_short => 'calTomS',
  62. daytimeStageLn => 'calTomDaytimeStageLn',
  63. daytimeT => 'calTomDaytimeT',
  64. day_desc => 'calTomDesc',
  65. # dstchange => 'calTomDSTchng',
  66. dst_long => 'calTomDST',
  67. isholiday => 'calTomHoliday',
  68. isly => 'calTomLeapyear',
  69. iswe => 'calTomWeekend',
  70. mday => 'calTomMonthday',
  71. mdayrem => 'calTomMonthdayRem',
  72. monISO => 'calTomMonthN',
  73. mon_long => 'calTomMonth',
  74. mon_short => 'calTomMonthS',
  75. rday_long => 'calTomRel',
  76. seasonAstroChng => 'calTomSAstroChng',
  77. seasonAstro_long => 'calTomSAstro',
  78. seasonMeteoChng => 'calTomSMeteoChng',
  79. seasonMeteo_long => 'calTomSMeteo',
  80. seasonPheno_long => 'calTodSPheno',
  81. sunrise => 'calTomSunrise',
  82. sunset => 'calTomSunset',
  83. wdaynISO => 'calTomWeekdayN',
  84. wday_long => 'calTomWeekday',
  85. wday_short => 'calTomWeekdayS',
  86. weekISO => 'calTomWeek',
  87. yday => 'calTomYearday',
  88. ydayrem => 'calTomYeardayRem',
  89. year => 'calTomYear',
  90. );
  91. # initialize ##################################################################
  92. sub HOMESTATEtk_Initialize($) {
  93. my ($hash) = @_;
  94. $hash->{InitDevFn} = "HOMESTATEtk_InitializeDev";
  95. $hash->{DefFn} = "HOMESTATEtk_Define";
  96. $hash->{UndefFn} = "HOMESTATEtk_Undefine";
  97. $hash->{SetFn} = "HOMESTATEtk_Set";
  98. $hash->{GetFn} = "HOMESTATEtk_Get";
  99. $hash->{AttrFn} = "HOMESTATEtk_Attr";
  100. $hash->{NotifyFn} = "HOMESTATEtk_Notify";
  101. $hash->{parseParams} = 1;
  102. $hash->{AttrList} =
  103. "disable:1,0 disabledForIntervals do_not_notify:1,0 "
  104. . $readingFnAttributes
  105. . " Lang:EN,DE DebugDate ";
  106. my $holidayFilter =
  107. $attr{global}{holiday2we}
  108. ? ":FILTER=NAME!=" . $attr{global}{holiday2we}
  109. : "";
  110. $hash->{AttrList} .=
  111. " HolidayDevices:multiple,"
  112. . join( ",", devspec2array( "TYPE=holiday" . $holidayFilter ) );
  113. $hash->{AttrList} .= " ResidentsDevices:multiple,"
  114. . join( ",", devspec2array("TYPE=RESIDENTS,TYPE=ROOMMATE,TYPE=GUEST") );
  115. }
  116. # module Fn ####################################################################
  117. sub HOMESTATEtk_InitializeDev($) {
  118. my ($hash) = @_;
  119. $hash = $defs{$hash} unless ( ref($hash) );
  120. my $name = $hash->{NAME};
  121. my $TYPE = $hash->{TYPE};
  122. my $changed = 0;
  123. my $lang =
  124. lc( AttrVal( $name, "Lang", AttrVal( "global", "language", "EN" ) ) );
  125. my $langUc = uc($lang);
  126. my @error;
  127. delete $hash->{NEXT_EVENT};
  128. RemoveInternalTimer($hash);
  129. no strict "refs";
  130. &{ $TYPE . "_Initialize" }( \%{ $modules{$TYPE} } ) if ($init_done);
  131. use strict "refs";
  132. # NOTIFYDEV
  133. my $NOTIFYDEV = "global,$name";
  134. $NOTIFYDEV .= "," . HOMESTATEtk_findHomestateSlaves($hash);
  135. unless ( defined( $hash->{NOTIFYDEV} ) && $hash->{NOTIFYDEV} eq $NOTIFYDEV )
  136. {
  137. $hash->{NOTIFYDEV} = $NOTIFYDEV;
  138. $changed = 1;
  139. }
  140. my $time = time;
  141. my $debugDate = AttrVal( $name, "DebugDate", "" );
  142. if ( $debugDate =~
  143. m/^((?:\d{4}\-)?\d{2}-\d{2})(?: (\d{2}:\d{2}(?::\d{2})?))?$/ )
  144. {
  145. my $date = "$1";
  146. $date .= " $2" if ($2);
  147. my ( $sec, $min, $hour, $mday, $mon, $year ) = UConv::_time();
  148. $date = "$year-$date" unless ( $date =~ /^\d{4}-/ );
  149. $date .= "00:00:00" unless ( $date =~ /\d{2}:\d{2}:\d{2}$/ );
  150. $date .= ":00" unless ( $date =~ /\d{2}:\d{2}:\d{2}$/ );
  151. $time = time_str2num($date);
  152. push @error, "WARNING: DebugDate in use ($date)";
  153. }
  154. delete $hash->{'.events'};
  155. delete $hash->{'.t'};
  156. $hash->{'.t'} = HOMESTATEtk_GetDaySchedule( $hash, $time, undef, $lang );
  157. $hash->{'.events'} = $hash->{'.t'}{events};
  158. ## begin reading updates
  159. #
  160. readingsBeginUpdate($hash);
  161. foreach ( sort keys %{ $hash->{'.t'} } ) {
  162. next if ( ref( $hash->{'.t'}{$_} ) );
  163. my $r = $readingsMap{$_} ? $readingsMap{$_} : undef;
  164. my $v = $hash->{'.t'}{$_};
  165. readingsBulkUpdateIfChanged( $hash, $r, $v )
  166. if ( defined($r) );
  167. $r = $readingsMap_tom{$_} ? $readingsMap_tom{$_} : undef;
  168. $v = $hash->{'.t'}{1}{$_} ? $hash->{'.t'}{1}{$_} : undef;
  169. readingsBulkUpdateIfChanged( $hash, $r, $v )
  170. if ( defined($r) && defined($v) );
  171. }
  172. unless ( $lang =~ /^en/i || !$hash->{'.t'}{$lang} ) {
  173. foreach ( sort keys %{ $hash->{'.t'}{$lang} } ) {
  174. next if ( ref( $hash->{'.t'}{$lang}{$_} ) );
  175. my $r = $readingsMap{$_} ? $readingsMap{$_} . "_$langUc" : undef;
  176. my $v = $hash->{'.t'}{$lang}{$_};
  177. readingsBulkUpdateIfChanged( $hash, $r, $v )
  178. if ( defined($r) );
  179. $r =
  180. $readingsMap_tom{$_} ? $readingsMap_tom{$_} . "_$langUc" : undef;
  181. $v =
  182. $hash->{'.t'}{1}{$lang}{$_} ? $hash->{'.t'}{1}{$lang}{$_} : undef;
  183. readingsBulkUpdateIfChanged( $hash, $r, $v )
  184. if ( defined($r) && defined($v) );
  185. }
  186. }
  187. # TODO: seasonSocial
  188. #
  189. # TODO: höchster Sonnenstand
  190. # > Trend Sonne aufgehend, abgehend?
  191. # > temperaturmaximum 14h / 15h bei DST
  192. # error
  193. if ( scalar @error ) {
  194. readingsBulkUpdateIfChanged( $hash, "error", join( "; ", @error ) );
  195. }
  196. else {
  197. delete $hash->{READINGS}{error};
  198. }
  199. HOMESTATEtk_UpdateReadings($hash);
  200. readingsEndUpdate( $hash, 1 );
  201. #
  202. ## end reading updates
  203. # schedule next timer
  204. foreach ( sort keys %{ $hash->{'.events'} } ) {
  205. next if ( $_ < $time );
  206. $hash->{NEXT_EVENT} =
  207. $hash->{'.events'}{$_}{TIME} . " - " . $hash->{'.events'}{$_}{DESC};
  208. InternalTimer( $_, "HOMESTATEtk_InitializeDev", $hash );
  209. last;
  210. }
  211. return 0 unless ($changed);
  212. return undef;
  213. }
  214. sub HOMESTATEtk_Define($$$) {
  215. my ( $hash, $a, $h ) = @_;
  216. my $name = shift @$a;
  217. my $TYPE = shift @{$a};
  218. my $name_attr;
  219. $hash->{MOD_INIT} = 1;
  220. $hash->{NOTIFYDEV} = "global";
  221. delete $hash->{NEXT_EVENT};
  222. RemoveInternalTimer($hash);
  223. # set default settings on first define
  224. if ( $init_done && !defined( $hash->{OLDDEF} ) ) {
  225. HOMESTATEtk_Attr( "init", $name, "Lang" );
  226. $attr{$name}{room} = "Homestate";
  227. $attr{$name}{devStateIcon} =
  228. '{(HOMESTATEtk_devStateIcon($name),"toggle")}';
  229. $attr{$name}{icon} = "control_building_control"
  230. if ( $TYPE eq "HOMESTATE" );
  231. $attr{$name}{icon} = "control_building_eg"
  232. if ( $TYPE eq "SECTIONSTATE" );
  233. $attr{$name}{icon} = "floor"
  234. if ( $TYPE eq "ROOMSTATE" );
  235. # find HOMESTATE device
  236. if ( $TYPE eq "ROOMSTATE" || $TYPE eq "SECTIONSTATE" ) {
  237. my @homestates = devspec2array("TYPE=HOMESTATE");
  238. if ( scalar @homestates ) {
  239. $attr{$name}{"HomestateDevices"} = $homestates[0];
  240. $attr{$name}{room} = $attr{ $homestates[0] }{room}
  241. if ( $attr{ $homestates[0] }
  242. && $attr{ $homestates[0] }{room} );
  243. $attr{$name}{"Lang"} = $attr{ $homestates[0] }{Lang}
  244. if ( $attr{ $homestates[0] }
  245. && $attr{ $homestates[0] }{Lang} );
  246. HOMESTATEtk_Attr( "set", $name, "Lang", $attr{$name}{"Lang"} )
  247. if $attr{$name}{"Lang"};
  248. }
  249. else {
  250. my $n = "Home";
  251. my $i = "";
  252. while ( IsDevice( $n . $i ) ) {
  253. $i = 0 if ( $i eq "" );
  254. $i++;
  255. }
  256. CommandDefine( undef, "$n HOMESTATE" );
  257. $attr{$n}{comment} =
  258. "Auto-created by $TYPE module for use with HOMESTATE Toolkit";
  259. $attr{$name}{"HomestateDevices"} = $n;
  260. $attr{$name}{room} = $attr{$n}{room};
  261. $attr{$name}{"Lang"} = $attr{ $homestates[0] }{Lang}
  262. if ( $attr{ $homestates[0] }
  263. && $attr{ $homestates[0] }{Lang} );
  264. HOMESTATEtk_Attr( "set", $name, "Lang", $attr{$name}{"Lang"} )
  265. if $attr{$name}{"Lang"};
  266. }
  267. }
  268. # find ROOMSTATE device
  269. if ( $TYPE eq "SECTIONSTATE" ) {
  270. my @roomstates = devspec2array("TYPE=ROOMSTATE");
  271. unless ( scalar @roomstates ) {
  272. my $n = "Room";
  273. my $i = "";
  274. while ( IsDevice( $n . $i ) ) {
  275. $i = 0 if ( $i eq "" );
  276. $i++;
  277. }
  278. CommandDefine( undef, "$n ROOMSTATE" );
  279. $attr{$n}{comment} =
  280. "Auto-created by $TYPE module for use with HOMESTATE Toolkit";
  281. $attr{$name}{room} = $attr{$n}{room};
  282. $attr{$name}{"Lang"} = $attr{ $roomstates[0] }{Lang}
  283. if ( $attr{ $roomstates[0] }
  284. && $attr{ $roomstates[0] }{Lang} );
  285. HOMESTATEtk_Attr( "set", $name, "Lang", $attr{$name}{"Lang"} )
  286. if $attr{$name}{"Lang"};
  287. }
  288. }
  289. # find RESIDENTS device
  290. if ( $TYPE eq "HOMESTATE" ) {
  291. my @residents = devspec2array("TYPE=RESIDENTS,TYPE=ROOMMATE");
  292. if ( scalar @residents ) {
  293. $attr{$name}{"ResidentsDevices"} = $residents[0];
  294. $attr{$name}{room} = $attr{ $residents[0] }{room}
  295. if ( $attr{ $residents[0] } && $attr{ $residents[0] }{room} );
  296. $attr{$name}{"Lang"} = $attr{ $residents[0] }{rgr_lang}
  297. if ( $attr{ $residents[0] }
  298. && $attr{ $residents[0] }{rgr_lang} );
  299. $attr{$name}{"Lang"} = $attr{ $residents[0] }{rr_lang}
  300. if ( $attr{ $residents[0] }
  301. && $attr{ $residents[0] }{rr_lang} );
  302. HOMESTATEtk_Attr( "set", $name, "Lang", $attr{$name}{"Lang"} )
  303. if $attr{$name}{"Lang"};
  304. }
  305. else {
  306. my $n = "rgr_Residents";
  307. my $i = "";
  308. while ( IsDevice( $n . $i ) ) {
  309. $i = 0 if ( $i eq "" );
  310. $i++;
  311. }
  312. CommandDefine( undef, "$n RESIDENTS" );
  313. $attr{$n}{comment} =
  314. "Auto-created by $TYPE module for use with HOMESTATE Toolkit";
  315. $attr{$name}{"ResidentsDevices"} = $n;
  316. $attr{$n}{room} = $attr{$name}{room};
  317. HOMESTATEtk_Attr( "set", $name, "Lang", $attr{$name}{"Lang"} )
  318. if $attr{$name}{"Lang"};
  319. $attr{$name}{group} = $attr{$n}{group};
  320. }
  321. }
  322. }
  323. return undef;
  324. }
  325. sub HOMESTATEtk_Undefine($$) {
  326. my ( $hash, $name ) = @_;
  327. delete $hash->{NEXT_EVENT};
  328. RemoveInternalTimer($hash);
  329. return undef;
  330. }
  331. sub HOMESTATEtk_Set($$$);
  332. sub HOMESTATEtk_Set($$$) {
  333. my ( $hash, $a, $h ) = @_;
  334. my $TYPE = $hash->{TYPE};
  335. my $name = shift @$a;
  336. my $lang =
  337. lc( AttrVal( $name, "Lang", AttrVal( "global", "language", "EN" ) ) );
  338. my $langUc = uc($lang);
  339. my $state = ReadingsVal( $name, "state", "" );
  340. my $mode = ReadingsVal( $name, "mode", "" );
  341. my $security = ReadingsVal( $name, "security", "" );
  342. my $autoMode =
  343. HOMESTATEtk_GetIndexFromArray( ReadingsVal( $name, "autoMode", "on" ),
  344. $stateOnoff{en} );
  345. my $silent = 0;
  346. my %rvals;
  347. return undef if ( IsDisabled($name) );
  348. return "No argument given" unless (@$a);
  349. my $cmd = shift @$a;
  350. my $usage = "toggle:noArg";
  351. my $usageL = "";
  352. # usage: mode
  353. my $i =
  354. $autoMode && ReadingsVal( $name, "daytime", "night" ) ne "night"
  355. ? HOMESTATEtk_GetIndexFromArray( ReadingsVal( $name, "daytime", 0 ),
  356. $UConv::daytimes{en} )
  357. : 0;
  358. $usage .= " mode:";
  359. $usageL .= " mode_$langUc:";
  360. while ( $i < scalar @{ $UConv::daytimes{en} } ) {
  361. last
  362. if ( $autoMode
  363. && $i == 6
  364. && ReadingsVal( $name, "daytime", "night" ) !~
  365. m/^evening|midevening|night$/ );
  366. if ( $autoMode
  367. && ReadingsVal( $name, "daytime", "night" ) eq "night"
  368. && $i > 3
  369. && $i != 6 )
  370. {
  371. $i++;
  372. }
  373. else {
  374. $usage .= $UConv::daytimes{en}[$i];
  375. $usageL .= $UConv::daytimes{$lang}[$i];
  376. $i++;
  377. unless ( $autoMode
  378. && $i == 6
  379. && ReadingsVal( $name, "daytime", "night" ) !~
  380. m/^evening|midevening|night$/ )
  381. {
  382. $usage .= "," unless ( $i == scalar @{ $UConv::daytimes{en} } );
  383. $usageL .= ","
  384. unless ( $i == scalar @{ $UConv::daytimes{$lang} } );
  385. }
  386. }
  387. }
  388. # usage: autoMode
  389. $usage .= " autoMode:" . join( ",", @{ $stateOnoff{en} } );
  390. $usageL .= " autoMode_$langUc:" . join( ",", @{ $stateOnoff{$lang} } );
  391. $usage .= " $usageL" unless ( $lang eq "en" );
  392. return "Set usage: choose one of $usage"
  393. unless ( $cmd && $cmd ne "?" );
  394. return
  395. "Device is currently $security and cannot be controlled at this state"
  396. unless ( $security =~ m/^unlocked|locked$/ );
  397. # mode
  398. if ( $cmd eq "state"
  399. || $cmd eq "mode"
  400. || $cmd eq "state_$langUc"
  401. || $cmd eq "mode_$langUc"
  402. || grep ( m/^$cmd$/i, @{ $UConv::daytimes{en} } )
  403. || grep ( /^$cmd$/i, @{ $UConv::daytimes{$lang} } ) )
  404. {
  405. $cmd = shift @$a
  406. if ( $cmd eq "state"
  407. || $cmd eq "mode"
  408. || $cmd eq "state_$langUc"
  409. || $cmd eq "mode_$langUc" );
  410. my $i = HOMESTATEtk_GetIndexFromArray( $cmd, $UConv::daytimes{en} );
  411. $i = HOMESTATEtk_GetIndexFromArray( $cmd, $UConv::daytimes{$lang} )
  412. unless ( $lang eq "en" || defined($i) );
  413. $i = $cmd
  414. if ( !defined($i)
  415. && $cmd =~ /^\d+$/
  416. && defined( $UConv::daytimes{en}[$cmd] ) );
  417. return "Invalid argument $cmd"
  418. unless ( defined($i) );
  419. my $id =
  420. HOMESTATEtk_GetIndexFromArray( ReadingsVal( $name, "daytime", 0 ),
  421. $UConv::daytimes{en} );
  422. if ($autoMode) {
  423. # during daytime, one cannot go back in time...
  424. $i = $id
  425. if ( ReadingsVal( $name, "daytime", "night" ) ne "night"
  426. && $i < $id );
  427. # evening is latest until evening itself was reached
  428. $i = $id if ( $i >= 6 && $id <= 3 );
  429. # afternoon is latest until morning was reached
  430. $i = 6 if ( $i >= 4 && $i != 6 && $id == 6 );
  431. $i = 0 if ( $i > 6 && $id == 6 );
  432. }
  433. Log3 $name, 2, "$TYPE set $name mode " . $UConv::daytimes{en}[$i];
  434. $rvals{mode} = $UConv::daytimes{en}[$i];
  435. }
  436. # toggle
  437. elsif ( $cmd eq "toggle" ) {
  438. my $i = HOMESTATEtk_GetIndexFromArray( $mode, $UConv::daytimes{en} );
  439. my $id =
  440. HOMESTATEtk_GetIndexFromArray( ReadingsVal( $name, "daytime", 0 ),
  441. $UConv::daytimes{en} );
  442. $i++;
  443. $i = 0 if ( $i == 7 );
  444. if ($autoMode) {
  445. # during daytime, one cannot go back in time...
  446. $i = $id
  447. if ( ReadingsVal( $name, "daytime", "night" ) ne "night"
  448. && $i < $id );
  449. # evening is latest until evening itself was reached
  450. $i = $id if ( $i >= 6 && $id <= 3 );
  451. # afternoon is latest until morning was reached
  452. $i = 6 if ( $i >= 4 && $i != 6 && $id == 6 );
  453. $i = 0 if ( $i > 6 && $id == 6 );
  454. }
  455. Log3 $name, 2, "$TYPE set $name mode " . $UConv::daytimes{en}[$i];
  456. $rvals{mode} = $UConv::daytimes{en}[$i];
  457. }
  458. # autoMode
  459. elsif ( $cmd eq "autoMode" || $cmd eq "autoMode_$langUc" ) {
  460. my $p1 = shift @$a;
  461. my $i = HOMESTATEtk_GetIndexFromArray( $p1, $stateOnoff{en} );
  462. $i = HOMESTATEtk_GetIndexFromArray( $p1, $stateOnoff{$lang} )
  463. unless ( $lang eq "en" || defined($i) );
  464. $i = $cmd
  465. if ( !defined($i)
  466. && $cmd =~ /^\d+$/
  467. && defined( $stateOnoff{en}[$cmd] ) );
  468. return "Invalid argument $cmd"
  469. unless ( defined($i) );
  470. Log3 $name, 2, "$TYPE set $name autoMode " . $stateOnoff{en}[$i];
  471. $rvals{autoMode} = $stateOnoff{en}[$i];
  472. }
  473. # usage
  474. else {
  475. return "Unknown set command $cmd, choose one of $usage";
  476. }
  477. readingsBeginUpdate($hash);
  478. # if autoMode changed
  479. if ( defined( $rvals{autoMode} ) ) {
  480. readingsBulkUpdateIfChanged( $hash, "autoMode", $rvals{autoMode} );
  481. readingsBulkUpdateIfChanged(
  482. $hash,
  483. "autoMode_$langUc",
  484. $stateOnoff{$lang}[
  485. HOMESTATEtk_GetIndexFromArray(
  486. $rvals{autoMode}, $stateOnoff{en}
  487. )
  488. ]
  489. ) unless ( $lang eq "en" );
  490. if ( $rvals{autoMode} eq "on" ) {
  491. my $im =
  492. HOMESTATEtk_GetIndexFromArray( $mode, $UConv::daytimes{en} );
  493. my $id =
  494. HOMESTATEtk_GetIndexFromArray( ReadingsVal( $name, "daytime", 0 ),
  495. $UConv::daytimes{en} );
  496. $rvals{mode} = $UConv::daytimes{en}[$id]
  497. if ( $im < $id || ( $im == 6 && $id < 6 ) )
  498. ; #TODO check when switching during evening and midevening time
  499. }
  500. }
  501. # if mode changed
  502. if ( defined( $rvals{mode} ) && $rvals{mode} ne $mode ) {
  503. my $modeL = ReadingsVal( $name, "mode_$langUc", "" );
  504. readingsBulkUpdate( $hash, "lastMode", $mode ) if ( $mode ne "" );
  505. readingsBulkUpdate( $hash, "lastMode_$langUc", $modeL )
  506. if ( $modeL ne "" );
  507. $mode = $rvals{mode};
  508. $modeL =
  509. $UConv::daytimes{$lang}
  510. [ HOMESTATEtk_GetIndexFromArray( $rvals{mode}, $UConv::daytimes{en} )
  511. ];
  512. readingsBulkUpdate( $hash, "mode", $mode );
  513. readingsBulkUpdate( $hash, "mode_$langUc", $modeL )
  514. unless ( $lang eq "en" );
  515. }
  516. HOMESTATEtk_UpdateReadings($hash);
  517. readingsEndUpdate( $hash, 1 );
  518. return undef;
  519. }
  520. sub HOMESTATEtk_Get($$$) {
  521. my ( $hash, $a, $h ) = @_;
  522. my $name = shift @$a;
  523. my $lang =
  524. lc( AttrVal( $name, "Lang", AttrVal( "global", "language", "EN" ) ) );
  525. my $langUc = uc($lang);
  526. return "No argument given" unless (@$a);
  527. my $cmd = shift @$a;
  528. my $usage = "Unknown argument $cmd, choose one of schedule";
  529. # schedule
  530. if ( $cmd eq "schedule" ) {
  531. my $date = shift @$a;
  532. my $time = shift @$a;
  533. if ($date) {
  534. return "invalid date format $date"
  535. unless ( !$date
  536. || $date =~ m/^\d{4}\-\d{2}-\d{2}$/
  537. || $date =~ m/^\d{2}-\d{2}$/
  538. || $date =~ m/^\d{10}$/ );
  539. return "invalid time format $time"
  540. unless ( !$time
  541. || $time =~ m/^\d{2}:\d{2}(:\d{2})?$/ );
  542. unless ( $date =~ m/^\d{10}$/ ) {
  543. my ( $sec, $min, $hour, $mday, $mon, $year ) = UConv::_time();
  544. $date = "$year-$date" if ( $date =~ m/^\d{2}-\d{2}$/ );
  545. $time .= ":00" if ( $time && $time =~ m/^\d{2}:\d{2}$/ );
  546. $date .= $time ? " $time" : " 00:00:00";
  547. # ( $year, $mon, $mday, $hour, $min, $sec ) =
  548. # split( /[\s.:-]+/, $date );
  549. # $date = timelocal( $sec, $min, $hour, $mday, $mon - 1, $year );
  550. $date = time_str2num($date);
  551. }
  552. }
  553. #TODO timelocal? 03-26 results in wrong timestamp
  554. # return PrintHash(
  555. # $date
  556. # ? %{ HOMESTATEtk_GetDaySchedule( $hash, $date, undef, $lang ) }
  557. # {events}
  558. # : $hash->{helper}{events},
  559. # 3
  560. # );
  561. return PrintHash(
  562. $date
  563. ? HOMESTATEtk_GetDaySchedule( $hash, $date, undef, $lang )
  564. : $hash->{'.events'},
  565. 3
  566. );
  567. }
  568. # return usage hint
  569. else {
  570. return $usage;
  571. }
  572. return undef;
  573. }
  574. sub HOMESTATEtk_Attr(@) {
  575. my ( $cmd, $name, $attribute, $value ) = @_;
  576. my $hash = $defs{$name};
  577. my $TYPE = $hash->{TYPE};
  578. my $security = ReadingsVal( $name, "security", "" );
  579. return
  580. "Device is currently $security and attributes cannot be changed at this state"
  581. unless ( !$init_done || $security =~ m/^unlocked|locked$/ );
  582. if ( $attribute eq "HomestateDevices" ) {
  583. return "Value for $attribute has invalid format"
  584. unless ( $cmd eq "del"
  585. || $value =~ m/^[A-Za-z\d._]+(?:,[A-Za-z\d._]*)*$/ );
  586. delete $hash->{HOMESTATES};
  587. $hash->{HOMESTATES} = $value unless ( $cmd eq "del" );
  588. }
  589. elsif ( $attribute eq "FloorstateDevices" ) {
  590. return "Value for $attribute has invalid format"
  591. unless ( $cmd eq "del"
  592. || $value =~ m/^[A-Za-z\d._]+(?:,[A-Za-z\d._]*)*$/ );
  593. delete $hash->{SECTIONSTATES};
  594. $hash->{SECTIONSTATES} = $value unless ( $cmd eq "del" );
  595. }
  596. elsif ( $attribute eq "RoomstateDevices" ) {
  597. return "Value for $attribute has invalid format"
  598. unless ( $cmd eq "del"
  599. || $value =~ m/^[A-Za-z\d._]+(?:,[A-Za-z\d._]*)*$/ );
  600. delete $hash->{ROOMSTATES};
  601. $hash->{ROOMSTATES} = $value unless ( $cmd eq "del" );
  602. }
  603. elsif ( $attribute eq "ResidentsDevices" ) {
  604. return "Value for $attribute has invalid format"
  605. unless ( $cmd eq "del"
  606. || $value =~ m/^[A-Za-z\d._]+(?:,[A-Za-z\d._]*)*$/ );
  607. delete $hash->{RESIDENTS};
  608. $hash->{RESIDENTS} = $value unless ( $cmd eq "del" );
  609. }
  610. elsif ( $attribute eq "HolidayDevices" ) {
  611. return "Value for $attribute has invalid format"
  612. unless ( $cmd eq "del"
  613. || $value =~ m/^[A-Za-z\d._]+(?:,[A-Za-z\d._]*)*$/ );
  614. }
  615. elsif ( $attribute eq "DebugDate" ) {
  616. return
  617. "Invalid format for $attribute. Can be:\n"
  618. . "\nYYYY-MM-DD"
  619. . "\nYYYY-MM-DD HH:MM"
  620. . "\nYYYY-MM-DD HH:MM:SS"
  621. . "\nMM-DD"
  622. . "\nMM-DD HH:MM"
  623. . "\nMM-DD HH:MM:SS"
  624. unless ( $cmd eq "del"
  625. || $value =~
  626. m/^((?:\d{4}\-)?\d{2}-\d{2})(?: (\d{2}:\d{2}(?::\d{2})?))?$/ );
  627. }
  628. elsif ( !$init_done ) {
  629. return undef;
  630. }
  631. elsif ( $attribute eq "disable" ) {
  632. if ( $value and $value == 1 ) {
  633. $hash->{STATE} = "disabled";
  634. }
  635. elsif ( $cmd eq "del" or !$value ) {
  636. evalStateFormat($hash);
  637. }
  638. }
  639. elsif ( $attribute eq "Lang" ) {
  640. my $lang =
  641. $cmd eq "set"
  642. ? lc($value)
  643. : lc( AttrVal( "global", "language", "EN" ) );
  644. my $langUc = uc($lang);
  645. # for initial define, ensure fallback to EN
  646. $lang = "en"
  647. if ( $cmd eq "init" && $lang !~ /^en|de$/i );
  648. if ( $lang eq "de" ) {
  649. $attr{$name}{alias} = "Modus"
  650. if ( !defined( $attr{$name}{alias} )
  651. || $attr{$name}{alias} eq "Mode" );
  652. $attr{$name}{webCmd} = "mode_$langUc"
  653. if ( !defined( $attr{$name}{webCmd} )
  654. || $attr{$name}{webCmd} eq "mode" );
  655. if ( $TYPE eq "HOMESTATE" ) {
  656. $attr{$name}{group} = "Zuhause Status"
  657. if ( !defined( $attr{$name}{group} )
  658. || $attr{$name}{group} eq "Home State" );
  659. }
  660. if ( $TYPE eq "SECTIONSTATE" ) {
  661. $attr{$name}{group} = "Bereichstatus"
  662. if ( !defined( $attr{$name}{group} )
  663. || $attr{$name}{group} eq "Section State" );
  664. }
  665. if ( $TYPE eq "ROOMSTATE" ) {
  666. $attr{$name}{group} = "Raumstatus"
  667. if ( !defined( $attr{$name}{group} )
  668. || $attr{$name}{group} eq "Room State" );
  669. }
  670. }
  671. elsif ( $lang eq "en" ) {
  672. $attr{$name}{alias} = "Mode"
  673. if ( !defined( $attr{$name}{alias} )
  674. || $attr{$name}{alias} eq "Modus" );
  675. $attr{$name}{webCmd} = "mode"
  676. if ( !defined( $attr{$name}{webCmd} )
  677. || $attr{$name}{webCmd} =~ /^mode_[A-Z]{2}$/ );
  678. if ( $TYPE eq "HOMESTATE" ) {
  679. $attr{$name}{group} = "Home State"
  680. if ( !defined( $attr{$name}{group} )
  681. || $attr{$name}{group} eq "Zuhause Status" );
  682. }
  683. if ( $TYPE eq "SECTIONSTATE" ) {
  684. $attr{$name}{group} = "Section State"
  685. if ( !defined( $attr{$name}{group} )
  686. || $attr{$name}{group} eq "Bereichstatus" );
  687. }
  688. if ( $TYPE eq "ROOMSTATE" ) {
  689. $attr{$name}{group} = "Room State"
  690. if ( !defined( $attr{$name}{group} )
  691. || $attr{$name}{group} eq "Raumstatus" );
  692. }
  693. }
  694. else {
  695. return "Unsupported language $langUc";
  696. }
  697. $attr{$name}{$attribute} = $value if ( $cmd eq "set" );
  698. evalStateFormat($hash);
  699. }
  700. return undef;
  701. }
  702. sub HOMESTATEtk_Notify($$) {
  703. my ( $hash, $dev ) = @_;
  704. my $name = $hash->{NAME};
  705. my $TYPE = $hash->{TYPE};
  706. my $devName = $dev->{NAME};
  707. my $devType = GetType($devName);
  708. if ( $devName eq "global" ) {
  709. my $events = deviceEvents( $dev, 1 );
  710. return "" unless ($events);
  711. foreach ( @{$events} ) {
  712. next if ( $_ =~ m/^[A-Za-z\d_-]+:/ );
  713. # module and device initialization
  714. if ( $_ =~ m/^INITIALIZED|REREADCFG$/ ) {
  715. if ( !defined( &{'DoInitDev'} ) ) {
  716. if ( $_ eq "REREADCFG" ) {
  717. delete $modules{$devType}{READY};
  718. delete $modules{$devType}{INIT};
  719. }
  720. RESIDENTStk_DoInitDev(
  721. devspec2array("TYPE=$TYPE:FILTER=MOD_INIT=.+") );
  722. }
  723. }
  724. # if any of our monitored devices was modified,
  725. # recalculate monitoring status
  726. elsif ( $_ =~
  727. m/^(DEFINED|MODIFIED|RENAMED|DELETED)\s+([A-Za-z\d_-]+)$/ )
  728. {
  729. if ( defined( &{'DoInitDev'} ) ) {
  730. # DELETED would normally be handled by fhem.pl and imply
  731. # DoModuleTrigger instead of DoInitDev to update module
  732. # init state
  733. next if ( $_ =~ /^DELETED/ );
  734. DoInitDev($name);
  735. }
  736. else {
  737. # for DELETED, we normally would want to use
  738. # DoModuleTrigger() but we miss the deleted
  739. # device's TYPE at this state :-(
  740. RESIDENTStk_DoInitDev($name);
  741. }
  742. }
  743. # only process attribute events
  744. next
  745. unless ( $_ =~
  746. m/^((?:DELETE)?ATTR)\s+([A-Za-z\d._]+)\s+([A-Za-z\d_\.\-\/]+)(?:\s+(.*)\s*)?$/
  747. );
  748. my $cmd = $1;
  749. my $d = $2;
  750. my $attr = $3;
  751. my $val = $4;
  752. my $type = GetType($d);
  753. # filter attributes to be processed
  754. next
  755. unless ( $attr =~ /[Dd]evices?$/
  756. || $attr eq "DebugDate" );
  757. # when own attributes were changed
  758. if ( $d eq $name ) {
  759. if ( defined( &{'DoInitDev'} ) ) {
  760. delete $hash->{NEXT_EVENT};
  761. RemoveInternalTimer($hash);
  762. InternalTimer( gettimeofday() + 0.5, "DoInitDev", $hash );
  763. }
  764. else {
  765. delete $hash->{NEXT_EVENT};
  766. RemoveInternalTimer($hash);
  767. InternalTimer( gettimeofday() + 0.5,
  768. "RESIDENTStk_DoInitDev", $hash );
  769. }
  770. return "";
  771. }
  772. }
  773. return "";
  774. }
  775. return "" if ( IsDisabled($name) or IsDisabled($devName) );
  776. # process events from RESIDENTS, ROOMMATE or GUEST devices
  777. # only when they hit HOMESTATE devices
  778. if ( $TYPE ne $devType
  779. && $devType =~
  780. m/^HOMESTATE|SECTIONSTATE|ROOMSTATE|RESIDENTS|ROOMMATE|GUEST$/ )
  781. {
  782. my $events = deviceEvents( $dev, 1 );
  783. return "" unless ($events);
  784. foreach my $event ( @{$events} ) {
  785. next unless ( defined($event) );
  786. # state changed
  787. if ( $event !~ /^[a-zA-Z\d._]+:/
  788. || $event =~ /^state:/
  789. || $event =~ /^presence:/
  790. || $event =~ /^mode:/
  791. || $event =~ /^security:/
  792. || $event =~ /^wayhome:/
  793. || $event =~ /^wakeup:/ )
  794. {
  795. if ( defined( &{'DoInitDev'} ) ) {
  796. DoInitDev($name);
  797. }
  798. else {
  799. RESIDENTStk_DoInitDev($name);
  800. }
  801. }
  802. }
  803. return "";
  804. }
  805. # process own events
  806. elsif ( $devName eq $name ) {
  807. my $events = deviceEvents( $dev, 1 );
  808. return "" unless ($events);
  809. foreach my $event ( @{$events} ) {
  810. next unless ( defined($event) );
  811. }
  812. return "";
  813. }
  814. return "";
  815. }
  816. sub HOMESTATEtk_GetIndexFromArray($$) {
  817. my ( $string, $array ) = @_;
  818. return undef unless ( ref($array) eq "ARRAY" );
  819. my ($index) = grep { $array->[$_] =~ /^$string$/i } ( 0 .. @$array - 1 );
  820. return defined $index ? $index : undef;
  821. }
  822. sub HOMESTATEtk_findHomestateSlaves($;$) {
  823. my ( $hash, $ret ) = @_;
  824. my @slaves;
  825. if ( $hash->{TYPE} eq "HOMESTATE" ) {
  826. my @SECTIONSTATES;
  827. foreach ( devspec2array("TYPE=SECTIONSTATE") ) {
  828. next
  829. unless (
  830. defined( $defs{$_}{SECTIONSTATES} )
  831. && grep { $hash->{NAME} eq $_ }
  832. split( /,/, $defs{$_}{SECTIONSTATES} )
  833. );
  834. push @SECTIONSTATES, $_;
  835. }
  836. if ( scalar @SECTIONSTATES ) {
  837. $hash->{SECTIONSTATES} = join( ",", @SECTIONSTATES );
  838. }
  839. elsif ( $hash->{SECTIONSTATES} ) {
  840. delete $hash->{SECTIONSTATES};
  841. }
  842. if ( $hash->{SECTIONSTATES} ) {
  843. $ret .= "," if ($ret);
  844. $ret .= $hash->{SECTIONSTATES};
  845. }
  846. }
  847. if ( $hash->{TYPE} eq "HOMESTATE" || $hash->{TYPE} eq "SECTIONSTATE" ) {
  848. my @ROOMSTATES;
  849. foreach ( devspec2array("TYPE=ROOMSTATE") ) {
  850. next
  851. unless (
  852. (
  853. defined( $defs{$_}{HOMESTATES} )
  854. && grep { $hash->{NAME} eq $_ }
  855. split( /,/, $defs{$_}{HOMESTATES} )
  856. )
  857. || (
  858. defined( $defs{$_}{SECTIONSTATES} )
  859. && grep { $hash->{NAME} eq $_ }
  860. split( /,/, $defs{$_}{SECTIONSTATES} )
  861. )
  862. );
  863. push @ROOMSTATES, $_;
  864. }
  865. if ( scalar @ROOMSTATES ) {
  866. $hash->{ROOMSTATES} = join( ",", @ROOMSTATES );
  867. }
  868. elsif ( $hash->{ROOMSTATES} ) {
  869. delete $hash->{ROOMSTATES};
  870. }
  871. if ( $hash->{ROOMSTATES} ) {
  872. $ret .= "," if ($ret);
  873. $ret .= $hash->{ROOMSTATES};
  874. }
  875. }
  876. if ( $hash->{RESIDENTS} ) {
  877. $ret .= "," if ($ret);
  878. $ret .= $hash->{RESIDENTS};
  879. }
  880. return HOMESTATEtk_findDummySlaves( $hash, $ret );
  881. }
  882. sub HOMESTATEtk_findDummySlaves($;$);
  883. sub HOMESTATEtk_findDummySlaves($;$) {
  884. my ( $hash, $ret ) = @_;
  885. $ret = "" unless ($ret);
  886. return $ret;
  887. }
  888. sub HOMESTATEtk_devStateIcon($) {
  889. my ($hash) = @_;
  890. $hash = $defs{$hash} if ( ref($hash) ne 'HASH' );
  891. return undef if ( !$hash );
  892. my $name = $hash->{NAME};
  893. my $lang =
  894. lc( AttrVal( $name, "Lang", AttrVal( "global", "language", "EN" ) ) );
  895. my $langUc = uc($lang);
  896. my @devStateIcon;
  897. # mode
  898. my $i = 0;
  899. foreach ( @{ $UConv::daytimes{en} } ) {
  900. push @devStateIcon, "$_:$UConv::daytimes{icons}[$i++]:toggle";
  901. }
  902. unless ( $lang eq "en" && defined( $UConv::daytimes{$lang} ) ) {
  903. $i = 0;
  904. foreach ( @{ $UConv::daytimes{$lang} } ) {
  905. push @devStateIcon, "$_:$UConv::daytimes{icons}[$i++]:toggle";
  906. }
  907. }
  908. # security
  909. $i = 0;
  910. foreach ( @{ $stateSecurity{en} } ) {
  911. push @devStateIcon, "$_:$stateSecurity{icons}[$i++]";
  912. }
  913. unless ( $lang eq "en" && defined( $UConv::daytimes{$lang} ) ) {
  914. $i = 0;
  915. foreach ( @{ $stateSecurity{$lang} } ) {
  916. push @devStateIcon, "$_:$stateSecurity{icons}[$i++]";
  917. }
  918. }
  919. return join( " ", @devStateIcon );
  920. }
  921. sub HOMESTATEtk_GetDaySchedule($;$$$$$) {
  922. my ( $hash, $time, $totalTemporalHours, $lang, @srParams ) = @_;
  923. my $name = $hash->{NAME};
  924. $lang = (
  925. $attr{global}{language}
  926. ? $attr{global}{language}
  927. : "EN"
  928. ) unless ($lang);
  929. return undef
  930. unless ( !$time || $time =~ /^\d{10}(?:\.\d+)?$/ );
  931. my $ret = UConv::GetDaytime( $time, $totalTemporalHours, $lang, @srParams );
  932. # consider user defined vacation days
  933. my $holidayDevs = AttrVal( $name, "HolidayDevices", "" );
  934. foreach my $holidayDev ( split( /,/, $holidayDevs ) ) {
  935. next
  936. unless ( IsDevice( $holidayDev, "holiday" )
  937. && AttrVal( "global", "holiday2we", "" ) ne $holidayDev );
  938. my $date = sprintf( "%02d-%02d", $ret->{monISO}, $ret->{mday} );
  939. my $tod = holiday_refresh( $holidayDev, $date );
  940. $date =
  941. sprintf( "%02d-%02d", $ret->{'-1'}{monISO}, $ret->{'-1'}{mday} );
  942. my $ytd = holiday_refresh( $holidayDev, $date );
  943. $date = sprintf( "%02d-%02d", $ret->{1}{monISO}, $ret->{1}{mday} );
  944. my $tom = holiday_refresh( $holidayDev, $date );
  945. if ( $tod ne "none" ) {
  946. $ret->{iswe} += 3;
  947. $ret->{isholiday} += 2;
  948. $ret->{day_desc} = $tod unless ( $ret->{isholiday} == 3 );
  949. $ret->{day_desc} .= ", $tod" if ( $ret->{isholiday} == 3 );
  950. }
  951. if ( $ytd ne "none" ) {
  952. $ret->{'-1'}{isholiday} += 2;
  953. $ret->{'-1'}{day_desc} = $ytd
  954. unless ( $ret->{'-1'}{isholiday} == 3 );
  955. $ret->{'-1'}{day_desc} .= ", $ytd"
  956. if ( $ret->{'-1'}{isholiday} == 3 );
  957. }
  958. if ( $tom ne "none" ) {
  959. $ret->{1}{isholiday} += 2;
  960. $ret->{1}{day_desc} = $tom;
  961. $ret->{1}{day_desc} = $tom unless ( $ret->{1}{isholiday} == 3 );
  962. $ret->{1}{day_desc} .= ", $tom"
  963. if ( $ret->{1}{isholiday} == 3 );
  964. }
  965. }
  966. return $ret;
  967. }
  968. sub HOMESTATEtk_UpdateReadings (@) {
  969. my ($hash) = @_;
  970. my $name = $hash->{NAME};
  971. my $TYPE = $hash->{TYPE};
  972. my $t = $hash->{'.t'};
  973. my $state = ReadingsVal( $name, "state", "" );
  974. my $security = ReadingsVal( $name, "security", "" );
  975. my $daytime = ReadingsVal( $name, "daytime", "" );
  976. my $mode = ReadingsVal( $name, "mode", "" );
  977. my $autoMode =
  978. HOMESTATEtk_GetIndexFromArray( ReadingsVal( $name, "autoMode", "on" ),
  979. $stateOnoff{en} );
  980. my $lang =
  981. lc( AttrVal( $name, "Lang", AttrVal( "global", "language", "EN" ) ) );
  982. my $langUc = uc($lang);
  983. # presence
  984. my $state_home = 0;
  985. my $state_gotosleep = 0;
  986. my $state_asleep = 0;
  987. my $state_awoken = 0;
  988. my $state_absent = 0;
  989. my $state_gone = 0;
  990. my $wayhome = 0;
  991. my $wayhomeDelayed = 0;
  992. my $wakeup = 0;
  993. foreach my $internal ( "RESIDENTS", "SECTIONSTATES", "ROOMSTATES" ) {
  994. next unless ( $hash->{$internal} );
  995. foreach my $presenceDev ( split( /,/, $hash->{$internal} ) ) {
  996. my $state = ReadingsVal( $presenceDev, "state", "gone" );
  997. $state_home++ if ( $state eq "home" );
  998. $state_gotosleep++ if ( $state eq "gotosleep" );
  999. $state_asleep++ if ( $state eq "asleep" );
  1000. $state_awoken++ if ( $state eq "awoken" );
  1001. $state_absent++ if ( $state eq "absent" );
  1002. $state_gone++ if ( $state eq "gone" || $state eq "none" );
  1003. my $wayhome = ReadingsVal( $presenceDev, "wayhome", 0 );
  1004. $wayhome++ if ($wayhome);
  1005. $wayhomeDelayed++ if ( $wayhome == 2 );
  1006. my $wakeup = ReadingsVal( $presenceDev, "wakeup", 0 );
  1007. $wakeup++ if ($wakeup);
  1008. }
  1009. }
  1010. $state_home = 1
  1011. unless ( $hash->{RESIDENTS}
  1012. || $hash->{SECTIONSTATES}
  1013. || $hash->{ROOMSTATES} );
  1014. # autoMode
  1015. if ( $autoMode && $mode ne $daytime ) {
  1016. my $im = HOMESTATEtk_GetIndexFromArray( $mode, $UConv::daytimes{en} );
  1017. my $id =
  1018. HOMESTATEtk_GetIndexFromArray( $daytime, $UConv::daytimes{en} );
  1019. if (
  1020. $mode eq "" || (
  1021. # follow daymode throughout the day until midnight
  1022. ( $im < $id && $id != 6 )
  1023. # first change after midnight
  1024. || ( $im >= 4 && $id == 6 )
  1025. # morning
  1026. || ( $im == 6 && $id >= 0 && $id <= 3 )
  1027. )
  1028. )
  1029. {
  1030. readingsBulkUpdate( $hash, "lastMode", $mode ) if ( $mode ne "" );
  1031. $mode = $daytime;
  1032. readingsBulkUpdate( $hash, "mode", $mode );
  1033. unless ( $lang eq "en" ) {
  1034. my $modeL = ReadingsVal( $name, "mode_$langUc", "" );
  1035. my $daytimeL = ReadingsVal( $name, "daytime_$langUc", "" );
  1036. readingsBulkUpdate( $hash, "lastMode_$langUc", $modeL )
  1037. if ( $modeL ne "" );
  1038. readingsBulkUpdate( $hash, "mode_$langUc", $daytimeL )
  1039. if ( $daytimeL ne "" );
  1040. }
  1041. }
  1042. }
  1043. #
  1044. # security calculation
  1045. #
  1046. my $newsecurity;
  1047. # unsecured
  1048. if ( $state_home > 0 && $mode !~ /^night|midevening$/ ) {
  1049. $newsecurity = "unlocked";
  1050. }
  1051. # locked
  1052. elsif ($state_home > 0
  1053. || $state_awoken > 0
  1054. || $state_gotosleep > 0
  1055. || $wakeup > 0 )
  1056. {
  1057. $newsecurity = "locked";
  1058. }
  1059. # night
  1060. elsif ( $state_asleep > 0 ) {
  1061. $newsecurity = "protected";
  1062. }
  1063. # secured
  1064. elsif ( $state_absent > 0 || $wayhome > 0 ) {
  1065. $newsecurity = "secured";
  1066. }
  1067. # extended
  1068. else {
  1069. $newsecurity = "guarded";
  1070. }
  1071. if ( $newsecurity ne $security ) {
  1072. readingsBulkUpdate( $hash, "lastSecurity", $security )
  1073. if ( $security ne "" );
  1074. $security = $newsecurity;
  1075. readingsBulkUpdate( $hash, "security", $security );
  1076. unless ( $lang eq "en" ) {
  1077. my $securityL = ReadingsVal( $name, "security_$langUc", "" );
  1078. readingsBulkUpdate( $hash, "lastSecurity_$langUc", $securityL )
  1079. if ( $securityL ne "" );
  1080. $securityL =
  1081. $stateSecurity{$lang}
  1082. [ HOMESTATEtk_GetIndexFromArray( $security, $stateSecurity{en} )
  1083. ];
  1084. readingsBulkUpdate( $hash, "security_$langUc", $securityL );
  1085. }
  1086. }
  1087. #
  1088. # state calculation:
  1089. # combine security and mode
  1090. #
  1091. my $newstate;
  1092. my $statesrc;
  1093. # mode
  1094. if ( $security =~ m/^unlocked|locked/ ) {
  1095. $newstate = $mode;
  1096. $statesrc = "mode";
  1097. }
  1098. # security
  1099. else {
  1100. $newstate = $security;
  1101. $statesrc = "security";
  1102. }
  1103. if ( $newstate ne $state ) {
  1104. readingsBulkUpdate( $hash, "lastState", $state ) if ( $state ne "" );
  1105. $state = $newstate;
  1106. readingsBulkUpdate( $hash, "state", $state );
  1107. unless ( $lang eq "en" ) {
  1108. my $stateL = ReadingsVal( $name, "state_$langUc", "" );
  1109. readingsBulkUpdate( $hash, "lastState_$langUc", $stateL )
  1110. if ( $stateL ne "" );
  1111. $stateL = ReadingsVal( $name, $statesrc . "_$langUc", "" );
  1112. readingsBulkUpdate( $hash, "state_$langUc", $stateL );
  1113. }
  1114. }
  1115. }
  1116. 1;