##################################################################################### # $Id: 22_HOMEMODE.pm 26813 2022-12-07 21:19:57Z DeeSPe $ # # Usage # # define HOMEMODE [RESIDENTS-MASTER-DEVICE] # ##################################################################################### package FHEM::HOMEMODE; use strict; use warnings; use Time::HiRes qw(gettimeofday); use List::Util qw(uniq); use HttpUtils; use FHEM::Meta; use Data::Dumper; use GPUtils qw(GP_Import); my $HOMEMODE_version = '2.0.0beta1'; my $HOMEMODE_Daytimes = '05:00|morning 10:00|day 14:00|afternoon 18:00|evening 23:00|night'; my $HOMEMODE_Seasons = '03.01|spring 06.01|summer 09.01|autumn 12.01|winter'; my $HOMEMODE_UserModes = 'gotosleep,awoken,asleep'; my $HOMEMODE_UserModesAll = $HOMEMODE_UserModes.',home,absent,gone'; my $HOMEMODE_AlarmModes = 'disarm,confirm,armhome,armnight,armaway'; my $HOMEMODE_Locations = 'arrival,home,bed,underway,wayhome'; my $HOMEMODE_batTypes = 'AutomowerConnectDevice|BOTVAC|CUL.*|GardenaSmartDevice|HOMBOT|HUEDevice|HusqvarnaAutomower|INDEGO|IT|LaCrosse|MAX|MQTT_DEVICE|MQTT2_DEVICE|netatmo|NUKIDevice|OBIS|PRESENCE|XiaomiDevice|XiaomiFlowerSens|XiaomiSmartHome_Device|ZWave'; my $langDE; my $ver = $HOMEMODE_version; $ver =~ s/\.\d{1,2}(beta\d+)?$//x; BEGIN { GP_Import( qw( AnalyzeCommandChain AttrNum AttrVal Calendar_GetEvents CommandAttr CommandDefine CommandDelete CommandDeleteAttr CommandDeleteReading CommandSet CommandTrigger Debug DoTrigger FileRead HttpUtils_NonblockingGet InternalVal InternalTimer IsDisabled Log3 ReadingsAge ReadingsNum ReadingsVal RemoveInternalTimer ReplaceEventMap SemicolonEscape FW_detail FW_hidden FW_select FW_textfieldv data attr defs deviceEvents devspec2array decode_base64 encode_base64 filter_true gettimeofday init_done modules init_done makeReadingName perlSyntaxCheck readingFnAttributes readingsBeginUpdate readingsBulkUpdate readingsBulkUpdateIfChanged readingsEndUpdate readingsSingleUpdate ) ); } sub ::HOMEMODE_Initialize { goto &Initialize } sub Initialize { my ($hash) = @_; $hash->{AttrFn} = \&Attr; $hash->{DefFn} = \&Define; $hash->{NotifyFn} = \&Notify; $hash->{GetFn} = \&Get; $hash->{SetFn} = \&Set; $hash->{UndefFn} = \&Undef; $hash->{FW_detailFn} = \&Details; $hash->{AttrList} = 'disable:1,0 disabledForIntervals Home.* '.$readingFnAttributes; $hash->{NotifyOrderPrefix} = '51-'; $hash->{FW_deviceOverview} = 1; $hash->{FW_addDetailToSummary} = 1; $data{FWEXT}{HOMEMODE}{SCRIPT} = 'homemode.js'; return FHEM::Meta::InitMod(__FILE__,$hash); } sub Define { my ($hash,$def) = @_; my @args = split ' ',$def; my ($name,$type,$resdev) = @args; Log3 $name,4,"$name: Define called"; $langDE = AttrVal('global','language','EN') eq 'DE' || AttrVal($name,'HomeLanguage','EN' eq 'DE') ? 1 : 0; my $text; if (@args < 2 || @args > 3) { $text = $langDE? 'Benutzung: define HOMEMODE [RESIDENTS-MASTER-GERAET]': 'Usage: define HOMEMODE [RESIDENTS-MASTER-DEVICE]'; return $text; } RemoveInternalTimer($hash); $hash->{NOTIFYDEV} = 'global'; if ($init_done && !defined $hash->{OLDDEF}) { my $count = int(devspec2array('TYPE=HOMEMODE')); $attr{$name}{devStateIcon} = "absent:user_away:dnd+on\n". "gone:user_ext_away:dnd+on\n". "dnd:audio_volume_mute:dnd+off\n". "gotosleep:scene_sleeping:dnd+on\n". "asleep:scene_sleeping_alternat:dnd+on\n". "awoken:weather_sunrise:dnd+on\n". "home:status_available:dnd+on\n". "morning:weather_sunrise:dnd+on\n". "day:weather_sun:dnd+on\n". "afternoon:weather_summer:dnd+on\n". "evening:weather_sunset:dnd+on\n". 'night:weather_moon_phases_2:dnd+on'; $attr{$name}{icon} = 'floor'; $attr{$name}{room} = 'HOMEMODE'; $attr{$name}{webCmd} = 'modeAlarm'; if ($langDE) { $attr{$name}{HomeTextAndAreIs} = 'und|sind|ist'; $attr{$name}{HomeTextClosedOpen} = 'geschlossen|geöffnet'; } if (int(devspec2array('TYPE=UWZ'))==1) { $attr{$name}{HomeUWZ} = (devspec2array('TYPE=UWZ'))[0]; } if (int(devspec2array('TYPE=Weather'))==1) { $attr{$name}{HomeWeatherDevice} = (devspec2array('TYPE=Weather'))[0]; if ($langDE) { $attr{$name}{HomeTextRisingConstantFalling} = 'steigend|gleichbleibend|fallend'; $attr{$name}{HomeTextTodayTomorrowAfterTomorrow} = 'Heute|Morgen|Übermorgen'; $attr{$name}{HomeTextWeatherForecastInSpecDays} = 'In %DAY% Tagen %CONDITION% bei Temperaturen von %LOW% bis %HIGH%°C.'; $attr{$name}{HomeTextWeatherForecastToday} = '%DAY% %CONDITION% bei Temperaturen von %LOW% bis %HIGH%°C. Aktuelle Temperatur %TEMPERATURE%°C bei einer Luftfeuchtigkeit von %HUMIDITY%%. Die gefühlte Temperatur ist %WINDCHILL%°C bei einer Windgeschwindigkeit von %WIND%km/h.'; $attr{$name}{HomeTextWeatherForecastTomorrow} = '%DAY% %CONDITION% bei Temperaturen von %LOW% bis %HIGH%°C.'; $attr{$name}{HomeTextWeatherLong} = 'Es %TOBE% %CONDITION% bei %TEMPERATURE%°C und %HUMIDITY%% relativer Luftfeuchtigkeit. Die gefühlte Temperatur ist %WINDCHILL%°C bei einer Windgeschwindigkeit von %WIND%km/h. Der Luftdruck ist %PRESSURE%hPa.'; $attr{$name}{HomeTextWeatherShort} = '%CONDITION%, %TEMPERATURE%°C, %HUMIDITY%% Luftfeuchtigkeit, Luftdruck %PRESSURE%hPa'; } } if (int(devspec2array('TYPE=Twilight'))==1) { $attr{$name}{HomeTwilightDevice} = (devspec2array('TYPE=Twilight'))[0]; } if (int(devspec2array('model=HM-WDS10-TH-O'))==1) { $attr{$name}{HomeSensorTemperatureOutside} = 'HM-WDS10-TH-O'; } if ($count==1) { if (devspec2array('model=HM-SEC-SD-2')) { $attr{$name}{HomeSensorsSmoke} = 'model=HM-SEC-SD-2'; } if (devspec2array('model=HM-SEC-SCO')) { $attr{$name}{HomeSensorsContact} = 'model=HM-SEC-SCO'; } if (devspec2array('r:state=motion|nomotion')) { $attr{$name}{HomeSensorsMotion} = 'r:state=motion|nomotion'; } if (devspec2array('r:sabotageError=.+')) { $attr{$name}{HomeSensorsTamper} = 'r:sabotageError=.+'; } if (devspec2array('r:luminance=.+')) { $attr{$name}{HomeSensorsLuminance} = 'r:luminance=.+'; } if (devspec2array('r:energy=.+')) { $attr{$name}{HomeSensorsEnergy} = 'r:energy=.+'; } if (devspec2array('r:power=.+')) { $attr{$name}{HomeSensorsPower} = 'r:power=.+'; } if (devspec2array('r:battery=.+')) { $attr{$name}{HomeSensorsBattery} = 'r:battery=.+'; } if (devspec2array('i:type=ZHAWater')) { $attr{$name}{HomeSensorsWater} = 'i:type=ZHAWater'; } if (devspec2array('TYPE=Calendar|holiday')) { $attr{$name}{HomeEventsDevices} = 'TYPE=Calendar|holiday'; } } readingsBeginUpdate($hash); readingsBulkUpdate($hash,'dnd','off') if (!defined ReadingsVal($name,'dnd',undef)); readingsBulkUpdate($hash,'anyoneElseAtHome','off') if (!defined ReadingsVal($name,'anyoneElseAtHome',undef)); readingsBulkUpdate($hash,'panic','off') if (!defined ReadingsVal($name,'panic',undef)); readingsBulkUpdate($hash,'.HOMEMODE_ver',$ver); readingsEndUpdate($hash,0); HomebridgeMapping($hash) if (devspec2array('TYPE=siri')); } Init($hash,$resdev) if ($init_done); return; } sub Undef { my ($hash,$arg) = @_; RemoveInternalTimer($hash); my $name = $hash->{NAME}; if (devspec2array('TYPE=HOMEMODE') == 1) { cleanUserattr($hash,$hash->{SENSORSBATTERY}) if ($hash->{SENSORSBATTERY}); cleanUserattr($hash,$hash->{SENSORSCONTACT}) if ($hash->{SENSORSCONTACT}); cleanUserattr($hash,$hash->{SENSORSENERGY}) if ($hash->{SENSORSENERGY}); cleanUserattr($hash,$hash->{SENSORSLIGHT}) if ($hash->{SENSORSLIGHT}); cleanUserattr($hash,$hash->{SENSORSMOTION}) if ($hash->{SENSORSMOTION}); cleanUserattr($hash,$hash->{SENSORSPOWER}) if ($hash->{SENSORSPOWER}); cleanUserattr($hash,$hash->{SENSORSSMOKE}) if ($hash->{SENSORSSMOKE}); cleanUserattr($hash,$hash->{SENSORSTAMPER}) if ($hash->{SENSORSTAMPER}); cleanUserattr($hash,$hash->{SENSORSWATER}) if ($hash->{SENSORSWATER}); } return; } sub Init { my ($hash,$resdev) = @_; my $name = $hash->{NAME}; Log3 $name,4,"Init called"; if (!$resdev) { my $text; my @resdevs; for (devspec2array('TYPE=RESIDENTS')) { push @resdevs,$_; } if (@resdevs == 1) { $text = $langDE? $resdevs[0].' existiert nicht': $resdevs[0].' doesn´t exists'; return $text if (!ID($resdevs[0])); $hash->{DEF} = $resdevs[0]; } elsif (@resdevs > 1) { $text = $langDE? 'Es gibt zu viele RESIDENTS Geräte! Bitte das Master RESIDENTS Gerät angeben! Verfügbare RESIDENTS Geräte:': 'Found too many available RESIDENTS devives! Please specify the RESIDENTS master device! Available RESIDENTS devices:'; return "$text ".join(',',@resdevs); } else { $text = $langDE? 'Kein RESIDENTS Gerät gefunden! Bitte erst ein RESIDENTS Gerät anlegen und ein paar ROOMMATE/GUEST/PET und ihre korrespondierenden PRESENCE Geräte hinzufügen um Spaß mit diesem Modul zu haben!': 'No RESIDENTS device found! Please define a RESIDENTS device first and add some ROOMMATE/GUEST/PET and their PRESENCE device(s) to have fun with this module!'; return $text; } } $hash->{helper}{migrate} = ReadingsVal($name,'.HOMEMODE_ver',1.5) < 1.6 ? 1 : 0; updateInternals($hash); Log3 $name,3,"$name: defined"; return; } sub migrate { my ($hash) = @_; my $name = $hash->{NAME}; my @tampers; my @tampread; my $contacts = InternalVal($name,'SENSORSCONTACT',''); my $hscr = AttrVal($name,'HomeSensorsContactReadings','state sabotageError'); my $hscv = AttrVal($name,'HomeSensorsContactValues','open|tilted|on|1|true'); if ($contacts) { for my $sen (split /,/x,$contacts) { my ($con,$sab) = split ' ',AttrVal($sen,'HomeReadings',$hscr); my $cv = AttrVal($sen,'HomeValues',$hscv); push @tampread,$sab; if (defined ReadingsVal($sen,$con,undef)) { CommandAttr(undef,$sen.' HomeReadingContact '.$con) if ($con ne 'state'); CommandAttr(undef,$sen.' HomeValueContact '.$cv) if ($cv ne 'open|tilted|on|1|true'); } if (defined ReadingsVal($sen,$sab,undef)) { push @tampers,$sen; CommandAttr(undef,$sen.' HomeReadingTamper '.$sab) if ($sab ne 'sabotageError'); CommandAttr(undef,$sen.' HomeValueTamper '.$cv) if ($cv ne 'open|tilted|on|1|true'); } CommandDeleteAttr(undef,$sen.' HomeValues') if (AttrVal($sen,'HomeValues',undef)); CommandDeleteAttr(undef,$sen.' HomeReadings') if (AttrVal($sen,'HomeReadings',undef)); } } CommandDeleteAttr(undef,$name.' HomeSensorsContactReadings') if ($hscr); CommandDeleteAttr(undef,$name.' HomeSensorsContactValues') if ($hscv); my $motions = InternalVal($name,'SENSORSMOTION',''); my $hsmr = AttrVal($name,'HomeSensorsMotionReadings','state sabotageError'); my $hsmv = AttrVal($name,'HomeSensorsMotionValues','motion|open|on|1|true'); if ($motions) { for my $sen (split /,/x,$motions) { my ($mo,$sab) = split ' ',AttrVal($sen,'HomeReadings',$hsmr); my $mv = AttrVal($sen,'HomeValues',$hsmv); push @tampread,$sab; if (defined ReadingsVal($sen,$mo,undef)) { CommandAttr(undef,$sen.' HomeReadingMotion '.$mo) if ($mo ne 'state'); CommandAttr(undef,$sen.' HomeValueMotion '.$mv) if (!grep {$_ eq $mv} split /\|/x,'motion|open|on|1|true'); } if (defined ReadingsVal($sen,$sab,undef)) { push @tampers,$sen; CommandAttr(undef,$sen.' HomeReadingTamper '.$sab) if ($sab ne 'sabotageError'); CommandAttr(undef,$sen.' HomeValueTamper '.$mv) if ($mv ne 'open|tilted|on|1|true'); } CommandDeleteAttr(undef,$sen.' HomeValues') if (AttrVal($sen,'HomeValues','')); CommandDeleteAttr(undef,$sen.' HomeReadings') if (AttrVal($sen,'HomeReadings','')); } } CommandDeleteAttr(undef,$name.' HomeSensorsMotionReadings') if ($hsmr); CommandDeleteAttr(undef,$name.' HomeSensorsMotionValues') if ($hsmv); if (@tampers) { CommandAttr(undef,$name.' HomeSensorsTamper '.join(',',uniq sort @tampers)) if (!AttrVal($name,'HomeSensorsTamper',undef)); @tampread = uniq @tampread; for my $sen (@tampers) { CommandAttr(undef,$sen.' HomeTamperReading '.$tampread[0]) if (int(@tampread)==1 && !AttrVal($sen,'HomeTamperReading','') && !grep {$sen eq $tampread[0]} split /\|/x,'tampared|open|on|yes|1|true'); } } my $batteries = InternalVal($name,'SENSORSBATTERY',''); my $hsbr = AttrVal($name,'HomeSensorsBatteryReading','battery'); if ($batteries && $hsbr ne 'battery') { for my $sen (split ',',$batteries) { CommandAttr(undef,"$sen HomeReadingBattery $hsbr"); } CommandDeleteAttr(undef,$name.' HomeSensorsBatteryReading'); } my $pe = AttrCheck($hash,'HomeSensorsPowerEnergy'); if ($pe) { my ($pr,$er) = split ' ',AttrVal($name,'HomeSensorsPowerEnergyReadings','power energy'); my @sensors; for my $sen (devspec2array($pe)) { next unless (ID($sen,undef,$pr) && ID($sen,undef,$er)); push @sensors,$sen; CommandAttr(undef,$sen.' HomeReadingEnergy '.$er) if ($er ne 'energy'); CommandAttr(undef,$sen.' HomeReadingPower '.$pr) if ($pr ne 'power'); } my $list = join(',',uniq sort @sensors); CommandAttr(undef,$name.' HomeSensorsEnergy '.$list); CommandAttr(undef,$name.' HomeSensorsPower '.$list); CommandDeleteAttr(undef,$name.' HomeSensorsPowerEnergy'); CommandDeleteAttr(undef,$name.' HomeSensorsPowerEnergyReadings') if (AttrVal($name,'HomeSensorsPowerEnergyReadings','')); } if (AttrVal($name,'HomeYahooWeatherDevice',undef)) { CommandAttr(undef,$name.' HomeWeatherDevice '.AttrVal($name,'HomeYahooWeatherDevice',undef)); CommandDeleteAttr(undef,$name.' HomeYahooWeatherDevice'); } if (AttrVal($name,'HomeAdvancedUserAttr',undef)) { CommandAttr(undef,$name.' HomeAdvancedAttributes '.AttrVal($name,'HomeAdvancedUserAttr',undef)); CommandDeleteAttr(undef,$name.' HomeAdvancedUserAttr'); } if (AttrVal($name,'HomeTextNosmokeSmoke',undef)) { CommandAttr(undef,$name.' HomeTextNoSmokeSmoke '.AttrVal($name,'HomeTextNosmokeSmoke',undef)); CommandDeleteAttr(undef,$name.' HomeTextNosmokeSmoke'); } my $smokes = InternalVal($name,'SENSORSSMOKE',''); if ($smokes) { my $hssr = AttrVal($name,'HomeSensorsSmokeReading',''); my $hssv = AttrVal($name,'HomeSensorsSmokeValue',''); for my $sen ($smokes) { CommandAttr(undef,$sen.' HomeReadingSmoke '.$hssr) if ($hssr && $hssr ne 'state' && !AttrVal($sen,'HomeReadingSmoke','')); CommandAttr(undef,$sen.' HomeValueSmoke '.$hssv) if ($hssv && $hssv ne 'smoke|open|on|yes|1|true' && !AttrVal($sen,'HomeValueSmoke','')); } CommandDeleteAttr(undef,$name.' HomeSensorsSmokeReading') if ($hssr); CommandDeleteAttr(undef,$name.' HomeSensorsSmokeValue') if ($hssv); } my $lights = InternalVal($name,'SENSORSLIGHT',''); my $hslr = AttrVal($name,'HomeSensorsLuminanceReading','luminance'); if ($lights && $hslr ne 'luminance') { for my $sen (split ',',$lights) { CommandAttr(undef,$sen.' HomeReadingLuminance '.$hslr) if (!AttrVal($sen,'HomeReadingLuminance','')); } CommandDeleteAttr(undef,$name.' HomeSensorsLuminanceReading') if ($hslr); } if (AttrVal($name,'HomeSensorWindspeed',undef)) { CommandAttr(undef,$name.' HomeSensorWind '.AttrVal($name,'HomeSensorWindspeed',undef)); CommandDeleteAttr(undef,$name.' HomeSensorWindspeed'); } my $hehd = AttrVal($name,'HomeEventsHolidayDevices',undef); my $hecd = AttrVal($name,'HomeEventsCalendarDevices',undef); my @cals; if ($hehd) { for (devspec2array($hehd)) { push @cals,$_; } } if ($hecd) { for (devspec2array($hecd)) { push @cals,$_; } } CommandAttr(undef,$name.' HomeEventsDevices '.join(',',sort @cals)) if (int(@cals)); CommandDeleteAttr(undef,$name.' HomeEventsHolidayDevices') if ($hehd); CommandDeleteAttr(undef,$name.' HomeEventsCalendarDevices') if ($hecd); readingsSingleUpdate($hash,'.HOMEMODE_ver',$ver,0); $hash->{helper}{migrate} = 0; updateInternals($hash,1,1); addSensorsUserAttr($hash,$hash->{NOTIFYDEV},$hash->{NOTIFYDEV}); return; } sub GetUpdate { my ($hash) = @_; my $name = $hash->{NAME}; RemoveInternalTimer($hash,'FHEM::HOMEMODE::GetUpdate'); return if (IsDis($name)); my $mode = DayTime($hash); SetDaytime($hash); SetSeason($hash); CommandSet(undef,"$name:FILTER=mode!=$mode mode $mode") if (ReadingsVal($hash->{DEF},'state','') eq 'home' && AttrNum($name,'HomeAutoDaytime',1)); checkIP($hash) if ((AttrNum($name,'HomePublicIpCheckInterval',0) && !$hash->{'.IP_TRIGGERTIME_NEXT'}) || (AttrNum($name,'HomePublicIpCheckInterval',0) && $hash->{'.IP_TRIGGERTIME_NEXT'} && $hash->{'.IP_TRIGGERTIME_NEXT'} < gettimeofday())); my $timer = gettimeofday() + 5; $hash->{'.TRIGGERTIME_NEXT'} = $timer; InternalTimer($timer,'FHEM::HOMEMODE::GetUpdate',$hash); return; } sub Notify { my ($hash,$dev) = @_; my $name = $hash->{NAME}; my $devname = $dev->{NAME}; return if (IsDis($name,$devname)); my $devtype = $dev->{TYPE}; my $events = deviceEvents($dev,1); return if (!$events); Log3 $name,5,"$name: Events from monitored device $devname: ". join ' --- ',@{$events}; my $prestype = AttrVal($name,'HomePresenceDeviceType','PRESENCE'); my @commands; if ($devname eq 'global') { if (grep {$_ eq 'INITIALIZED'} @{$events}) { Init($hash,$defs{$name}{DEF}); InternalTimer(gettimeofday()+30,'FHEM::HOMEMODE::AttrList',$hash); push @commands,AttrVal($name,'HomeCMDfhemINITIALIZED','') if (AttrVal($name,'HomeCMDfhemINITIALIZED','')); } elsif (grep {$_ eq 'SAVE'} @{$events}) { push @commands,AttrVal($name,'HomeCMDfhemSAVE','') if (AttrVal($name,'HomeCMDfhemSAVE','')); } elsif (grep {$_ eq 'UPDATE'} @{$events}) { push @commands,AttrVal($name,'HomeCMDfhemUPDATE','') if (AttrVal($name,'HomeCMDfhemUPDATE','')); } elsif (grep {/^DEFINED\s/} @{$events}) { for my $evt (@{$events}) { next unless ($evt =~ /^DEFINED\s(.+)$/); my $cal = $1; my $cmd = AttrVal($name,'HomeCMDfhemDEFINED',''); if ($cmd) { $cmd =~ s/%DEFINED%/$cal/xgm; push @commands,$cmd; } CommandAttr(undef,"$cal room ".AttrVal($name,'HomeAtTmpRoom','')) if (AttrVal($name,'HomeAtTmpRoom','') && $cal =~ /^atTmp_.+_$name$/x && ID($cal,'at')); last; } } elsif (grep {/^REREADCFG|MODIFIED\s$name$/} @{$events}) { updateInternals($hash,1,1); } elsif (grep {/^(DELETE)?ATTR\s.+$/} @{$events}) { for my $evt (@{$events}) { next unless ($evt =~ /^(DELETE)?ATTR\s.+$/); my @ev = split ' ',$evt; my $aname = $ev[1]; my $aattr = $ev[2]; my $avalue = $ev[3]; BatteryCheck($hash) if ($aattr =~ /^Home(BatteryLowPercentage|ReadingBattery)$/x); Luminance($hash) if ($aattr eq 'HomeDividerLuminance'); EnergyPower($hash,$1) if ($aattr =~ /^HomeDivider(Energy|Power)$/x); last; } } } else { if ($devtype =~ /^(RESIDENTS|ROOMMATE|GUEST|PET)$/x && grep {/^(state|wayhome|presence|location):\s/} @{$events}) { RESIDENTS($hash,$devname); } elsif ($devname eq AttrVal($name,'HomeWeatherDevice','')) { Weather($hash,$devname); } elsif ($devname eq AttrVal($name,'HomeTwilightDevice','')) { Twilight($hash,$devname); } elsif ($devname eq AttrVal($name,'HomeUWZ','') && grep {/^WarnCount:\s/} @{$events}) { UWZCommands($hash,$events); } elsif (grep {$_ eq $devname} split /,/x,InternalVal($name,'CALENDARS','')) { for my $evt (@{$events}) { next unless ((ID($devname,'Calendar') && $evt =~ /^(start|end):\s(.+)$/) || (ID($devname,'holiday') && $evt =~ /^(state):\s(.+)$/)); EventCommands($hash,$devname,$1,$2); } } else { if (AttrNum($name,'HomeAutoPresence',0) && $devtype =~ /^$prestype$/x && grep {/^presence:\s(absent|present|appeared|disappeared)$/} @{$events}) { my $resident; my $residentregex; for (split /,/x,$hash->{RESIDENTS}) { my $regex = $_; $regex =~ s/^(rr_|rg_|rp_)//x; next unless ($devname =~ /$regex/xi); $resident = $_; $residentregex = $regex; last; } if ($resident && $residentregex) { $hash->{helper}{lar} = $resident; my $residentstate = ReadingsVal($resident,'state',''); my $suppressstate = '[gn]one|absent'; if (ReadingsVal($devname,'presence','') !~ /^maybe/x) { my @presentdevicespresent; for my $device (devspec2array("TYPE=$prestype:FILTER=presence=^(maybe.)?(absent|present|appeared|disappeared)")) { next unless ($device =~ /$residentregex/xi); push @presentdevicespresent,$device if (ReadingsVal($device,'presence','') =~ /^(present|appeared|maybe.absent)$/x); } my $presdevspresent = int(@presentdevicespresent); Log3 $name,5,"$name: var presdevspresent=$presdevspresent"; if (grep {/^presence:\s(present|appeared)$/} @{$events}) { readingsBeginUpdate($hash); readingsBulkUpdate($hash,'lastActivityByPresenceDevice',$devname); readingsBulkUpdate($hash,'lastPresentByPresenceDevice',$devname); readingsEndUpdate($hash,1); push @commands,AttrVal($name,'HomeCMDpresence-present-device','') if (AttrVal($name,'HomeCMDpresence-present-device',undef)); push @commands,AttrVal($name,"HomeCMDpresence-present-$resident-device",'') if (AttrVal($name,"HomeCMDpresence-present-$resident-device",undef)); push @commands,AttrVal($name,"HomeCMDpresence-present-$resident-$devname",'') if (AttrVal($name,"HomeCMDpresence-present-$resident-$devname",undef)); Log3 $name,5,"$name: attr HomePresenceDevicePresentCount-$resident=".AttrVal($name,"HomePresenceDevicePresentCount-$resident",'unset'); if ($presdevspresent >= AttrNum($name,"HomePresenceDevicePresentCount-$resident",1) && $residentstate =~ /^($suppressstate)$/x) { Log3 $name,5,"$name: set $resident:FILTER=state!=home state home"; CommandSet(undef,"$resident:FILTER=state!=home state home"); } } elsif (grep {/^presence:\s(absent|disappeared)$/} @{$events}) { readingsBeginUpdate($hash); readingsBulkUpdate($hash,'lastActivityByPresenceDevice',$devname); readingsBulkUpdate($hash,'lastAbsentByPresenceDevice',$devname); readingsEndUpdate($hash,1); push @commands,AttrVal($name,'HomeCMDpresence-absent-device','') if (AttrVal($name,'HomeCMDpresence-absent-device',undef)); push @commands,AttrVal($name,"HomeCMDpresence-absent-$resident-device",'') if (AttrVal($name,"HomeCMDpresence-absent-$resident-device",undef)); push @commands,AttrVal($name,"HomeCMDpresence-absent-$resident-$devname",'') if (AttrVal($name,"HomeCMDpresence-absent-$resident-$devname",undef)); my $devcount = 1; $devcount = int(@{$hash->{helper}{presdevs}{$resident}}) if ($hash->{helper}{presdevs}{$resident}); my $presdevsabsent = $devcount - $presdevspresent; Log3 $name,5,"$name: var presdevsabsent=$presdevsabsent"; $suppressstate .= '|'.AttrVal($name,'HomeAutoPresenceSuppressState','') if (AttrVal($name,'HomeAutoPresenceSuppressState','')); Log3 $name,5,"$name: attr HomePresenceDeviceAbsentCount-$resident=".AttrVal($name,"HomePresenceDeviceAbsentCount-$resident",'unset'); if ($presdevsabsent >= AttrNum($name,"HomePresenceDeviceAbsentCount-$resident",1) && $residentstate !~ /^$suppressstate$/x) { Log3 $name,5,"$name: set $resident:FILTER=state!=absent state absent"; CommandSet(undef,"$resident:FILTER=state!=absent state absent"); } } } } } if (AttrVal($name,'HomeTriggerPanic','') && $devname eq (split /:/x,AttrVal($name,'HomeTriggerPanic',''))[0]) { my ($d,$r,$on,$off) = split /:/x,AttrVal($name,'HomeTriggerPanic',''); if ($devname eq $d) { if (grep {$_ eq "$r: $on"} @{$events}) { if ($off) { CommandSet(undef,"$name:FILTER=panic=off panic on"); } else { if (ReadingsVal($name,'panic','off') eq 'off') { CommandSet(undef,"$name:FILTER=panic=off panic on"); } else { CommandSet(undef,"$name:FILTER=panic=on panic off"); } } } elsif ($off && grep {$_ eq "$r: $off"} @{$events}) { CommandSet(undef,"$name:FILTER=panic=on panic off"); } } } if (AttrVal($name,'HomeTriggerAnyoneElseAtHome','') && $devname eq (split /:/x,AttrVal($name,'HomeTriggerAnyoneElseAtHome',''))[0]) { my (undef,$r,$on,$off) = split /:/x,AttrVal($name,'HomeTriggerAnyoneElseAtHome',''); if (grep {$_ eq "$r: $on"} @{$events}) { CommandSet(undef,"$name:FILTER=anyoneElseAtHome=off anyoneElseAtHome on"); } elsif (grep {$_ eq "$r: $off"} @{$events}) { CommandSet(undef,"$name:FILTER=anyoneElseAtHome=on anyoneElseAtHome off"); } } if (AttrVal($name,'HomeSensorTemperatureOutside',undef) && $devname eq AttrVal($name,'HomeSensorTemperatureOutside','') && grep {/^(temperature|humidity):\s/} @{$events}) { my $temp; my $humi; for my $evt (@{$events}) { next unless ($evt =~ /^(humidity|temperature):\s(.+)$/); $temp = (split ' ',$2)[0] if ($1 eq 'temperature'); $humi = (split ' ',$2)[0] if ($1 eq 'humidity'); } if (defined $temp) { readingsSingleUpdate($hash,'temperature',$temp,1); ReadingTrend($hash,'temperature',$temp); Icewarning($hash); } if (defined $humi && !AttrVal($name,'HomeSensorHumidityOutside',undef)) { readingsSingleUpdate($hash,'humidity',$humi,1); ReadingTrend($hash,'humidity',$humi); } Weather($hash,AttrVal($name,'HomeWeatherDevice','')) if (AttrVal($name,'HomeWeatherDevice','')); } if (AttrVal($name,'HomeSensorHumidityOutside',undef) && $devname eq AttrVal($name,'HomeSensorHumidityOutside','') && grep {/^humidity:\s/} @{$events}) { for my $evt (@{$events}) { next unless ($evt =~ /^humidity:\s(.+)$/); my $val = (split ' ',$1)[0]; readingsSingleUpdate($hash,'humidity',$val,1); ReadingTrend($hash,'humidity',$val); Weather($hash,AttrVal($name,'HomeWeatherDevice','')) if (AttrVal($name,'HomeWeatherDevice','')); last; } } if (AttrVal($name,'HomeSensorRain',undef) && $devname eq (split /:/x,AttrVal($name,'HomeSensorRain',''))[0]) { my $read = (split /:/x,AttrVal($name,'HomeSensorRain',''))[1]; if (grep {/^$read:\s(.+)$/} @{$events}) { for my $evt (@{$events}) { next unless ($evt =~ /^$read:\s(.+)$/); my $val = (split ' ',$1)[0]; readingsSingleUpdate($hash,'rainLevel',$val,1); ReadingTrend($hash,'rainLevel',$val); last; } } } if (AttrVal($name,'HomeSensorWind',undef) && $devname eq (split /:/x,AttrVal($name,'HomeSensorWind',''))[0]) { my $read = (split /:/x,AttrVal($name,'HomeSensorWind',''))[1]; if (grep {/^$read:\s(.+)$/} @{$events}) { for my $evt (@{$events}) { next unless ($evt =~ /^$read:\s(.+)$/); my $val = (split ' ',$1)[0]; readingsSingleUpdate($hash,'wind',$val,1); ReadingTrend($hash,'wind',$val); Weather($hash,AttrVal($name,'HomeWeatherDevice','')) if (AttrVal($name,'HomeWeatherDevice','')); last; } } } if (AttrVal($name,'HomeSensorAirpressure',undef) && $devname eq (split /:/x,AttrVal($name,'HomeSensorAirpressure',''))[0]) { my $read = (split /:/x,AttrVal($name,'HomeSensorAirpressure',''))[1]; if (grep {/^$read:\s(.+)$/} @{$events}) { for my $evt (@{$events}) { next unless ($evt =~ /^$read:\s(.+)$/); my $val = (split ' ',$1)[0]; readingsSingleUpdate($hash,'pressure',$val,1); ReadingTrend($hash,'pressure',$val); Weather($hash,AttrVal($name,'HomeWeatherDevice','')) if (AttrVal($name,'HomeWeatherDevice','')); last; } } } if (grep {$_ eq $devname} split /,/x,InternalVal($name,'SENSORSBATTERY','')) { my $read = AttrVal($devname,'HomeReadingBattery','battery'); if (grep {/^$read:\s.+$/} @{$events}) { for my $evt (@{$events}) { next unless ($evt =~ /^$read:\s(.+)$/); BatteryCheck($hash,$devname); inform($hash,"$devname.$read",$1); last; } } } if (grep {$_ eq $devname} split /,/x,InternalVal($name,'SENSORSCONTACT','')) { my $read = AttrVal($devname,'HomeReadingContact','state'); if (grep {/^$read:\s(.+)$/} @{$events}) { for my $evt (@{$events}) { next if ($evt !~ /^$read:\s(.+)$/); TriggerState($name,undef,undef,$devname); inform($hash,"$devname.$read",$1); last; } } } if (grep {$_ eq $devname} split /,/x,InternalVal($name,'SENSORSENERGY','')) { my $read = AttrVal($devname,'HomeReadingEnergy','energy'); for my $evt (@{$events}) { next unless ($evt =~ /^$read:\s(.+)$/); EnergyPower($hash,'Energy'); inform($hash,"$devname.$read",$1); last; } } if (grep {$_ eq $devname} split /,/x,InternalVal($name,'SENSORSLIGHT','')) { my $read = AttrVal($devname,'HomeReadingLuminance','luminance'); for my $evt (@{$events}) { next unless ($evt =~ /^$read:\s(.+)$/); Luminance($hash); inform($hash,"$devname.$read",$1); last; } } if (grep {$_ eq $devname} split /,/x,InternalVal($name,'SENSORSMOTION','')) { my $read = AttrVal($devname,'HomeReadingMotion','state'); if (grep {/^$read:\s.+$/} @{$events}) { for my $v (@{$events}) { next if ($v !~ /^$read:\s(.+)$/); TriggerState($name,undef,undef,$devname); inform($hash,"$devname.$read",$1); last; } } } if (grep {$_ eq $devname} split /,/x,InternalVal($name,'SENSORSPOWER','')) { my $read = AttrVal($devname,'HomeReadingPower','power'); for my $evt (@{$events}) { next unless ($evt =~ /^$read:\s(.*)$/); EnergyPower($hash,'Power'); inform($hash,"$devname.$read",$1); last; } } if (grep {$_ eq $devname} split /,/x,InternalVal($name,'SENSORSSMOKE','')) { my $read = AttrVal($devname,'HomeReadingSmoke','state'); for my $evt (@{$events}) { next unless ($evt =~ /^$read:\s(.+)$/); twoStateSensor($hash,'Smoke',$devname,$1); inform($hash,"$devname.$read",$1); last; } } if (grep {$_ eq $devname} split /,/x,InternalVal($name,'SENSORSTAMPER','')) { my $read = AttrVal($devname,'HomeReadingTamper','sabotageError'); for my $evt (@{$events}) { next unless ($evt =~ /^$read:\s(.*)$/); twoStateSensor($hash,'Tamper',$devname,$1); inform($hash,"$devname.$read",$1); last; } } if (grep {$_ eq $devname} split /,/x,InternalVal($name,'SENSORSWATER','')) { my $read = AttrVal($devname,'HomeReadingWater','state'); for my $evt (@{$events}) { next unless ($evt =~ /^$read:\s(.*)$/); twoStateSensor($hash,'Water',$devname,$1); inform($hash,"$devname.$read",$1); last; } } } } execCMDs($hash,serializeCMD($hash,@commands)) if (@commands); GetUpdate($hash) if (!$hash->{'.TRIGGERTIME_NEXT'} || $hash->{'.TRIGGERTIME_NEXT'} + 1 < gettimeofday()); return; } sub updateInternals { my ($hash,$force,$setter) = @_; my $name = $hash->{NAME}; my $resdev = $hash->{DEF}; my $text; if (!ID($resdev)) { $text = $langDE? "$resdev ist nicht definiert!": "$resdev is not defined!"; readingsSingleUpdate($hash,'state',$text,0); } elsif (!ID($resdev,'RESIDENTS')) { $text = $langDE? "$resdev ist kein gültiges RESIDENTS Gerät!": "$resdev is not a valid RESIDENTS device!"; readingsSingleUpdate($hash,'state',$text,0); } else { my $oldBatts = $hash->{SENSORSBATTERY} // ''; my $oldContacts = $hash->{SENSORSCONTACT} // ''; my $oldEnergies = $hash->{SENSORSENERGY} // ''; my $oldLumis = $hash->{SENSORSLIGHT} // ''; my $oldMotions = $hash->{SENSORSMOTION} // ''; my $oldPowers = $hash->{SENSORSPOWER} // ''; my $oldSmokes = $hash->{SENSORSSMOKE} // ''; my $oldTampers = $hash->{SENSORSTAMPER} // ''; my $oldWaters = $hash->{SENSORSWATER} // ''; delete $hash->{helper}{presdevs}; delete $hash->{RESIDENTS}; delete $hash->{SENSORSBATTERY}; delete $hash->{SENSORSCONTACT}; delete $hash->{SENSORSENERGY}; delete $hash->{SENSORSLIGHT}; delete $hash->{SENSORSMOTION}; delete $hash->{SENSORSPOWER}; delete $hash->{SENSORSTAMPER}; delete $hash->{SENSORSSMOKE}; delete $hash->{SENSORSWATER}; delete $hash->{CALENDARS}; $hash->{VERSION} = $HOMEMODE_version; my @residents; push @residents,$defs{$resdev}->{ROOMMATES} if ($defs{$resdev}->{ROOMMATES}); push @residents,$defs{$resdev}->{GUESTS} if ($defs{$resdev}->{GUESTS}); push @residents,$defs{$resdev}->{PETS} if ($defs{$resdev}->{PETS}); if (@residents < 1) { $text = $langDE? "Keine verfügbaren ROOMMATE/GUEST/PET im RESIDENTS Gerät $resdev": "No available ROOMMATE/GUEST/PET in RESIDENTS device $resdev"; Log3 $name,2,$text; readingsSingleUpdate($hash,'HomeInfo',$text,1); return; } else { $hash->{RESIDENTS} = join(',',sort @residents); } my @allMonitoredDevices = ('global'); push @allMonitoredDevices,$resdev if ($resdev); my $autopresence = AttrCheck($hash,'HomeAutoPresence',0); my $presencetype = AttrCheck($hash,'HomePresenceDeviceType','PRESENCE'); my @presdevs = devspec2array("TYPE=$presencetype:FILTER=presence=^(maybe.)?(absent|present|appeared|disappeared)"); my @residentsshort; my @logtexte; for my $resident (split /,/x,$hash->{RESIDENTS}) { push @allMonitoredDevices,$resident; my $short = $resident; $short =~ s/^r[rgp]_//x; push @residentsshort,$short; if ($autopresence) { my @residentspresdevs; for my $p (@presdevs) { next unless ($p =~ /$short/xi); push @residentspresdevs,$p; push @allMonitoredDevices,$p; } if (@residentspresdevs) { my $c = int(@residentspresdevs); my $devlist = join(',',@residentspresdevs); $text = $langDE? "Gefunden wurden $c übereinstimmende(s) Anwesenheits Gerät(e) vom Devspec \"TYPE=$presencetype\" für Bewohner \"$resident\"! Übereinstimmende Geräte: \"$devlist\"": "Found $c matching presence devices of devspec \"TYPE=$presencetype\" for resident \"$resident\"! Matching devices: \"$devlist\""; push @logtexte,$text; CommandAttr(undef,"$name HomePresenceDeviceAbsentCount-$resident $c") if ($init_done && ((!defined AttrNum($name,"HomePresenceDeviceAbsentCount-$resident",undef) && $c > 1) || (AttrNum($name,"HomePresenceDeviceAbsentCount-$resident",undef) && $c < AttrNum($name,"HomePresenceDeviceAbsentCount-$resident",1)))); } else { $text = $langDE? "Keine Geräte mit presence Reading gefunden vom Devspec \"TYPE=$presencetype\" für Bewohner \"$resident\"!": "No devices with presence reading found of devspec \"TYPE=$presencetype\" for resident \"$resident\"!"; push @logtexte,$text; } $hash->{helper}{presdevs}{$resident} = \@residentspresdevs if (@residentspresdevs > 1); } } if (@logtexte && $setter) { $text = $langDE? 'Falls ein oder mehr Anweseheits Geräte falsch zugeordnet wurden, so benenne diese bitte so um dass die Bewohner Namen ('.join(',',@residentsshort).") nicht Bestandteil des Namen sind.\nNach dem Umbenennen führe einfach \"set $name updateInternalsForce\" aus um diese Überprüfung zu wiederholen.": 'If any recognized presence device is wrong, please rename this device so that it will NOT match the residents names ('.join(',',@residentsshort).") somewhere in the device name.\nAfter renaming simply execute \"set $name updateInternalsForce\" to redo this check."; push @logtexte,"\n$text"; my $log = join('\n',@logtexte); Log3 $name,3,"$name: $log"; $log =~ s/\n/
/xgm; readingsSingleUpdate($hash,'HomeInfo',"$log",1); } my $migrate = $hash->{helper}{migrate}; my $contacts = AttrCheck($hash,'HomeSensorsContact'); if ($contacts) { my @sensors; for my $s (devspec2array($contacts)) { push @sensors,$s; push @allMonitoredDevices,$s; } my $list = join(',',sort @sensors); $hash->{SENSORSCONTACT} = $list; addSensorsUserAttr($hash,$list,$oldContacts) if ($migrate || ($force && !$oldContacts) || ($oldContacts && $list ne $oldContacts)); } elsif (!$contacts && $oldContacts) { cleanUserattr($hash,$oldContacts); } my $motion = AttrCheck($hash,'HomeSensorsMotion'); if ($motion) { my @sensors; for my $s (devspec2array($motion)) { push @sensors,$s; push @allMonitoredDevices,$s; } my $list = join(',',sort @sensors); $hash->{SENSORSMOTION} = $list; addSensorsUserAttr($hash,$list,$oldMotions) if ($migrate || ($force && !$oldMotions) || ($oldMotions && $list ne $oldMotions)); } elsif (!$motion && $oldMotions) { cleanUserattr($hash,$oldMotions); } my $energy = AttrCheck($hash,'HomeSensorsEnergy'); if ($energy) { my @sensors; for my $s (devspec2array($energy)) { push @sensors,$s; push @allMonitoredDevices,$s; } my $list = join(',',sort @sensors); $hash->{SENSORSENERGY} = $list; addSensorsUserAttr($hash,$list,$oldEnergies) if ($migrate || ($force && !$oldEnergies) || ($oldEnergies && $list ne $oldEnergies)); } elsif (!$energy && $oldEnergies) { cleanUserattr($hash,$oldEnergies); } my $power = AttrCheck($hash,'HomeSensorsPower'); if ($power) { my @sensors; for my $s (devspec2array($power)) { push @sensors,$s; push @allMonitoredDevices,$s; } my $list = join(',',sort @sensors); $hash->{SENSORSPOWER} = $list; addSensorsUserAttr($hash,$list,$oldPowers) if ($migrate || ($force && !$oldPowers) || ($oldPowers && $list ne $oldPowers)); } elsif (!$power && $oldPowers) { cleanUserattr($hash,$oldPowers); } my $tamper = AttrCheck($hash,'HomeSensorsTamper'); if ($tamper) { my @sensors; for my $s (devspec2array($tamper)) { push @sensors,$s; push @allMonitoredDevices,$s; } my $list = join(',',sort @sensors); $hash->{SENSORSTAMPER} = $list; addSensorsUserAttr($hash,$list,$oldTampers) if ($migrate || ($force && !$oldTampers) || ($oldTampers && $list ne $oldTampers)); } elsif (!$tamper && $oldTampers) { cleanUserattr($hash,$oldTampers); } my $smoke = AttrCheck($hash,'HomeSensorsSmoke'); if ($smoke) { my @sensors; for my $s (devspec2array($smoke)) { push @sensors,$s; push @allMonitoredDevices,$s; } my $list = join(',',sort @sensors); $hash->{SENSORSSMOKE} = $list; addSensorsUserAttr($hash,$list,$oldSmokes) if ($migrate || ($force && !$oldSmokes) || ($oldSmokes && $list ne $oldSmokes)); } elsif (!$smoke && $oldSmokes) { cleanUserattr($hash,$oldSmokes); } my $water = AttrCheck($hash,'HomeSensorsWater'); if ($water) { my @sensors; for my $s (devspec2array($water)) { push @sensors,$s; push @allMonitoredDevices,$s; } my $list = join(',',sort @sensors); $hash->{SENSORSWATER} = $list; addSensorsUserAttr($hash,$list,$oldWaters) if ($migrate || ($force && !$oldWaters) || ($oldWaters && $list ne $oldWaters)); } elsif (!$water && $oldWaters) { cleanUserattr($hash,$oldWaters); } my $battery = AttrCheck($hash,'HomeSensorsBattery'); if ($battery) { my @sensors; for my $s (devspec2array($battery)) { my $t = AttrCheck($hash,'HomeSensorsBatteryTypes',''); my $types = $t?"$HOMEMODE_batTypes|$t":$HOMEMODE_batTypes; next unless ($defs{$s}->{TYPE} =~ /^$types$/x); push @sensors,$s; push @allMonitoredDevices,$s; } my $list = join(',',uniq sort @sensors); $hash->{SENSORSBATTERY} = $list; addSensorsUserAttr($hash,$list,$oldBatts) if ($migrate || ($force && !$oldBatts) || ($oldBatts && $list ne $oldBatts)); } elsif (!$battery && $oldBatts) { cleanUserattr($hash,$oldBatts); } my $luminance = AttrCheck($hash,'HomeSensorsLuminance'); if ($luminance) { my @sensors; for my $s (devspec2array($luminance)) { push @sensors,$s; push @allMonitoredDevices,$s; } my $list = join(',',sort @sensors); $hash->{SENSORSLIGHT} = $list; addSensorsUserAttr($hash,$list,$oldLumis) if ($migrate || ($force && !$oldLumis) || ($oldLumis && $list ne $oldLumis)); } my $weather = AttrCheck($hash,'HomeWeatherDevice'); push @allMonitoredDevices,$weather if ($weather); my $twilight = AttrCheck($hash,'HomeTwilightDevice'); push @allMonitoredDevices,$twilight if ($twilight); my $temperature = AttrCheck($hash,'HomeSensorTemperatureOutside'); push @allMonitoredDevices,$temperature if ($temperature); my $humidity = AttrCheck($hash,'HomeSensorHumidityOutside'); push @allMonitoredDevices,$humidity if ($humidity); CommandDeleteReading(undef,"$name event-.+"); my $calendar = AttrCheck($hash,'HomeEventsDevices'); if ($calendar) { my @cals; for my $c (devspec2array($calendar)) { push @cals,$c; push @allMonitoredDevices,$c; if (ID($c,'Calendar')) { EventCommands($hash,$c,'modeStarted',ReadingsVal($c,'modeStarted','none')); } else { readingsSingleUpdate($hash,"event-$c",ReadingsVal($c,'state','none'),1); } } my $list = join(',',uniq sort @cals); $hash->{CALENDARS} = $list; } my $uwz = AttrCheck($hash,'HomeUWZ',''); push @allMonitoredDevices,$uwz; my $pressure = (split /:/x,AttrCheck($hash,'HomeSensorAirpressure'))[0]; push @allMonitoredDevices,$pressure if ($pressure); my $wind = (split /:/x,AttrCheck($hash,'HomeSensorWind'))[0]; push @allMonitoredDevices,$wind if ($wind); my $panic = (split /:/x,AttrCheck($hash,'HomeTriggerPanic'))[0]; push @allMonitoredDevices,$panic if ($panic); my $aeah = (split /:/x,AttrCheck($hash,'HomeTriggerAnyoneElseAtHome'))[0]; push @allMonitoredDevices,$aeah if ($aeah); @allMonitoredDevices = uniq sort @allMonitoredDevices; Log3 $name,5,"$name: new monitored device count: ".@allMonitoredDevices; $hash->{NOTIFYDEV} = join(',',@allMonitoredDevices); GetUpdate($hash); return if (!@allMonitoredDevices); AttrList($hash); return migrate($hash) if ($hash->{helper}{migrate}); RESIDENTS($hash); TriggerState($name) if ($hash->{SENSORSCONTACT} || $hash->{SENSORSMOTION}); Luminance($hash) if ($hash->{SENSORSLIGHT}); EnergyPower($hash,'Energy') if ($hash->{SENSORSENERGY}); EnergyPower($hash,'Power') if ($hash->{SENSORSPOWER}); twoStateSensor($hash,'Smoke') if ($hash->{SENSORSSMOKE}); twoStateSensor($hash,'Tamper') if ($hash->{SENSORSTAMPER}); twoStateSensor($hash,'Water') if ($hash->{SENSORSWATER}); BatteryCheck($hash) if ($hash->{SENSORSBATTERY}); Weather($hash,$weather) if ($weather); Twilight($hash,$twilight,1) if ($twilight); ToggleDevice($hash,undef); readingsSingleUpdate($hash,'.HOMEMODE_ver',$ver,0); } return; } sub Get { my ($hash,$name,@aa) = @_; my ($cmd,@args) = @aa; return if (IsDis($name) && $cmd ne '?'); my $params = 'mode:noArg modeAlarm:noArg publicIP:noArg devicesDisabled:noArg'; $params .= ' contactsOpen:all,doorsinside,doorsoutside,doorsmain,outside,windows' if ($hash->{SENSORSCONTACT}); $params .= ' sensorsTampered:noArg' if ($hash->{SENSORSCONTACT} || $hash->{SENSORSMOTION}); if (AttrVal($name,'HomeWeatherDevice',undef)) { if (AttrVal($name,'HomeTextWeatherLong',undef) || AttrVal($name,'HomeTextWeatherShort',undef)) { $params .= ' weather:'; $params .= 'long' if (AttrVal($name,'HomeTextWeatherLong',undef)); $params .= ',' if (AttrVal($name,'HomeTextWeatherLong',undef) && AttrVal($name,'HomeTextWeatherShort',undef)); $params .= 'short' if (AttrVal($name,'HomeTextWeatherShort',undef)) } $params .= ' weatherForecast' if (AttrVal($name,'HomeTextWeatherLong',undef)); } my $value = $args[0]; my $text; if ($cmd eq 'devicesDisabled') { return join '\n',split /,/x,ReadingsVal($name,'devicesDisabled','none'); } elsif ($cmd eq 'mode') { return ReadingsVal($name,'mode','no mode available'); } elsif ($cmd eq 'modeAlarm') { return ReadingsVal($name,'modeAlarm','no modeAlarm available'); } elsif ($cmd eq 'contactsOpen') { $text = $langDE? "$cmd benötigt ein Argument": "$cmd needs one argument!"; return $text if (!$value); TriggerState($name,$cmd,$value); } elsif ($cmd eq 'sensorsTampered') { $text = $langDE? "$cmd benötigt kein Argument": "$cmd needs no argument!"; return $text if ($value); return ReadingsVal($name,'alarmTampered','-'); } elsif ($cmd eq 'weather') { $text = $langDE? "$cmd benötigt ein Argument, entweder long oder short!": "$cmd needs one argument of long or short!"; return $text if (!$value || $value !~ /^long|short$/x); my $m = $value eq 'short'?'Short':'Long'; WeatherTXT($hash,AttrVal($name,"HomeTextWeather$m",'')); } elsif ($cmd eq 'weatherForecast') { $text = $langDE? "Der Wert für $cmd muss zwischen 1 und 10 sein. Falls der Wert weggelassen wird, so wird 2 (für morgen) benutzt.": "Value for $cmd must be from 1 to 10. If omitted the value will be 2 for tomorrow."; return $text if ($value && $value !~ /^[1-9]0?$/x && ($value < 1 || $value > 10)); ForecastTXT($hash,$value); } elsif ($cmd eq 'publicIP') { return checkIP($hash); } else { return "Unknown argument $cmd for $name, choose one of $params"; } return; } sub Set { my ($hash,$name,@aa) = @_; my ($cmd,@args) = @aa; return if (IsDis($name) && $cmd ne '?'); $langDE = AttrVal('global','language','EN') eq 'DE' || AttrVal($name,'HomeLanguage','EN') eq 'DE' ? 1 : 0; my $text = $langDE? '"set '.$name.'" benötigt mindestens ein und maximal drei Argumente': '"set '.$name.'" needs at least one argument and maximum three arguments'; return $text if (@aa > 3); my $option = defined $args[0] ? $args[0] : undef; my $value = defined $args[1] ? $args[1] : undef; my $mode = ReadingsVal($name,'mode',''); my $amode = ReadingsVal($name,'modeAlarm',''); my $plocation = ReadingsVal($name,'location',''); my $presence = ReadingsVal($name,'presence',''); my @locations = split /,/x,$HOMEMODE_Locations; my $slocations = AttrCheck($hash,'HomeSpecialLocations'); if ($slocations) { for (split /,/x,$slocations) { push @locations,$_; } } my @modeparams = split /,/x,$HOMEMODE_UserModesAll; my $smodes = AttrCheck($hash,'HomeSpecialModes'); if ($smodes) { for (split /,/x,$smodes) { push @modeparams,$_; } } my @sensorsSet; push @sensorsSet,'all' if ($hash->{SENSORSCONTACT} || $hash->{SENSORSMOTION} || $hash->{SENSORSBATTERY} || $hash->{SENSORSENERGY} || $hash->{SENSORSPOWER} || $hash->{SENSORSLIGHT} || $hash->{SENSORSSMOKE} || $hash->{SENSORSTAMPER}); push @sensorsSet,'sensorsContact' if ($hash->{SENSORSCONTACT}); push @sensorsSet,'sensorsMotion' if ($hash->{SENSORSMOTION}); push @sensorsSet,'sensorsBattery' if ($hash->{SENSORSBATTERY}); push @sensorsSet,'sensorsSmoke' if ($hash->{SENSORSSMOKE}); push @sensorsSet,'sensorsEnergy' if ($hash->{SENSORSENERGY}); push @sensorsSet,'sensorsPower' if ($hash->{SENSORSPOWER}); push @sensorsSet,'sensorsTamper' if ($hash->{SENSORSTAMPER}); push @sensorsSet,'sensorsLuminance' if ($hash->{SENSORSLIGHT}); my $readd = join(',',sort @sensorsSet); my $para; $para .= 'mode:'.join(',',sort @modeparams).' ' if (!AttrNum($name,'HomeAutoDaytime',1)); $para .= 'anyoneElseAtHome:on,off'; $para .= ' deviceDisable:'; $para .= $hash->{helper}{enabledDevices} ? $hash->{helper}{enabledDevices} : 'noArg'; $para .= ' deviceEnable:'; $para .= ReadingsVal($name,'devicesDisabled','') ? ReadingsVal($name,'devicesDisabled','') : 'noArg'; $para .= ' dnd:on,off'; $para .= ' dnd-for-minutes'; $para .= ' location:'.join(',', uniq sort @locations); $para .= ' modeAlarm:'.$HOMEMODE_AlarmModes; $para .= ' modeAlarm-for-minutes'; $para .= ' panic:on,off'; $para .= ' updateInternalsForce:noArg'; $para .= ' updateHomebridgeMapping:noArg'; $para .= ' updateSensorsUserattr:noArg' if ($readd); return "$cmd is not a valid command for $name, please choose one of $para" if (!$cmd || $cmd eq '?'); my @commands; if ($cmd eq 'mode') { my $namode = 'disarm'; my $present = 'absent'; my $location = 'underway'; $option = DayTime($hash) if ($option && $option eq 'home' && AttrNum($name,'HomeAutoDaytime',1)); if ($option !~ /^absent|gone$/x) { push @commands,AttrVal($name,'HomeCMDpresence-present','') if (AttrVal($name,'HomeCMDpresence-present',undef) && $mode =~ /^(absent|gone)$/x); $present = 'present'; $location = (grep {/^$plocation$/x} split /,/x,$slocations) ? $plocation : 'home'; if ($presence eq 'absent') { if (AttrNum($name,'HomeAutoArrival',0)) { my $hour = hourMaker(AttrNum($name,'HomeAutoArrival',0)); CommandDelete(undef,"atTmp_set_home_$name") if (ID("atTmp_set_home_$name",'at')); CommandDefine(undef,"atTmp_set_home_$name at +$hour set $name:FILTER=location=arrival location home"); $location = 'arrival'; } } if ($option eq 'asleep') { $namode = 'armnight'; $location = 'bed'; } } elsif ($option =~ /^absent|gone$/x) { push @commands,AttrVal($name,'HomeCMDpresence-absent','') if (AttrVal($name,'HomeCMDpresence-absent',undef) && $mode !~ /^absent|gone$/x); $namode = ReadingsVal($name,'anyoneElseAtHome','off') eq 'off' ? 'armaway':'armhome'; if (AttrNum($name,'HomeModeAbsentBelatedTime',0) && AttrVal($name,'HomeCMDmode-absent-belated',undef)) { my $hour = hourMaker(AttrNum($name,'HomeModeAbsentBelatedTime',0)); CommandDelete(undef,"atTmp_absent_belated_$name") if (ID("atTmp_absent_belated_$name",'at')); CommandDefine(undef,"atTmp_absent_belated_$name at +$hour {FHEM::HOMEMODE::execCMDs_belated(\"$name\",\"HomeCMDmode-absent-belated\",\"$option\")}"); } } ContactOpenWarningAfterModeChange($hash,$option,$mode) if ($hash->{SENSORSCONTACT} && $option && $mode ne $option); push @commands,AttrVal($name,'HomeCMDmode','') if ($mode && AttrVal($name,'HomeCMDmode',undef)); push @commands,AttrVal($name,'HomeCMDmode-'.makeReadingName($option),'') if (AttrVal($name,'HomeCMDmode-'.makeReadingName($option),undef)); readingsBeginUpdate($hash); readingsBulkUpdate($hash,$cmd,$option); readingsBulkUpdate($hash,'prevMode',$mode); readingsBulkUpdateIfChanged($hash,'presence',$present); readingsBulkUpdate($hash,'state',$option); readingsEndUpdate($hash,1); CommandSet(undef,"$name:FILTER=location!=$location location $location"); if (AttrNum($name,'HomeAutoAlarmModes',1)) { CommandDelete(undef,"atTmp_modeAlarm_delayed_arm_$name") if (ID("atTmp_modeAlarm_delayed_arm_$name",'at')); CommandSet(undef,"$name:FILTER=modeAlarm!=$namode modeAlarm $namode"); } } elsif ($cmd eq 'modeAlarm-for-minutes') { $text = $langDE? "$cmd benötigt zwei Parameter: einen modeAlarm und die Minuten": "$cmd needs two paramters: a modeAlarm and minutes"; return $text if (!$option || !$value); my $timer = "atTmp_alarmMode_for_timer_$name"; my $time = hourMaker($value); CommandDelete(undef,$timer) if (ID($timer,'at')); CommandDefine(undef,"$timer at +$time set $name:FILTER=modeAlarm!=$amode modeAlarm $amode"); CommandSet(undef,"$name:FILTER=modeAlarm!=$option modeAlarm $option"); } elsif ($cmd eq 'dnd-for-minutes') { $text = $langDE? "$cmd benötigt einen Paramter: Minuten": "$cmd needs one paramter: minutes"; return $text if (!$option); $text = $langDE? "$name darf nicht im dnd Modus sein um diesen Modus für bestimmte Minuten zu setzen! Bitte deaktiviere den dnd Modus zuerst!": "$name can't be in dnd mode to turn dnd on for minutes! Please disable dnd mode first!"; return $text if (ReadingsVal($name,'dnd','off') eq 'on'); my $timer = "atTmp_dnd_for_timer_$name"; my $time = hourMaker($option); CommandDelete(undef,$timer) if (ID($timer,'at')); CommandDefine(undef,"$timer at +$time set $name:FILTER=dnd!=off dnd off"); CommandSet(undef,"$name:FILTER=dnd!=on dnd on"); } elsif ($cmd eq 'dnd') { push @commands,AttrVal($name,'HomeCMDdnd','') if (AttrVal($name,'HomeCMDdnd',undef)); push @commands,AttrVal($name,"HomeCMDdnd-$option",'') if (AttrVal($name,"HomeCMDdnd-$option",undef)); readingsBeginUpdate($hash); readingsBulkUpdate($hash,$cmd,$option); readingsBulkUpdate($hash,'state','dnd') if ($option eq 'on'); readingsBulkUpdate($hash,'state',$mode) if ($option ne 'on'); readingsEndUpdate($hash,1); } elsif ($cmd eq 'location') { Log3 $name,4,"$name: Set location: $option"; push @commands,AttrVal($name,'HomeCMDlocation','') if (AttrVal($name,'HomeCMDlocation',undef)); push @commands,AttrVal($name,'HomeCMDlocation-'.makeReadingName($option),'') if (AttrVal($name,'HomeCMDlocation-'.makeReadingName($option),undef)); readingsBeginUpdate($hash); readingsBulkUpdate($hash,'prevLocation',$plocation); readingsBulkUpdate($hash,$cmd,$option); readingsEndUpdate($hash,1); } elsif ($cmd eq 'modeAlarm') { CommandDelete(undef,"atTmp_modeAlarm_delayed_arm_$name") if (ID("atTmp_modeAlarm_delayed_arm_$name",'at')); my $delay; if ($option =~ /^arm/x && AttrVal($name,'HomeModeAlarmArmDelay',0)) { $delay = returnDelay($option,split ' ',AttrVal($name,'HomeModeAlarmArmDelay',0)); } if ($delay) { my $hours = hourMaker(sprintf('%.2f',$delay / 60)); CommandDefine(undef,"atTmp_modeAlarm_delayed_arm_$name at +$hours {FHEM::HOMEMODE::set_modeAlarm(\"$name\",\"$option\",\"$amode\")}"); } else { set_modeAlarm($name,$option,$amode); } } elsif ($cmd eq 'anyoneElseAtHome') { $text = $langDE? "Zulässige Werte für $cmd sind nur on/off gefolgt von einem optionalen einzigartigem Namen!": "Values for $cmd can only be on/off with optional subsequent unique name!"; return $text if ($option !~ /^(on|off)$/x); return $text if (defined($value) && $value !~ /^[\w\d-]+$/x); if (!defined($value)) { push @commands,AttrVal($name,'HomeCMDanyoneElseAtHome','') if (AttrVal($name,'HomeCMDanyoneElseAtHome',undef)); push @commands,AttrVal($name,"HomeCMDanyoneElseAtHome-$option",'') if (AttrVal($name,"HomeCMDanyoneElseAtHome-$option",undef)); if (AttrNum($name,'HomeAutoAlarmModes',1)) { CommandSet(undef,"$name:FILTER=modeAlarm=armaway modeAlarm armhome") if ($option eq 'on'); CommandSet(undef,"$name:FILTER=modeAlarm=armhome modeAlarm armaway") if ($option eq 'off'); } readingsSingleUpdate($hash,'anyoneElseAtHome',$option,1); } else { my $aeh = ReadingsVal($name,'anyoneElseAtHomeBy',undef)?ReadingsVal($name,'anyoneElseAtHomeBy',undef):ReadingsVal($name,'anyoneElseAtHome',undef) eq 'on'?$name:'none'; my @arr; if ($option eq 'on') { if ($aeh ne 'none') { for my $item (split(/,/x,$aeh)) { push @arr,$item; } } push @arr,$value; @arr = uniq @arr; my $ret = join(',',sort @arr); readingsSingleUpdate($hash,'anyoneElseAtHomeBy',$ret,1) if ($ret ne $aeh); CommandSet(undef,"$name:FILTER=anyoneElseAtHome!=on anyoneElseAtHome on"); } else { return if ($aeh eq 'none' || $value eq 'none'); for my $item (split /,/x,$aeh) { next if ($item eq $value); push @arr,$item; } @arr = uniq @arr; my $ret = int(@arr) ? join(',',sort @arr) : 'none'; readingsSingleUpdate($hash,'anyoneElseAtHomeBy',$ret,1) if ($ret ne $aeh); CommandSet(undef,"$name:FILTER=anyoneElseAtHome!=off anyoneElseAtHome off") if ($ret eq 'none'); } return; } } elsif ($cmd eq 'panic') { $text = $langDE? "Zulässige Werte für $cmd sind nur on oder off!": "Values for $cmd can only be on or off!"; return $text if ($option !~ /^(on|off)$/x); push @commands,AttrVal($name,'HomeCMDpanic','') if (AttrVal($name,'HomeCMDpanic',undef)); push @commands,AttrVal($name,"HomeCMDpanic-$option",'') if (AttrVal($name,"HomeCMDpanic-$option",undef)); readingsSingleUpdate($hash,'panic',$option,1); } elsif ($cmd =~ /^device(Dis|En)able$/x) { ToggleDevice($hash,$option) if (($1 eq 'En' && grep {$_ eq $option} split /,/x,ReadingsVal($name,'devicesDisabled','')) || ($1 eq 'Dis' && grep {$_ eq $option} split /,/x,$hash->{helper}{enabledDevices})); } elsif ($cmd eq 'updateInternalsForce') { updateInternals($hash,1,1); } elsif ($cmd eq 'updateHomebridgeMapping') { HomebridgeMapping($hash); } elsif ($cmd eq 'updateSensorsUserattr') { addSensorsUserAttr($hash,$hash->{NOTIFYDEV},$hash->{NOTIFYDEV}); return; } execCMDs($hash,serializeCMD($hash,@commands)) if (@commands); return; } sub set_modeAlarm { my ($name,$option,$amode) = @_; my $hash = $defs{$name}; my $resident = $hash->{helper}{lar} ? $hash->{helper}{lar} : ReadingsVal($name,'lastActivityByResident',''); delete $hash->{helper}{lar} if ($hash->{helper}{lar}); my @commands; push @commands,AttrVal($name,'HomeCMDmodeAlarm','') if (AttrVal($name,'HomeCMDmodeAlarm',undef)); push @commands,AttrVal($name,"HomeCMDmodeAlarm-$option",'') if (AttrVal($name,"HomeCMDmodeAlarm-$option",undef)); if ($option eq 'confirm') { CommandDefine(undef,"atTmp_modeAlarm_confirm_$name at +00:00:30 setreading $name:FILTER=alarmState=confirmed alarmState $amode"); readingsSingleUpdate($hash,'alarmState',$option.'ed',1); execCMDs($hash,serializeCMD($hash,@commands),$resident) if (@commands); } else { readingsBeginUpdate($hash); readingsBulkUpdate($hash,'prevModeAlarm',$amode); readingsBulkUpdate($hash,'modeAlarm',$option); readingsBulkUpdateIfChanged($hash,'alarmState',$option); readingsEndUpdate($hash,1); TriggerState($name) if ($hash->{SENSORSCONTACT} || $hash->{SENSORSMOTION}); execCMDs($hash,serializeCMD($hash,@commands),$resident) if (@commands); } return; } sub execCMDs_belated { my ($name,$attrib,$option) = @_; return if (!AttrVal($name,$attrib,undef) || ReadingsVal($name,'mode','') ne $option); my $hash = $defs{$name}; my @commands; push @commands,AttrVal($name,$attrib,''); execCMDs($hash,serializeCMD($hash,@commands)) if (@commands); return; } sub alarmTriggered { my ($hash,@triggers) = @_; my $name = $hash->{NAME}; my @commands; my $text = makeHR($hash,0,@triggers); push @commands,AttrVal($name,'HomeCMDalarmTriggered','') if (AttrVal($name,'HomeCMDalarmTriggered',undef)); readingsBeginUpdate($hash); readingsBulkUpdateIfChanged($hash,'alarmTriggered_ct',int(@triggers)); if ($text) { push @commands,AttrVal($name,'HomeCMDalarmTriggered-on','') if (AttrVal($name,'HomeCMDalarmTriggered-on',undef)); readingsBulkUpdateIfChanged($hash,'alarmTriggered',join ',',@triggers); readingsBulkUpdateIfChanged($hash,'alarmTriggered_hr',$text); readingsBulkUpdateIfChanged($hash,'alarmState','alarm'); } else { push @commands,AttrVal($name,'HomeCMDalarmTriggered-off','') if (AttrVal($name,'HomeCMDalarmTriggered-off',undef) && ReadingsVal($name,'alarmTriggered','')); readingsBulkUpdateIfChanged($hash,'alarmTriggered',''); readingsBulkUpdateIfChanged($hash,'alarmTriggered_hr',''); readingsBulkUpdateIfChanged($hash,'alarmState',ReadingsVal($name,'modeAlarm','disarm')); } readingsEndUpdate($hash,1); execCMDs($hash,serializeCMD($hash,@commands)) if (@commands && ReadingsAge($name,'modeAlarm','') > 5); return; } sub makeHR { my ($hash,$noart,@names) = @_; my $name = $hash->{NAME}; my @aliases; my $and = (split /\|/x,AttrVal($name,'HomeTextAndAreIs','and|are|is'))[0]; my $text; for (@names) { my $alias = $noart ? name2alias($_) : name2alias($_,1); push @aliases,$alias; } if (@aliases > 0) { my $alias = $aliases[0]; $alias =~ s/^d/D/x; $text = $alias; if (@aliases > 1) { for (my $i = 1; $i < @aliases; $i++) { $text .= " $and " if ($i == int(@aliases) - 1); $text .= ', ' if ($i < @aliases - 1); $text .= $aliases[$i]; } } } $text = $text ? $text : ''; return $text; } sub RESIDENTS { my ($hash,$dev) = @_; $dev = $hash->{DEF} if (!$dev); my $name = $hash->{NAME}; my $events = deviceEvents($defs{$dev},1); my $devtype = $defs{$dev}->{TYPE}; Log3 $name,5,"$name: RESIDENTS dev: $dev type: $devtype"; my $lad = ReadingsVal($name,'lastActivityByResident',''); my $mode; my $ema = ReplaceEventMap($dev,'absent',1); my $emp = ReplaceEventMap($dev,'present',1); if (grep {/^state:\s/} @{$events}) { for my $evt (@{$events}) { next unless ($evt =~ /^state:\s(.+)$/ && grep {$_ eq $1} split /,/x,$HOMEMODE_UserModesAll); $mode = $1; Log3 $name,4,"$name: RESIDENTS mode: $mode"; last; } } if ($mode && $devtype eq 'RESIDENTS') { readingsSingleUpdate($hash,'lastActivityByResident',ReadingsVal($dev,'lastActivityByDev',''),1); $mode = $mode eq 'home' && AttrNum($name,'HomeAutoDaytime',1) ? DayTime($hash) : $mode; CommandSet(undef,"$name:FILTER=mode!=$mode mode $mode"); } elsif ($devtype =~ /^ROOMMATE|GUEST|PET$/x) { readingsBeginUpdate($hash); readingsBulkUpdateIfChanged($hash,'lastActivityByResident',$dev); readingsBulkUpdate($hash,'prevActivityByResident',$lad); readingsEndUpdate($hash,1); my @commands; if (grep {$_ eq 'wayhome: 1'} @{$events}) { CommandSet(undef,"$name:FILTER=location!=wayhome location wayhome") if (ReadingsVal($name,'state','') =~ /^absent|gone$/x); } elsif (grep {$_ eq 'wayhome: 0'} @{$events}) { my $rx = $hash->{RESIDENTS}; $rx =~ s/,/|/xg; CommandSet(undef,"$name:FILTER=location!=underway location underway") if (ReadingsVal($name,'state','') =~ /^absent|gone$/x && !devspec2array("$rx:FILTER=wayhome=1")); } if (grep {$_ eq "presence: $ema"} @{$events}) { Log3 $name,5,"$name: RESIDENTS dev: $dev - presence: $ema"; readingsSingleUpdate($hash,'lastAbsentByResident',$dev,1); push @commands,AttrVal($name,'HomeCMDpresence-absent-resident','') if (AttrVal($name,'HomeCMDpresence-absent-resident',undef)); push @commands,AttrVal($name,"HomeCMDpresence-absent-$dev",'') if (AttrVal($name,"HomeCMDpresence-absent-$dev",undef)); } elsif (grep {$_ eq "presence: $emp"} @{$events}) { Log3 $name,5,"$name: RESIDENTS dev: $dev - presence: $emp"; readingsSingleUpdate($hash,'lastPresentByResident',$dev,1); push @commands,AttrVal($name,'HomeCMDpresence-present-resident','') if (AttrVal($name,'HomeCMDpresence-present-resident',undef)); push @commands,AttrVal($name,"HomeCMDpresence-present-$dev",'') if (AttrVal($name,"HomeCMDpresence-present-$dev",undef)); } if (grep {/^location:\s/} @{$events}) { my $loc; for (@{$events}) { Log3 $name,4,"$name: RESIDENTS dev: $dev - event: $_"; next unless ($_ =~ /^location:\s(.+)$/); $loc = $1; last; } if ($loc) { Log3 $name,4,"$name: RESIDENTS dev: $dev - location: $loc"; readingsSingleUpdate($hash,'lastLocationByResident',"$dev - $loc",1); push @commands,AttrVal($name,'HomeCMDlocation-resident','') if (AttrVal($name,'HomeCMDlocation-resident',undef)); push @commands,AttrVal($name,'HomeCMDlocation-'.makeReadingName($loc).'-resident','') if (AttrVal($name,'HomeCMDlocation-'.makeReadingName($loc).'-resident',undef)); push @commands,AttrVal($name,'HomeCMDlocation-'.makeReadingName($loc).'-'.$dev,'') if (AttrVal($name,'HomeCMDlocation-'.makeReadingName($loc).'-'.$dev,undef)); } } if ($mode) { my $ls = ReadingsVal($dev,'lastState',''); if ($mode =~ /^(home|awoken)$/x && AttrNum($name,'HomeAutoAwoken',0)) { if ($mode eq 'home' && $ls eq 'asleep') { AnalyzeCommandChain(undef,"sleep 0.1; set $dev:FILTER=state!=awoken state awoken"); return; } elsif ($mode eq 'awoken') { my $hours = hourMaker(AttrNum($name,'HomeAutoAwoken',0)); CommandDelete(undef,'atTmp_awoken_'.$dev."_$name") if (ID('atTmp_awoken_'.$dev."_$name",'at')); CommandDefine(undef,'atTmp_awoken_'.$dev."_$name at +$hours set $dev:FILTER=state=awoken state home"); } } if ($mode eq 'home' && $ls =~ /^absent|[gn]one$/x && AttrNum($name,'HomeAutoArrival',0)) { my $hours = hourMaker(AttrNum($name,'HomeAutoArrival',0)); AnalyzeCommandChain(undef,"sleep 0.1; set $dev:FILTER=location!=arrival location arrival"); CommandDelete(undef,'atTmp_location_home_'.$dev."_$name") if (ID('atTmp_location_home_'.$dev."_$name",'at')); CommandDefine(undef,'atTmp_location_home_'.$dev."_$name at +$hours set $dev:FILTER=location=arrival location home"); } elsif ($mode eq 'gotosleep' && AttrNum($name,'HomeAutoAsleep',0)) { my $hours = hourMaker(AttrNum($name,'HomeAutoAsleep',0)); CommandDelete(undef,'atTmp_asleep_'.$dev."_$name") if (ID('atTmp_asleep_'.$dev."_$name",'at')); CommandDefine(undef,'atTmp_asleep_'.$dev."_$name at +$hours set $dev:FILTER=state=gotosleep state asleep"); } push @commands,AttrVal($name,"HomeCMDmode-$mode-resident",'') if (AttrVal($name,"HomeCMDmode-$mode-resident",undef)); push @commands,AttrVal($name,"HomeCMDmode-$mode-$dev",'') if (AttrVal($name,"HomeCMDmode-$mode-$dev",undef)); readingsBeginUpdate($hash); readingsBulkUpdate($hash,'lastAsleepByResident',$dev) if ($mode eq 'asleep'); readingsBulkUpdate($hash,'lastAwokenByResident',$dev) if ($mode eq 'awoken'); readingsBulkUpdate($hash,'lastGoneByResident',$dev) if ($mode =~ /^[gn]one$/x); readingsBulkUpdate($hash,'lastGotosleepByResident',$dev) if ($mode eq 'gotosleep'); readingsEndUpdate($hash,1); ContactOpenWarningAfterModeChange($hash,undef,undef,$dev); } if (@commands) { my $delay = AttrNum($name,'HomeResidentCmdDelay',1); my $cmd = encode_base64(serializeCMD($hash,@commands),''); InternalTimer(gettimeofday() + $delay,'FHEM::HOMEMODE::execUserCMDs',"$name|$cmd|$dev"); } } return; } sub AttrList { my ($hash) = @_; my $name = $hash->{NAME}; my $adv = AttrCheck($hash,'HomeAdvancedAttributes',0); my @attribs = ( 'disable:1,0', 'disabledForIntervals:textField', 'HomeAdvancedDetails:none,detail,both,room', 'HomeAdvancedAttributes:0,1', 'HomeAutoAlarmModes:1,0', 'HomeAutoArrival:textField', 'HomeAutoAsleep:textField', 'HomeAutoAwoken:textField', 'HomeAutoDaytime:1,0', 'HomeAutoPresence:0,1', 'HomeCMDalarmTriggered', 'HomeCMDalarmTriggered-off', 'HomeCMDalarmTriggered-on', 'HomeCMDanyoneElseAtHome', 'HomeCMDanyoneElseAtHome-on', 'HomeCMDanyoneElseAtHome-off', 'HomeCMDdaytime', 'HomeCMDdeviceDisable', 'HomeCMDdeviceEnable', 'HomeCMDdnd', 'HomeCMDdnd-off', 'HomeCMDdnd-on', 'HomeCMDfhemDEFINED', 'HomeCMDfhemINITIALIZED', 'HomeCMDfhemSAVE', 'HomeCMDfhemUPDATE', 'HomeCMDlocation', 'HomeCMDlocation-resident', 'HomeCMDmode', 'HomeCMDmode-absent-belated', 'HomeCMDpanic', 'HomeCMDpanic-on', 'HomeCMDpanic-off', 'HomeCMDpresence-absent', 'HomeCMDpresence-present', 'HomeCMDpresence-absent-resident', 'HomeCMDpresence-present-resident', 'HomeCMDpublic-ip-change', 'HomeCMDseason', 'HomeDaytimes', 'HomeEventsDevices:textField', 'HomeLanguage:EN,DE', 'HomeModeAlarmArmDelay:textField', 'HomeModeAbsentBelatedTime:textField', 'HomeAtTmpRoom:textField', 'HomePresenceDeviceType:textField', 'HomePublicIpCheckInterval:textField', 'HomeResidentCmdDelay:textField', 'HomeSeasons', 'HomeSensorAirpressure:textField', 'HomeSensorHumidityOutside:textField', 'HomeSensorRain:textField', 'HomeSensorTemperatureOutside:textField', 'HomeSensorWind:textField', 'HomeSensorsAlarmDelay:textField', 'HomeSensorsBattery:textField', 'HomeSensorsBatteryTypes:textField', 'HomeSensorsContact:textField', 'HomeSensorsLuminance:textField', 'HomeSensorsMotion:textField', 'HomeSensorsEnergy:textField', 'HomeSensorsPower:textField', 'HomeSensorsSmoke:textField', 'HomeSensorsTamper:textField', 'HomeSensorsWater:textField', 'HomeSpecialLocations:textField', 'HomeSpecialModes:textField', 'HomeTextAndAreIs:textField', 'HomeTextTodayTomorrowAfterTomorrow:textField', 'HomeTrendCalcAge:900,1800,2700,3600', 'HomeTriggerAnyoneElseAtHome:textField', 'HomeTriggerPanic:textField', 'HomeTwilightDevice:textField', 'HomeUWZ:textField', 'HomeUserCSS', 'HomeWeatherDevice:textField' ); for (split /,/x,$HOMEMODE_Locations) { push @attribs,"HomeCMDlocation-$_"; } for (split /,/x,$HOMEMODE_UserModesAll) { push @attribs,"HomeCMDmode-$_"; push @attribs,"HomeCMDmode-$_-resident"; } push @attribs,'HomeCMDmodeAlarm'; for (split /,/x,$HOMEMODE_AlarmModes) { push @attribs,"HomeCMDmodeAlarm-$_"; } if (AttrVal($name,'HomeAutoPresence',0)) { push @attribs,'HomeAutoPresenceSuppressState:textField'; push @attribs,'HomeCMDpresence-absent-device'; push @attribs,'HomeCMDpresence-present-device'; } if ($hash->{SENSORSSMOKE}) { push @attribs,'HomeCMDalarmSmoke'; push @attribs,'HomeCMDalarmSmoke-on'; push @attribs,'HomeCMDalarmSmoke-off'; push @attribs,'HomeTextNoSmokeSmoke:textField'; } if ($hash->{SENSORSTAMPER}) { push @attribs,'HomeCMDalarmTampered'; push @attribs,'HomeCMDalarmTampered-on'; push @attribs,'HomeCMDalarmTampered-off'; push @attribs,'HomeTextNoTamperTamper:textField'; } if ($hash->{SENSORSBATTERY}) { push @attribs,'HomeCMDbattery'; push @attribs,'HomeCMDbatteryLow'; push @attribs,'HomeCMDbatteryNormal'; push @attribs,'HomeSensorsBatteryLowPercentage:textField'; } if ($hash->{SENSORSCONTACT}) { push @attribs,'HomeCMDcontact'; push @attribs,'HomeCMDcontactClosed'; push @attribs,'HomeCMDcontactOpen'; push @attribs,'HomeCMDcontactDoormain'; push @attribs,'HomeCMDcontactDoormainClosed'; push @attribs,'HomeCMDcontactDoormainOpen'; push @attribs,'HomeCMDcontactOpenWarning1'; push @attribs,'HomeCMDcontactOpenWarning2'; push @attribs,'HomeCMDcontactOpenWarningLast'; push @attribs,'HomeSensorsContactOpenTimeDividers:textField'; push @attribs,'HomeSensorsContactOpenTimeMin:textField'; push @attribs,'HomeSensorsContactOpenTimes:textField'; push @attribs,'HomeSensorsContactOpenWarningUnified:0,1'; push @attribs,'HomeTextClosedOpen:textField'; } if (AttrVal($name,'HomeSensorTemperatureOutside',undef) || AttrVal($name,'HomeWeatherDevice',undef)) { push @attribs,'HomeCMDicewarning'; push @attribs,'HomeCMDicewarning-on'; push @attribs,'HomeCMDicewarning-off'; push @attribs,'HomeIcewarningOnOffTemps:textField'; } if (AttrVal($name,'HomeWeatherDevice',undef)) { push @attribs,'HomeTextWeatherForecastToday'; push @attribs,'HomeTextWeatherForecastTomorrow'; push @attribs,'HomeTextWeatherForecastInSpecDays'; push @attribs,'HomeTextWeatherNoForecast'; push @attribs,'HomeTextWeatherLong'; push @attribs,'HomeTextWeatherShort'; push @attribs,'HomeTextRisingConstantFalling:textField'; } if ($hash->{SENSORSMOTION}) { push @attribs,'HomeCMDmotion'; push @attribs,'HomeCMDmotion-on'; push @attribs,'HomeCMDmotion-off'; push @attribs,'HomeTextClosedOpen:textField'; } if (AttrVal($name,'HomeTwilightDevice',undef)) { push @attribs,'HomeCMDtwilight'; push @attribs,'HomeCMDtwilight-sr'; push @attribs,'HomeCMDtwilight-sr_astro'; push @attribs,'HomeCMDtwilight-sr_civil'; push @attribs,'HomeCMDtwilight-sr_indoor'; push @attribs,'HomeCMDtwilight-sr_naut'; push @attribs,'HomeCMDtwilight-sr_weather'; push @attribs,'HomeCMDtwilight-ss'; push @attribs,'HomeCMDtwilight-ss_astro'; push @attribs,'HomeCMDtwilight-ss_civil'; push @attribs,'HomeCMDtwilight-ss_indoor'; push @attribs,'HomeCMDtwilight-ss_naut'; push @attribs,'HomeCMDtwilight-ss_weather'; } if ($hash->{SENSORSENERGY}) { push @attribs,'HomeSensorsEnergyDivider:textField'; } if ($hash->{SENSORSPOWER}) { push @attribs,'HomeSensorsPowerDivider:textField'; } if ($hash->{SENSORSLIGHT}) { push @attribs,'HomeSensorsLuminanceDivider:textField'; } if ($hash->{SENSORSWATER}) { push @attribs,'HomeCMDalarmWater'; push @attribs,'HomeCMDalarmWater-on'; push @attribs,'HomeCMDalarmWater-off'; push @attribs,'HomeTextNoWaterWater:textField'; } if (AttrVal($name,'HomeUWZ',undef)) { push @attribs,'HomeCMDuwz-warn'; push @attribs,'HomeCMDuwz-warn-begin'; push @attribs,'HomeCMDuwz-warn-end'; } for (split /,/x,AttrCheck($hash,'HomeSpecialModes')) { push @attribs,'HomeCMDmode-'.makeReadingName($_); } for (split /,/x,AttrCheck($hash,'HomeSpecialLocations')) { push @attribs,'HomeCMDlocation-'.makeReadingName($_); } if (InternalVal($name,'CALENDARS','')) { push @attribs,'HomeCMDevent'; for my $cal (devspec2array(InternalVal($name,'CALENDARS',''))) { my $evts = CalendarEvents($name,$cal); push @attribs,'HomeEventsFilter-'.$cal.':textField'; push @attribs,"HomeCMDevent-$cal-each"; if ($adv) { for my $evt (@{$evts}) { push @attribs,"HomeCMDevent-$cal-".makeReadingName($evt).'-begin'; push @attribs,"HomeCMDevent-$cal-".makeReadingName($evt).'-end'; } } } } for my $resident (split /,/x,$hash->{RESIDENTS}) { my $devtype = ID($resident,'ROOMMATE|GUEST|PET') ? $defs{$resident}->{TYPE} : ''; next unless ($devtype); if ($adv) { my $states = 'absent'; $states .= ",$HOMEMODE_UserModesAll" if ($devtype =~ /^ROOMMATE|PET$/x); $states .= ",home,$HOMEMODE_UserModes" if ($devtype eq 'GUEST'); for (split /,/x,$states) { push @attribs,"HomeCMDmode-$_-$resident"; } push @attribs,"HomeCMDpresence-absent-$resident"; push @attribs,"HomeCMDpresence-present-$resident"; my $locs = $devtype eq 'ROOMMATE' ? AttrVal($resident,'rr_locations','') : $devtype eq 'GUEST' ? AttrVal($resident,'rg_locations','') : AttrVal($resident,'rp_locations',''); for (split/,/x,$locs) { my $loc = makeReadingName($_); push @attribs,'HomeCMDlocation-'.$loc.'-'.$resident; push @attribs,'HomeCMDlocation-'.$loc.'-resident'; } } my @presdevs = $hash->{helper}{presdevs}{$resident}?@{$hash->{helper}{presdevs}{$resident}}:(); if (@presdevs) { my $count = 0; my $numbers = ''; for (@presdevs) { $count++; $numbers .= ',' if ($numbers); $numbers .= $count; } push @attribs,"HomePresenceDeviceAbsentCount-$resident:$numbers"; push @attribs,"HomePresenceDevicePresentCount-$resident:$numbers"; if ($adv) { for (@presdevs) { push @attribs,"HomeCMDpresence-absent-$resident-device"; push @attribs,"HomeCMDpresence-present-$resident-device"; push @attribs,"HomeCMDpresence-absent-$resident-$_"; push @attribs,"HomeCMDpresence-present-$resident-$_"; } } } } for (split ' ',AttrCheck($hash,'HomeDaytimes',$HOMEMODE_Daytimes)) { my $text = makeReadingName((split /\|/x)[1]); my $d = "HomeCMDdaytime-$text"; my $m = "HomeCMDmode-$text"; push @attribs,$d; push @attribs,$m; } for (split ' ',AttrCheck($hash,'HomeSeasons',$HOMEMODE_Seasons)) { my $text = (split /\|/x)[1]; my $s = 'HomeCMDseason-'.makeReadingName($text); push @attribs,$s; } my @list; for my $attrib (@attribs) { $attrib = $attrib =~ /^.+:.+$/x ? $attrib : "$attrib:textField-long"; push @list,$attrib; } @list = uniq sort @list; my $ret = join(' ',@list); $hash->{'.AttrList'} = "$ret $readingFnAttributes"; ####################### # for compatibiliy reasons: clean & delete userattr if possible my $ua = AttrVal($name,'userattr',undef); if ($ua) { my @stayattr; for (split ' ',$ua) { # cleaning if ($_ !~ /^Home/x) { push @stayattr,$_; } } if (int(@stayattr)) { CommandAttr(undef,"$name userattr ".join ' ',sort @stayattr); } else { CommandDeleteAttr(undef,"$name userattr"); } } ####################### return; } sub cleanUserattr { my ($hash,$devs,$newdevs) = @_; my $name = $hash->{NAME}; my @devspec = devspec2array($devs); return if (!@devspec); my @newdevspec = $newdevs?devspec2array($newdevs):(); for my $dev (@devspec) { my $ua = AttrVal($dev,'userattr',''); next if (!$ua); my @stayattr; for my $attr (split ' ',$ua) { if ($attr =~ /^Home/x) { $attr =~ s/:.*$//x; CommandDeleteAttr(undef,"$dev $attr") if ((AttrVal($dev,$attr,'') && !@newdevspec) || (AttrVal($dev,$attr,'') && @newdevspec && !grep {$_ eq $dev} @newdevspec)); next; } push @stayattr,$attr; } if (@stayattr) { my $list = join(' ',uniq sort @stayattr); CommandAttr(undef,"$dev userattr $list"); } else { CommandDeleteAttr(undef,"$dev userattr"); } } return; } sub Attr { my ($cmd,$name,$attr_name,$attr_value) = @_; my $hash = $defs{$name}; return if (!$init_done || ReadingsNum($name,'.HOMEMODE_ver',1.5) < 1.6); delete $hash->{helper}{lastChangedAttr}; delete $hash->{helper}{lastChangedAttrValue}; my $attr_value_old = AttrVal($name,$attr_name,''); $hash->{helper}{lastChangedAttr} = $attr_name; my $text; if ($cmd eq 'set') { $hash->{helper}{lastChangedAttrValue} = $attr_value; if ($attr_name =~ /^(HomeAutoAwoken|HomeAutoAsleep|HomeAutoArrival|HomeModeAbsentBelatedTime)$/x) { $text = $langDE? "Ungültiger Wert $attr_value für Attribut $attr_name. Muss eine Zahl von 0 bis 6000 sein.": "Invalid value $attr_value for attribute $attr_name. Must be a number from 0 to 6000."; return $text if ($attr_value !~ /^(\d{1,4})(\.\d{1,2})?$/x || $1 > 6000 || $1 < 0); } elsif ($attr_name eq 'HomeLanguage') { $text = $langDE? "Ungültiger Wert $attr_value für Attribut $attr_name. Kann nur \"EN\" oder \"DE\" sein, Vorgabewert ist Sprache aus global.": "Invalid value $attr_value for attribute $attr_name. Must be \"EN\" or \"DE\", default is language from global."; return $text if ($attr_value !~ /^(DE|EN)$/x); $langDE = $attr_value eq 'DE'?1:0; } elsif ($attr_name eq 'HomeAdvancedDetails') { $text = $langDE? "Ungültiger Wert $attr_value für Attribut $attr_name. Kann nur \"none\", \"detail\", \"both\" oder \"room\" sein, Vorgabewert ist \"none\".": "Invalid value $attr_value for attribute $attr_name. Must be \"none\", \"detail\", \"both\" or \"room\", default is \"none\"."; return $text if ($attr_value !~ /^(none|detail|both|room)$/x); if ($attr_value eq 'detail') { $modules{HOMEMODE}->{FW_deviceOverview} = 1; $modules{HOMEMODE}->{FW_addDetailToSummary} = 0; } else { $modules{HOMEMODE}->{FW_deviceOverview} = 1; $modules{HOMEMODE}->{FW_addDetailToSummary} = 1; } } elsif ($attr_name =~ /^(disable|HomeAdvancedAttributes|HomeAutoDaytime|HomeAutoAlarmModes|HomeAutoPresence|HomeSensorsContactOpenWarningUnified)$/x) { my $n = $attr_name eq 'HomeAutoAlarmModes'?1:0; $text = $langDE? "Ungültiger Wert $attr_value für Attribut $attr_name. Kann nur 1 oder 0 sein, Vorgabewert ist $n.": "Invalid value $attr_value for attribute $attr_name. Must be 1 or 0, default is $n."; return $text if ($attr_value !~ /^[01]$/x); RemoveInternalTimer($hash) if ($attr_name eq 'disable' && $attr_value); GetUpdate($hash) if ($attr_name eq 'disable' && !$attr_value); updateInternals($hash,1) if ($attr_name =~ /^HomeAutoPresence$/x && $init_done); AttrList($hash) if ($attr_name =~ /^HomeAdvancedAttributes$/x && $init_done); addSensorsUserAttr($hash,$hash->{SENSORSCONTACT},$hash->{SENSORSCONTACT}) if ($attr_name eq 'HomeSensorsContactOpenWarningUnified' && $init_done); } elsif ($attr_name =~ /^HomeCMD/x && $init_done) { if ($attr_value_old ne $attr_value) { my $err = perlSyntaxCheck(replacePlaceholders($hash,$attr_value)); return $err if ($err); } } elsif ($attr_name =~ /^HomeAutoPresenceSuppressState$/x && $init_done) { $text = $langDE? "Ungültiger Wert $attr_value für Attribut $attr_name. Es wird wenigstens ein Wert oder maximal 3 Pipe separierte Werte benötigt! z.B. asleep|gotosleep": "Invalid value $attr_value for attribute $attr_name. You have to provide at least one value or max 3 values pipe separated, e.g. asleep|gotosleep"; return $text if ($attr_value !~ /^(asleep|gotosleep|awoken)(\|(asleep|gotosleep|awoken)){0,2}$/x); } elsif ($attr_name =~ /^HomeEventsDevices$/x && $init_done) { my $d = devspec2array($attr_value); if ($d eq $attr_value) { $text = $langDE? 'Keine gültigen Calendar/holiday Geräte gefunden in devspec "'.$attr_value.'"': 'No valid Calendar/holiday device(s) found in devspec "'.$attr_value.'"'; return $text; } else { updateInternals($hash); } } elsif ($attr_name =~ /^HomeEventsFilter-.+$/x && $init_done) { updateInternals($hash); } elsif ($attr_name =~ /^(HomePresenceDeviceType)$/x && $init_done) { $text = $langDE? "$attr_value muss ein gültiger TYPE sein": "$attr_value must be a valid TYPE"; return $text if (!CheckIfIsValidDevspec($name,"TYPE=$attr_value",'presence')); updateInternals($hash); } elsif ($attr_name eq "HomeSensorsBatteryTypes") { $text = $langDE? "Ungültiger Wert $attr_value für Attribut $attr_name. Es wird wenigstens ein Wert oder mehrere Pipe separierte Werte benötigt! z.B. dummy|YOURMODULE": "Invalid value $attr_value for attribute $attr_name. You have to provide at least one value or more values pipe separated, e.g. dummy|YOURMODULE"; return $text if ($attr_value !~ /^[\w\-\+\*\.\(\)]+(\|[\w\-\+\*\.\(\)]+){0,}$/xi); updateInternals($hash,1); } elsif ($attr_name eq 'HomeIcewarningOnOffTemps') { $text = $langDE? "Ungültiger Wert $attr_value für Attribut $attr_name. Es werden 2 Leerzeichen separierte Temperaturen benötigt, z.B. -0.1 2.5": "Invalid value $attr_value for attribute $attr_name. You have to provide 2 space separated temperatures, e.g. -0.1 2.5"; return $text if ($attr_value !~ /^-?\d{1,2}(\.\d)?\s-?\d{1,2}(\.\d)?$/); } elsif ($attr_name eq 'HomeSensorsContactOpenTimeDividers') { $text = $langDE? "Ungültiger Wert $attr_value für Attribut $attr_name. Es werden Leerzeichen separierte Zahlen für jede Jahreszeit (aus Attribut HomeSeasons) benötigt, z.B. 2 1 2 3.333": "Invalid value $attr_value for attribute $attr_name. You have to provide space separated numbers for each season in order of the seasons provided in attribute HomeSeasons, e.g. 2 1 2 3.333"; return $text if ($attr_value !~ /^\d{1,2}(\.\d{1,3})?(\s\d{1,2}(\.\d{1,3})?){0,}$/); my @times = split ' ',$attr_value; my $s = int(split ' ',AttrVal($name,'HomeSeasons',$HOMEMODE_Seasons)); my $t = int(@times); $text = $langDE? "Anzahl von $attr_name Werten ($t) ungleich zu den verfügbaren Jahreszeiten ($s) im Attribut HomeSeasons!": "Number of $attr_name values ($t) not matching the number of available seasons ($s) in attribute HomeSeasons!"; return $text if ($s != $t); } elsif ($attr_name eq 'HomeSensorsContactOpenTimeMin') { $text = $langDE? "Ungültiger Wert $attr_value für Attribut $attr_name. Zahlen von 1 bis 9.9 sind nur erlaubt!": "Invalid value $attr_value for attribute $attr_name. Numbers from 1 to 9.9 are allowed only!"; return $text if ($attr_value !~ /^[1-9](\.\d)?$/x); } elsif ($attr_name eq 'HomeSensorsContactOpenTimes') { $text = $langDE? "Ungültiger Wert $attr_value für Attribut $attr_name. Es werden Leerzeichen separierte Zahlen benötigt, z.B. 5 10 15 17.5": "Invalid value $attr_value for attribute $attr_name. You have to provide space separated numbers, e.g. 5 10 15 17.5"; return $text if ($attr_value !~ /^\d{1,4}(\.\d)?((\s\d{1,4}(\.\d)?)?){0,}$/); for (split ' ',$attr_value) { $text = $langDE? '0 ist kein gültiger Zeitwert!': '0 is not a valid time value!'; return $text if ($_ == 0); } } elsif ($attr_name =~ /^HomeSensors(Energy|Power)Divider$/x) { $text = $langDE? "Ungültiger Wert $attr_value für Attribut $attr_name. Es wird nur eine Zahl benötigt (auch negativ möglich um Wert umzukehren, z.B. PV-Anlage), z.B. 1000": "Invalid value $attr_value for attribute $attr_name. You have to provide a single number (even negativ numbers are valid to reverse the sign of a number), e.g. 1000"; return $text if ($attr_value !~ /^-?\d{1,}(\.\d{1,})?$/); } elsif ($attr_name eq 'HomeResidentCmdDelay') { $text = $langDE? "Ungültiger Wert $attr_value für Attribut $attr_name. Zahlen von 0 bis 9999 sind nur erlaubt!": "Invalid value $attr_value for attribute $attr_name. Numbers from 0 to 9999 are allowed only!"; return $text if ($attr_value !~ /^\d{1,4}$/x); } elsif ($attr_name =~ /^HomeSpecial(Modes|Locations)$/x && $init_done) { $text = $langDE? "Ungültiger Wert $attr_value für Attribut $attr_name. Muss eine Komma separierte Liste von Wörtern sein!": "Invalid value $attr_value for attribute $attr_name. Must be a comma separated list of words!"; return $text if ($attr_value !~ /^[\w\-äöüß\.]+(,[\w\-äöüß\.]+){0,}$/xi); AttrList($hash); } elsif ($attr_name eq 'HomePublicIpCheckInterval') { $text = $langDE? "Ungültiger Wert $attr_value für Attribut $attr_name. Muss eine Zahl von 1 bis 99999 für das Interval in Minuten sein!": "Invalid value $attr_value for attribute $attr_name. Must be a number from 1 to 99999 for interval in minutes!"; return $text if ($attr_value !~ /^\d{1,5}$/x); } elsif ($attr_name =~ /^HomeSensors(Battery|Contact|Energy|Luminance|Motion|Power|Smoke|Tamper|Water)$/x && $init_done) { $text = $langDE? "$attr_value muss ein gültiger Devspec sein!": "$attr_value must be a valid devspec!"; return $text if (!CheckIfIsValidDevspec($name,$attr_value)); updateInternals($hash,1) if ($attr_value ne $attr_value_old); } elsif ($attr_name eq 'HomeTwilightDevice' && $init_done) { $text = $langDE? "$attr_value muss ein gültiges Gerät vom TYPE Twilight sein!": "$attr_value must be a valid device of TYPE Twilight!"; return $text if (!CheckIfIsValidDevspec($name,"$attr_value:FILTER=TYPE=Twilight")); if ($attr_value_old ne $attr_value) { CommandDeleteReading(undef,"$name light|twilight|twilightEvent"); updateInternals($hash); } } elsif ($attr_name eq 'HomeWeatherDevice' && $init_done) { $text = $langDE? "$attr_value muss ein gültiges Gerät vom TYPE Weather sein!": "$attr_value must be a valid device of TYPE Weather!"; return $text if (!CheckIfIsValidDevspec($name,"$attr_value:FILTER=TYPE=Weather")); if ($attr_value_old ne $attr_value) { CommandDeleteReading(undef,"$name pressure") if (!AttrVal($name,'HomeSensorAirpressure',undef)); CommandDeleteReading(undef,"$name wind.*") if (!AttrVal($name,'HomeSensorWind',undef)); CommandDeleteReading(undef,"$name temperature.*") if (!AttrVal($name,'HomeSensorTemperatureOutside',undef)); CommandDeleteReading(undef,"$name humidity.*") if (!AttrVal($name,'HomeSensorHumidityOutside',undef)); updateInternals($hash); } } elsif ($attr_name eq 'HomeSensorTemperatureOutside' && $init_done) { $text = $langDE? "$attr_value muss ein gültiger Devspec mit temperature Reading sein!": "$attr_value must be a valid device with temperature reading!"; return $text if (!CheckIfIsValidDevspec($name,$attr_value,'temperature')); CommandDeleteAttr(undef,"$name HomeSensorHumidityOutside") if (AttrVal($name,'HomeSensorHumidityOutside',undef) && $attr_value eq AttrVal($name,'HomeSensorHumidityOutside',undef)); if ($attr_value_old ne $attr_value) { CommandDeleteReading(undef,"$name temperature") if (!AttrVal($name,'HomeWeatherDevice',undef)); updateInternals($hash); } } elsif ($attr_name eq 'HomeSensorHumidityOutside' && $init_done) { $text = $langDE? 'Dieses Attribut ist wegzulassen wenn es den gleichen Wert haben sollte wie HomeSensorTemperatureOutside!': 'You have to omit this attribute if it should have the same value like HomeSensorTemperatureOutside!'; return $text if ($attr_value eq AttrVal($name,'HomeSensorTemperatureOutside',undef)); $text = $langDE? "$attr_value muss ein gültiger Devspec mit humidity Reading sein!": "$attr_value must be a valid device with humidity reading!"; return $text if (!CheckIfIsValidDevspec($name,$attr_value,'humidity')); if ($attr_value_old ne $attr_value) { CommandDeleteReading(undef,"$name humidity") if (!AttrVal($name,'HomeWeatherDevice',undef)); updateInternals($hash); } } elsif ($attr_name eq 'HomeDaytimes' && $init_done) { $text = $langDE? "$attr_value für $attr_name muss eine Leerzeichen separierte Liste aus Uhrzeit|Text Paaren sein! z.B. $HOMEMODE_Daytimes": "$attr_value for $attr_name must be a space separated list of time|text pairs! e.g. $HOMEMODE_Daytimes"; return $text if ($attr_value !~ /^([0-2]\d:[0-5]\d\|[\w\-äöüß\.]+)(\s[0-2]\d:[0-5]\d\|[\w\-äöüß\.]+){0,}$/i); if ($attr_value_old ne $attr_value) { my @ts; for (split ' ',$attr_value) { my $time = (split /\|/x)[0]; my ($h,$m) = split /:/x,$time; my $minutes = $h * 60 + $m; my $lastminutes = @ts ? $ts[int(@ts)-1] : -1; if ($minutes > $lastminutes) { push @ts,$minutes; } else { $text = $langDE? "Falsche Reihenfolge der Zeiten in $attr_value": "Wrong times order in $attr_value"; return $text; } } AttrList($hash); } } elsif ($attr_name eq 'HomeSeasons' && $init_done) { $text = $langDE? "$attr_value für $attr_name muss eine Leerzeichen separierte Liste aus Datum|Text Paaren mit mindestens 4 Werten sein! z.B. $HOMEMODE_Seasons": "$attr_value for $attr_name must be a space separated list of date|text pairs with at least 4 values! e.g. $HOMEMODE_Seasons"; return $text if (int(split ' ',$attr_value) < 4 || int(split /\|/x,$attr_value) < 5); if ($attr_value_old ne $attr_value) { my @ds; for (split ' ',$attr_value) { my $time = (split /\|/x)[0]; my ($m,$d) = split /\./x,$time; my $days = $m * 31 + $d; my $lastdays = @ds ? $ds[int(@ds)-1] : -1; if ($days > $lastdays) { push @ds,$days; } else { $text = $langDE? "Falsche Reihenfolge der Datumsangaben in $attr_value": "Wrong dates order in $attr_value"; return $text; } } AttrList($hash); } } elsif ($attr_name =~ /^(HomeTextAndAreIs|HomeTextTodayTomorrowAfterTomorrow|HomeTextRisingConstantFalling)$/x) { $text = $langDE? "$attr_value für $attr_name muss eine Pipe separierte Liste mit 3 Werten sein!": "$attr_value for $attr_name must be a pipe separated list with 3 values!"; return $text if (int(split /\|/x,$attr_value) != 3); } elsif ($attr_name eq 'HomeTextClosedOpen') { $text = $langDE? "$attr_value für $attr_name muss eine Pipe separierte Liste mit 2 Werten sein!": "$attr_value for $attr_name must be a pipe separated list with 2 values!"; return $text if (int(split /\|/x,$attr_value) != 2); } elsif ($attr_name eq 'HomeUWZ' && $init_done) { $text = $langDE? "$attr_value muss ein gültiges Gerät vom TYPE Weather sein!": "$attr_value must be a valid device of TYPE Weather!"; return "$attr_value must be a valid device of TYPE UWZ!" if (!CheckIfIsValidDevspec($name,"$attr_value:FILTER=TYPE=UWZ")); updateInternals($hash) if ($attr_value_old ne $attr_value); } elsif ($attr_name =~ /^HomeSensor(Energy|Power)Divider$/x && $init_done) { $text = $langDE? "$attr_name muss ein einzelne Zahl, aber nicht 0 sein, z.B. 1000 oder 0.001!": "$attr_name must be a single number, but not 0, p.e. 1000 or 0.001!"; return $text if ($attr_value !~ /^(?!0)\d+(\.\d+)?$/x || !CheckIfIsValidDevspec($name,$1,$2)); } elsif ($attr_name =~ /^HomeSensor(Airpressure|Wind|Rain)$/x && $init_done) { $text = $langDE? "$attr_name muss ein einzelnes gültiges Gerät und Reading sein (Sensor:Reading)!": "$attr_name must be a single valid device and reading (sensor:reading)!"; return $text if ($attr_value !~ /^([\w\.]+):([\w\-\.]+)$/x || !CheckIfIsValidDevspec($name,$1,$2)); updateInternals($hash) if ($attr_value_old ne $attr_value); } elsif ($attr_name =~ /^Home(SensorsAlarmDelay|ModeAlarmArmDelay)$/) { $text = $langDE? "$attr_name muss eine einzelne Zahl (in Sekunden) sein oder drei leerzeichengetrennte Zahlen (in Sekunden sein für jeden Alarm Modus individuell (order: armaway armnight armhome).": "$attr_name must be a single number (in seconds) or three space separated numbers (in seconds)
for each alarm mode individually (order: armaway armnight armhome)."; return $text if ($attr_value !~ /^\d{1,3}((\s\d{1,3}){2})?$/); } elsif ($attr_name eq 'HomeSensorsBatteryLowPercentage') { $text = $langDE? "$attr_name muss ein Wert zwischen 0 und 99 sein!": "$attr_name must be a value from 0 to 99!"; return $text if ($attr_value !~ /^\d{1,2}$/x); updateInternals($hash); } elsif ($attr_name eq 'HomeTriggerPanic' && $init_done) { $text = $langDE? "$attr_name muss ein gültiges Gerät, Reading und Wert in Form von \"Gerät:Reading:WertAn:WertAus\" (WertAus ist optional) sein!": "$attr_name must be a valid device, reading and value like \"device:reading:valueOn:valueOff\" (valueOff is optional)!"; return $text if ($attr_value !~ /^([\w\.]+):([\w\.]+):[\w\-\.]+(:[\w\-\.]+)?$/x || !CheckIfIsValidDevspec($name,$1,$2)); updateInternals($hash); } elsif ($attr_name eq 'HomeTriggerAnyoneElseAtHome' && $init_done) { $text = $langDE? "$attr_name muss ein gültiges Gerät, Reading und Wert in Form von \"Gerät:Reading:WertAn:WertAus\" sein!": "$attr_name must be a valid device, reading and value like \"device:reading:valueOn:valueOff\" !"; return $text if ($attr_value !~ /^([\w\.]+):([\w\.]+):[\w\-\.]+(:[\w\-\.]+)$/x || !CheckIfIsValidDevspec($name,$1,$2)); updateInternals($hash); } elsif ($attr_name eq 'HomeUserCSS' && $init_done) { $text = $langDE? "$attr_name muss gültiger CSS Code sein!": "$attr_name must be valid CSS code!"; return $text if ($attr_value !~ /^[\.\w\#]+\{.+\}$/xmgis); } } else { $hash->{helper}{lastChangedAttrValue} = '---'; if ($attr_name eq 'disable') { GetUpdate($hash); } elsif ($attr_name eq 'HomeLanguage') { $langDE = AttrVal('global','language','DE') ? 1 : undef; } elsif ($attr_name =~ /^(HomeAdvancedAttributes|HomeAutoPresence|HomePresenceDeviceType|HomeEventsDevices|HomeSensorAirpressure|HomeSensorWind|HomeSensorRain|HomeSensorsBattery)$/x) { CommandDeleteReading(undef,"$name event.*") if ($attr_name eq 'HomeEventsDevices'); CommandDeleteReading(undef,"$name rain.*") if ($attr_name eq 'HomeSensorRain'); CommandDeleteReading(undef,"$name battery.*|lastBatteryLow") if ($attr_name eq 'HomeSensorsBattery'); AttrList($hash) if ($attr_name =~ /^HomeAdvancedAttributes$/x); updateInternals($hash); } elsif ($attr_name =~ /^HomeEventsFilter-.+$/x) { updateInternals($hash); } elsif ($attr_name eq 'HomeSensorsContactOpenWarningUnified') { addSensorsUserAttr($hash,$hash->{SENSORSCONTACT},$hash->{SENSORSCONTACT}) if ($init_done); } elsif ($attr_name =~ /^(HomeSensorsContact|HomeSensorsMotion)$/x) { my $olddevs = ($attr_name eq 'HomeSensorsMotion')?$hash->{SENSORSMOTION}:$hash->{SENSORSCONTACT}; my $read = ($attr_name eq 'HomeSensorsMotion')?'lastMotion|prevMotion|motion.*':'lastContact|prevContact|contact.*'; CommandDeleteReading(undef,"$name $read"); updateInternals($hash); cleanUserattr($hash,$olddevs); } elsif ($attr_name eq 'HomeSensorsSmoke') { CommandDeleteReading(undef,"$name alarmSmoke.*"); updateInternals($hash); } elsif ($attr_name eq 'HomeSensorsWater') { CommandDeleteReading(undef,"$name alarmWater.*"); updateInternals($hash); } elsif ($attr_name eq 'HomeSensorsEnergy') { CommandDeleteReading(undef,"$name energy.*"); updateInternals($hash); } elsif ($attr_name eq 'HomeSensorsPower') { CommandDeleteReading(undef,"$name power.*"); updateInternals($hash); } elsif ($attr_name eq 'HomePublicIpCheckInterval') { delete $hash->{'.IP_TRIGGERTIME_NEXT'}; } elsif ($attr_name =~ /^(HomeWeatherDevice|HomeTwilightDevice)$/x) { if ($attr_name eq 'HomeWeatherDevice') { CommandDeleteReading(undef,"$name pressure|wind.*"); CommandDeleteReading(undef,"$name temperature.*") if (!AttrVal($name,'HomeSensorTemperatureOutside',undef)); CommandDeleteReading(undef,"$name humidity.*") if (!AttrVal($name,'HomeSensorHumidityOutside',undef)); } else { CommandDeleteReading(undef,"$name twilight|twilightEvent|light"); } updateInternals($hash); } elsif ($attr_name =~ /^(HomeSensorTemperatureOutside|HomeSensorHumidityOutside)$/x) { CommandDeleteReading(undef,"$name .*temperature.*") if (!AttrVal($name,'HomeWeatherDevice',undef) && $attr_name eq 'HomeSensorTemperatureOutside'); CommandDeleteReading(undef,"$name .*humidity.*") if (!AttrVal($name,'HomeWeatherDevice',undef) && $attr_name eq 'HomeSensorHumidityOutside'); updateInternals($hash); } elsif ($attr_name =~ /^(HomeAdvancedAttributes|HomeDaytimes|HomeSeasons|HomeSpecialLocations|HomeSpecialModes)$/x && $init_done) { AttrList($hash); } elsif ($attr_name =~ /^(HomeUWZ|HomeSensorsLuminance)$/x) { CommandDeleteReading(undef,"$name uwz.*") if ($attr_name eq 'HomeUWZ'); CommandDeleteReading(undef,"$name .*luminance.*") if ($attr_name eq 'HomeSensorsLuminance'); updateInternals($hash); } elsif ($attr_name eq 'HomeSensorsBatteryLowPercentage') { updateInternals($hash); } elsif ($attr_name eq 'HomeSensorsBatteryTypes') { updateInternals($hash,1); } } return; } sub replacePlaceholders { my ($hash,$cmd,$resident) = @_; my $name = $hash->{NAME}; my $sensor = AttrVal($name,'HomeWeatherDevice','nOtDeFiNeDwEaThErDeViCe'); $resident = $resident ? $resident : ReadingsVal($name,'lastActivityByResident',''); my $alias = AttrVal($resident,'alias',''); my $audio = AttrVal($resident,'msgContactAudio',''); $audio = AttrVal('globalMsg','msgContactAudio','no msg audio device available') if (!$audio); my $lastabsencedur = ReadingsVal($resident,'lastDurAbsence_cr',0); my $lastpresencedur = ReadingsVal($resident,'lastDurPresence_cr',0); my $lastsleepdur = ReadingsVal($resident,'lastDurSleep_cr',0); my $durabsence = ReadingsVal($resident,'durTimerAbsence_cr',0); my $durpresence = ReadingsVal($resident,'durTimerPresence_cr',0); my $dursleep = ReadingsVal($resident,'durTimerSleep_cr',0); my $contactsOpen = ReadingsVal($name,'contactsOutsideOpen',''); my $contactsOpenCt = ReadingsVal($name,'contactsOutsideOpen_ct',0); my $contactsOpenHr = ReadingsVal($name,'contactsOutsideOpen_hr',0); my $dnd = ReadingsVal($name,'dnd','off') eq 'on' ? 1 : 0; my $aeah = ReadingsVal($name,'anyoneElseAtHome','off') eq 'on' ? 1 : 0; my $panic = ReadingsVal($name,'panic','off') eq 'on' ? 1 : 0; my $tampered = ReadingsVal($name,'alarmTamper_hr',''); my $tamperedc = ReadingsVal($name,'alarmTamper_ct',''); my $tamperedhr = ReadingsVal($name,'alarmTamper_hr',''); my $ice = ReadingsVal($name,'icewarning',0); my $ip = ReadingsVal($name,'publicIP',''); my $light = ReadingsVal($name,'light',0); my $twilight = ReadingsVal($name,'twilight',0); my $twilightevent = ReadingsVal($name,'twilightEvent',''); my $location = ReadingsVal($name,'location',''); my $rlocation = ReadingsVal($resident,'location',''); my $alarm = ReadingsVal($name,'alarmTriggered',0); my $alarmc = ReadingsVal($name,'alarmTriggered_ct',0); my $alarmhr = ReadingsVal($name,'alarmTriggered_hr',0); my $daytime = DayTime($hash); my $mode = ReadingsVal($name,'mode',''); my $amode = ReadingsVal($name,'modeAlarm',''); my $pamode = ReadingsVal($name,'prevModeAlarm',''); my $season = ReadingsVal($name,'season',''); my $pmode = ReadingsVal($name,'prevMode',''); my $rpmode = ReadingsVal($resident,'lastState',''); my $pres = ReadingsVal($name,'presence','') eq 'present' ? 1 : 0; my $rpres = ReadingsVal($resident,'presence','') eq 'present' ? 1 : 0; my $pdevice = ReadingsVal($name,'lastActivityByPresenceDevice',''); my $apdevice = ReadingsVal($name,'lastAbsentByPresenceDevice',''); my $ppdevice = ReadingsVal($name,'lastPresentByPresenceDevice',''); my $paddress = InternalVal($pdevice,'ADDRESS',''); my $pressure = ReadingsNum($name,'pressure',0); # my $weatherlong = WeatherTXT($hash,AttrVal($name,'HomeTextWeatherLong','')); # my $weathershort = WeatherTXT($hash,AttrVal($name,'HomeTextWeatherShort','')); my $forecast = ForecastTXT($hash); my $forecasttoday = ForecastTXT($hash,1); my $luminance = ReadingsVal($name,'luminance',0); my $luminancetrend = ReadingsVal($name,'luminanceTrend',0); my $humi = ReadingsNum($name,'humidity',0); # my $humitrend = ReadingsVal($name,'humidityTrend',0); my $temp = ReadingsNum($name,'temperature',0); # my $temptrend = ReadingsVal($name,'temperatureTrend','constant'); my $wind = ReadingsNum($name,'wind',0); my $windchill = ReadingsNum($sensor,'apparentTemperature',0); my $motion = ReadingsVal($name,'lastMotion',''); my $pmotion = ReadingsVal($name,'prevMotion',''); my $contact = ReadingsVal($name,'lastContact',''); my $pcontact = ReadingsVal($name,'prevContact',''); my $uwzc = ReadingsVal($name,'uwz_warnCount',0); my $uwzs = uwzTXT($hash,$uwzc,undef); my $uwzl = uwzTXT($hash,$uwzc,1); my $lowBat = name2alias(ReadingsVal($name,'lastBatteryLow','')); my $normBat = name2alias(ReadingsVal($name,'lastBatteryNormal','')); my $lowBatAll = ReadingsVal($name,'batteryLow_hr',''); my $lowBatCount = ReadingsVal($name,'batteryLow_ct',0); my $disabled = ReadingsVal($name,'devicesDisabled',''); my $openwarn = ReadingsVal($name,'contactOpenWarning',''); my $openwarnct = ReadingsNum($name,'contactOpenWarning_ct',0); my $openwarnhr = ReadingsVal($name,'contactOpenWarning_hr',''); my $sensorsbattery = $hash->{SENSORSBATTERY}; my $sensorscontact = $hash->{SENSORSCONTACT}; my $sensorsenergy = $hash->{SENSORSENERGY}; my $sensorsmotion = $hash->{SENSORSMOTION}; my $sensorssmoke = $hash->{SENSORSSMOKE}; my $ure = $hash->{RESIDENTS}; $ure =~ s/,/\|/xg; my $arrivers = makeHR($hash,1,devspec2array("$ure:FILTER=location=arrival")); my $water = ReadingsVal($name,'alarmWater',0); my $waterc = ReadingsVal($name,'alarmWater_ct',0); my $waterhr = ReadingsVal($name,'alarmWater_hr',0); my $unified = AttrNum($name,'HomeSensorsContactOpenWarningUnified',0); my $rainLevel = ReadingsVal($name,'rainLevel',0); $cmd = WeatherTXT($hash,$cmd); $cmd =~ s/%ADDRESS%/$paddress/xg; $cmd =~ s/%ALARM%/$alarm/xg; $cmd =~ s/%ALARMCT%/$alarmc/xg; $cmd =~ s/%ALARMHR%/$alarmhr/xg; $cmd =~ s/%ALIAS%/$alias/xg; $cmd =~ s/%AMODE%/$amode/xg; $cmd =~ s/%AEAH%/$aeah/xg; $cmd =~ s/%ARRIVERS%/$arrivers/xg; $cmd =~ s/%AUDIO%/$audio/xg; $cmd =~ s/%BATTERYNORMAL%/$normBat/xg; $cmd =~ s/%BATTERYLOW%/$lowBat/xg; $cmd =~ s/%BATTERYLOWALL%/$lowBatAll/xg; $cmd =~ s/%BATTERYLOWCT%/$lowBatCount/xg; # $cmd =~ s/%CONDITION%/$condition/xg; $cmd =~ s/%CONTACT%/$contact/xg; $cmd =~ s/%DAYTIME%/$daytime/xg; $cmd =~ s/%DEVICE%/$pdevice/xg; $cmd =~ s/%DEVICEA%/$apdevice/xg; $cmd =~ s/%DEVICEP%/$ppdevice/xg; $cmd =~ s/%DISABLED%/$disabled/xg; $cmd =~ s/%DND%/$dnd/xg; my $hed = AttrCheck($hash,'HomeEventsDevices',undef); if ($hed) { my @cals; for my $c (devspec2array($hed)) { push @cals,$c; } @cals = uniq @cals; for my $cal (@cals) { my $state = ReadingsVal($name,"event-$cal",'none') ne 'none' ? ReadingsVal($name,"event-$cal",'') : ''; $cmd =~ s/%$cal%/$state/xg; my $events = CalendarEvents($name,$cal); if (ID($cal,'holiday')) { for my $evt (@{$events}) { my $val = $state eq $evt ? 1 : ''; $cmd =~ s/%$cal-$evt%/$val/xg; } } else { for my $evt (@{$events}) { for my $e (split /,/x,$state) { my $val = $e eq $evt ? 1 : ''; $cmd =~ s/%$cal-$evt%/$val/xg; } } } } } $cmd =~ s/%DURABSENCE%/$durabsence/xg; $cmd =~ s/%DURABSENCELAST%/$lastabsencedur/xg; $cmd =~ s/%DURPRESENCE%/$durpresence/xg; $cmd =~ s/%DURPRESENCELAST%/$lastpresencedur/xg; $cmd =~ s/%DURSLEEP%/$dursleep/xg; $cmd =~ s/%DURSLEEPLAST%/$lastsleepdur/xg; $cmd =~ s/%FORECAST%/$forecast/xg; $cmd =~ s/%FORECASTTODAY%/$forecasttoday/xg; $cmd =~ s/%HUMIDITY%/$humi/xg; # $cmd =~ s/%HUMIDITYTREND%/$humitrend/xg; $cmd =~ s/%ICE%/$ice/xg; $cmd =~ s/%IP%/$ip/xg; $cmd =~ s/%LIGHT%/$light/xg; $cmd =~ s/%LOCATION%/$location/xg; $cmd =~ s/%LOCATIONR%/$rlocation/xg; $cmd =~ s/%LUMINANCE%/$luminance/xg; $cmd =~ s/%LUMINANCETREND%/$luminancetrend/xg; $cmd =~ s/%MODE%/$mode/xg; $cmd =~ s/%MODEALARM%/$amode/xg; $cmd =~ s/%MOTION%/$motion/xg; $cmd =~ s/%NAME%/$name/xg; $cmd =~ s/%OPEN%/$contactsOpen/xg; $cmd =~ s/%OPENCT%/$contactsOpenCt/xg; $cmd =~ s/%OPENHR%/$contactsOpenHr/xg; $cmd =~ s/%OPENWARN%/$openwarn/xg; $cmd =~ s/%OPENWARNCT%/$openwarnct/xg; $cmd =~ s/%OPENWARNHR%/$openwarnhr/xg; $cmd =~ s/%RESIDENT%/$resident/xg; $cmd =~ s/%PANIC%/$panic/xg; $cmd =~ s/%PRESENT%/$pres/xg; $cmd =~ s/%PRESENTR%/$rpres/xg; $cmd =~ s/%PRESSURE%/$pressure/xg; $cmd =~ s/%PREVAMODE%/$pamode/xg; $cmd =~ s/%PREVCONTACT%/$pcontact/xg; $cmd =~ s/%PREVMODE%/$pmode/xg; $cmd =~ s/%PREVMODER%/$rpmode/xg; $cmd =~ s/%PREVMOTION%/$pmotion/xg; $cmd =~ s/%RAINLEVEL%/$rainLevel/xg; $cmd =~ s/%SEASON%/$season/xg; $cmd =~ s/%SELF%/$name/xg; $cmd =~ s/%SENSORSBATTERY%/$sensorsbattery/xg; $cmd =~ s/%SENSORSCONTACT%/$sensorscontact/xg; $cmd =~ s/%SENSORSENERGY%/$sensorsenergy/xg; $cmd =~ s/%SENSORSMOTION%/$sensorsmotion/xg; $cmd =~ s/%SENSORSSMOKE%/$sensorssmoke/xg; $cmd =~ s/%TAMPERED%/$tampered/xg; $cmd =~ s/%TAMPEREDCT%/$tamperedc/xg; $cmd =~ s/%TAMPEREDHR%/$tamperedhr/xg; $cmd =~ s/%TEMPERATURE%/$temp/xg; # $cmd =~ s/%TEMPERATURETREND%/$temptrend/xg; # $cmd =~ s/%TOBE%/$conditionart/xg; $cmd =~ s/%TWILIGHT%/$twilight/xg; $cmd =~ s/%TWILIGHTEVENT%/$twilightevent/xg; $cmd =~ s/%UNIFIED%/$unified/xg; $cmd =~ s/%UWZ%/$uwzc/xg; $cmd =~ s/%UWZLONG%/$uwzl/xg; $cmd =~ s/%UWZSHORT%/$uwzs/xg; $cmd =~ s/%WATER%/$water/xg; $cmd =~ s/%WATERCT%/$waterc/xg; $cmd =~ s/%WATERHR%/$waterhr/xg; # $cmd =~ s/%WEATHER%/$weathershort/xg; # $cmd =~ s/%WEATHERLONG%/$weatherlong/xg; $cmd =~ s/%WIND%/$wind/xg; $cmd =~ s/%WINDCHILL%/$windchill/xg; return $cmd; } sub serializeCMD { my ($hash,@cmds) = @_; my $name = $hash->{NAME}; my @newcmds; for my $cmd (@cmds) { $cmd =~ s/\r\n/\n/xgm; my @newcmd; for (split /\n+/x,$cmd) { next if ($_ =~ /^\s*(#|$)/); $_ =~ s/\s{2,}/ /g; push @newcmd,$_; } $cmd = join(' ',@newcmd); Log3 $name,5,"$name: cmdnew: $cmd"; push @newcmds,SemicolonEscape($cmd) if ($cmd !~ /^[\t\s]*$/); } my $cmd = join(';',@newcmds); $cmd =~ s/\}\s{0,1};\s{0,1}\{/\};;\{/g; return $cmd; } sub ReadingTrend { my ($hash,$read,$val) = @_; my $name = $hash->{NAME}; $val = ReadingsNum($name,$read,5) if (!$val); my $time = AttrNum($name,'HomeTrendCalcAge',900); my $pval = ReadingsNum($name,".$read",undef); if (defined $pval && ReadingsAge($name,".$read",0) >= $time) { my ($rising,$constant,$falling) = split /\|/x,AttrVal($name,'HomeTextRisingConstantFalling','rising|constant|falling'); my $trend = $constant; $trend = $rising if ($val > $pval); $trend = $falling if ($val < $pval); readingsBeginUpdate($hash); readingsBulkUpdate($hash,".$read",$val); readingsBulkUpdate($hash,$read.'Trend',$trend); readingsEndUpdate($hash,1); } elsif (!defined $pval) { readingsSingleUpdate($hash,".$read",$val,0); } return; } sub WeatherTXT { my ($hash,$text) = @_; my $name = $hash->{NAME}; my $weather = AttrVal($name,'HomeWeatherDevice',''); my $condition = ReadingsVal($weather,'condition',''); my $conditionart = ReadingsVal($name,'.be',''); my $pressure = ReadingsVal($name,'pressure',''); my $pressuret = ReadingsVal($name,'pressureTrend',''); my $humi = ReadingsVal($name,'humidity',0); my $humitrend = ReadingsVal($name,'humidityTrend',0); my $temp = ReadingsVal($name,'temperature',0); my $tempt = ReadingsVal($name,'temperatureTrend',0); my $windchill = ReadingsNum($weather,'apparentTemperature',0); my $wind = ReadingsVal($name,'wind',0); my $ws = ReadingsVal($name,'weatherTextShort',''); my $wl = ReadingsVal($name,'weatherTextLong',''); $text =~ s/%CONDITION%/$condition/xg; $text =~ s/%HUMIDITY%/$humi/xg; $text =~ s/%HUMIDITYTREND%/$humitrend/xg; $text =~ s/%PRESSURE%/$pressure/xg; $text =~ s/%PRESSURETREND%/$pressuret/xg; $text =~ s/%TEMPERATURE%/$temp/xg; $text =~ s/%TEMPERATURETREND%/$tempt/xg; $text =~ s/%TOBE%/$conditionart/xg; $text =~ s/%WEATHER%/$ws/xg; $text =~ s/%WEATHERLONG%/$wl/xg; $text =~ s/%WINDCHILL%/$windchill/xg; $text =~ s/%WIND%/$wind/xg; return $text; } sub ForecastTXT { my ($hash,$day) = @_; $day = 2 if (!$day); my $name = $hash->{NAME}; my $weather = AttrVal($name,'HomeWeatherDevice',''); my $cond = ReadingsVal($weather,'fc'.$day.'_condition','n.a.'); my $low = ReadingsVal($weather,'fc'.$day.'_low_c','n.a.'); my $high = ReadingsVal($weather,'fc'.$day.'_high_c','n.a.'); my $temp = ReadingsVal($name,'temperature',''); my $hum = ReadingsVal($name,'humidity',''); my $chill = ReadingsNum($weather,'apparentTemperature',0); my $wind = ReadingsVal($name,'wind',''); my $text; if (defined $cond && defined $low && defined $high) { my ($today,$tomorrow,$atomorrow) = split /\|/x,AttrVal($name,'HomeTextTodayTomorrowAfterTomorrow','today|tomorrow|day after tomorrow'); my $d = $today; $d = $tomorrow if ($day == 2); $d = $atomorrow if ($day == 3); $d = $day-1 if ($day > 3); $text = AttrVal($name,'HomeTextWeatherForecastToday',''); $text = AttrVal($name,'HomeTextWeatherForecastTomorrow','') if ($day =~ /^[23]$/x); $text = AttrVal($name,'HomeTextWeatherForecastInSpecDays','') if ($day > 3); $text =~ s/%CONDITION%/$cond/xg; $text =~ s/%DAY%/$d/xg; $text =~ s/%HIGH%/$high/xg; $text =~ s/%LOW%/$low/xg; $text = WeatherTXT($hash,$text); } else { $text = AttrVal($name,'HomeTextWeatherNoForecast','No forecast available'); } return $text; } sub uwzTXT { my ($hash,$count,$sl) = @_; my $name = $hash->{NAME}; $count = defined $count ? $count : ReadingsVal($name,'uwz_warnCount',0); my $text = ''; for (my $i = 0; $i < $count; $i++) { $text .= ' ' if ($i > 0); $text .= $i + 1 . '. ' if ($count > 1); $sl = $sl ? 'LongText':'ShortText'; $text .= ReadingsVal(AttrVal($name,'HomeUWZ',''),'Warn_'.$i.'_'.$sl,''); } return $text; } sub ID { my ($devname,$devtype,$devread,$readval) = @_; return 0 if (!defined($devname) || !defined($defs{$devname})); return 0 if (defined($devtype) && $defs{$devname}{TYPE} !~ /^$devtype$/x); return 0 if (defined($devread) && !defined(ReadingsVal($devname,$devread,undef))); return 0 if (defined($readval) && ReadingsVal($devname,$devread,'') !~ /^$readval$/x); return $devname; } sub CheckIfIsValidDevspec { my ($name,$spec,$read) = @_; my $hash = $defs{$name}; my @names; for (devspec2array($spec)) { next unless (ID($_,undef,$read)); push @names,$_; } return \@names if (@names); return; } sub execUserCMDs { my ($string) = @_; my ($name,$cmds,$resident) = split /\|/x,$string; my $hash = $defs{$name}; $cmds = decode_base64($cmds); execCMDs($hash,$cmds,$resident); return; } sub execCMDs { my ($hash,$cmds,$resident) = @_; my $name = $hash->{NAME}; my $cmd = replacePlaceholders($hash,$cmds,$resident); my $err = AnalyzeCommandChain(undef,$cmd); if ($err && $err !~ /^Deleted.reading|Wrote.configuration|good|Scheduled.for.sending.after.WAKEUP/) { Log3 $name,3,"$name: error: $err"; Log3 $name,3,"$name: error in command: $cmd"; readingsSingleUpdate($hash,'lastCMDerror',"error: >$err< in CMD: $cmd",1); } Log3 $name,4,"$name: executed CMDs: $cmd"; return; } sub AttrCheck { my ($hash,$attribute,$default) = @_; $default = '' if (!defined $default); my $name = $hash->{NAME}; my $value; if ($hash->{helper}{lastChangedAttr} && $hash->{helper}{lastChangedAttr} eq $attribute) { $value = defined $hash->{helper}{lastChangedAttrValue} && $hash->{helper}{lastChangedAttrValue} ne '---' ? $hash->{helper}{lastChangedAttrValue} : $default; } else { $value = AttrVal($name,$attribute,$default); } return $value; } sub DayTime { my ($hash) = @_; my $name = $hash->{NAME}; my $daytimes = AttrCheck($hash,'HomeDaytimes',$HOMEMODE_Daytimes); my ($sec,$min,$hour,$mday,$month,$year,$wday,$yday,$isdst) = localtime; my $loctime = $hour * 60 + $min; my @texts; my @times; for (split ' ',$daytimes) { my ($dt,$text) = split /\|/x; my ($h,$m) = split /:/x,$dt; my $minutes = $h * 60 + $m; push @times,$minutes; push @texts,$text; } my $daytime = $texts[int(@texts) - 1]; for (my $x = 0; $x < int(@times); $x++) { my $y = $x==int(@times)-1?0:$x+1; $daytime = $texts[$x] if ($y > $x && $loctime >= $times[$x] && $loctime < $times[$y]); } return $daytime; } sub SetDaytime { my ($hash) = @_; my $name = $hash->{NAME}; my $dt = DayTime($hash); my $dtr = makeReadingName($dt); if (ReadingsVal($name,'daytime','') ne $dt) { Log3 $name,4,"$name SetDaytime daytime: $dt"; my @commands; push @commands,AttrVal($name,'HomeCMDdaytime','') if (AttrVal($name,'HomeCMDdaytime',undef)); push @commands,AttrVal($name,"HomeCMDdaytime-$dtr",'') if (AttrVal($name,"HomeCMDdaytime-$dtr",undef)); readingsSingleUpdate($hash,'daytime',$dt,1); execCMDs($hash,serializeCMD($hash,@commands)) if (@commands); } return; } sub SetSeason { my ($hash) = @_; my $name = $hash->{NAME}; my $seasons = AttrCheck($hash,'HomeSeasons',$HOMEMODE_Seasons); my ($sec,$min,$hour,$mday,$month,$year,$wday,$yday,$isdst) = localtime; my $locdays = ($month + 1) * 31 + $mday; my @texts; my @dates; for (split ' ',$seasons) { my ($date,$text) = split /\|/x; my ($m,$d) = split /\./x,$date; my $days = $m * 31 + $d; push @dates,$days; push @texts,$text; } my $season = $texts[int(@texts)-1]; for (my $x = 0; $x < int(@dates); $x++) { my $y = $x==int(@dates)-1?0:$x+1; $season = $texts[$x] if ($y > $x && $locdays >= $dates[$x] && $locdays < $dates[$y]); } if (ReadingsVal($name,'season','') ne $season) { my @commands; push @commands,AttrVal($name,'HomeCMDseason','') if (AttrVal($name,'HomeCMDseason',undef)); push @commands,AttrVal($name,'HomeCMDseason-'.makeReadingName($season),'') if (AttrVal($name,'HomeCMDseason-'.makeReadingName($season),undef)); readingsSingleUpdate($hash,'season',$season,1); execCMDs($hash,serializeCMD($hash,@commands)) if (@commands); } return; } sub hourMaker { my ($minutes) = @_; my $text = $langDE? 'keine gültigen Minuten übergeben': 'no valid minutes given'; return $text if ($minutes !~ /^(\d{1,4})(\.\d{0,2})?$/x || $1 >= 6000 || $minutes < 0.01); my $hours = int($minutes / 60); $hours = length $hours > 1 ? $hours : "0$hours"; my $min = $minutes % 60; $min = length $min > 1 ? $min : "0$min"; my $sec = int(($minutes - int($minutes)) * 60); $sec = length $sec > 1 ? $sec : "0$sec"; return "$hours:$min:$sec"; } sub addSensorsUserAttr { my ($hash,$devs,$olddevs) = @_; return if (!$devs || !$init_done); my $name = $hash->{NAME}; my @devspec = devspec2array($devs); my @olddevspec; @olddevspec = devspec2array($olddevs) if ($olddevs); my $migrate = $hash->{helper}{migrate}; $olddevs = $devs if (!$olddevs && $migrate); cleanUserattr($hash,$olddevs,$devs) if ($olddevs); for my $sensor (@devspec) { next if (InternalVal($sensor,'TYPE','') =~ /^global|calendar|holiday|weather|uwz$/xi); my $inolddevspec = @olddevspec && (grep {$_ eq $sensor} @olddevspec) ? 1 : 0; my $alias = AttrVal($sensor,'alias',''); my @list; if ((grep {$_ eq $sensor} split /,/x,InternalVal($name,'SENSORSCONTACT','')) || (grep {$_ eq $sensor} split /,/x,InternalVal($name,'SENSORSMOTION',''))) { push @list,'HomeModeAlarmActive'; push @list,'HomeAlarmDelay'; } if (grep {$_ eq $sensor} split /,/x,InternalVal($name,'SENSORSCONTACT','')) { push @list,'HomeContactType:doorinside,dooroutside,doormain,window'; push @list,'HomeOpenDontTriggerModes'; push @list,'HomeOpenDontTriggerModesResidents'; push @list,'HomeOpenMaxTrigger'; if (AttrVal($name,'HomeSensorsContactOpenWarningUnified',0)) { push @list,'HomeOpenTimes'; push @list,'HomeOpenTimeDividers'; } push @list,'HomeReadingContact'; push @list,'HomeValueContact'; push @list,'HomeReadingTamper' if ($migrate); push @list,'HomeValueTamper' if ($migrate); if (!$inolddevspec) { my $dr = '[Dd]oor|[Tt](ü|ue)r'; my $wr = '[Ww]indow|[Ff]enster'; CommandAttr(undef,"$sensor HomeContactType doorinside") if (($alias =~ /$dr/x || $sensor =~ /$dr/x) && !AttrVal($sensor,'HomeContactType','')); CommandAttr(undef,"$sensor HomeContactType window") if (($alias =~ /$wr/x || $sensor =~ /$wr/x) && !AttrVal($sensor,'HomeContactType','')); CommandAttr(undef,"$sensor HomeModeAlarmActive armaway") if (!AttrVal($sensor,'HomeModeAlarmActive','')); } } if (grep {$_ eq $sensor} split /,/x,InternalVal($name,'SENSORSMOTION','')) { push @list,'HomeSensorLocation:inside,outside'; push @list,'HomeReadingMotion'; push @list,'HomeValueMotion'; if (!$inolddevspec) { my $loc = 'inside'; $loc = 'outside' if ($alias =~ /([Aa]u(ss|ß)en)|([Oo]ut)/x || $sensor =~ /([Aa]u(ss|ß)en)|([Oo]ut)/x); CommandAttr(undef,"$sensor HomeSensorLocation $loc") if (!AttrVal($sensor,'HomeSensorLocation','')); CommandAttr(undef,"$sensor HomeModeAlarmActive armaway") if (!AttrVal($sensor,'HomeModeAlarmActive','') && $loc eq 'inside'); } } if (grep {$_ eq $sensor} split /,/x,InternalVal($name,'SENSORSBATTERY','')) { push @list,'HomeReadingBattery'; push @list,'HomeBatteryLowPercentage'; } if (grep {$_ eq $sensor} split /,/x,InternalVal($name,'SENSORSSMOKE','')) { push @list,'HomeReadingSmoke'; push @list,'HomeValueSmoke'; } if (grep {$_ eq $sensor} split /,/x,InternalVal($name,'SENSORSTAMPER','')) { push @list,'HomeReadingTamper'; push @list,'HomeValueTamper'; } if (grep {$_ eq $sensor} split /,/x,InternalVal($name,'SENSORSWATER','')) { push @list,'HomeReadingWater'; push @list,'HomeValueWater'; } if (grep {$_ eq $sensor} split /,/x,InternalVal($name,'SENSORSENERGY','')) { push @list,'HomeReadingEnergy'; push @list,'HomeDividerEnergy'; push @list,'HomeAllowNegativeEnergy:0,1'; } if (grep {$_ eq $sensor} split /,/x,InternalVal($name,'SENSORSPOWER','')) { push @list,'HomeReadingPower'; push @list,'HomeDividerPower'; push @list,'HomeAllowNegativePower:0,1'; } if (grep {$_ eq $sensor} split /,/x,InternalVal($name,'SENSORSLIGHT','')) { push @list,'HomeReadingLuminance'; push @list,'HomeDividerLuminance'; } @list = uniq sort @list; set_userattr($sensor,\@list); } return; } sub set_userattr { my ($name,$list) = @_; my $hash = $defs{$name}; Log3 $name,4,"$name set_userattr"; my $val = AttrVal($name,'userattr',''); my $l = join ' ',@{$list}; $l .= $val?" $val":''; CommandAttr(undef,"$name userattr $l") if ($l && $l ne $val); return; } sub Luminance { my ($hash) = @_; my $name = $hash->{NAME}; my @sensorsa; my $lum = 0; for (split /,/x,$hash->{SENSORSLIGHT}) { next if (IsDis($name,$_)); push @sensorsa,$_; my $read = AttrVal($_,'HomeReadingLuminance','luminance'); my $val = ReadingsNum($_,$read,0); next unless ($val > 0); my $div = AttrVal($_,'HomeDividerLuminance',1); $lum += $val/$div; } return if (!int(@sensorsa)); my $lumval = int($lum/int(@sensorsa)); readingsSingleUpdate($hash,'luminance',$lumval,1); ReadingTrend($hash,'luminance',$lumval); return; } sub TriggerState { my ($name,$getter,$type,$trigger,$retrigger) = @_; my $exit = (!$getter && !$type && $trigger)?1:undef; $getter = $getter?$getter:'contactsOpen'; $type = $type?$type:'all'; my $hash = $defs{$name}; my $events = $trigger?deviceEvents($defs{$trigger},1):undef; my $contacts = $hash->{SENSORSCONTACT}; my $motions = $hash->{SENSORSMOTION}; my $alarm = ReadingsVal($name,'alarmTriggered',''); my @contactsOpen; my @doorsOOpen; my @doorsMOpen; my @insideOpen; my @outsideOpen; my @windowsOpen; my @motionsOpen; my @motionsInsideOpen; my @motionsOutsideOpen; my @alarmSensors; my @lightSensors; my $amode = ReadingsVal($name,'modeAlarm','disarm'); my $marker = '.HOMEMODE_'.$name.'_alarmDelayed'; if ($contacts) { for my $sensor (split /,/,$contacts) { next if (IsDis($name,$sensor)); my $read = AttrVal($sensor,'HomeReadingContact','state'); my $val = AttrVal($sensor,'HomeValueContact','open|tilted|on|1|true'); my $state = ReadingsVal($sensor,$read,''); next if (!$state); my $amodea = AttrVal($sensor,'HomeModeAlarmActive','-'); my $kind = AttrVal($sensor,'HomeContactType','window'); my @delays = split ' ',AttrVal($sensor,'HomeAlarmDelay',AttrVal($name,'HomeSensorsAlarmDelay','')); my $delay; if ($amode ne 'disarmed' && int(@delays)) { $delay = returnDelay($amode,@delays); } if ($state =~ /^($val)$/) { push @contactsOpen,$sensor; push @insideOpen,$sensor if ($kind eq 'doorinside'); push @doorsOOpen,$sensor if ($kind && $kind eq 'dooroutside'); push @doorsMOpen,$sensor if ($kind && $kind eq 'doormain'); push @outsideOpen,$sensor if ($kind =~ /^dooroutside|doormain|window$/x); push @windowsOpen,$sensor if ($kind eq 'window'); my $timer = 'atTmp_alarmDelayedTimer_'.$sensor.'_'.$name; if ($amode =~ /^($amodea)$/) { if ($delay && !$retrigger && !ReadingsVal($sensor,$marker,0)) { readingsSingleUpdate($defs{$sensor},$marker,1,0); CommandDefine(undef,$timer.' at +'.hourMaker($delay/60)." {fhem \"sleep 0.1 quiet;{FHEM::HOMEMODE::TriggerState('$name',undef,undef,'$sensor',1)}\"}"); } else { push @alarmSensors,$sensor if (!ID($timer,'at')); } } if (defined $exit && $trigger eq $sensor && grep {/^$read:/x} @{$events}) { readingsBeginUpdate($hash); readingsBulkUpdate($hash,'prevContact',ReadingsVal($name,'lastContact','')); readingsBulkUpdate($hash,'lastContact',$sensor); readingsEndUpdate($hash,1); ContactCommands($hash,$sensor,'open',$kind); ContactOpenWarning($name,$sensor); } } else { if (defined $exit && $trigger eq $sensor && grep {/^$read:/x} @{$events}) { readingsBeginUpdate($hash); readingsBulkUpdate($hash,'prevContactClosed',ReadingsVal($name,'lastContactClosed','')); readingsBulkUpdate($hash,'lastContactClosed',$sensor); readingsEndUpdate($hash,1); ContactCommands($hash,$sensor,'closed',$kind); my $timer = 'atTmp_HomeOpenTimer_'.$sensor.'_'.$name; CommandDelete(undef,$timer) if (ID($timer,'at')); CommandDeleteReading(undef,"$trigger $marker"); CommandDeleteReading(undef,"$trigger .HOMEMODE_".$name.'_HomeOpenTrigger'); ContactOpenWarning($name,$sensor); } } } } if ($motions) { for my $sensor (split /,/,$motions) { next if (IsDis($name,$sensor)); my $read = AttrVal($sensor,'HomeReadingMotion','state'); my $val = AttrVal($sensor,'HomeValueMotion','open|on|motion|1|true'); my $state = ReadingsVal($sensor,$read,''); next if (!$state); my $amodea = AttrVal($sensor,'HomeModeAlarmActive','-'); my $kind = AttrVal($sensor,'HomeSensorLocation','inside'); my @delays = split ' ',AttrVal($sensor,'HomeAlarmDelay',AttrVal($name,'HomeSensorsAlarmDelay','')); my $delay; if ($amode ne 'disarmed' && int(@delays)) { $delay = returnDelay($amode,@delays); } if ($state =~ /^($val)$/x) { push @motionsOpen,$sensor; push @motionsInsideOpen,$sensor if ($kind eq 'inside'); push @motionsOutsideOpen,$sensor if ($kind eq 'outside'); my $timer = 'atTmp_alarmDelayedTimer_'.$sensor.'_'.$name; if ($amode =~ /^($amodea)$/x) { if ($delay && !$retrigger && !ReadingsVal($sensor,$marker,0)) { readingsSingleUpdate($defs{$sensor},$marker,1,0); CommandDefine(undef,"$timer at +".hourMaker($delay/60)." {fhem \"sleep 0.1 quiet;{FHEM::HOMEMODE::TriggerState('$name',undef,undef,'$sensor',1)}\"}"); } else { push @alarmSensors,$sensor if (!ID($timer,'at')); } } if (defined $exit && $trigger eq $sensor && grep {/^$read:/x} @{$events}) { readingsBeginUpdate($hash); readingsBulkUpdate($hash,'prevMotion',ReadingsVal($name,'lastMotion','')); readingsBulkUpdate($hash,'lastMotion',$sensor); readingsEndUpdate($hash,1); MotionCommands($hash,$sensor,'open'); } } else { if (defined $exit && $trigger eq $sensor && grep {/^$read:/x} @{$events}) { readingsBeginUpdate($hash); readingsBulkUpdate($hash,'prevMotionClosed',ReadingsVal($name,'lastMotionClosed','')); readingsBulkUpdate($hash,'lastMotionClosed',$sensor); readingsEndUpdate($hash,1); MotionCommands($hash,$sensor,'closed'); } } } } alarmTriggered($hash,@alarmSensors) if (join(',',@alarmSensors) ne $alarm); my $open = @contactsOpen ? join(',',@contactsOpen) : ''; my $opendo = @doorsOOpen ? join(',',@doorsOOpen) : ''; my $opendm = @doorsMOpen ? join(',',@doorsMOpen) : ''; my $openi = @insideOpen ? join(',',@insideOpen) : ''; my $openm = @motionsOpen ? join(',',@motionsOpen) : ''; my $openmi = @motionsInsideOpen ? join(',',@motionsInsideOpen) : ''; my $openmo = @motionsOutsideOpen ? join(',',@motionsOutsideOpen) : ''; my $openo = @outsideOpen ? join(',',@outsideOpen) : ''; my $openw = @windowsOpen ? join(',',@windowsOpen) : ''; readingsBeginUpdate($hash); if ($contacts) { readingsBulkUpdateIfChanged($hash,'contactsDoorsInsideOpen',$openi); readingsBulkUpdateIfChanged($hash,'contactsDoorsInsideOpen_ct',@insideOpen); readingsBulkUpdateIfChanged($hash,'contactsDoorsInsideOpen_hr',makeHR($hash,0,@insideOpen)); readingsBulkUpdateIfChanged($hash,'contactsDoorsOutsideOpen',$opendo); readingsBulkUpdateIfChanged($hash,'contactsDoorsOutsideOpen_ct',@doorsOOpen); readingsBulkUpdateIfChanged($hash,'contactsDoorsOutsideOpen_hr',makeHR($hash,0,@doorsOOpen)); readingsBulkUpdateIfChanged($hash,'contactsDoorsMainOpen',$opendm); readingsBulkUpdateIfChanged($hash,'contactsDoorsMainOpen_ct',@doorsMOpen); readingsBulkUpdateIfChanged($hash,'contactsDoorsMainOpen_hr',makeHR($hash,0,@doorsMOpen)); readingsBulkUpdateIfChanged($hash,'contactsOpen',$open); readingsBulkUpdateIfChanged($hash,'contactsOpen_ct',@contactsOpen); readingsBulkUpdateIfChanged($hash,'contactsOpen_hr',makeHR($hash,0,@contactsOpen)); readingsBulkUpdateIfChanged($hash,'contactsOutsideOpen',$openo); readingsBulkUpdateIfChanged($hash,'contactsOutsideOpen_ct',@outsideOpen); readingsBulkUpdateIfChanged($hash,'contactsOutsideOpen_hr',makeHR($hash,0,@outsideOpen)); readingsBulkUpdateIfChanged($hash,'contactsWindowsOpen',$openw); readingsBulkUpdateIfChanged($hash,'contactsWindowsOpen_ct',@windowsOpen); readingsBulkUpdateIfChanged($hash,'contactsWindowsOpen_hr',makeHR($hash,0,@windowsOpen)); } if ($motions) { readingsBulkUpdateIfChanged($hash,'motionsSensors',$openm); readingsBulkUpdateIfChanged($hash,'motionsSensors_ct',@motionsOpen); readingsBulkUpdateIfChanged($hash,'motionsSensors_hr',makeHR($hash,0,@motionsOpen)); readingsBulkUpdateIfChanged($hash,'motionsInside',$openmi); readingsBulkUpdateIfChanged($hash,'motionsInside_ct',@motionsInsideOpen); readingsBulkUpdateIfChanged($hash,'motionsInside_hr',makeHR($hash,0,@motionsInsideOpen)); readingsBulkUpdateIfChanged($hash,'motionsOutside',$openmo); readingsBulkUpdateIfChanged($hash,'motionsOutside_ct',@motionsOutsideOpen); readingsBulkUpdateIfChanged($hash,'motionsOutside_hr',makeHR($hash,0,@motionsOutsideOpen)); } readingsEndUpdate($hash,1); return if ($retrigger); if ($getter eq 'contactsOpen') { return "open contacts: $open" if ($open && $type eq 'all'); return 'no open contacts' if (!$open && $type eq 'all'); return "open doorsinside: $openi" if ($openi && $type eq 'doorsinside'); return 'no open doorsinside' if (!$openi && $type eq 'doorsinside'); return "open doorsoutside: $opendo" if ($opendo && $type eq 'doorsoutside'); return 'no open doorsoutside' if (!$opendo && $type eq 'doorsoutside'); return "open doorsmain: $opendm" if ($opendm && $type eq 'doorsmain'); return 'no open doorsmain' if (!$opendm && $type eq 'doorsmain'); return "open outside: $openo" if ($openo && $type eq 'outside'); return 'no open outside' if (!$openo && $type eq 'outside'); return "open windows: $openw" if ($openw && $type eq 'windows'); return 'no open windows' if (!$openw && $type eq 'windows'); } return; } sub returnDelay { my ($amode,@delays) = @_; my $delay; if (defined $delays[1]) { $delay = $delays[0] if ($amode eq 'armaway'); $delay = $delays[1] if ($amode eq 'armnight'); $delay = $delays[2] if ($amode eq 'armhome'); } else { $delay = $delays[0]; } return $delay; } sub name2alias { my ($name,$witharticle) = @_; my $alias = AttrVal($name,'alias',$name); my $art; $art = 'die' if ($alias =~ /t(ü|ue)r/xi); $art = 'das' if ($alias =~ /fenster/xi); $art = 'der' if ($alias =~ /(sensor|dete[ck]tor|melder|kontakt)$/xi); $art = 'der' if ($alias =~ /^(sensor|dete[ck]tor|melder|kontakt)\s.+/i); my $ret = $witharticle && $art ? "$art $alias" : $alias; return $ret; } sub ContactOpenWarning { my ($name,$contact,$retrigger) = @_; my $maxtrigger = AttrNum($contact,'HomeOpenMaxTrigger',0); return if (!$maxtrigger); my $hash = $defs{$name}; $retrigger = defined $retrigger?$retrigger:0; my $unified = AttrNum($name,'HomeSensorsContactOpenWarningUnified',0); my $timer = 'atTmp_HomeOpenTimer_'.($unified?'unified':$contact).'_'.$name; return if ($unified && ID($timer,'at')); my $mode = ReadingsVal($name,'mode',''); my @warn; my $donttrigger; my (@warn1,@warnexp); my ($mt,$rt); for my $sen (devspec2array($hash->{SENSORSCONTACT})) { my $omt = AttrNum($sen,'HomeOpenMaxTrigger',0); my $otr = ReadingsNum($sen,'.HOMEMODE_'.$name.'_HomeOpenTrigger',0); my $dif = $omt-$otr; next if ($dif > $omt+1); my $sta = ReadingsVal($sen,AttrVal($sen,'HomeReadingContact','state'),''); my $reg = AttrVal($sen,'HomeValueContact','open|tilted|on|1|true'); next if ($sta !~ /^$reg$/); my $dtmode = AttrVal($sen,'HomeOpenDontTriggerModes',''); my $dtres = AttrVal($sen,'HomeOpenDontTriggerModesResidents',''); if ($dtres && $dtmode) { for (devspec2array($dtres)) { $donttrigger = 1 if (ReadingsVal($_,'state','') =~ /^$dtmode$/x); } } elsif ($dtmode) { $donttrigger = 1 if ($mode =~ /^$dtmode$/) } next if ($donttrigger); if ($dif>-1) { $mt += $omt; $rt += $otr; push @warn,$sen if (!$retrigger || ($retrigger && $dif<$omt)); } push @warn1,$sen if ($dif>0 && $dif<$omt); push @warnexp,$sen if ($dif<1); $otr++; readingsSingleUpdate($defs{$sen},'.HOMEMODE_'.$name.'_HomeOpenTrigger',$otr,0) if ($otr<=$omt+1); } if (!$unified) { @warn = (grep {$_ eq $contact} @warn)?($contact):(); } else { $maxtrigger = $mt; $retrigger = $rt; } my $openwarn = join ',',sort @warn1; my $openwarnexp = join ',',sort @warnexp; readingsBeginUpdate($hash); readingsBulkUpdateIfChanged($hash,'contactWarning',$openwarn); readingsBulkUpdateIfChanged($hash,'contactWarning_ct',int(@warn1)); readingsBulkUpdateIfChanged($hash,'contactWarning_hr',makeHR($hash,0,@warn1)); readingsBulkUpdateIfChanged($hash,'contactWarningExpired',$openwarnexp); readingsBulkUpdateIfChanged($hash,'contactWarningExpired_ct',int(@warnexp)); readingsBulkUpdateIfChanged($hash,'contactWarningExpired_hr',makeHR($hash,0,@warnexp)); readingsEndUpdate($hash,1); CommandDelete(undef,$timer) if (ID($timer,'at') && ($retrigger || $donttrigger)); Debug Dumper @warn; return if ($donttrigger || !int(@warn)); my $season = ReadingsVal($name,'season',''); my $seasons = AttrVal($name,'HomeSeasons',$HOMEMODE_Seasons); my $dividers = $unified?AttrVal($name,'HomeSensorsContactOpenTimeDividers',''):AttrVal($contact,'HomeOpenTimeDividers',AttrVal($name,'HomeSensorsContactOpenTimeDividers','')); my $mintime = AttrNum($name,'HomeSensorsContactOpenTimeMin',0); my $otimes = $unified?AttrVal($name,'HomeSensorsContactOpenTimes',10):AttrVal($contact,'HomeOpenTimes',AttrVal($name,'HomeSensorsContactOpenTimes',10)); my @wt = split ' ',$otimes; my $waittime; Log3 $name,5,"$name: retrigger: $retrigger"; $waittime = $wt[$retrigger] if ($wt[$retrigger]); $waittime = $wt[int(@wt) - 1] if (!defined $waittime); Log3 $name,5,"$name: waittime real: $waittime"; if ($dividers && AttrVal($contact,'HomeContactType','window') !~ /^door(inside|main)$/x) { my @divs = split ' ',$dividers; my $divider = 1; my $count = 0; for (split ' ',$seasons) { my (undef,$text) = split /\|/x; if ($season eq $text) { my $div = $divs[$count]; $divider = $div if ($div && $div =~ /^\d{1,2}(\.\d{1,3})?$/x); last; } $count++; } return if (!$divider); $waittime = $waittime / $divider; $waittime = sprintf('%.2f',$waittime) * 1; } $waittime = $mintime if ($waittime < $mintime); $retrigger++; Log3 $name,5,"$name: waittime divided: $waittime"; $waittime = hourMaker($waittime); Debug "maxtrigger $maxtrigger"; Debug "retrigger $retrigger"; if ($retrigger > 1) { my @commands; Log3 $name,5,"$name: maxtrigger: $maxtrigger"; my $cmd = AttrVal($name,'HomeCMDcontactOpenWarning1',''); $cmd = AttrVal($name,'HomeCMDcontactOpenWarning2','') if (AttrVal($name,'HomeCMDcontactOpenWarning2',undef) && $retrigger > 2); $cmd = AttrVal($name,'HomeCMDcontactOpenWarningLast','') if (AttrVal($name,'HomeCMDcontactOpenWarningLast',undef) && ($retrigger == int($maxtrigger+1) || ($maxtrigger == 0 && $retrigger == 1))); if ($cmd) { my $alias = name2alias($contact,1); my $openwarnct = int(@warn); my $openwarnhr = makeHR($hash,0,@warn); $cmd =~ s/%ALIAS%/$alias/xgm; $cmd =~ s/%SENSOR%/$contact/xgm; $cmd =~ s/%OPENWARN%/$openwarn/xg; $cmd =~ s/%OPENWARNCT%/$openwarnct/xg; $cmd =~ s/%OPENWARNHR%/$openwarnhr/xg; $cmd =~ s/%OPENWARNHR%/$openwarnhr/xg; $cmd =~ s/%UNIFIED%/$unified/xg; push @commands,$cmd; } execCMDs($hash,serializeCMD($hash,@commands)) if (@commands); } CommandDefine(undef,"$timer at +$waittime {fhem \"sleep 0.1 quiet;{FHEM::HOMEMODE::ContactOpenWarning('$name','$warn[0]',$retrigger)}\"}") if (!ID($timer) && $retrigger<=$maxtrigger); return; } sub ContactOpenWarningAfterModeChange { my ($hash,$mode,$pmode,$resident) = @_; my $name = $hash->{NAME}; my $contacts = ReadingsVal($name,'contactsOpen',''); $mode = ReadingsVal($name,'mode','') if (!$mode); $pmode = ReadingsVal($name,'prevMode','') if (!$pmode); my $state = $resident?ReadingsVal($resident,'state',''):undef; my $pstate = $resident?ReadingsVal($resident,'lastState',''):undef; if ($contacts) { for (split /,/x,$contacts) { my $m = AttrVal($_,'HomeOpenDontTriggerModes',''); my $r = AttrVal($_,'HomeOpenDontTriggerModesResidents',''); $r = s/,/\|/xg; if ($resident && $m && $r && $resident =~ /^$r$/x && $state =~ /^$m$/x && $pstate !~ /^$m$/x) { ContactOpenWarning($name,$_); } elsif ($m && !$r && $pmode =~ /^$m$/x && $mode !~ /^$m$/x) { ContactOpenWarning($name,$_); } } } return; } sub ContactCommands { my ($hash,$contact,$state,$kind) = @_; my $name = $hash->{NAME}; my $alias = name2alias($contact,1); my @cmds; push @cmds,AttrVal($name,'HomeCMDcontact','') if (AttrVal($name,'HomeCMDcontact',undef)); push @cmds,AttrVal($name,'HomeCMDcontactOpen','') if (AttrVal($name,'HomeCMDcontactOpen',undef) && $state eq 'open'); push @cmds,AttrVal($name,'HomeCMDcontactClosed','') if (AttrVal($name,'HomeCMDcontactClosed',undef) && $state eq 'closed'); push @cmds,AttrVal($name,'HomeCMDcontactDoormain','') if (AttrVal($name,'HomeCMDcontactDoormain',undef) && $kind eq 'doormain'); push @cmds,AttrVal($name,'HomeCMDcontactDoormainOpen','') if (AttrVal($name,'HomeCMDcontactDoormainOpen',undef) && $kind eq 'doormain' && $state eq 'open'); push @cmds,AttrVal($name,'HomeCMDcontactDoormainClosed','') if (AttrVal($name,'HomeCMDcontactDoormainClosed',undef) && $kind eq 'doormain' && $state eq 'closed'); if (@cmds) { for (@cmds) { my ($c,$o) = split /\|/x,AttrVal($name,'HomeTextClosedOpen','closed|open'); my $sta = $state eq 'open' ? $o : $c; $_ =~ s/%ALIAS%/$alias/xgm; $_ =~ s/%SENSOR%/$contact/xgm; $_ =~ s/%STATE%/$sta/xgm; } execCMDs($hash,serializeCMD($hash,@cmds)); } return; } sub MotionCommands { my ($hash,$sensor,$state) = @_; my $name = $hash->{NAME}; my $alias = name2alias($sensor,1); my @cmds; push @cmds,AttrVal($name,'HomeCMDmotion','') if (AttrVal($name,'HomeCMDmotion',undef)); push @cmds,AttrVal($name,'HomeCMDmotion-on','') if (AttrVal($name,'HomeCMDmotion-on',undef) && $state eq 'open'); push @cmds,AttrVal($name,'HomeCMDmotion-off','') if (AttrVal($name,'HomeCMDmotion-off',undef) && $state eq 'closed'); if (@cmds) { for (@cmds) { my ($c,$o) = split /\|/x,AttrVal($name,'HomeTextClosedOpen','closed|open'); $state = $state eq 'open' ? $o : $c; $_ =~ s/%ALIAS%/$alias/xgm; $_ =~ s/%SENSOR%/$sensor/xgm; $_ =~ s/%STATE%/$state/xgm; } execCMDs($hash,serializeCMD($hash,@cmds)); } return; } sub EventCommands { my ($hash,$cal,$read,$event) = @_; my $name = $hash->{NAME}; my $prevevent = ReadingsVal($name,"event-$cal",''); my @cmds; if ($read ne 'modeStarted') { push @cmds,AttrVal($name,'HomeCMDevent','') if (AttrVal($name,'HomeCMDevent',undef)); push @cmds,AttrVal($name,"HomeCMDevent-$cal-each",'') if (AttrVal($name,"HomeCMDevent-$cal-each",undef)); } if (ID($cal,'holiday')) { if ($event ne $prevevent) { $event =~ s/[,;:\?\!\|\\\/\^\$]/-/xg; my $evt = $event; $evt =~ s/[\s ]+/-/g; my $pevt = $prevevent; $pevt =~ s/[\s ]+/-/g; push @cmds,AttrVal($name,"HomeCMDevent-$cal-".makeReadingName($pevt).'-end','') if (AttrVal($name,"HomeCMDevent-$cal-".makeReadingName($pevt).'-end',undef)); push @cmds,AttrVal($name,"HomeCMDevent-$cal-".makeReadingName($evt).'-begin','') if (AttrVal($name,"HomeCMDevent-$cal-".makeReadingName($evt).'-begin',undef)); readingsSingleUpdate($hash,"event-$cal",$event,1); for (@cmds) { $_ =~ s/%EVENT%/$event/xgm; $_ =~ s/%PREVEVENT%/$prevevent/xgm; } } } else { my @prevevents; @prevevents = split /,/x,$prevevent if ($prevevent ne 'none'); for (split /;/x,$event) { $event =~ s/[\s ]//g; my $summary; my $description = ''; my $t = time(); my @filters = ( { ref => \&filter_true, param => undef } ); for (Calendar_GetEvents($defs{$cal},$t,@filters)) { next unless ($_->{uid} eq $event); $summary = $_->{summary}; $description = $_->{description}; last; } next unless $summary; $summary =~ s/[,;:\?\!\|\\\/\^\$]/-/xg; Log3 $name,5,"Calendar_GetEvents event: $summary"; my $sum = $summary; $sum =~ s/[\s ]+/-/g; if ($read eq 'start') { push @prevevents,$summary; push @cmds,AttrVal($name,"HomeCMDevent-$cal-".makeReadingName($sum).'-begin','') if (AttrVal($name,"HomeCMDevent-$cal-".makeReadingName($sum).'-begin',undef)); } elsif ($read eq 'end') { push @cmds,AttrVal($name,"HomeCMDevent-$cal-".makeReadingName($sum).'-end','') if (AttrVal($name,"HomeCMDevent-$cal-".makeReadingName($sum).'-end',undef)); if (grep {$_ eq $summary} @prevevents) { my @sevents; for (@prevevents) { push @sevents,$_ if ($_ ne $summary); } @prevevents = @sevents; } } elsif ($read eq 'modeStarted') { push @prevevents,$summary; } for (@cmds) { if ($read eq 'start') { $_ =~ s/%EVENT%/$summary/xgm; $_ =~ s/%PREVEVENT%/none/xgm; $_ =~ s/%DESCRIPTION%/$description/xgm; } elsif ($read eq 'end') { $_ =~ s/%EVENT%/none/xgm; $_ =~ s/%PREVEVENT%/$summary/xgm; $_ =~ s/%DESCRIPTION%/$description/xgm; } } } my $update = 'none'; if (@prevevents) { @prevevents = uniq sort @prevevents; $update = join ',',@prevevents; } readingsSingleUpdate($hash,"event-$cal",$update,1); } for (@cmds) { $_ =~ s/%CALENDAR%/$cal/xgm; } execCMDs($hash,serializeCMD($hash,@cmds)) if (@cmds); return; } sub UWZCommands { my ($hash,$events) = @_; my $name = $hash->{NAME}; my $prev = ReadingsNum($name,'uwz_warnCount',-1); my $uwz = AttrVal($name,'HomeUWZ',''); my $count; for my $evt (@{$events}) { next unless (grep {/^WarnCount:\s[0-9]$/} $evt); $count = $evt; $count =~ s/^WarnCount:\s//; last; } if (defined $count) { readingsSingleUpdate($hash,'uwz_warnCount',$count,1); if ($count != $prev) { my $se = $count > 0 ? 'begin':'end'; my @cmds; push @cmds,AttrVal($name,'HomeCMDuwz-warn','') if (AttrVal($name,'HomeCMDuwz-warn',undef)); push @cmds,AttrVal($name,"HomeCMDuwz-warn-$se",'') if (AttrVal($name,"HomeCMDuwz-warn-$se",undef)); execCMDs($hash,serializeCMD($hash,@cmds)) if (@cmds); } } return; } sub HomebridgeMapping { my ($hash) = @_; my $name = $hash->{NAME}; my $mapping = 'SecuritySystemCurrentState=alarmState,values=armhome:0;armaway:1;armnight:2;disarm:3;alarm:4'; $mapping .= "\nSecuritySystemTargetState=modeAlarm,values=armhome:0;armaway:1;armnight:2;disarm:3,cmds=0:modeAlarm+armhome;1:modeAlarm+armaway;2:modeAlarm+armnight;3:modeAlarm+disarm,delay=1"; $mapping .= "\nSecuritySystemAlarmType=alarmTriggered_ct,values=0:0;/.*/:1"; $mapping .= "\nOccupancyDetected=presence,values=present:1;absent:0"; $mapping .= "\nMute=dnd,valueOn=on,cmdOn=dnd+on,cmdOff=dnd+off"; $mapping .= "\nOn=anyoneElseAtHome,valueOn=on,cmdOn=anyoneElseAtHome+on,cmdOff=anyoneElseAtHome+off"; $mapping .= "\nContactSensorState=contactsOutsideOpen_ct,values=0:0;/.*/:1" if (ID($name,undef,'contactsOutsideOpen_ct')); $mapping .= "\nStatusTampered=alarmTampered_ct,values=0:0;/.*/:1" if (ID($name,undef,'alarmTampered_ct')); $mapping .= "\nMotionDetected=motionsInside_ct,values=0:0;/.*/:1" if (ID($name,undef,'motionsInside_ct')); $mapping .= "\nStatusLowBattery=batteryLow_ct,values=0:0;/.*/:1" if (ID($name,undef,'batteryLow_ct')); $mapping .= "\nSmokeDetected=alarmSmoke_ct,values=0:0;/.*/:1" if (ID($name,undef,'alarmSmoke_ct')); $mapping .= "\nLeakDetected=alarmWater_ct,values=0:0;/.*/:1" if (ID($name,undef,'alarmWater_ct')); $mapping .= "\nAirPressure=pressure" if (ID($name,undef,'pressure')); $mapping .= "\nAirQuality=voc" if (ID($name,undef,'voc')); $mapping .= "\nCurrentAmbientLightLevel=luminance" if (ID($name,undef,'luminance')); $mapping .= "\nC53F35CE-C615-4AA4-9112-EBF679C5EB14=rainLevel,name=Regenmenge,minValue=0,maxValue=1000,minStep=0.001,format=FLOAT,unit=mm" if (ID($name,undef,'rainLevel')); $mapping .= "\n49C8AE5A-A3A5-41AB-BF1F-12D5654F9F41=wind,name=Windgeschwindigkeit,minValue=0,maxValue=200,minStep=0.1,format=FLOAT,unit=km/h" if (ID($name,undef,'wind')); $mapping .= "\n6B8861E5-D6F3-425C-83B6-069945FFD1F1=windGust,name=Böengeschwindigkeit,minValue=0,maxValue=200,minStep=0.1,format=FLOAT,unit=km/h" if (ID($name,undef,'wind')); $mapping .= "\n46F1284C-1912-421B-82F5-EB75008B167E=windDirection,name=Windrichtung,format=STRING" if (ID($name,undef,'wind')); addToDevAttrList($name,'genericDeviceType') if (!grep {/^genericDeviceType/x} split ' ',AttrVal('global','userattr','')); addToDevAttrList($name,'homebridgeMapping:textField-long') if (!grep {/^homebridgeMapping/x} split ' ',AttrVal('global','userattr','')); CommandAttr(undef,"$name genericDeviceType security"); CommandAttr(undef,"$name homebridgeMapping $mapping"); return; } sub BatteryCheck { my ($hash,$devname) = @_; my $name = $hash->{NAME}; my @lowOld = split /,/x,ReadingsVal($name,'batteryLow',''); my @low; my @cmds; for my $s (split ',',$hash->{SENSORSBATTERY}) { my $r = AttrVal($s,'HomeReadingBattery','battery'); my $v = ReadingsVal($s,$r,''); next unless ($v); if (($v =~ /^(\d{1,3})(%|\s%)?$/ && $1 <= AttrNum($s,'HomeBatteryLowPercentage',AttrNum($name,'HomeSensorsBatteryLowPercentage',30))) || $v =~ /^nok|low$/x) { push @low,$s; } } readingsBeginUpdate($hash); my $rlow = @low?join(',',@low):''; my $lowct = int(@low); my $lowhr = @low?makeHR($hash,1,@low):''; if (@low) { readingsBulkUpdateIfChanged($hash,'batteryLow',$rlow); readingsBulkUpdateIfChanged($hash,'batteryLow_ct',$lowct); readingsBulkUpdateIfChanged($hash,'batteryLow_hr',$lowhr); readingsBulkUpdateIfChanged($hash,'lastBatteryLow',$devname) if ($devname && (grep {$_ eq $devname} @low) && !grep {$_ eq $devname} @lowOld); } else { readingsBulkUpdateIfChanged($hash,'batteryLow',''); readingsBulkUpdateIfChanged($hash,'batteryLow_ct',0); readingsBulkUpdateIfChanged($hash,'batteryLow_hr',''); } readingsBulkUpdateIfChanged($hash,'lastBatteryNormal',$devname) if ($devname && (!grep {$_ eq $devname} @low) && grep {$_ eq $devname} @lowOld); readingsEndUpdate($hash,1); if ($devname && (@low || @lowOld)) { push @cmds,AttrVal($name,'HomeCMDbattery','') if (AttrVal($name,'HomeCMDbattery','') && ((grep {$_ eq $devname} @low) || (grep {$_ eq $devname} @lowOld))); push @cmds,AttrVal($name,'HomeCMDbatteryLow','') if (AttrVal($name,'HomeCMDbatteryLow','') && (grep {$_ eq $devname} @low) && (!grep {$_ eq $devname} @lowOld)); push @cmds,AttrVal($name,'HomeCMDbatteryNormal','') if (AttrVal($name,'HomeCMDbatteryNormal','') && (!grep {$_ eq $devname} @low) && (grep {$_ eq $devname} @lowOld)); execCMDs($hash,serializeCMD($hash,@cmds)) if (@cmds); } return; } sub EnergyPower { my ($hash,$ep) = @_; my $name = $hash->{NAME}; my $val = 0; my @sensors = split /,/x,InternalVal($name,'SENSORS'.uc($ep),''); for (@sensors) { my $r = AttrVal($_,'HomeReading'.$ep,AttrVal($name,'HomeSensors'.$ep.'Reading',lc($ep))); my $d = AttrVal($_,'HomeDivider'.$ep,AttrVal($name,'HomeSensors'.$ep.'Divider',1)); $d = $d && $d =~ /^(?!0)\d+(\.\d+)?$/?$d:1; my $e = ReadingsNum($_,$r,0); if ($e) { if (AttrVal($_,'HomeAllowNegative'.$ep,0)) { $val += $e/$d; } else { $val += $e if (sprintf('%.2f',$e/$d)>0); } } } $val = sprintf('%.2f',$val); readingsSingleUpdate($hash,lc($ep),$val,1); readingsSingleUpdate($hash,lc($ep).'_avg',sprintf('%.2f',($val/int(@sensors))),1); return; } sub twoStateSensor { my ($hash,$type,$trigger,$state) = @_; my $name = $hash->{NAME}; my @sensors; my $internal = 'SENSORS'.uc $type; my $v; for (split /,/x,InternalVal($name,$internal,'')) { my $s = $type eq 'Tamper'?'sabotageError':'state'; my $r = AttrVal($_,"HomeReading$type",AttrVal($name,'HomeSensors'.$type.'Reading',$s)); $v = AttrVal($_,"HomeValue$type",AttrVal($name,'HomeSensors'.$type.'Value',lc $type.'|open|on|yes|1|true')); push @sensors,$_ if (ReadingsVal($_,$r,'') =~ /^$v$/x); } readingsBeginUpdate($hash); readingsBulkUpdate($hash,"alarm$type",join(',',@sensors)); readingsBulkUpdate($hash,'alarm'.$type.'_ct',int(@sensors)); readingsBulkUpdate($hash,'alarm'.$type.'_hr',makeHR($hash,0,@sensors)); readingsEndUpdate($hash,1); if ($trigger && $state) { my $t = "$type$type"; my $no = 'no'; my $ty = $type; if ($type eq 'Tamper') { $ty .= 'ed'; $no .= 't'; } my @cmds; push @cmds,AttrVal($name,'HomeCMDalarm'.$ty,'') if (AttrVal($name,'HomeCMDalarm'.$ty,'')); if (@sensors) { push @cmds,AttrVal($name,'HomeCMDalarm'.$ty.'-on','') if (AttrVal($name,'HomeCMDalarm'.$ty.'-on','')); } else { push @cmds,AttrVal($name,'HomeCMDalarm'.$ty.'-off','') if (AttrVal($name,'HomeCMDalarm'.$ty.'-off','')); } for (@cmds) { my $alias = name2alias($trigger,1); $_ =~ s/%ALIAS%/$alias/xgm; $_ =~ s/%SENSOR%/$trigger/xgm; my ($n,$s) = split /\|/x,AttrVal($name,"HomeTextNo$t","$no ".lc $ty.'|'.lc $ty); my $sta = $state =~ /^$v$/x ? $s : $n; $_ =~ s/%STATE%/$sta/xgm; my $no = '%'.uc $ty.'%'; my $nor = join(',',@sensors); my $ct = '%'.uc $ty.'CT%'; my $ctr = int(@sensors); my $hr = '%'.uc $ty.'HR%'; my $hrr = makeHR($hash,0,@sensors); $_ =~ s/$no/$nor/xgm; $_ =~ s/$ct/$ctr/xgm; $_ =~ s/$hr/$hrr/xgm; } execCMDs($hash,serializeCMD($hash,@cmds)) if (@cmds); } return; } sub Weather { my ($hash,$dev) = @_; my $name = $hash->{NAME}; my $cond = ReadingsVal($dev,'condition',''); my ($and,$are,$is) = split /\|/x,AttrVal($name,'HomeTextAndAreIs','and|are|is'); my $be = $cond =~ /(und|and|[Gg]ewitter|[Tt]hunderstorm|[Ss]chauer|[Ss]hower)/x ? $are : $is; my $wl = WeatherTXT($hash,AttrVal($name,'HomeTextWeatherLong','')); my $ws = WeatherTXT($hash,AttrVal($name,'HomeTextWeatherShort','')); my $wf = ForecastTXT($hash); my $wf1 = ForecastTXT($hash,1); readingsBeginUpdate($hash); readingsBulkUpdate($hash,'humidity',ReadingsNum($dev,'humidity',5)) if ((!AttrVal($name,'HomeSensorTemperatureOutside',undef) || (AttrVal($name,'HomeSensorTemperatureOutside',undef) && !ID(AttrVal($name,'HomeSensorTemperatureOutside',undef),'.*','humidity'))) && !AttrVal($name,'HomeSensorHumidityOutside',undef)); readingsBulkUpdate($hash,'temperature',ReadingsNum($dev,'temperature',5)) if (!AttrVal($name,'HomeSensorTemperatureOutside',undef)); readingsBulkUpdate($hash,'wind',ReadingsNum($dev,'wind',0)) if (!AttrVal($name,'HomeSensorWind',undef)); readingsBulkUpdate($hash,'windGust',ReadingsNum($dev,'windGust',0)) if (!AttrVal($name,'HomeSensorWind',undef)); readingsBulkUpdate($hash,'windDirection',ReadingsNum($dev,'wind_direction',0)) if (!AttrVal($name,'HomeSensorWind',undef)); readingsBulkUpdate($hash,'pressure',ReadingsNum($dev,'pressure',5)) if (!AttrVal($name,'HomeSensorAirpressure',undef)); readingsBulkUpdate($hash,'weatherTextLong',$wl); readingsBulkUpdate($hash,'weatherTextShort',$ws); readingsBulkUpdate($hash,'weatherTextForecastToday',$wf1); readingsBulkUpdate($hash,'weatherTextForecastTomorrow',$wf); readingsBulkUpdate($hash,'.be',$be); readingsEndUpdate($hash,1); ReadingTrend($hash,'humidity') if (!AttrVal($name,'HomeSensorHumidityOutside',undef)); ReadingTrend($hash,'temperature') if (!AttrVal($name,'HomeSensorTemperatureOutside',undef)); ReadingTrend($hash,'pressure') if (!AttrVal($name,'HomeSensorAirpressure',undef)); Icewarning($hash); return; } sub Twilight { my ($hash,$dev,$force) = @_; my $name = $hash->{NAME}; my $events = deviceEvents($defs{$dev},1); if ($force) { readingsBeginUpdate($hash); readingsBulkUpdate($hash,'light',ReadingsVal($dev,'light',5)); readingsBulkUpdate($hash,'twilight',ReadingsVal($dev,'twilight',5)); readingsBulkUpdate($hash,'twilightEvent',ReadingsVal($dev,'aktEvent',5)); readingsEndUpdate($hash,1); } else { my $pevent = ReadingsVal($name,'twilightEvent',''); for my $event (@{$events}) { my $val = (split ' ',$event)[1]; readingsBeginUpdate($hash); readingsBulkUpdate($hash,'light',$val) if ($event =~ /^light:/x); readingsBulkUpdate($hash,'twilight',$val) if ($event =~ /^twilight:/x); if ($event =~ /^aktEvent:/x) { readingsBulkUpdate($hash,'twilightEvent',$val); if ($val ne $pevent) { my @commands; push @commands,AttrVal($name,'HomeCMDtwilight','') if (AttrVal($name,'HomeCMDtwilight',undef)); push @commands,AttrVal($name,"HomeCMDtwilight-$val",'') if (AttrVal($name,"HomeCMDtwilight-$val",undef)); execCMDs($hash,serializeCMD($hash,@commands)) if (@commands); } } readingsEndUpdate($hash,1); } } return; } sub Icewarning { my ($hash) = @_; my $name = $hash->{NAME}; my $ice = ReadingsVal($name,'icewarning',2); my $temp = ReadingsVal($name,'temperature',5); my $temps = AttrVal($name,'HomeIcewarningOnOffTemps','2 3'); my $iceon = (split ' ',$temps)[0] * 1; my $iceoff = (split ' ',$temps)[1] ? (split ' ',$temps)[1] * 1 : $iceon; my $icewarning = 0; my $icewarningcmd = 'off'; $icewarning = 1 if ((!$ice && $temp <= $iceon) || ($ice && $temp <= $iceoff)); $icewarningcmd = 'on' if ($icewarning == 1); if ($ice != $icewarning) { my @commands; push @commands,AttrVal($name,'HomeCMDicewarning','') if (AttrVal($name,'HomeCMDicewarning',undef)); push @commands,AttrVal($name,"HomeCMDicewarning-$icewarningcmd",'') if (AttrVal($name,"HomeCMDicewarning-$icewarningcmd",undef)); readingsSingleUpdate($hash,'icewarning',$icewarning,1); execCMDs($hash,serializeCMD($hash,@commands)) if (@commands); } return; } sub CalendarEvents { my ($name,$cal) = @_; my $hash = $defs{$name}; my $filt = AttrCheck($hash,'HomeEventsFilter-'.$cal,''); my @events; if (ID($cal,'holiday')) { my (undef,@holidaylines) = FileRead(InternalVal($cal,'HOLIDAYFILE','')); for (@holidaylines) { next unless ($_ =~ /^[1234]\s/); my @parts = split; my $part = $parts[0] =~ /^[12]$/x ? 2 : $parts[0] == 3 ? 4 : $parts[0] == 4 ? 3 : 5; for (my $p = 0; $p < $part; $p++) { shift @parts; } my $evt = join('-',@parts); if ($filt) { push @events,$evt if ($evt =~ /^$filt$/); } else { push @events,$evt; } } } else { my $t = time(); my @filters = ( { ref => \&filter_true, param => undef } ); for (Calendar_GetEvents($defs{$cal},$t,@filters)) { my $evt = $_->{summary}; Log3 $name,5,"Calendar_GetEvents event: $evt"; $evt =~ s/[,;:\?\!\|\\\/\^\$\s ]/-/g; if ($filt) { push @events,$evt if ($evt =~ /^$filt$/); } else { push @events,$evt; } } } @events = uniq @events; return \@events; } sub checkIP { my ($hash) = @_; return if ($hash->{helper}{RUNNING_IPCHECK}); $hash->{helper}{RUNNING_IPCHECK} = 1; my $param = { url => 'http://icanhazip.com/', timeout => 5, hash => $hash, callback => \&FHEM::HOMEMODE::setIP }; return HttpUtils_NonblockingGet($param); } sub setIP { my ($param,$err,$data) = @_; my $hash = $param->{hash}; delete $hash->{helper}{RUNNING_IPCHECK}; my $name = $hash->{NAME}; if ($err ne '') { Log3 $name,3,"$name: Error while requesting ".$param->{url}." - $err"; } if (!$data || $data =~ /[<>]/x) { $err = 'Error - publicIP service check is temporary not available'; readingsSingleUpdate($hash,'publicIP',$err,1); Log3 $name,3,"$name: $err"; } elsif ($data ne '') { $data =~ s/\s+//g; chomp $data; if (ReadingsVal($name,'publicIP','') ne $data) { my @commands; readingsSingleUpdate($hash,'publicIP',$data,1); push @commands,AttrVal($name,'HomeCMDpublic-ip-change','') if (AttrVal($name,'HomeCMDpublic-ip-change',undef)); execCMDs($hash,serializeCMD($hash,@commands)) if (@commands); } } if (AttrNum($name,'HomePublicIpCheckInterval',0)) { my $timer = gettimeofday() + 60 * AttrNum($name,'HomePublicIpCheckInterval',0); $hash->{'.IP_TRIGGERTIME_NEXT'} = $timer; } return; } sub ToggleDevice { my ($hash,$devname) = @_; my $name = $hash->{NAME}; my @disabled; @disabled = split /,/x,ReadingsVal($name,'devicesDisabled','') if (ReadingsVal($name,'devicesDisabled','')); if ($devname) { my @cmds; if (grep {$_ eq $devname} @disabled) { push @cmds,AttrVal($name,'HomeCMDdeviceEnable','') if (AttrVal($name,'HomeCMDdeviceEnable','')); my @new; for (@disabled) { push @new,$_ if ($_ ne $devname); } @disabled = @new; } else { push @cmds,AttrVal($name,'HomeCMDdeviceDisable','') if (AttrVal($name,'HomeCMDdeviceDisable','')); push @disabled,$devname; } my $dis = @disabled?join(',',@disabled):''; readingsSingleUpdate($hash,'devicesDisabled',$dis,1); if (@cmds) { for (@cmds) { my $a = name2alias($devname); $_ =~ s/%ALIAS%/$a/xgm; $_ =~ s/%DEVICE%/$devname/xgm; } execCMDs($hash,serializeCMD($hash,@cmds)); } } my @list; for my $d (split /,/x,$hash->{NOTIFYDEV}) { push @list,$d if (!grep {$_ eq $d} @disabled); } $hash->{helper}{enabledDevices} = join ',',@list; return; } sub IsDis { my ($name,$devname) = @_; $devname = $devname?$devname:$name; return 1 if (IsDisabled($devname)); return 1 if (grep {$_ eq $devname} split /,/x,ReadingsVal($name,'devicesDisabled','')); return 0; } sub Details { my (undef,$name,$room) = @_; my $hash = $defs{$name}; my @batteries; my @contacts; my @energies; my @lights; my @motions; my @powers; my @smokes; my @tampers; my @waters; my $text; for my $s (split /,/x,$hash->{NOTIFYDEV}) { push @batteries,$s if (grep {$_ eq $s} split /,/x,InternalVal($name,'SENSORSBATTERY','')); push @contacts,$s if (grep {$_ eq $s} split /,/x,InternalVal($name,'SENSORSCONTACT','')); push @energies,$s if (grep {$_ eq $s} split /,/x,InternalVal($name,'SENSORSENERGY','')); push @lights,$s if (grep {$_ eq $s} split /,/x,InternalVal($name,'SENSORSLIGHT','')); push @motions,$s if (grep {$_ eq $s} split /,/x,InternalVal($name,'SENSORSMOTION','')); push @powers,$s if (grep {$_ eq $s} split /,/x,InternalVal($name,'SENSORSPOWER','')); push @smokes,$s if (grep {$_ eq $s} split /,/x,InternalVal($name,'SENSORSSMOKE','')); push @tampers,$s if (grep {$_ eq $s} split /,/x,InternalVal($name,'SENSORSTAMPER','')); push @waters,$s if (grep {$_ eq $s} split /,/x,InternalVal($name,'SENSORSWATER','')); } # Start HTML my $html = ''; my $usercss = AttrVal($name,'HomeUserCSS',''); $html .= "" if ($usercss); $html .= ''; $html .= ''; if (AttrVal($name,'HomeAdvancedDetails','none') eq 'none' || (AttrVal($name,'HomeAdvancedDetails','') eq 'room' && $FW_detail eq $name)) { ### } else { my $iid = ReadingsVal($name,'.lastInfo',''); $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; } if ($FW_detail eq $name) { my $deact = $langDE?'Verarbeitung der Events des Geräts inneralb von HOMEMODE deaktivieren':'Deactivate processing of this device´s events within HOMEMODE'; my $sensor = $langDE?'Sensorname':'Sensor name'; $hash->{helper}{inDetails} = 1; if (@batteries || @contacts || @energies || @lights || @motions || @powers || @smokes || @tampers || @waters) { $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; } $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; } else { delete $hash->{helper}{inDetails}; } $html .= ''; $html .= '
'; $html .= ''; $html .= ''; if (AttrVal($name,'HomeWeatherDevice','') || (AttrVal($name,'HomeSensorAirpressure','') && AttrVal($name,'HomeSensorHumidityOutside','') && AttrVal($name,'HomeSensorTemperatureOutside','')) || (AttrVal($name,'HomeSensorAirpressure','') && AttrVal($name,'HomeSensorTemperatureOutside','') && ID(AttrVal($name,'HomeSensorTemperatureOutside',''),undef,'humidity'))) { $html .= ''; $text = $langDE?'Temperatur':'Temperature'; $html .= ''; $text = $langDE?'Wettervorhersage':'Weather forecast'; $html .= ''; $text = $langDE?'Luftfeuchte':'Humidity'; $html .= ''; $html .= ''; $text = $langDE?'Luftdruck':'Air pressure'; $html .= ''; $html .= ''; $html .= ''; } if (ReadingsVal($name,'wind','')) { $html .= ''; $text = $langDE?'Windgeschwindigkeit':'Wind speed'; $html .= ''; $text = $langDE?'Wettervorhersage':'Weather forecast'; $html .= ''; $text = $langDE?'Böengeschwindigkeit':'Gust speed'; $html .= ''; $html .= ''; $text = $langDE?'Windrichtung':'Wind direction'; $html .= ''; $html .= ''; $html .= ''; } if (int(@powers) || int(@energies) || int(@lights)) { $html .= ''; $text = $langDE?'Leistung':'Power'; $html .= ''; $html .= ''; $text = $langDE?'Verbrauch':'Energy'; $html .= ''; $html .= ''; $text = $langDE?'Licht':'Light'; $html .= ''; $html .= ''; $html .= ''; } if (int(@contacts)) { $html .= ''; $text = $langDE?'Offen':'Open'; $html .= ''; $html .= ''; $text = $langDE?'Offen-Warnungen':'Open warnings'; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; } if (int(@batteries) || int(@smokes) || int(@tampers)) { $html .= ''; $text = $langDE?ReadingsNum($name,'batteryLow_ct',0) == 1 ? 'Batterie leer':'Batterien leer' : ReadingsNum($name,'batteryLow_ct',0) == 1 ? 'Battery empty':'Batteries empty'; $html .= ''; $html .= ''; $text = $langDE?'Rauch':'Smoke'; $html .= ''; $html .= ''; $text = $langDE?'Sabotiert':'Tampered'; $html .= ''; $html .= ''; $html .= ''; } if (int(@waters) || int(@motions) || int(@contacts)) { $html .= ''; $text = $langDE?'Wasser':'Water'; $html .= ''; $html .= ''; $text = $langDE?'Bewegung':'Motion'; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; } $html .= ''; $html .= '
'.$text.':'.ReadingsNum($name,'temperature',0).' °C'.ReadingsVal($name,'weatherTextForecastToday','').''.$text.':'.ReadingsNum($name,'humidity',0).' %'.$text.':'.ReadingsNum($name,'pressure',0).' hPa
'.$text.':'.ReadingsNum($name,'wind',0).' km/h'.ReadingsVal($name,'weatherTextForecastToday','').''.$text.':'.ReadingsVal($name,'windGust','-').' km/h'.$text.':'.ReadingsVal($name,'windDirection','-').'
'.$text.':'; $html .= int(@powers)?''.ReadingsNum($name,'power',0).' W':'-'; $html .= ''.$text.':'; $html .= int(@energies)?''.ReadingsNum($name,'energy',0).' kWh':'-'; $html .= ''.$text.':'; $html .= int(@lights)?''.ReadingsNum($name,'luminance',0).' lux':'-'; $html .= '
'.$text.':'; $text = $langDE?'Offene Kontakte':'Open contacts'; $html .= int(@contacts)?''.ReadingsNum($name,'contactsOpen_ct',0).''.ReadingsVal($name,'contactsOpen_hr','').'':'-'; $html .= ''.$text.':'; $text = $langDE?'Aktive Offen-Warnungen':'Active open warnings'; $html .= int(@contacts)?''.ReadingsNum($name,'contactWarning_ct',0).''.ReadingsVal($name,'contactWarning_hr','').'':'-'; $html .= ''.($langDE?'Abgel. Offen-Warnungen':'Expired open warnings').':'; $text = $langDE?'Abgelaufene Offen-Warnungen':'Expired open warnings'; $html .= ''.ReadingsNum($name,'contactWarningExpired_ct',0).''.ReadingsVal($name,'contactWarningExpired_hr','').''; $html .= '
'.$text.':'; $text = $langDE?'Niedrige Batteriestände':'Low batteries'; $html .= int(@batteries)?''.ReadingsNum($name,'batteryLow_ct',0).''.ReadingsVal($name,'batteryLow_hr','').'':'-'; $html .= ''.$text.':'; $text = $langDE?'Aktive Rauch/Feuer Alarme':'Active smoke/fire alarms'; $html .= int(@smokes)?''.ReadingsNum($name,'alarmSmoke_ct',0).''.ReadingsVal($name,'alarmSmoke_hr','').'':'-'; $html .= ''.$text.':'; $text = $langDE?'Aktive Sabotagealarme':'Active tamper alarms'; $html .= int(@tampers)?''.ReadingsNum($name,'alarmTamper_ct',0).''.ReadingsVal($name,'alarmTamper_hr','').'':'-'; $html .= '
'.$text.':'; $text = $langDE?'Aktive Wasseralarme':'Active water alarms'; $html .= int(@waters)?''.ReadingsNum($name,'alarmWater_ct',0).''.ReadingsVal($name,'alarmWater_hr','').'':'-'; $html .= ''.$text.':'; $text = $langDE?'Aktive Bewegungen':'Active motions'; $html .= int(@motions)?''.ReadingsNum($name,'motionsSensors_ct',0).''.ReadingsVal($name,'motionsSensors_hr','').'':'-'; $html .= 'Alarm:'; $text = $langDE?'Aktive Alarme':'Active alarms'; $html .= ''.ReadingsNum($name,'alarmTriggered_ct',0).''.ReadingsVal($name,'alarmTriggered_hr','').''; $html .= '
'; $html .= '
'; $html .= '

'; $html .= ($langDE?'Anzeige von detaillierten Informationen':'Display of detailed informations').'

'; $html .= '
'; $text = $langDE?'Bisher wurden keine anzuzeigenden Informationen ausgewählt':'No informations to display have been chosen so far'; $html .= $text; $html .= '
'; $text = $langDE?'Konfiguration der überwachten Sensoren':'Configuration of monitored sensors'; $html .= "

$text

"; $html .= '
'; $text = $langDE?'Batterien':'Batteries'; $html .= '' if (@batteries); $text = $langDE?'Kontakte':'Contacts'; $html .= '' if (@contacts); $text = $langDE?'Lichtmesser':'Light sensors'; $html .= '' if (@lights); $text = $langDE?'Bewegungsmelder':'Motion sensors'; $html .= '' if (@motions); $text = $langDE?'Leistungsmesser':'Power sensors'; $html .= '' if (@powers); $text = $langDE?'Verbrauchsmesser':'Energy sensors'; $html .= '' if (@energies); $text = $langDE?'Rauchmelder':'Smoke sensors'; $html .= '' if (@smokes); $text = $langDE?'Sabotagekontakte':'Tamper sensors'; $html .= '' if (@tampers); $text = $langDE?'Wassermelder':'Water sensors'; $html .= '' if (@waters); $html .= '
'; $html .= '
'; if (@batteries) { $html .= ''; $html .= ''; $html .= ''; $html .= ''; $text = $langDE?'Füge neue Batteriesensoren hinzu (kommaseparierte Liste)':'Add new battery sensors (comma separated list)'; # $html .= ''; $html .= ''; $text = $langDE?'Name des Reading welches den Batteriewert anzeigt':'Name of the reading indicating the battery value'; $html .= ''; $text = $langDE?'Prozentwert des Batteriereading ab dem eine Batteriestandswarnung ausgelöst werden soll':'Percentage of the value of the battery reading to trigger battery low warning'; $html .= ''; $html .= ''; $html .= ''; $html .= ''; my $c = 1; for my $s (sort @batteries) { my $alias = AttrVal($s,'alias',''); $html .= ''.$s.''; $html .= ' ('.$alias.')' if ($alias); $html .= ''.FW_hidden('devname',$s).''; $html .= ''; $html .= ''; $c++; } $html .= ''; $html .= '
#'.$sensor.' '.$sensor.'Home
ReadingBattery
Home
BatteryLowPercentage
'.FW_textfieldv('HomeReadingBattery',10,'',AttrVal($s,'HomeReadingBattery',''),'battery'); my $val = ReadingsVal($s,AttrVal($s,'HomeReadingBattery','battery'),''); $html .= ''.(defined $val?$val:'--').''; $html .= ''; my $cl = $val !~ /^\d{1,3}/x?'ui-helper-hidden':''; $html .= FW_textfieldv('HomeBatteryLowPercentage',3,$cl,AttrVal($s,'HomeBatteryLowPercentage',''),AttrVal($name,'HomeSensorsBatteryLowPercentage',30)); $html .= '
'; } if (@contacts) { my @hct = ('doorinside','dooroutside','doormain','window'); my @seasons; for (split ' ',AttrVal($name,'HomeSeasons',$HOMEMODE_Seasons)) { push @seasons,(split /\|/x,$_)[1]; } my $sea = join ' ',@seasons; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $text = $langDE? 'Alarm Modus in denen offen als Alarm zu werten ist': 'Alarm modes to treat open as alarm'; $html .= ''; $text = $langDE? '1-3 leerzeichenseparierte Werte in Sekunden um den Alarm in den verschiedenen Alarmmodi zu verzögern (Reihenfolge: armaway armnight armhome)': '1-3 space separated values in seconds to delay the alarm for the different alarm modes (order: armaway armnight armhome)'; $html .= ''; $text = $langDE? 'Maximale Anzahl wie oft Offen-Warnungen ausgelöst werden sollen': 'Maximum number how often open warnings should be triggered'; $html .= ''; if (!AttrVal($name,'HomeSensorsContactOpenWarningUnified',0)) { $text = $langDE? 'Leerzeichen separierte Liste von Minuten nach denen Offen-Warnungen ausgelöst werden sollen': 'space separated list of minutes after open warning should be triggered'; $html .= ''; $text = $langDE? 'Leerzeichen separierte Liste von Auslösezeit Teilern für Kontaktsensoren Offen-Warnungen abhängig der Jahreszeiten ('.$sea.')': 'space separated list of trigger time dividers for contact sensor open warnings depending on the seasons ('.$sea.')'; $html .= ''; } $text = $langDE? 'Modus von HOMEMODE in welchen Kontaktsensoren keine Offen-Warnungen auslösen sollen': 'modes of HOMEMODE in which the contact sensor should not trigger open warnings'; $html .= ''; $text = $langDE? 'Bewohner deren Status als Referenz für OpenDontTriggerModes gelten soll statt der Modus von HOMEMODE': 'Residents whose state should be the reference for OpenDontTriggerModes instead of the modes of HOMEMODE'; $html .= ''; $text = $langDE? 'Typ des Kontakts': 'type of the contact sensor'; $html .= ''; $text = $langDE? 'Name des Reading welches den Kontaktzustand anzeigt': 'Name of the reading indicating the contact state'; $html .= ''; $text = $langDE? 'Regex von Werten die als offen gewertet werden sollen': 'Regex of values to be treated as open'; $html .= ''; $html .= ''; $html .= ''; $html .= ''; my $c = 1; for my $s (sort @contacts) { my $alias = AttrVal($s,'alias',''); $html .= ''.$s.''; $html .= ' ('.$alias.')' if ($alias); $html .= ''.FW_hidden('devname',$s).''; $html .= ''; if (!AttrVal($name,'HomeSensorsContactOpenWarningUnified',0)) { $html .= ''; $html .= ''; } $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $c++; } $html .= ''; $html .= '
#'.$sensor.'Home
ModeAlarmActive
Home
AlarmDelay
Home
OpenMaxTrigger
Home
OpenTimes
Home
OpenTimeDividers
Home
OpenDontTriggerModes
Home
OpenDontTriggerModesResidents
Home
ContactType
Home
ReadingContact
Home
ValueContact
'; $html .= ''.FW_textfieldv('HomeOpenMaxTrigger',2,'',AttrVal($s,'HomeOpenMaxTrigger',''),0).''.FW_textfieldv('HomeOpenTimes',8,'',AttrVal($s,'HomeOpenTimes',''),AttrVal($name,'HomeSensorsContactOpenTimes','10')).''.FW_textfieldv('HomeOpenTimeDividers',7,'',AttrVal($s,'HomeOpenTimeDividers',''),AttrVal($name,'HomeSensorsContactOpenTimeDividers',0)).''; for my $m (split /,/x,$HOMEMODE_UserModesAll) { $html .= ''; for my $r (split /,/x,$hash->{RESIDENTS}) { $html .= ''; $html .= FW_select('HomeContactType','HomeContactType',\@hct,AttrVal($s,'HomeContactType',''),'dropdown',''); $html .= ''.FW_textfieldv('HomeReadingContact',10,'',AttrVal($s,'HomeReadingContact',''),'state'); my $val = ReadingsVal($s,AttrVal($s,'HomeReadingContact','state'),undef); $html .= ''.(defined $val?$val:'--').''; $html .= ''.FW_textfieldv('HomeValueContact',15,'',AttrVal($s,'HomeValueContact',''),'open|tilted|on|1|true').'
'; } if (@energies) { $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $text = $langDE? 'Name des Reading welches den Verbrauchswert anzeigt': 'Name of the reading indicating the energy value'; $html .= ''; $text = $langDE? 'Divident für den Verbrauchswert': 'Divider for the energy value'; $html .= ''; $text = $langDE? 'Erlaube negative Verbrauchswerte': 'Allow negative energy values'; $html .= ''; $html .= ''; $html .= ''; $html .= ''; my $c = 1; for my $s (sort @energies) { my $alias = AttrVal($s,'alias',''); $html .= ''.$s.''; $html .= ' ('.$alias.')' if ($alias); $html .= ''.FW_hidden('devname',$s).''; $html .= ''; $html .= ''; $html .= ''; $text = $langDE? 'Name des Reading welches den Lichtwert anzeigt': 'Name of the reading indicating the light value'; $html .= ''; $text = $langDE? 'Divident für den Lichtwert': 'Divider for the luminance value'; $html .= ''; $html .= ''; $html .= ''; $html .= ''; my $c = 1; for my $s (sort @motions) { my $alias = AttrVal($s,'alias',''); my @hmaa = split /\|/x,AttrVal($s,'HomeModeAlarmActive',''); $html .= ''.$s.''; $html .= ' ('.$alias.')' if ($alias); $html .= ''.FW_hidden('devname',$s).''; $html .= ''; $html .= ''; $html .= ''; $c++; } $html .= ''; $html .= '
#'.$sensor.'Home
ReadingEnergy
Home
DividerEnergy
Home
AllowNegativeEnergy
'.FW_textfieldv('HomeReadingEnergy',10,'',AttrVal($s,'HomeReadingEnergy',''),'energy'); my $val = ReadingsVal($s,AttrVal($s,'HomeReadingEnergy','energy'),undef); $html .= ''.(defined $val?$val:'--').''; $html .= ''.FW_textfieldv('HomeDividerEnergy',5,'',AttrNum($s,'HomeDividerEnergy',''),AttrNum($name,'HomeSensorsEnergyDivider',1)).''; $html .= ''.$sensor.'Home
ReadingLuminance
Home
DividerLuminance
'.FW_textfieldv('HomeReadingLuminance',10,'',AttrVal($s,'HomeReadingLuminance',''),'luminance'); my $val = ReadingsVal($s,AttrVal($s,'HomeReadingLuminance','luminance'),undef); $html .= ''.(defined $val?$val:'--').''; $html .= ''.FW_textfieldv('HomeDividerLuminance',5,'',AttrNum($s,'HomeDividerLuminance',''),AttrNum($name,'HomeSensorsLuminanceDivider',1)).'
'; } if (@motions) { my @hml = ('inside','outside'); $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $text = $langDE? 'Alarmmodi wenn open/motion den Alarm auslösen soll': 'Alarm modes to trigger open/motion as alarm'; $html .= ''; $text = $langDE? '1-3 leerzeichenseparierte Werte in Sekunden um den Alarm in den verschiedenen Alarmmodi zu verzögern (Reihenfolge: armaway armnight armhome)': '1-3 space separated values in seconds to delay the alarm for the different alarm modes (order: armaway armnight armhome)'; $html .= ''; $text = $langDE? 'Standort des Bewegungsmelders': 'Location of the motion sensor'; $html .= ''; $text = $langDE? 'Name des Reading welches den Bewegungszustand anzeigt': 'Name of the reading indicating the motion state'; $html .= ''; $text = $langDE? 'Regex der Werte die als open/motion gewertet werden sollen': 'Regex of values of ReadingMotion to treat as open/motion'; $html .= ''; $html .= ''; $html .= ''; $html .= ''; my $c = 1; for my $s (sort @motions) { my $alias = AttrVal($s,'alias',''); my @hmaa = split /\|/x,AttrVal($s,'HomeModeAlarmActive',''); $html .= ''.$s.''; $html .= ' ('.$alias.')' if ($alias); $html .= ''.FW_hidden('devname',$s).''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $c++; } $html .= ''; $html .= '
#'.$sensor.'Home
ModeAlarmActive
Home
AlarmDelay
Home
SensorLocation
Home
ReadingMotion
Home
ValueMotion
'; $html .= ''.FW_select("$s",'HomeSensorLocation',\@hml,AttrVal($s,'HomeSensorLocation',''),'dropdown','').''.FW_textfieldv('HomeReadingMotion',10,'',AttrVal($s,'HomeReadingMotion',''),'state'); my $val = ReadingsVal($s,AttrVal($s,'HomeReadingMotion','state'),undef); $html .= ''.(defined $val?$val:'--').''; $html .= ''.FW_textfieldv('HomeValueMotion',15,'',AttrVal($s,'HomeValueMotion',''),'open|on|motion|1|true').'
'; } if (@powers) { $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $text = $langDE? 'Name des Reading welches den Leistungswert anzeigt': 'Name of the reading indicating the power value'; $html .= ''; $text = $langDE? 'Divident für den Leistungswert': 'Divider for the power value'; $html .= ''; $text = $langDE? 'Erlaube negative Leistungswerte': 'Allow negative power values'; $html .= ''; $html .= ''; $html .= ''; $html .= ''; my $c = 1; for my $s (sort @powers) { my $alias = AttrVal($s,'alias',''); $html .= ''.$s.''; $html .= ' ('.$alias.')' if ($alias); $html .= ''.FW_hidden('devname',$s).''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $text = $langDE? 'Name des Reading welches den Rauchmelderstatus anzeigt': 'Name of the reading indicating the smoke state'; $html .= ''; $text = $langDE? 'Regex von Werten des ReadingSmoke die als Rauchalarm gelten sollen': 'Regex of values of ReadingSmoke to treat as smoke alarm'; $html .= ''; $html .= ''; $html .= ''; $html .= ''; my $c = 1; for my $s (sort @smokes) { my $alias = AttrVal($s,'alias',''); $html .= ''.$s.''; $html .= ' ('.$alias.')' if ($alias); $html .= ''.FW_hidden('devname',$s).''; $html .= ''; $html .= ''; $html .= ''; $c++; } $html .= ''; $html .= '
#'.$sensor.'Home
ReadingPower
Home
DividerPower
Home
AllowNegativePower
'.FW_textfieldv('HomeReadingPower',10,'',AttrVal($s,'HomeReadingPower',''),'power'); my $val = ReadingsVal($s,AttrVal($s,'HomeReadingPower','power'),undef); $html .= ''.(defined $val?$val:'--').''; $html .= ''.FW_textfieldv('HomeDividerPower',5,'',AttrNum($s,'HomeDividerPower',''),AttrNum($name,'HomeSensorsPowerDivider',1)).''; $html .= ''.$sensor.'Home
ReadingSmoke
Home
ValueSmoke
'.FW_textfieldv('HomeReadingSmoke',10,'',AttrVal($s,'HomeReadingSmoke',''),'state'); my $val = ReadingsVal($s,AttrVal($s,'HomeReadingSmoke','state'),undef); $html .= ''.(defined $val?$val:'--').''; $html .= ''.FW_textfieldv('HomeValueSmoke',15,'',AttrVal($s,'HomeValueSmoke',''),'smoke|open|on|yes|1|true').'
'; } if (@tampers) { $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $text = $langDE? 'Name des Reading welches den Sabotagekontaktstatus anzeigt': 'Name of the reading indicating the tamper state'; $html .= ''; $text = $langDE? 'Regex von Werten des ReadingTamper die als Sabotagealarm gelten sollen': 'Regex of values of ReadingTamper to treat as tamper alarm'; $html .= ''; $html .= ''; $html .= ''; $html .= ''; my $c = 1; for my $s (sort @tampers) { my $alias = AttrVal($s,'alias',''); $html .= ''.$s.''; $html .= ' ('.$alias.')' if ($alias); $html .= ''.FW_hidden('devname',$s).''; $html .= ''; $html .= ''; $html .= ''; $c++; } $html .= ''; $html .= '
#'.$sensor.'Home
ReadingTamper
Home
ValueTamper
'.FW_textfieldv('HomeReadingTamper',10,'',AttrVal($s,'HomeReadingTamper',''),'sabotageError'); my $val = ReadingsVal($s,AttrVal($s,'HomeReadingTamper','sabotageError'),undef); $html .= ''.(defined $val?$val:'--').''; $html .= ''.FW_textfieldv('HomeValueTamper',15,'',AttrVal($s,'HomeValueTamper',''),'tamper|open|on|yes|1|true').'
'; } if (@waters) { $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $text = $langDE? 'Name des Reading welches den Wassermelderstatus anzeigt': 'Name of the reading indicating the water state'; $html .= ''; $text = $langDE? 'Regex von Werten des ReadingWater die als Wasseralarm gelten sollen': 'Regex of values of ReadingWater to treat as water alarm'; $html .= ''; $html .= ''; $html .= ''; $html .= ''; my $c = 1; for my $s (sort @waters) { my $alias = AttrVal($s,'alias',''); $html .= ''.$s.''; $html .= ' ('.$alias.')' if ($alias); $html .= ''.FW_hidden('devname',$s).''; $html .= ''; $html .= ''; $html .= ''; $c++; } $html .= ''; $html .= '
#'.$sensor.'Home
ReadingWater
Home
ValueWater
'.FW_textfieldv('HomeReadingWater',10,'',AttrVal($s,'HomeReadingWater',''),'state'); my $val = ReadingsVal($s,AttrVal($s,'HomeReadingWater','state'),undef); $html .= ''.(defined $val?$val:'--').''; $html .= ''.FW_textfieldv('HomeValueWater',15,'',AttrVal($s,'HomeValueWater',''),'water|open|on|yes|1|true').'
'; } $html .= '
'; $html .= '
'; $text = $langDE?'Verstecke Internals und Readings':'hide internals and readings'; $html .= ''; $html .= '
'; return $html; } sub inform { my ($hash,$item,$value) = @_; my $name = $hash->{NAME}; if ($hash->{helper}{inDetails}) { DoTrigger($name,"$item: $value"); } return; } 1; =pod =item helper =item summary home device with ROOMMATE/GUEST/PET integration and much more =item summary_DE Zuhause Gerät mit ROOMMATE/GUEST/PET Integration und Vielem mehr =begin html

HOMEMODE

    HOMEMODE is designed to represent the overall home state(s) in one device.
    It uses the attribute userattr extensively.
    It has been optimized for usage with homebridge as GUI.
    You can also configure CMDs to be executed on specific events.
    There is no need to create notify(s) or DOIF(s) to achieve common tasks depending on the home state(s).
    It's also possible to control ROOMMATE/GUEST/PET devices states depending on their associated presence device.
    If the RESIDENTS device is on state home, the HOMEMODE device can automatically change its mode depending on the local time (morning,day,afternoon,evening,night)
    There is also a daytime reading and associated HomeCMD attributes that will execute the HOMEMODE state CMDs independend of the presence of any RESIDENT.
    A lot of placeholders are available for usage within the HomeCMD or HomeText attributes (see Placeholders).
    All your energy and power measuring sensors can be added and calculated total readings for energy and power will be created.
    You can also add your local outside temperature and humidity sensors and you'll get ice warning e.g.
    If you also add your Weather device you'll also get short and long weather informations and weather forecast.
    You can monitor added contact and motion sensors and execute CMDs depending on their state.
    A simple alarm system is included, so your contact and motion sensors can trigger alarms depending on the current alarm mode.
    A lot of customizations are possible, e.g. special events calendars and locations.

    General information:

    • The HOMEMODE device is refreshing itselfs every 5 seconds by calling GetUpdate and subfunctions.
      This is the reason why some automations (e.g. daytime or season) are delayed up to 4 seconds.
      All automations triggered by external events (other devices monitored by HOMEMODE) and the execution of the HomeCMD attributes will not be delayed.
    • Each created timer will be created as at device and its name will start with 'atTmp_' and end with '_<name of your HOMEMODE device>'. You may list them with 'list TYPE=at:FILTER=NAME=atTmp_.*_<name of your HOMEMODE device>'.
    • Seasons can also be adjusted (date and text) in attribute HomeSeasons
    • There's a special function, which you may use, which is converting given minutes (up to 5999.99) to a timestamp that can be used for creating at devices.
      This function is called HOMEMODE::hourMaker and the only value you need to pass is the number in minutes with max. two decimal places.
    • Each set command and each updated reading of the HOMEMODE device will create an event within FHEM, so you're able to create additional notify or DOIF devices if needed.

    A german Wiki page is also available at https://wiki.fhem.de/wiki/Modul_HOMEMODE. There you can find lots of example code.


    define [optional]

      define <name> HOMEMODE

      define <name> HOMEMODE [RESIDENTS-MASTER-DEVICE]

    set <required> [optional]

    • anyoneElseAtHome <on/off> [NAME]
      turn this on if anyone else is alone at home who is not a registered resident
      e.g. an animal or unregistered guest
      If turned on the alarm mode will be set to armhome instead of armaway while leaving, if turned on after leaving the alarm mode will change from armaway to armhome, e.g. to disable motion sensors alerts.
      If you add a subsequent unique name to the on command, the name will be added to a comma separated list in the reading anyoneElseAtHomeBy.
      If you add a subsequent unique name to the off command, the name will be removed from that list.
      When adding the first name anyoneElseAtHome will be set to on and it will be set to off after removing the last name from that list.
      This is really helpful if you need anyoneElseAtHome mode for more than one use case, e.g. dog at home and a working vacuum cleaner robot.
      placeholder %AEAH% is available in all HomeCMD attributes
    • deviceDisable <DEVICE>
      disable HOMEMODE integration for given device
      placeholder %DISABLED% is available in all HomeCMD attributes
      placeholders %DEVICE% and %ALIAS% are available in HomeCMDdeviceDisable attribute
    • deviceEnable <DEVICE>
      enable HOMEMODE integration for given device
      placeholder %DISABLED% is available in all HomeCMD attributes
      placeholders %DEVICE% and %ALIAS% are available in HomeCMDdeviceEnable attribute
    • dnd <on/off>
      turn 'do not disturb' mode on or off
      e.g. to disable notification or alarms or, or, or...
      placeholder %DND% is available in all HomeCMD attributes
    • dnd-for-minutes <MINUTES>
      turn 'do not disturb' mode on for given minutes
      will return to the current (daytime) mode
    • location <arrival/home/bed/underway/wayhome>
      switch to given location manually
      placeholder %LOCATION% is available in all HomeCMD attributes
    • mode <morning/day/afternoon/evening/night/gotosleep/asleep/absent/gone/home>
      switch to given mode manually
      placeholder %MODE% is available in all HomeCMD attributes
    • modeAlarm <armaway/armhome/armnight/confirm/disarm>
      switch to given alarm mode manually
      placeholder %MODEALARM% is available in all HomeCMD attributes
    • modeAlarm-for-minutes <armaway/armhome/armnight/disarm> <MINUTES>
      switch to given alarm mode for given minutes
      will return to the previous alarm mode
    • panic <on/off>
      turn panic mode on or off
      placeholder %PANIC% is available in all HomeCMD attributes
    • updateHomebridgeMapping
      will update the attribute homebridgeMapping of the HOMEMODE device depending on the available informations
    • updateInternalsForce
      will force update all internals of the HOMEMODE device
      use this if you just reload this module after an update or if you made changes on any HOMEMODE monitored device, e.g. after adding residents/guest or after adding new sensors with the same devspec as before
    • updateSensorsUserattr
      will force readding of all userattr to all sensors
      use this if you just want to cleanup the userattr on all sensors and readd the nessessary HOMEMODE attributes

    get <required> [optional]

    • contactsOpen <all/doorsinside/doorsoutside/doorsmain/outside/windows>
      get a list of all/doorsinside/doorsoutside/doorsmain/outside/windows open contacts
      placeholders %OPEN% (open contacts outside) and %OPENCT% (open contacts outside count) are available in all HomeCMD attributes
    • devicesDisabled
      get new line separated list of currently disabled devices
      placeholder %DISABLED% is available in all HomeCMD attributes
    • mode
      get current mode
      placeholder %MODE% is available in all HomeCMD attributes
    • modeAlarm
      get current modeAlarm
      placeholder %MODEALARM% is available in all HomeCMD attributes
    • publicIP
      get the public IP address
      placeholder %IP% is available in all HomeCMD attributes
    • sensorsTampered
      get a list of all tampered sensors
      placeholder %TAMPERED% is available in all HomeCMD attributes
    • weather <long/short>
      get weather information in given format
      please specify the outputs in attributes HomeTextWeatherLong and HomeTextWeatherShort
      placeholders %WEATHER% and %WEATHERLONG% are available in all HomeCMD attributes
    • weatherForecast [DAY]
      get weather forecast for given day
      if DAY is omitted the forecast for tomorrow (2) will be returned
      please specify the outputs in attributes HomeTextWeatherForecastToday, HomeTextWeatherForecastTomorrow and HomeTextWeatherForecastInSpecDays
      placeholders %FORECAST% (tomorrow) and %FORECASTTODAY% (today) are available in all HomeCMD attributes

    Attributes

    • HomeAdvancedDetails
      show more details depending on the monitored devices
      value detail will only show advanced details in detail view, value both will show advanced details also in room view, room will show advanced details only in room view
      values: none, detail, both, room
      default: none
    • HomeAdvancedAttributes
      more HomeCMD attributes will be provided
      additional attributes for each resident and each calendar event
      values: 0 or 1
      default: 0
    • HomeAtTmpRoom
      add this room to temporary at(s) (generated by HOMEMODE)
      default:
    • HomeAutoAlarmModes
      set modeAlarm automatically depending on mode
      if mode is set to 'home', modeAlarm will be set to 'disarm'
      if mode is set to 'absent', modeAlarm will be set to 'armaway'
      if mode is set to 'asleep', modeAlarm will be set to 'armnight'
      modeAlarm 'home' can only be set manually
      values 0 or 1, value 0 disables automatically set modeAlarm
      default: 1
    • HomeAutoArrival
      set resident's location to arrival (on arrival) and after given minutes to home
      values from 0 to 5999.9 in minutes, value 0 disables automatically set arrival
      default: 0
    • HomeAutoAsleep
      set user from gotosleep to asleep after given minutes
      values from 0 to 5999.9 in minutes, value 0 disables automatically set asleep
      default: 0
    • HomeAutoAwoken
      force set resident from asleep to awoken, even if changing from alseep to home
      after given minutes awoken will change to home
      values from 0 to 5999.9 in minutes, value 0 disables automatically set awoken after asleep
      default: 0
    • HomeAutoDaytime
      daytime depending home mode
      values 0 or 1, value 0 disables automatically set daytime
      default: 1
    • HomeAutoPresence
      automatically change the state of residents between home and absent depending on their associated presence device
      values 0 or 1, value 0 disables auto presence
      default: 0
    • HomeAutoPresenceSuppressState
      suppress state(s) for HomeAutoPresence (p.e. gotosleep|asleep)
      if set this/these state(s) of a resident will not affect the residents to change to absent by its presence device
      p.e. for misteriously disappearing presence devices in the middle of the night
      default:
    • HomeCMDalarmSmoke
      cmds to execute on any smoke alarm state
    • HomeCMDalarmSmoke-<on/off>
      cmds to execute on smoke alarm state on/off
    • HomeCMDalarmTampered
      cmds to execute on any tamper alarm state
    • HomeCMDalarmTampered-<on/off>
      cmds to execute on tamper alarm state on/off
    • HomeCMDalarmTriggered
      cmds to execute on any alarm state
    • HomeCMDalarmTriggered-<on/off>
      cmds to execute on alarm state on/off
    • HomeCMDalarmWater
      cmds to execute on any water alarm state
    • HomeCMDalarmWater-<on/off>
      cmds to execute on water alarm state on/off
    • HomeCMDanyoneElseAtHome
      cmds to execute on any anyoneElseAtHome state
    • HomeCMDanyoneElseAtHome-<on/off>
      cmds to execute on anyoneElseAtHome state on/off
    • HomeCMDcontact
      cmds to execute if any contact has been triggered (open/tilted/closed)
    • HomeCMDbattery
      cmds to execute on any battery change of a battery sensor
    • HomeCMDbatteryLow
      cmds to execute if any battery sensor has low battery
    • HomeCMDbatteryNormal
      cmds to execute if any battery sensor returns to normal battery
    • HomeCMDcontactClosed
      cmds to execute if any contact has been closed
    • HomeCMDcontactOpen
      cmds to execute if any contact has been opened
    • HomeCMDcontactDoormain
      cmds to execute if any contact of type doormain has been triggered (open/tilted/closed)
    • HomeCMDcontactDoormainClosed
      cmds to execute if any contact of type doormain has been closed
    • HomeCMDcontactDoormainOpen
      cmds to execute if any contact of type doormain has been opened
    • HomeCMDcontactOpenWarning1
      cmds to execute on first contact open warning
    • HomeCMDcontactOpenWarning2
      cmds to execute on second (and more) contact open warning
    • HomeCMDcontactOpenWarningLast
      cmds to execute on last contact open warning
    • HomeCMDdaytime
      cmds to execute on any daytime change
    • HomeCMDdaytime-<%DAYTIME%>
      cmds to execute on specific day time change
    • HomeCMDdeviceDisable
      cmds to execute on set HOMEMODE deviceDisable
    • HomeCMDdeviceEnable
      cmds to execute on set HOMEMODE deviceEnable
    • HomeCMDdnd
      cmds to execute on any dnd state
    • HomeCMDdnd-<on/off>
      cmds to execute on dnd state on/off
    • HomeCMDevent
      cmds to execute on each calendar event
    • HomeCMDevent-<%CALENDAR%>-each
      cmds to execute on each event of the calendar
    • HomeCMDevent-<%CALENDAR%>-<%EVENT%>-begin
      cmds to execute on start of a specific calendar event
    • HomeCMDevent-<%CALENDAR%>-<%EVENT%>-end
      cmds to execute on end of a specific calendar event
    • HomeCMDfhemDEFINED
      cmds to execute on any defined device
    • HomeCMDfhemINITIALIZED
      cmds to execute on fhem start
    • HomeCMDfhemSAVE
      cmds to execute on fhem save
    • HomeCMDfhemUPDATE
      cmds to execute on fhem update
    • HomeCMDicewarning
      cmds to execute on any ice warning state
    • HomeCMDicewarning-<on/off>
      cmds to execute on ice warning state on/off
    • HomeCMDlocation
      cmds to execute on any location change of the HOMEMODE device
    • HomeCMDlocation-<%LOCATION%>
      cmds to execute on specific location change of the HOMEMODE device
    • HomeCMDlocation-resident
      cmds to execute on any location change of any RESIDENT/GUEST/PET device
    • HomeCMDlocation-<%LOCATIONR%>-<%RESIDENT%>
      cmds to execute on specific location change of a specific RESIDENT/GUEST/PET device
    • HomeCMDlocation-<%LOCATIONR%>-resident
      cmds to execute on specific location change of any RESIDENT/GUEST/PET device
    • HomeCMDmode
      cmds to execute on any mode change of the HOMEMODE device
    • HomeCMDmode-absent-belated
      cmds to execute belated to absent
      belated time can be adjusted with attribute 'HomeModeAbsentBelatedTime'
    • HomeCMDmode-<%MODE%>
      cmds to execute on specific mode change of the HOMEMODE device
    • HomeCMDmode-<%MODE%>-<%RESIDENT%>
      cmds to execute on specific mode change of the HOMEMODE device triggered by a specific resident
    • HomeCMDmode-<%MODE%>-resident
      cmds to execute on specific mode change of the HOMEMODE device triggered by any resident
    • HomeCMDmodeAlarm
      cmds to execute on any alarm mode change
    • HomeCMDmodeAlarm-<armaway/armhome/armnight/confirm/disarm>
      cmds to execute on specific alarm mode change
    • HomeCMDmotion
      cmds to execute on any recognized motion of any motion sensor
    • HomeCMDmotion-<on/off>
      cmds to execute if any recognized motion of any motion sensor ends/starts
    • HomeCMDpanic
      cmds to execute on any panic state
    • HomeCMDpanic-<on/off>
      cmds to execute on if panic is turned on/off
    • HomeCMDpresence-<absent/present>
      cmds to execute on specific presence change of the HOMEMODE device
    • HomeCMDpresence-<absent/present>-<%RESIDENT%>
      cmds to execute on specific presence change of a specific resident
    • HomeCMDpresence-<absent/present>-device
      cmds to execute on specific presence change of any presence device
    • HomeCMDpresence-<absent/present>-resident
      cmds to execute on specific presence change of a specific resident
    • HomeCMDpresence-<absent/present>-<%RESIDENT%>-<%DEVICE%>
      cmds to execute on specific presence change of a specific resident's presence device
      only available if more than one presence device is available for a resident
    • HomeCMDpublic-ip-change
      cmds to execute on any detected public IP change
    • HomeCMDseason
      cmds to execute on any season change
    • HomeCMDseason-<%SEASON%>
      cmds to execute on specific season change
    • HomeCMDtwilight
      cmds to execute on any twilight event
    • HomeCMDtwilight-<EVENT>
      cmds to execute on a specific twilight event
    • HomeCMDuwz-warn
      cmds to execute on any UWZ warning state
    • HomeCMDuwz-warn-<begin/end>
      cmds to execute on UWZ warning state begin/end
    • HomeDaytimes
      space separated list of time|text pairs for possible daytimes starting with the first event of the day (lowest time)
      default: 05:00|morning 10:00|day 14:00|afternoon 18:00|evening 23:00|night
    • HomeEventsDevices
      devspec of Calendar/holiday calendars
    • HomeEventsFilter-<%CALENDAR%>
      regex to get filtered calendar/holiday events
      default:
    • HomeIcewarningOnOffTemps
      2 space separated temperatures for ice warning on and off
      default: 2 3
    • HomeLanguage
      overwrite language from gloabl device
      default: EN (language setting from global device)
    • HomeModeAbsentBelatedTime
      time in minutes after changing to absent to execute 'HomeCMDmode-absent-belated'
      if mode changes back (to home e.g.) in this time frame 'HomeCMDmode-absent-belated' will not be executed
      default:
    • HomeModeAlarmArmDelay
      time in seconds for delaying modeAlarm arm... commands
      must be a single number (valid for all modeAlarm arm... commands) or 3 space separated numbers for each modeAlarm arm... command individually (order: armaway armnight armhome)
      values from 0 to 99999
      default: 0
    • HomePresenceDeviceAbsentCount-<ROOMMATE/GUEST/PET>
      number of resident associated presence device to turn resident to absent
      default: maximum number of available presence device for each resident
    • HomePresenceDevicePresentCount-<ROOMMATE/GUEST/PET>
      number of resident associated presence device to turn resident to home
      default: 1
    • HomePresenceDeviceType
      comma separated list of presence device types
      default: PRESENCE
    • HomePublicIpCheckInterval
      numbers from 1-99999 for interval in minutes for public IP check
      default: 0 (disabled)
    • HomeResidentCmdDelay
      time in seconds to delay the execution of specific residents commands after the change of the residents master device
      normally the resident events occur before the HOMEMODE events, to restore this behavior set this value to 0
      default: 1 (second)
    • HomeSeasons
      space separated list of date|text pairs for possible seasons starting with the first season of the year (lowest date)
      default: 01.01|spring 06.01|summer 09.01|autumn 12.01|winter
    • HomeSensorAirpressure
      main outside airpressure sensor
    • HomeSensorRain
      main outside rain sensor
    • HomeSensorWind
      main outside wind sensor
    • HomeSensorsAlarmDelay
      1 or 3 space separated values in seconds to delay the alarm for the different alarm modes (order: armaway armnight armhome)
      this is the global setting, you can also set this in each contact and motion sensor individually in attribute HomeAlarmDelay once they are added to the HOMEMODE device
      default: 0
    • HomeSensorsBattery
      devspec of battery sensors with a battery reading
      all sensors with a percentage battery value or a ok/low/nok battery value are applicable
      each applied battery sensor will get the following attributes, attributes will be removed after removing the battery sensors from the HOMEMODE device.
      • HomeReadingBattery
        Single word of name of the reading indicating the battery value
        default: battery
      • HomeBatteryLowPercentage
        percentage to recognize a sensors battery as low (only percentage based sensors)
        this is the device setting which will override the global setting from attribute HomeSensorsBatteryLowPercentage of the HOMEMODE device
        default:
    • HomeSensorsBatteryLowPercentage
      percentage to recognize a sensors battery as low (only percentage based sensors)
      default: 30
    • HomeSensorsBatteryTypes
      regex of device types to use as battery sensors
      default:
    • HomeSensorsContact
      devspec of contact sensors
      each applied contact sensor will get the following attributes, attributes will be removed after removing the contact sensors from the HOMEMODE device.
      • HomeAlarmDelay
        1 or 3 space separated values in seconds to delay the alarm for the different alarm modes (order: armaway armnight armhome)
        this is the device setting, you can also set this globally in attribute HomeSensorsAlarmDelay
        default: 0
      • HomeContactType
        specify each contacts sensor's type, choose one of: doorinside, dooroutside, doormain, window
        while applying contact sensors to the HOMEMODE device, the value of this attribute will be guessed by device name or device alias
      • HomeModeAlarmActive
        specify the alarm mode(s) by regex in which the contact sensor should trigger open/tilted as alerts
        while applying contact sensors to the HOMEMODE device, the value of this attribute will be set to armaway by default
        choose one or a combination of: armaway|armhome|armnight
        default: armaway
      • HomeOpenDontTriggerModes
        specify the HOMEMODE mode(s)/state(s) by regex in which the contact sensor should not trigger open warnings
        choose one or a combination of all available modes of the HOMEMODE device
        if you don't want open warnings while sleeping a good choice would be: gotosleep|asleep
        default:
      • HomeOpenDontTriggerModesResidents
        comma separated list of residents whose state should be the reference for HomeOpenDontTriggerModes instead of the mode of the HOMEMODE device
        if one of the listed residents is in the state given by attribute HomeOpenDontTriggerModes, open warnings will not be triggered for this contact sensor
        default:
      • HomeOpenMaxTrigger
        maximum number how often open warning should be repeated
        default: 0
      • HomeReadingContact
        single word of name of the reading indicating the contact state
        default: state
      • HomeValueContact
        regex of open and tilted values for contact sensors
        default: open|tilted|on
      • HomeOpenTimes
        space separated list of minutes after open warning should be triggered
        first value is for first warning, second value is for second warning, ...
        if less values are available than the number given by HomeOpenMaxTrigger, the very last available list entry will be used
        this is the device setting which will override the global setting from attribute HomeSensorsContactOpenTimes of the HOMEMODE device
        default: 10
      • HomeOpenTimeDividers
        space separated list of trigger time dividers for contact sensor open warnings depending on the season of the HOMEMODE device.
        dividers in same order and same number as seasons in attribute HomeSeasons
        dividers are not used for contact sensors of type doormain and doorinside!
        this is the device setting which will override the global setting from attribute HomeSensorsContactOpenTimeDividers of the HOMEMODE device
        values from 0.001 to 99.999
        default:
    • HomeSensorsContactOpenTimeDividers
      space separated list of trigger time dividers for contact sensor open warnings depending on the season of the HOMEMODE device.
      dividers in same order and same number as seasons in attribute HomeSeasons
      dividers are not used for contact sensors of type doormain and doorinside!
      this is the global setting, you can also set these dividers in each contact sensor individually in attribute HomeOpenTimeDividers once they are added to the HOMEMODE device
      values from 0.001 to 99.999
      default:
    • HomeSensorsContactOpenTimeMin
      minimal open time for contact sensors open warnings
      default:
    • HomeSensorsContactOpenTimes
      space separated list of minutes after open warning should be triggered
      first value is for first warning, second value is for second warning, ...
      if less values are available than the number given by HomeOpenMaxTrigger, the very last available list entry will be used
      this is the global setting, you can also set these times(s) in each contact sensor individually in attribute HomeOpenTimes once they are added to the HOMEMODE device
      default: 10
    • HomeSensorsContactOpenWarningUnified
      if enabled all contact open warnings will be treated as one instead of individual ones
      now only the global values for HomeSensorsContactOpenTimeDividers and HomeSensorsContactOpenTimes are used and the ones on the individual sensors are being removed
      values: 0 or 1
      default: 0
    • HomeSensorHumidityOutside
      main outside humidity sensor
      if HomeSensorTemperatureOutside also has a humidity reading, you don't need to add the same sensor here
    • HomeSensorTemperatureOutside
      main outside temperature sensor
      if this sensor also has a humidity reading, you don't need to add the same sensor to HomeSensorHumidityOutside
    • HomeSensorsLuminance
      devspec of sensors with luminance measurement capabilities
      these devices will be used for total luminance calculations
      each applied luminance sensor will get the following attributes, attributes will be removed after removing the luminance sensors from the HOMEMODE device.
      • HomeReadingLuminance
        single word of name of the reading indicating the contact state
        default: state sabotageError
      • HomeDividerLuminance
        divider for proper calculation of total luminance
        this is the device setting which will override the global setting from attribute HomeSensorsLuminanceDivider of the HOMEMODE device
        default: 1
    • HomeSensorsLuminanceDivider
      divider for proper calculation of total luminance
      this is the global setting, you can also set these values in each contact sensor individually in attribute HomeDividerLuminance once they are added to the HOMEMODE device
      default: 1
    • HomeSensorsMotion
      devspec of motion sensors
      each applied motion sensor will get the following attributes, attributes will be removed after removing the motion sensors from the HOMEMODE device.
      • HomeModeAlarmActive
        specify the alarm mode(s) by regex in which the motion sensor should trigger motions as alerts
        while applying motion sensors to the HOMEMODE device, the value of this attribute will be set to armaway by default
        choose one or a combination of: armaway|armhome|armnight
        default: armaway (if sensor is of type inside)
      • HomeSensorLocation
        specify each motion sensor's location, choose one of: inside, outside
        default: inside
      • HomeReadingMotion
        single word of name of the reading indicating the motion state
        default: state sabotageError
      • HomeValueMotion
        regex of open values for the motion sensor
        default: open|on|motion|1|true
    • HomeSensorsEnergy
      devspec of sensors with energy reading
      these devices will be used for total energy calculations
      each applied energy sensor will get the following attributes, attributes will be removed after removing the energy sensors from the HOMEMODE device.
      • HomeAllowNegativeEnergy
        aalow negative energy values for total calculation
        default: 0
      • HomeDividerEnergy
        divider for proper calculation of total energy consumption
        this is the device setting which will override the global setting from attribute HomeSensorsEnergyDivider of the HOMEMODE device
        default: 1
      • HomeReadingEnergy
        single word of name of the reading indicating the energy value
        default: energy
    • HomeSensorsEnergyDivider
      divider for proper calculation of total energy consumption
      default: 1
    • HomeSensorsPower
      devspec of sensors with power reading
      these devices will be used for total calculations
      each applied power sensor will get the following attributes, attributes will be removed after removing the power sensors from the HOMEMODE device.
      • HomeAllowNegativePower
        allow negative power values for total calculation
        default: 0
      • HomeDividerPower
        divider for proper calculation of total power consumption
        this is the device setting which will override the global setting from attribute HomeSensorsPowerDivider of the HOMEMODE device
        default: 1
      • HomeReadingPower
        single word of name of the reading indicating the power value
        default: state sabotageError
    • HomeSensorsPowerDivider
      divider for proper calculation of total power consumption
      this is the global setting, you can also set these values in each contact sensor individually in attribute HomeDividerPower once they are added to the HOMEMODE device
      default: 1
    • HomeSensorsSmoke
      devspec of smoke sensors
      • HomeReadingSmoke
        single word of name of the reading indicating the smoke state
        default: state
      • HomeValueSmoke
        regex of on values for the smoke sensor
        default: smoke|open|on|yes|1|true
    • HomeSensorsTamper
      devspec of smoke sensors
      • HomeReadingTamper
        single word of name of the reading indicating the tamper state
        default: state sabotageError
      • HomeValueTamper
        regex of on values for the tamper sensor
        default: tamper|open|on|yes|1|true
    • HomeSensorsWater
      devspec of smoke sensors
      • HomeReadingWater
        single word of name of the reading indicating the water state
        default: state sabotageError
      • HomeValueWater
        regex of on values for the water sensor
        default: water|open|on|yes|1|true
    • HomeSpecialLocations
      comma separated list of additional locations
      default:
    • HomeSpecialModes
      comma separated list of additional modes
      default:
    • HomeTextAndAreIs
      pipe separated list of your local translations for 'and', 'are' and 'is'
      default: and|are|is
    • HomeTextClosedOpen
      pipe separated list of your local translation for 'closed' and 'open'
      default: closed|open
    • HomeTextRisingConstantFalling
      pipe separated list of your local translation for 'rising', 'constant' and 'falling'
      default: rising|constant|falling
    • HomeTextNoSmokeSmoke
      pipe separated list of your local translation for 'no smoke' and 'smoke'
      default: no smoke|smoke
    • HomeTextNoTamperTamper
      pipe separated list of your local translation for 'not tampered' and 'tampered'
      default: not tampered|tampered
    • HomeTextNoWaterWater
      pipe separated list of your local translation for 'no water' and 'water'
      default: no water|water
    • HomeTextTodayTomorrowAfterTomorrow
      pipe separated list of your local translations for 'today', 'tomorrow' and 'day after tomorrow'
      this is used by weather forecast
      default: today|tomorrow|day after tomorrow
    • HomeTextWeatherForecastInSpecDays
      your text for weather forecast in specific days
      placeholders can be used!
      default:
    • HomeTextWeatherForecastToday
      your text for weather forecast today
      placeholders can be used!
      default:
    • HomeTextWeatherForecastTomorrow
      your text for weather forecast tomorrow and the day after tomorrow
      placeholders can be used!
      default:
    • HomeTextWeatherNoForecast
      your text for no available weather forecast
      default: No forecast available
    • HomeTextWeatherLong
      your text for long weather information
      placeholders can be used!
      default:
    • HomeTextWeatherShort
      your text for short weather information
      placeholders can be used!
      default:
    • HomeTrendCalcAge
      time in seconds for the max age of the previous measured value for calculating trends
      default: 900
    • HomeTriggerAnyoneElseAtHome
      your anyoneElseAtHome trigger device (device:reading:valueOn:valueOff)
      default:
    • HomeTriggerPanic
      your panic alarm trigger device (device:reading:valueOn[:valueOff])
      valueOff is optional
      valueOn will toggle panic mode if valueOff is not given
      default:
    • HomeTwilightDevice
      your local Twilight device
      default:
    • HomeUserCSS
      CSS code to override the default styling of HOMEMODE
      default:
    • HomeUWZ
      your local UWZ device
      default:
    • HomeWeatherDevice
      your local Weather device
      default:

    Readings

    Placeholders

    These placeholders can be used in all HomeCMD attributes

    • %ADDRESS%
      mac address of the last triggered presence device
    • %ALIAS%
      alias of the last triggered resident
    • %ALARM%
      value of the alarmTriggered reading of the HOMEMODE device
      will return 0 if no alarm is triggered or a list of triggered sensors if alarm is triggered
    • %ALARMCT%
      value of the alarmTriggered_ct reading of the HOMEMODE device
    • %ALARMHR%
      value of the alarmTriggered_hr reading of the HOMEMODE device
      will return 0 if no alarm is triggered or a (human readable) list of triggered sensors if alarm is triggered
      can be used for sending msg e.g.
    • %AMODE%
      current alarm mode
    • %AEAH%
      state of anyoneElseAtHome, will return 1 if on and 0 if off
    • %ARRIVERS%
      will return a list of aliases of all registered residents/guests with location arrival
      this can be used to welcome residents after main door open/close
      e.g. Peter, Paul and Marry
    • %AUDIO%
      audio device of the last triggered resident (attribute msgContactAudio)
      if attribute msgContactAudio of the resident has no value the value is trying to be taken from device globalMsg (if available)
      can be used to address resident specific msg(s) of type audio, e.g. night/morning wishes
    • %BE%
      is or are of condition reading of monitored Weather device
      can be used for weather (forecast) output
    • %BATTERYLOW%
      alias (or name if alias is not set) of the last battery sensor which reported low battery
    • %BATTERYLOWALL%
      list of aliases (or names if alias is not set) of all battery sensor which reported low battery currently
    • %BATTERYLOWCT%
      number of battery sensors which reported low battery currently
    • %BATTERYNORMAL%
      alias (or name if alias is not set) of the last battery sensor which reported normal battery
    • %CONDITION%
      value of the condition reading of monitored Weather device
      can be used for weather (forecast) output
    • %CONTACT%
      value of the lastContact reading (last opened sensor)
    • %DEFINED%
      name of the previously defined device
      can be used to trigger actions based on the name of the defined device
      only available within HomeCMDfhemDEFINED
    • %DAYTIME%
      value of the daytime reading of the HOMEMODE device
      can be used to trigger day time specific actions
    • %DEVICE%
      name of the last triggered presence device
      can be used to trigger actions depending on the last present/absent presence device
    • %DEVICEA%
      name of the last triggered absent presence device
    • %DEVICEP%
      name of the last triggered present presence device
    • %DISABLED%
      comma separated list of disabled devices
    • %DND%
      state of dnd, will return 1 if on and 0 if off
    • %DURABSENCE%
      value of the durTimerAbsence_cr reading of the last triggered resident
    • %DURABSENCELAST%
      value of the lastDurAbsence_cr reading of the last triggered resident
    • %DURPRESENCE%
      value of the durTimerPresence_cr reading of the last triggered resident
    • %DURPRESENCELAST%
      value of the lastDurPresence_cr reading of the last triggered resident
    • %DURSLEEP%
      value of the durTimerSleep_cr reading of the last triggered resident
    • %DURSLEEPLAST%
      value of the lastDurSleep_cr reading of the last triggered resident
    • %CALENDARNAME%
      will return the current event of the given calendar name, will return 0 if event is none
      can be used to trigger actions on any event of the given calendar
    • %CALENDARNAME-EVENTNAME%
      will return 1 if given event of given calendar is current, will return 0 if event is not current
      can be used to trigger actions during specific events only (Christmas?)
    • %FORECAST%
      will return the weather forecast for tomorrow
      can be used in msg or tts
    • %FORECASTTODAY%
      will return the weather forecast for today
      can be used in msg or tts
    • %HUMIDITY%
      value of the humidity reading of the HOMEMODE device
      can be used for weather info in HomeTextWeather attributes e.g.
    • %HUMIDITYTREND%
      value of the humidityTrend reading of the HOMEMODE device
      possible values: constant, rising, falling
    • %ICE%
      will return 1 if ice warning is on, will return 0 if ice warning is off
      can be used to send ice warning specific msg(s) in specific situations, e.g. to warn leaving residents
    • %IP%
      value of reading publicIP
      can be used to send msg(s) with (new) IP address
    • %LIGHT%
      value of the light reading of the HOMEMODE device
    • %LOCATION%
      value of the location reading of the HOMEMODE device
    • %LOCATIONR%
      value of the location reading of the last triggered resident
    • %LUMINANCE%
      average luminance of motion sensors (if available)
    • %LUMINANCETREND%
      value of the luminanceTrend reading of the HOMEMODE device
      possible values: constant, rising, falling
    • %MODE%
      current mode of the HOMEMODE device
    • %MODEALARM%
      current alarm mode
    • %MOTION%
      value of the lastMotion reading (last opened sensor)
    • %NAME%
      name of the HOMEMODE device itself (same as %SELF%)
    • %OPEN%
      value of the contactsOutsideOpen reading of the HOMEMODE device
      can be used to send msg(s) in specific situations, e.g. to warn leaving residents of open contact sensors
    • %OPENCT%
      value of the contactsOutsideOpen_ct reading of the HOMEMODE device
      can be used to send msg(s) in specific situations depending on the number of open contact sensors, maybe in combination with placeholder %OPEN%
    • %OPENHR%
      value of the contactsOutsideOpen_hr reading of the HOMEMODE device
      can be used to send msg(s)
    • %PANIC%
      state of panic, will return 1 if on and 0 if off
    • %RESIDENT%
      name of the last triggered resident
    • %PRESENT%
      presence of the HOMEMODE device
      will return 1 if present or 0 if absent
    • %PRESENTR%
      presence of last triggered resident
      will return 1 if present or 0 if absent
    • %PRESSURE%
      value of the pressure reading of the HOMEMODE device
      can be used for weather info in HomeTextWeather attributes e.g.
    • %PREVAMODE%
      previous alarm mode of the HOMEMODE device
    • %PREVCONTACT%
      previous open contact sensor
    • %PREVMODE%
      previous mode of the HOMEMODE device
    • %PREVMODER%
      previous state of last triggered resident
    • %PREVMOTION%
      previous open motion sensor
    • %RAINLEVEL%
      value of the rainLevel reading of the HOMEMODE device
    • %SEASON%
      value of the season reading of the HOMEMODE device
    • %SELF%
      name of the HOMEMODE device itself (same as %NAME%)
    • %SENSORSBATTERY%
      all battery sensors from internal SENSORSBATTERY
    • %SENSORSCONTACT%
      all contact sensors from internal SENSORSCONTACT
    • %SENSORSENERGY%
      all energy sensors from internal SENSORSENERGY
    • %SENSORSMOTION%
      all motion sensors from internal SENSORSMOTION
    • %SENSORSSMOKE%
      all smoke sensors from internal SENSORSSMOKE
    • %SMOKE%
      value of the alarmSmoke reading of the HOMEMODE device
      will return 0 if no smoke alarm is triggered or a list of triggered sensors if smoke alarm is triggered
    • %SMOKECT%
      value of the alarmSmoke_ct reading of the HOMEMODE device
    • %SMOKEHR%
      value of the alarmSmoke_hr reading of the HOMEMODE device
      will return 0 if no smoke alarm is triggered or a (human readable) list of triggered sensors if smoke alarm is triggered
      can be used for sending msg e.g.
    • %TAMPERED%
      value of the sensorsTampered reading of the HOMEMODE device
    • %TAMPEREDCT%
      value of the sensorsTampered_ct reading of the HOMEMODE device
    • %TAMPEREDHR%
      value of the sensorsTampered_hr reading of the HOMEMODE device
      can be used for sending msg e.g.
    • %TEMPERATURE%
      value of the temperature reading of the HOMEMODE device
      can be used for weather info in HomeTextWeather attributes e.g.
    • %TEMPERATURETREND%
      value of the temperatureTrend reading of the HOMEMODE device
      possible values: constant, rising, falling
    • %TWILIGHT%
      value of the twilight reading of the HOMEMODE device
    • %TWILIGHTEVENT%
      current twilight event
    • %TOBE%
      are or is of the weather condition
      useful for phrasing sentences
    • %UWZ%
      UWZ warnings count
    • %UWZLONG%
      all current UWZ warnings as long text
    • %UWZSHORT%
      all current UWZ warnings as short text
    • %WATER%
      value of the alarmWater reading of the HOMEMODE device
      will return 0 if no water alarm is triggered or a list of triggered sensors if water alarm is triggered
    • %WATERCT%
      value of the alarmWater_ct reading of the HOMEMODE device
    • %WATERHR%
      value of the alarmWater_hr reading of the HOMEMODE device
      will return 0 if no water alarm is triggered or a (human readable) list of triggered sensors if water alarm is triggered
      can be used for sending msg e.g.
    • %WEATHER%
      value of 'get <HOMEMODE> weather short'
      can be used for for msg weather info e.g.
    • %WEATHERLONG%
      value of 'get <HOMEMODE> weather long'
      can be used for for msg weather info e.g.
    • %WIND%
      value of the wind reading of the HOMEMODE device
      can be used for weather info in HomeTextWeather attributes e.g.
    • %WINDCHILL%
      value of the apparentTemperature reading of the Weather device
      can be used for weather info in HomeTextWeather attributes e.g.

    These placeholders can only be used within HomeTextWeatherForecast attributes

    • %CONDITION%
      value of weather forecast condition
    • %DAY%
      day number of weather forecast
    • %HIGH%
      value of maximum weather forecast temperature
    • %LOW%
      value of minimum weather forecast temperature

    These placeholders can only be used within HomeCMDcontact, HomeCMDmotion and HomeCMDalarm attributes

    • %ALIAS%
      alias of the last triggered contact/motion/smoke sensor
    • %SENSOR%
      name of the last triggered contact/motion/smoke sensor
    • %STATE%
      state of the last triggered contact/motion/smoke sensor

    These placeholders can only be used within calendar event related HomeCMDevent attributes

    • %CALENDAR%
      name of the calendar
    • %DESCRIPTION%
      description of current event of the calendar (not applicable for holiday devices)
    • %EVENT%
      summary of current event of the calendar
    • %PREVEVENT%
      summary of previous event of the calendar

    These placeholders can only be used within HomeCMDdeviceDisable and HomeCMDdeviceEnable attributes

    • %DEVICE%
      name of the disabled/enabled device
    • %ALIAS%
      alias of the disabled/enabled device
=end html =for :application/json;q=META.json 22_HOMEMODE.pm { "abstract": "home device with ROOMMATE/GUEST/PET integration and much more", "x_lang": { "de": { "abstract": "" } }, "keywords": [ "fhem-core", "automation", "alarm", "integration", "wow" ], "release_status": "beta", "license": "GPL_2", "version": "v2.0.0", "author": [ "DeeSPe " ], "x_fhem_maintainer": [ "DeeSPe" ], "x_fhem_maintainer_github": [ "DeeSPe" ], "prereqs": { "runtime": { "requires": { "FHEM": 6.0, "perl": 5.16, "Meta": 0, "JSON": 0 }, "recommends": { }, "suggests": { } } } } =end :application/json;q=META.json =cut