/*********************************************************************************************************************** * * A SmartThings child smartapp which creates the "room" device using the rooms occupancy DTH and allows executing * various rules based on occupancy state. this alllows lights and other devices to be turned on and off based on * occupancy. it also allows many other actions like executing a routine or piston, turning on/off music and much * more. see the wiki for more details. (note wiki is still in progress. ok there is really no content in the wiki. * yet. but this is to reinforce my intention of putting the wiki together. ;-) will update with link once in place.) * * Copyright (C) 2017 bangali * * Contributors: * https://github.com/Johnwillliam * https://github.com/TonyFleisher * https://github.com/BamaRayne * * License: * This program is free software: you can redistribute it and/or modify it under the terms of the GNU * General Public License as published by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU General Public License along with this program. * If not, see . * * Attribution: * icons licensed and used with permission from: https://www.iconfinder.com/aha-soft * H letter icon resused from user: https://www.flickr.com/photos/lwr/ under CC BY-NC-SA 2.0 * * convertRGBToHueSaturation(...) adpated from code by ady624 for webCoRE. original code can be found at: * https://github.com/ady624/webCoRE/blob/master/smartapps/ady624/webcore-piston.src/webcore-piston.groovy * colorsRGB array color name and RGB values from code by ady624 for webCoRE. * * Name: Rooms Child App * Source: https://github.com/adey/bangali/blob/master/smartapps/bangali/rooms-child-app.src/rooms-child-app.groovy * ***********************************************************************************************************************/ public static String version() { return "v1.2.0" } boolean isDebug() { return debugLogging } import groovy.transform.Field @Field final String lastMotionActive = '1' @Field final String lastMotionInactive = '2' @Field final String occupancy = 'occupancy' @Field final String asleep = 'asleep' @Field final String engaged = 'engaged' @Field final String occupied = 'occupied' @Field final String vacant = 'vacant' @Field final String checking = 'checking' @Field final String locked = 'locked' @Field final String open = 'open' @Field final String closed = 'closed' @Field final String active = 'active' @Field final String inactive = 'inactive' @Field final String on = 'on' @Field final String off = 'off' @Field final String present = 'present' @Field final String notpresent = 'not present' // @Field final String noTraffic = '0' @Field final String lightTraffic = '5' @Field final String mediumTraffic = '7' @Field final String heavyTraffic = '9' @Field final int pauseMSecST = 10 @Field final int pauseMSecHU = 50 @Field final int fanLow = 33 @Field final int fanMedium = 67 @Field final int fanHigh = 99 @Field final String _SmartThings = 'ST' @Field final String _Hubitat = 'HU' @Field final long _SecondsInDay = 86400000 @Field final Map occupancyButtons = [1:"occupied",2:"checking",3:"vacant",4:"locked",5:"reserved",6:"kaput",7:"donotdisturb",8:"asleep",9:"engaged"] @Field final Map genericButtons = [1:"One",2:"Two",3:"Three",4:"Four",5:"Five",6:"Six",7:"Seven",8:"Eight",9:"Nine",10:"Ten",11:"Eleven",12:"Twelve",13:"Thirteen",14:"Fourteen",15:"Fifteen",16:"Sixteen"] @Field final String padChar = '・' @Field final String _ImgSize = '36' @Field final String pushAButton = 'pushableButton' @Field final String holdAButton = 'holdableButton' @Field final String doubleTapAButton = 'doubleTapableButton' @Field final Map heCapToAttrMap = [(pushAButton):'pushed', (holdAButton):'held', (doubleTapAButton):'doubleTapped'] @Field final int maxRules = 10 @Field final int maxHolis = 10 @Field final int maxButtons = 5 @Field final long _ProcessCHEvery = 30000L @Field final long _ProcessHMEvery = 30000L @Field final String _ERule = 'e' @Field final String _TRule = 't' @Field final String _HRule = 'h' @Field final String _TapToChg = 'Tap to change existing settings' @Field final String _TapToCon = 'Tap to configure' @Field final String _timeSunrise = '1' @Field final String _timeSunset = '2' @Field final String _timeTime = '3' definition ( name: "rooms child app", namespace: "bangali", parent: "bangali:rooms manager", author: "bangali", description: "DO NOT INSTALL DIRECTLY. Rooms child smartapp will create new rooms using 'rooms occupancy' DTH from the Rooms Manager smartapp.", category: "My Apps", iconUrl: "https://cdn.rawgit.com/adey/bangali/master/resources/icons/roomOccupancy.png", iconX2Url: "https://cdn.rawgit.com/adey/bangali/master/resources/icons/roomOccupancy@2x.png", iconX3Url: "https://cdn.rawgit.com/adey/bangali/master/resources/icons/roomOccupancy@3x.png" ) preferences { page(name: "roomName", title: "Room Name and Settings") page(name: "pageOnePager", title: "Easy Settings") page(name: "pageOccupiedSettings", title: "Occupied State Settings") page(name: "pageEngagedSettings", title: "Engaged State Settings") page(name: "pageCheckingSettings", title: "Checking State Settings") page(name: "pageVacantSettings", title: "Vacant State Settings") page(name: "pageOtherDevicesSettings", title: "Room Devices") page(name: "pageAutoLevelSettings", title: "Light Auto Level Settings") page(name: "pageHolidayLightPatterns", title: "Holiday Light Patterns") page(name: "pageHolidayLight", title: "Holiday Light Pattern") page(name: "pageRules", title: "Maintain Rules") page(name: "pageRuleDelete", title: "Delete Rules") page(name: "pageRule", title: "Edit Lighting Rule") page(name: "pageHumidity", title: "Edit Rule Humidity") page(name: "pageRuleDate", title: "Edit Rule Date") page(name: "pageRuleTime", title: "Edit Rule Time") page(name: "pageRuleCommands", title: "Edit Device Commands Rule Settings") page(name: "pageRuleOthers", title: "Edit Other Execution Rule Settings") page(name: "pageRuleTimer", title: "Edit Rule Timers") page(name: "pageAsleepSettings", title: "Asleep State Settings") page(name: "pageLockedSettings", title: "Locked State Settings") page(name: "pagePowerTime", title: "Power Time Range") page(name: "pageAdjacentRooms", title: "Adjacent Rooms Settings") page(name: "pageRoomTemperature", title: "Room Temperature Settings") page(name: "pageRoomHumidity", title: "Room Humidity Settings") page(name: "pageAnnouncementSettings", title: "Announcement Settings") page(name: "pageAnnouncementSpeakerTimeSettings", title: "Announcement Speaker Settings") page(name: "pageAnnouncementColorTimeSettings", title: "Announcement Color Settings") page(name: "pageAnnounceContacts", title: "Announce Contact Settings") page(name: "pageGeneralSettings", title: "General Settings") page(name: "pageAllSettings", title: "All Settings") page(name: "pageRoomButton", title: "Room Button Settings") page(name: "pageButtonDetails", title: "Button Details Settings") } def roomName() { // def roomNames = parent.getRoomNames(app.id) state.roomDevices = parent.getRoomNames(app.id) def luxAndTimeSettings = (luxSensor || timeSettings) def autoLevelSettings = (minLevel || maxLevel || state.ruleHasAL || autoColorTemperature) def holidayLightsSettings = false for (def i = 1; i <= maxHolis; i++) if (settings["holiName$i"] || settings["holiColorString$i"]) { holidayLightsSettings = true break } def timeSettings = (fromTimeType || toTimeType) def adjRoomSettings = (adjRooms ? true : false) def miscSettings = (awayModes || pauseModes || dayOfWeek) def engagedSettings = (busyCheck || engagedButton || buttonIs || engagedSwitch || contactSensor || noMotionEngaged) def otherDevicesSettings = (personsPresence || luxAndTimeSettings || musicDevice || powerMeter) def asleepSettings = (asleepSensor || nightSwitches) state.passedOn = false state.holiPassedOn = false def roomIconURL roomIconURL = (iconURL ? iconURL : gitURL(_RIimage)) getHubType() def playerDevice = (speakerDevices || speechDevices || musicPlayers || (state.hT == _SmartThings && listOfMQs) ? true : false) dynamicPage(name:"roomName", title:"MAIN SETTINGS PAGE", install:true, uninstall:childCreated()) { section("") { if (!childCreated()) paragraph "ENTER ROOM NAME AND SAVE THE ROOM. THEN EDIT ROOM, TO ADD SETTINGS AND RULES. DO NOT ADD SETTINGS AND RULES WITHOUT FIRST SAVING THE ROOM ONCE." label title:"${addImage(roomIconURL)}Room Name:", required:true, image:(state.hT != _Hubitat ? "$roomIconURL" : null) } section("") { input "onePager", "bool", title:"${addImage(gitURL(_OPimage))}Switch to easy settings?", required:false, defaultValue:false, submitOnChange:true, image:(state.hT != _Hubitat ? gitURL(_OPimage) : null) } if (onePager) { section("") { paragraph "App is in easy settings mode for first time users to get started quickly. For all settings please unset the toggle above. Any settings entered will be preserved." href "pageOnePager", title:"${addImage(gitURL(_REimage))}EASY SETTINGS", description:(motionSensors ? _TapToChg : _TapToCon), image:(state.hT != _Hubitat ? gitURL(_REimage) : null) } } else { section("") { input "hideAdvanced", "bool", title:"${addImage(gitURL(_HAimage))}Hide advanced settings?", required:false, defaultValue:true, submitOnChange:true, image:(state.hT != _Hubitat ? gitURL(_HAimage) : null) if (hideAdvanced) paragraph "Advanced settings are currently hidden so you can start with commonly used settings. To see all settings please unset the toggle above. Any settings entered will be preserved." } section("") { href "pageOtherDevicesSettings", title:"${addImage(gitURL(_ODimage))}ROOM DEVICES", description:(otherDevicesSettings ? _TapToChg : _TapToCon), image:(state.hT != _Hubitat ? gitURL(_ODimage) : null) } section("") { href "pageOccupiedSettings", title:"${addImage(gitURL(_OCimage))}OCCUPIED SETTINGS", description:((hasOccupiedDevice() || noMotionOccupied) ? _TapToChg : _TapToCon), image:(state.hT != _Hubitat ? gitURL(_OCimage) : null) } section("") { href "pageEngagedSettings", title:"${addImage(gitURL(_ENimage))}ENGAGED SETTINGS", description:(engagedSettings ? _TapToChg : _TapToCon), image:(state.hT != _Hubitat ? gitURL(_ENimage) : null) } if (!hideAdvanced) { section("") { href "pageCheckingSettings", title:"${addImage(gitURL(_CHimage))}CHECKING SETTINGS", description:((dimTimer || dimByLevel) ? _TapToChg : _TapToCon), image:(state.hT != _Hubitat ? gitURL(_CHimage) : null) } section("") { href "pageVacantSettings", title:"${addImage(gitURL(_VAimage))}VACANT SETTINGS", description:(vacantButton || vacantSwitches || turnOffMusic ? _TapToChg : _TapToCon), image:(state.hT != _Hubitat ? gitURL(_VAimage) : null) } section("") { href "pageAsleepSettings", title:"${addImage(gitURL(_ASimage))}ASLEEP SETTINGS", description:(asleepSettings ? _TapToChg : _TapToCon), image:(state.hT != _Hubitat ? gitURL(_ASimage) : null) } section("") { href "pageLockedSettings", title:"${addImage(gitURL(_LOimage))}LOCKED SETTINGS", description:(lockedSwitch ? _TapToChg : _TapToCon), image:(state.hT != _Hubitat ? gitURL(_LOimage) : null) } } section("") { href "pageAutoLevelSettings", title:"${addImage(gitURL(_ALimage))}AUTO LEVEL 'AL' SETTINGS", description:(autoLevelSettings ? _TapToChg : _TapToCon), image:(state.hT != _Hubitat ? gitURL(_ALimage) : null) } if (!hideAdvanced) { section("") { href "pageHolidayLightPatterns", title:"${addImage(gitURL(_HLimage))}HOLIDAY LIGHTS 'HL' SETTINGS", description:(holidayLightsSettings ? _TapToChg : _TapToCon), image:(state.hT != _Hubitat ? gitURL(_HLimage) : null) } section("") { href "pageRoomTemperature", title:"${addImage(gitURL(_RTimage))}TEMPERATURE SETTINGS", description:(tempSensors || maintainRoomTemp ? _TapToChg : _TapToCon), image:(state.hT != _Hubitat ? gitURL(_RTimage) : null) } section("") { href "pageRoomHumidity", title:"${addImage(gitURL(_RHimage))}HUMIDITY SETTINGS", description:(tempSensors || maintainRoomTemp ? _TapToChg : _TapToCon), image:(state.hT != _Hubitat ? gitURL(_RHimage) : null) } } section("") { href "pageRules", title: "${addImage(gitURL(_RUimage))}MAINTAIN RULES", description:"Create/Edit/Disable rules", image:(state.hT != _Hubitat ? gitURL(_RUimage) : null) } if (!hideAdvanced) { section("") { href "pageAdjacentRooms", title:"${addImage(gitURL(_ARimage))}ADJACENT ROOMS SETTINGS", description:(adjRoomSettings ? _TapToChg : _TapToCon), image:(state.hT != _Hubitat ? gitURL(_ARimage) : null) } section("") { href "pageAnnouncementSettings", title:"${addImage(gitURL(_ANimage))}ANNOUNCEMENT SETTINGS", description:(playerDevice || announceSwitches ? _TapToChg : _TapToCon), image:(state.hT != _Hubitat ? gitURL(_ANimage) : null) } } section("") { href "pageGeneralSettings", title:"${addImage(gitURL(_GEimage))}GENERAL SETTINGS", description:(miscSettings ? _TapToChg : _TapToCon), image:(state.hT != _Hubitat ? gitURL(_GEimage) : null) } } section("") { href "pageAllSettings", title:"${addImage(gitURL(_VIimage))}VIEW ALL SETTINGS", description:"Tap to view all settings", image:(state.hT != _Hubitat ? gitURL(_VIimage) : null) } section("") { href "", title:"${addImage(gitURL(_GHimage))}Detailed readme on Github", style:"external", url:_gitREADME, description:"Click link to open in browser", image:(state.hT != _Hubitat ? gitURL(_GHimage) : null), required:false } if (state.hT == _SmartThings) remove("Remove Room", "Remove Room ${app.label}") else remove } } private addImage(url) { return (state.hT == _Hubitat ? " " : '') } private gitURL(imgName) { return "https://cdn.rawgit.com/adey/bangali/master/resources/icons/" + imgName } private getHubType() { if (!state.hubId) state.hubId = location.hubs[0].id.toString() state.hT = (state.hubId.length() > 5 ? _SmartThings : _Hubitat) return state.hT } def pageOnePager() { def pOP1 = 'Use which motion event for timeout?' def pOP2 = 'When room is busy?' def pOP3 = 'If any light is on dim by what level?' def pOP4 = 'If no light is on turn on and dim to what level?' dynamicPage(name:"pageOnePager", title:"One Pager", install:false, uninstall:false) { section("Motion sensor for OCCUPIED:", hideable: false) { inputDRMS('motionSensors', 'motionSensor', 'Room motion sensors?', true, true, true) if (motionSensors) inputERMSDO('whichNoMotion', pOP1, true, false, true, 2, [[1:"Last Motion Active"], [2:"Last Motion Inactive"]]) else paragraph pOP1 + '\nselect motion sensor above to set' } section("Timeout for OCCUPIED:", hideable:fase) { inputNRDRS('noMotionOccupied', "After how many seconds?", true, 300, "5..99999") } section("Change room to ENGAGED with traffic?", hideable: false) { if (motionSensors) inputERMSDO('busyCheck', pOP2, false, false, false, 7, [[null:"No auto engaged"],[5:"Light traffic"],[7:"Medium Traffic"],[9:"Heavy Traffic"]]) else paragraph pOP2 '\nselect motion sensors above to set' } section("Timeout for ENGAGED:", hideable:false) { inputNRDRS('noMotionEngaged', "After how many seconds?", false, 1800, "5..99999") } section("Timeout for CHECKING:", hideable: false) { inputNRDRS('dimTimer', "After how many seconds?", true, 90, "5..99999", true) if (dimTimer) { inputNRDRS('dimByLevel', pOP3, false, null, "1..99") inputNRDRS('dimToLevel', pOP4, false, null, "1..99") } else { paragraph pOP3 + '\nselect timer seconds above to set' paragraph pOP4 } } section("States and switches:", hideable:false) { inputERMSDO('state1', 'Which state?', true, true, false, [occupied, engaged], [occupied, engaged]) inputDRMS('switchesOn1', 'switch', 'Turn on which switches?', true, true) inputERMSDO('setLevelTo1', 'Set level when turning on?', false, false, false, null, [[1:"1%"],[5:"5%"],[10:"10%"],[15:"15%"],[20:"20%"],[25:"25%"],[30:"30%"],[40:"40%"],[50:"50%"],[60:"60%"],[70:"70%"],[80:"80%"],[90:"90%"],[99:"99%"],[100:"100%"]]) } section("Turn off all switches when no rule matches?", hideable: false) { if (state.vacant) { if (allSwitchesOff) app.updateSetting('allSwitchesOff', [type: 'bool', value: false]) paragraph 'Turn OFF option disabled when there is a rule for vacant state' } else inputBRDS('allSwitchesOff', 'Turn OFF?', false, true) } } } def pageOtherDevicesSettings() { def buttonNames = genericButtons def roomButtonOptions = [:] if (roomButton) { def roomButtonAttributes = roomButton.supportedAttributes def attributeNameFound = false for (def att : roomButtonAttributes) { if (att.name == occupancy) buttonNames = occupancyButtons if (att.name == 'numberOfButtons') attributeNameFound = true } def numberOfButtons = roomButton.currentNumberOfButtons if (attributeNameFound && numberOfButtons) { for (def i = 1; i <= numberOfButtons && i <= 16; i++) roomButtonOptions << [(i.toString()):(buttonNames[i])] } else roomButtonOptions << [null:"No buttons"] } def roomMotionSensors = motionSensors.collect{ [(it.id): "${it.displayName}"] } if (ht == _Hubitat && roomButton && !roomButtonType) app.updateSetting("roomButtonType", [type:"enum", value:"$pushAButton"]) def pODS1 = 'Use which motion event for timeout?' def pODS11 = 'Trigger from vacant only when all motion and acceleration sensors Active?' def pODS2 = 'Rotate thru which room states?' def pODS3 = 'Inside door require motion after door closed to set ENGAGED?' def pODS4 = 'Contact sensor on outside door?' dynamicPage(name: "pageOtherDevicesSettings", title:"Room Sensors", install:false, uninstall:false) { section("MOTION SENSORS:", hideable:false) { inputDRMS('motionSensors', 'motionSensor', 'Room motion sensors?', false, true, true) if (!hideAdvanced) inputERMSDO('triggerMotionSensors', 'Room motion sensors that trigger from VACANT?', false, true, false, null, roomMotionSensors) if (motionSensors) inputERMSDO('whichNoMotion', pODS1, true, false, false, 2, [[1:"Last Motion Active"],[2:"Last Motion Inactive"]]) else paragraph pODS1 + '\nselect motion sensor above to set' } section("ACCELERATION SENSORS:", hideable:false) { inputDRMS('accelSensors', 'accelerationSensor', 'Room acceleration sensors?', false, true, true) } section("ALL ACTIVE:", hideable:false) { def x = (motionSensors ? (triggerMotionSensors ? triggerMotionSensors.size() : motionSensors.size()) : 0) + (accelSensors ? accelSensors.size() : 0) if (x > 1 && whichNoMotion == lastMotionActive) inputBRDS('allMotionAndAccel', pODS11, false, false) else paragraph pODS11 + '\nrequires multiple trigger devices above to set' } section("ROOM BUTTONS:", hideable:false) { /* if (state.hT == _Hubitat) input "roomButtonType", "enum", title: "Button type?", required: (roomButton ? true : false), multiple: false, defaultValue: null, submitOnChange: true, options: [["$pushAButton":'Push button'],["$holdAButton":'Hold button'],["$doubleTapAButton":'Double tap button']] if (state.hT == _SmartThings || roomButtonType) input "roomButton", "capability.${(state.hT == _SmartThings ? 'button' : roomButtonType)}", title: "Button to rotate states?", required: false, multiple: false, submitOnChange: true else paragraph "Button to rotate states?\nselect button type to set" if (roomButton) input "buttonForRoom", "enum", title: "Button Number?", required: true, multiple: false, defaultValue: null, options: roomButtonOptions else paragraph "Button number?\nselect button to set." */ def hasButton = false for (def i = 0; i <= maxButtons; i++) { if (settings["roomButton$i"] || settings["roomButtonNumber$i"]) { hasButton = true break } } href "pageRoomButton", title:"ROOM BUTTON SETTINGS", description:(hasButton ? "Tap to change existing settings" : "Tap to configure") if (hasButton) inputERMSDO('roomButtonStates', pODS2, true, true, false, null, [engaged, occupied, asleep, locked, vacant]) else paragraph pODS2 } section("PRESENCE SENSORS:", hideable:false) { inputDRMS('personsPresence', 'presenceSensor', 'Presence sensors?', false, true) } section("LUX SENSORS:", hideable:false) { inputDRMS('luxSensor', 'illuminanceMeasurement', 'Room lux sensors?', false, true) } section("TEMPERATURE SENSORS:", hideable:false) { inputDRMS('tempSensors', 'temperatureMeasurement', 'Room temperature sensors?', false, true) } section("HUMIDITY SENSORS:", hideable:false) { inputDRMS('humiditySensor', 'relativeHumidityMeasurement', 'Room humidity sensors?', false, true) } section("ROOM DOOR SENSORS:", hideable:false) { inputDRMS('contactSensor', 'contactSensor', 'Contact sensors?', false, true, true) if (contactSensor && motionSensors) // if (!contactSensorOutsideDoor) inputBRDS('contactEngagedRequiresMotion', pODS3, false, false) // else { // app.updateSetting('contactEngagedRequiresMotion', [type: 'bool', value: false]) // paragraph pODS3 + '\ncannot be set for outside door' // } else { app.updateSetting('contactEngagedRequiresMotion', [type: 'bool', value: false]) paragraph pODS3 + '\nselect contact sensor and motion sensor to set' } if (contactSensor) // if (!contactEngagedRequiresMotion) inputBRDS('contactSensorOutsideDoor', pODS4, false, false) // else { // app.updateSetting('contactSensorOutsideDoor', [type: 'bool', value: false]) // paragraph pODS4 + '\nreset requires motion to set' // } else { app.updateSetting('contactSensorOutsideDoor', [type: 'bool', value: false]) paragraph pODS4 + '\nselect contact sensor to set' } } section("ROOM WINDOW SENSORS:", hideable:false) { inputDRMS('contactSensorsRT', 'contactSensor', 'Window sensors?', false, true) } section("ROOM WATER SENSORS:", hideable:false) { inputDRMS('waterSensors', 'waterSensor', 'Water sensors?', false, true) } section("OUTDOOR TEMPERATURE SENSOR:", hideable:false) { inputDRMS('outTempSensor', 'temperatureMeasurement', 'Outdoor temperature sensor?', false, false) } section("POWER METER:", hideable:false) { inputDRMS('powerDevice', 'powerMeter', 'Room power meter?', false, true) } section("MUSIC PLAYER:", hideable:false) { inputDRMS('musicDevice', 'musicPlayer', 'Room music player?', false, false) } } } def pageRoomButton() { state.buttonPassed = false def bName = 'roomButton' dynamicPage(name:"pageRoomButton", title:"Room Button Settings", install:false, uninstall:false) { section("", hideable: false) { def eCS = 99 for (def i = 1; i <= maxButtons; i++) { if (settings["$bName$i"] || settings["${bName}Number$i"]) { href "pageButtonDetails", title:("${settings["$bName$i"]} : ${(state.hT == _Hubitat ? heCapToAttrMap[settings["${bName}Type$i"]] + ' : ' : '')}${settings["${bName}Number$i"]}"), params:[bName:"roomButton", bNo:"$i"], required:false if (!(settings["$bName$i"] && settings["${bName}Number$i"])) eCS = 88 } } if (eCS == 99) { for (def i = 1; i <= maxButtons; i++) if (!(settings["$bName$i"] && settings["${bName}Number$i"])) { eCS = i break } } if (eCS <= maxButtons) href "pageButtonDetails", title:"Select new button", params:[bName:"roomButton", bNo:"$eCS"], required:false } } } def pageButtonDetails(params) { if (!state.buttonPassed && params) { state.buttonPassed = true state.buttonPassedParams = params } if (params.bName) state.buttonName = params.bName else if (state.buttonPassedParams) state.buttonName = state.buttonPassedParams.bName if (params.bNo) state.buttonNumber = params.bNo else if (state.buttonPassedParams) state.buttonNumber = state.buttonPassedParams.bNo def bName = state.buttonName def bNo = state.buttonNumber ifDebug("bName: $bName | bNo: $bNo") def buttonNames = genericButtons def buttonOptions = [:] if (settings["$bName$bNo"]) { def buttonAttributes = settings["$bName$bNo"].supportedAttributes def attributeNameFound = false for (def att : buttonAttributes) { if (att.name == occupancy) buttonNames = occupancyButtons if (att.name == 'numberOfButtons') attributeNameFound = true } def numberOfButtons = settings["$bName$bNo"].currentNumberOfButtons if (attributeNameFound && numberOfButtons) for (def i = 1; i <= numberOfButtons && i <= 16; i++) buttonOptions << [(i.toString()):(buttonNames[i])]; else buttonOptions << [null:"No buttons"] } def pBD1 = 'Button to rotate states?' def pBD2 = 'Button number?' dynamicPage(name:"pageButtonDetails", title:"Pick a button", install:false, uninstall:false) { section() { if (state.hT == _Hubitat) inputERMSDO("${bName}Type$bNo", 'Button type?', (settings["$bName$bNo"] ? true : false), false, true, null, [["$pushAButton":'Push button'], ["$holdAButton":'Hold button'], ["$doubleTapAButton":'Double tap button']]) if (state.hT == _SmartThings || settings["${bName}Type$bNo"]) inputDRMS("$bName$bNo", "${(state.hT == _SmartThings ? 'button' : settings["${bName}Type$bNo"])}", pBD1, false, false, true) else paragraph pBD1 + '\nselect button type to set' if (settings["$bName$bNo"]) inputERMSDO("${bName}Number$bNo", pBD2, true, false, false, null, buttonOptions) else paragraph pBD2 + '\nselect button to set' } } } def pageOccupiedSettings() { def buttonNames = genericButtons def occupiedButtonOptions = [:] if (occupiedButton) { def occupiedButtonAttributes = occupiedButton.supportedAttributes def attributeNameFound = false for (def att : occupiedButtonAttributes) { if (att.name == occupancy) buttonNames = occupancyButtons; if (att.name == 'numberOfButtons') attributeNameFound = true; } def numberOfButtons = occupiedButton.currentNumberOfButtons if (attributeNameFound && numberOfButtons) for (def i = 1; i <= numberOfButtons && i <= 16; i++) occupiedButtonOptions << [(i.toString()):(buttonNames[i])]; else occupiedButtonOptions << [null:"No buttons"] } if (ht == _Hubitat && occupiedButton && !occupiedButtonType) app.updateSetting("occupiedButtonType", [type: "enum", value: "$pushAButton"]); def pOS1 = 'Change room to OCCUPIED' def pOS2 = 'Timeout for OCCUPIED' def pOS3 = 'Button to set OCCUPIED?' def pOS4 = 'Button Number?' def pOS5 = 'Button only sets Occupied?' dynamicPage(name:"pageOccupiedSettings", title:"Occupied Settings", install:false, uninstall:false) { section((state.hT != _Hubitat ? pOS1 : ''), hideable:false) { if (state.hT == _Hubitat) { paragraph subHeaders(pOS1) inputERMSDO('occupiedButtonType', 'Button type?', (occupiedButton ? true : false), false, true, null, [["$pushAButton":'Push button'],["$holdAButton":'Hold button'],["$doubleTapAButton":'Double tap button']]) } if (state.hT == _SmartThings || occupiedButtonType) inputDRMS('occupiedButton', "${(state.hT == _SmartThings ? 'button' : occupiedButtonType)}", pOS3, false, false, true) else paragraph pOS3 + '\nselect button type to set' if (occupiedButton) { inputERMSDO('buttonIsOccupied', pOS4, true, false, false, null, occupiedButtonOptions) inputBRDS('buttonOnlySetsOccupied', pOS5, false, false) } else { paragraph pOS4 + '\nselect button above to set' paragraph pOS5 } inputDRMS('occSwitches', 'switch', 'If switch turns ON?', false, true, true) } section((state.hT != _Hubitat ? pOS2 : ''), hideable:fase) { if (state.hT == _Hubitat) paragraph subHeaders(pOS2) inputNRDRS('noMotionOccupied', "After how many seconds?", false, null, "5..99999") } } } def pageEngagedSettings() { def buttonNames = genericButtons def engagedButtonOptions = [:] if (engagedButton) { def engagedButtonAttributes = engagedButton.supportedAttributes def attributeNameFound = false for (def att : engagedButtonAttributes) { if (att.name == occupancy) buttonNames = occupancyButtons if (att.name == 'numberOfButtons') attributeNameFound = true } def numberOfButtons = engagedButton.currentNumberOfButtons if (attributeNameFound && numberOfButtons) { for (def i = 1; i <= numberOfButtons && i <= 16; i++) engagedButtonOptions << [(i.toString()):(buttonNames[i])] } else engagedButtonOptions << [null:"No buttons"] } // def roomDevices = parent.getRoomNames(app.id) def powerFromTimeHHmm = (powerFromTime ? format24hrTime(timeToday(powerFromTime, location.timeZone)) : '') def powerToTimeHHmm = (powerToTime ? format24hrTime(timeToday(powerToTime, location.timeZone)) : '') if (ht == _Hubitat && engagedButton && !engagedButtonType) app.updateSetting("engagedButtonType", [type: "enum", value: "$pushAButton"]) def pES1 = 'Change room to ENGAGED' def pES2 = 'Timeout for ENGAGED and other settings' def pES3 = 'Power value to set room to ENGAGED state?' def pES4 = 'Power time range?' def pES5 = 'Power value triggers ENGAGED from VACANT?' def pES6 = 'Power value triggers ENGAGED from OCCUPIED?' def pES60 = 'Power value keeps room ENGAGED?' def pES61 = 'Power stays above for how many seconds to set ENGAGED?' def pES7 = 'Power stays below for how many seconds to reset ENGAGED?' def pES8 = 'When room is busy?' def pES9 = 'Repeated motion triggers busy check?' def pES10 = 'Button to set ENGAGED?' def pES11 = 'Button number?' def pES12 = 'Button only sets ENGAGED?' def pES13 = 'Presence sensor actions?' def pES14 = 'Keep room engaged when presence sensor present?' def pES15 = 'Set room to engaged when music starts playing?' def pES16 = 'Reset ENGAGED with power when contact sensor open?' def pES18 = 'Contact sensor does not trigger ENGAGED?' dynamicPage(name:"pageEngagedSettings", title:"Engaged Settings", install:false, uninstall:false) { section((state.hT != _Hubitat ? pES1 : ''), hideable:false) { if (state.hT == _Hubitat) paragraph subHeaders(pES1); if (motionSensors || accelSensors) inputERMSDO('busyCheck', pES8, false, false, true, null, [[null:"No auto engaged"], [5:"Light traffic"], [7:"Medium Traffic"], [9:"Heavy Traffic"]]) else paragraph pES8 + '\nselect motion sensors above to set' if ((motionSensors || accelSensors) && busyCheck) inputBRDS('repeatedMotion', pES9, false, false) else paragraph pES9 + '\nselect motion sensor and busy check to set' if (state.hT == _Hubitat) inputERMSDO('engagedButtonType', 'Button type?', (engagedButton ? true : false), false, true, null, [["$pushAButton":'Push button'], ["$holdAButton":'Hold button'], ["$doubleTapAButton":'Double tap button']]) if (state.hT == _SmartThings || engagedButtonType) inputDRMS('engagedButton', "${(state.hT == _SmartThings ? 'button' : engagedButtonType)}", pES10, false, false, true) else paragraph pES10 + '\nselect button type to set' if (engagedButton) { inputERMSDO('buttonIs', pES11, true, false, false, null, engagedButtonOptions) inputBRDS('buttonOnlySetsEngaged', pES12, false, false) } else { paragraph pES11 + '\nselect button above to set' paragraph pES12 } if (personsPresence) { inputERMSDO('presenceAction', pES13, true, false, false, 3, [[1:"Set state to ENGAGED on Arrival"], [2:"Set state to VACANT on Departure"], [3:"Both actions"],[4:"Neither action"]]) inputBRDS('presenceActionContinuous', pES14, false, false) } else { paragraph pES13 + '\nselect presence sensors to set' paragraph pES14 } inputDRMS('engagedSwitch', 'switch', 'If switch turns ON?', false, true) if (musicDevice) input "musicEngaged", "bool", title:pES15, required:false, defaultValue:false else paragraph pES15 + '\nselect music device in speaker settings to set' if (state.hT == _Hubitat) paragraph subHeaders('Via POWER WATTAGE'); if (powerDevice && !powerValueAsleep && !powerValueLocked) { inputNRDRS('powerValueEngaged', pES3, false, null, "0..99999", true) // inputERMSDO('powerValueEngagedCheck', 'Power value below?', (powerValueEngaged ? true : false), false, false, null, [null:"Not Below", 1:"Yes below"]) href "pagePowerTime", title:pES4, description:"${(powerFromTimeType || powerToTimeType ? (powerFromTimeType == _timeTime ? "$powerFromTimeHHmm" : (powerFromTimeType == _timeSunrise ? "Sunrise" : "Sunset") + (powerFromTimeOffset ? " $powerFromTimeOffset" : "")) + ' : ' + (powerToTimeType == _timeTime ? "$powerToTimeHHmm" : (powerToTimeType == _timeSunrise ? "Sunrise" : "Sunset") + (powerToTimeOffset ? " $powerToTimeOffset" : "")) : 'Add power time range')}" inputBRDS('powerTriggerFromVacant', pES5, false, true) inputBRDS('powerTriggerFromOccupied', pES6, false, true) inputBRDS('powerKeepsEngaged', pES60, false, true) inputNRDRS('powerStaysAbove', pES61, (powerValueEngaged ? true : false), 0, "0..300") inputNRDRS('powerStays', pES7, (powerValueEngaged ? true : false), 30, "0..999") } else { paragraph pES3 + '\nno power device or power value already used for another state' paragraph pES4 paragraph pES5 paragraph pES6 paragraph pES7 } if (state.hT == _Hubitat) paragraph subHeaders('Via CONTACT SENSOR'); // inputDRMS('contactSensor', 'contactSensor', 'Contact sensor closes?', false, true, true) if (contactSensor) { // inputBRDS('contactSensorOutsideDoor', pES17, false, false) inputBRDS('contactSensorNotTriggersEngaged', pES18, false, false) } else { // paragraph pES17 + '\nselect contact sensor to set' paragraph pES18 + '\nselect contact sensor to set' } if (contactSensor && powerValueEngaged) inputERMSDO('resetEngagedWithContact', pES16, false, false, false, null, [null:"Never", 5:"5 mins", 10:"10 mins", 15:"15 mins", 30:"30 mins", 60:"60 mins"]) else paragraph pES16 + '\nselect contact sensor and power value to set' } section((state.hT != _Hubitat ? pES2 : ''), hideable:false) { if (state.hT == _Hubitat) paragraph subHeaders(pES2); inputNRDRS('noMotionEngaged', "Require motion within how many seconds when room is ENGAGED?", false, null, "5..99999") inputERMSDO('anotherRoomEngaged', 'Reset ENGAGED OR ASLEEP state for room when another room changes to ENGAGED OR ASLEEP?', false, true, false, null, state.roomDevices) inputBRDS('resetEngagedDirectly', 'When resetting room from ENGAGED directly move to VACANT?', false, false) inputBRDS('engagedOverrides', 'ENGAGED overrides trigger devices for OCCUPIED?', false, false) } } } def pageCheckingSettings() { def pCS1 = 'If any light is on dim by what level?' def pCS2 = 'If no light is on turn on and dim to what level?' def pCS3 = 'Below what lux value?' dynamicPage(name:"pageCheckingSettings", title:"Checking Settings", install:false, uninstall:false) { section("CHECKING state timer before room changes to VACANT:", hideable: false) { inputNRDRS('dimTimer', "How many seconds? (recommended 2x motion sensor blind window. doubles as dim timer.)", false, 5, "5..99999", true) } section("Light level", hideable:false) { if (dimTimer) { inputNRDRS('dimByLevel', pCS1, false, null, "1..99") inputNRDRS('dimToLevel', pCS2, false, null, "1..99") } else { paragraph pCS1 + '\nselect timer seconds above to set' paragraph pCS2 } if (dimTimer && dimToLevel && luxSensor) inputNRDRS('luxCheckingDimTo', pCS3, false, null, "0..*") else paragraph pCS3 + '\nset dim timer, dim to level and lux sensor to select' inputBRDS('notRestoreLL', 'No restore light level after dimming during CHECKING if VACANT now?', false, false) } } } def pageVacantSettings() { def buttonNames = genericButtons def vacantButtonOptions = [:] if (vacantButton) { def vacantButtonAttributes = vacantButton.supportedAttributes def attributeNameFound = false for (def att : vacantButtonAttributes) { if (att.name == occupancy) buttonNames = occupancyButtons; if (att.name == 'numberOfButtons') attributeNameFound = true; } def numberOfButtons = vacantButton.currentNumberOfButtons if (attributeNameFound && numberOfButtons) for (def i = 1; i <= numberOfButtons && i <= 16; i++) vacantButtonOptions << [(i.toString()):(buttonNames[i])]; else vacantButtonOptions << [null:"No buttons"] } if (ht == _Hubitat && vacantButton && !vacantButtonType) app.updateSetting("vacantButtonType", [type:"enum", value:"$pushAButton"]); def pVS1 = 'Button to set VACANT?' def pVS2 = 'Button Number?' def pVS3 = 'Pause speaker on VACANT?' dynamicPage(name:"pageVacantSettings", title:"Vacant Settings", install:false, uninstall:false) { section("Change room to VACANT when?", hideable:false) { if (state.hT == _Hubitat) inputERMSDO('vacantButtonType', 'Button type?', (vacantButton ? true : false), false, true, null, [["$pushAButton":'Push button'], ["$holdAButton":'Hold button'], ["$doubleTapAButton":'Double tap button']]) if (state.hT == _SmartThings || vacantButtonType) inputDRMS('vacantButton', "${(state.hT == _SmartThings ? 'button' : vacantButtonType)}", pVS1, false, false, true) else paragraph pVS1 = '\nselect button type to set' if (vacantButton) inputERMSDO('buttonIsVacant', pVS2, true, false, false, null, vacantButtonOptions) else paragraph pVS2 + '\nselect button to set' inputDRMS('vacantSwitches', 'switch', 'If switch turns OFF?', false, true) } section("Pause music on VACANT:", hideable:false) { if (musicDevice) inputBRDS('turnOffMusic', pVS3, false, false) else paragraph pVS3 + '\nselect music player in speaker settings to set' } } } def pageAutoLevelSettings() { def wTime, sTime if (state.ruleHasAL || wakeupTime || sleepTime) if (state.hT == _SmartThings && (!wakeupTime || !sleepTime)) sendNotification("Invalid time range!", [method:"push"]) updateRulesToState() def levelRequired = (autoColorTemperature || state.ruleHasAL || minLevel || maxLevel ? true : false) def pALS1 = 'Starting how many hours before?' def pALS2 = 'Ending how many hours after?' dynamicPage(name: "pageAutoLevelSettings", title:"'AL' Level Settings", install:false, uninstall:false) { section("Auto light level min/max:", hideable:false) { inputNRDRS('minLevel', "Minimum level?", levelRequired, (levelRequired ? 1 : null), "1..${maxLevel ?: 100}", true) inputNRDRS('maxLevel', "Maximum level?", levelRequired, (levelRequired ? 100 : null), "${minLevel ?: 1}..100", true) } if (levelRequired) { section("Wake and sleep time:", hideable:false) { input "wakeupTime", "time", title:"Wakeup Time?", required: levelRequired, defaultValue:"07:00", submitOnChange:true input "sleepTime", "time", title:"Sleep Time?", required: levelRequired, defaultValue:"23:00", submitOnChange:true } section("Fade level up:", hideable:false) { inputBRDS('fadeLevelWake', 'Fade up to wake time?', true, false, true) if (fadeLevelWake) { inputNRDRS('fadeWakeBefore', pALS1, true, 1, "0..10", true) inputNRDRS('fadeWakeAfter', pALS2, true, 0, "0..10", true) } else { paragraph pALS1 + '\nset fade level up to set' paragraph pALS2 } } section("Fade level down:", hideable:false) { inputBRDS('fadeLevelSleep', 'Fade down to sleep time?', true, false, true) if (fadeLevelSleep) { inputNRDRS('fadeSleepBefore', pALS1, true, 2, "0..10", true) inputNRDRS('fadeSleepAfter', pALS2, true, 0, "0..10", true) } else { paragraph pALS1 + '\nset fade level down to set' paragraph pALS2 } } section("Auto color temperature:", hideable:false) { inputBRDS('autoColorTemperature', 'Auto set color temperature?', false, false, true) if (autoColorTemperature) { inputNRDRS('minKelvin', "Minimum kelvin?", true, 1900, "1500..${maxKelvin?:9000}", true) inputNRDRS('maxKelvin', "Maximum kelvin?", true, 6500, "$minKelvin..9000", true) } else { paragraph "Minimum kelvin?\nenable auto color temperature above to set" paragraph "Maximum kelvin?" } } section("Fade color temperature up:", hideable:false) { inputBRDS('fadeCTWake', 'Fade up to wake time?', true, false, true) if (autoColorTemperature && fadeCTWake) { inputNRDRS('fadeKWakeBefore', pALS1, true, 1, "0..10", true) inputNRDRS('fadeKWakeAfter', pALS2, true, 0, "0..10", true) } else { paragraph pALS1 + '\nset fade auto color temperature up to set' paragraph pALS2 } } section("Fade color temperature down:", hideable:false) { inputBRDS('fadeCTSleep', 'Fade down to sleep time?', true, false, true) if (autoColorTemperature && fadeCTSleep) { inputNRDRS('fadeKSleepBefore', pALS1, true, 5, "0..10", true) inputNRDRS('fadeKSleepAfter', pALS2, true, 0, "0..10", true) } else { paragraph pALS1 + '\nset fade color temperature down to set' paragraph pALS2 } } } } } def pageHolidayLightPatterns() { state.holiPassedOn = false updateRulesToState() dynamicPage(name:"pageHolidayLightPatterns", title:"'HL' Settings", install:false, uninstall:false) { section("", hideable: false) { def eCS = 99 for (def i = 1; i <= maxHolis; i++) { if (settings["holiName$i"] || settings["holiColorString$i"]) { href "pageHolidayLight", title: (settings["holiName$i"] ?: settings["holiColorString$i"]), params:[holiLightNo:"$i"], required:false if (!(settings["holiName$i"] && settings["holiColorString$i"])) eCS = 88 } } if (eCS == 99) for (def i = 1; i <= maxHolis; i++) if (!(settings["holiName$i"] && settings["holiColorString$i"])) { eCS = i; break; } if (eCS < 11) href "pageHolidayLight", title:"Create new holiday light pattern", params:[holiLightNo:"$eCS"], required:false } } } def pageHolidayLight(params) { if (!state.holiPassedOn && params) { state.holiPassedOn = true state.holiPassedParams = params } if (params.holiLightNo) state.pageHoliLightNo = params.holiLightNo else if (state.holiPassedParams) state.pageHoliLightNo = state.holiPassedParams.holiLightNo def holiLightNo = state.pageHoliLightNo //log.debug "params: $params | state.holiPassedOn: $state.holiPassedOn | state.holiPassedParams: $state.holiPassedParams | state.pageHoliLightNo: $state.pageHoliLightNo | holiLightNo: $holiLightNo" //def holiStyle = settings["holiStyle$holiLightNo"] def holiColors = settings["holiColorString$holiLightNo"] //def pHL1 = "${(settings["holiStyle$holiLightNo"] == 'RO' ? 'Rotate' : 'Twinkle')} every how many seconds?" def pHL1 = "Rotate every how many seconds?" def pHL2 = 'Set light level to?' dynamicPage(name:"pageHolidayLight", title:"Holiday Light Pattern", install:false, uninstall:false) { section() { input "holiName$holiLightNo", "text", title:"Color string name?", required:true, submitOnChange:true input "holiColorString$holiLightNo", "text", title:"Comma delimited colors?", required:true, submitOnChange:true //inputERMSDO("holiStyle$holiLightNo", 'Light routine?', true, false, true, null, [[RO:"Rotate"], [TW:"Twinkle"]]) //if (holiStyle) { if (holiColors) { //inputNRDRS("holiSeconds$holiLightNo", pHL1, true, "${(holiStyle == 'RO' ? 15 : 3)}", "${(holiStyle == 'RO' ? "5..300" : "2..10")}", true) //inputERMSDO("holiLevel$holiLightNo", pHL2, false, false, false, null, [[1:"1%"], [5:"5%"], [10:"10%"], [15:"15%"], [20:"20%"], [25:"25%"], [30:"30%"], [40:"40%"], [50:"50%"], [60:"60%"], [70:"70%"], [80:"80%"], [90:"90%"], [99:"99%"], [100:"100%"]]) inputNRDRS("holiSeconds$holiLightNo", pHL1, true, 15, "5..300") inputERMSDO("holiLevel$holiLightNo", pHL2, false, false, false, null, [[1:"1%"], [5:"5%"], [10:"10%"], [15:"15%"], [20:"20%"], [25:"25%"], [30:"30%"], [40:"40%"], [50:"50%"], [60:"60%"], [70:"70%"], [80:"80%"], [90:"90%"], [100:"100%"]]) } else { paragraph pHL1 + '\nSelect holiday light style to set' paragraph pHL2 } } } } def pageRules() { webCoRE_init() updateRulesToState() state.passedOn = false state.pList = [] for (def wC : webCoRE_list()) state.pList << [(wC.id):(wC.name)]; dynamicPage(name: "pageRules", title: "Maintain Rules", install: false, uninstall: false) { section() { def emptyRule = null if (!state.rules) emptyRule = 1 else { for (def i = 1; i <= maxRules; i++) { def ruleNo = String.valueOf(i) def thisRule = getRule(ruleNo, '*', false) if (thisRule) { def ruleDesc = "$ruleNo: $thisRule.name -" ruleDesc = (thisRule.mode ? "$ruleDesc Mode=$thisRule.mode" : "$ruleDesc") ruleDesc = (thisRule.state ? "$ruleDesc State=$thisRule.state" : "$ruleDesc") if (!thisRule.type || thisRule.type == _ERule) { ruleDesc = (thisRule.luxThreshold != null ? "$ruleDesc Lux=$thisRule.luxThreshold" : "$ruleDesc") ruleDesc = (thisRule.luxCheck ? "$ruleDesc Lux Check=$thisRule.luxCheck" : "$ruleDesc") ruleDesc = (thisRule.powerThreshold ? "$ruleDesc Power=$thisRule.powerThreshold" : "$ruleDesc") ruleDesc = (thisRule.powerCheck ? "$ruleDesc Power Check=$thisRule.powerCheck" : "$ruleDesc") ruleDesc = (thisRule.presence ? "$ruleDesc Presence=$thisRule.presence" : "$ruleDesc") ruleDesc = (thisRule.checkOn ? "$ruleDesc Check ON=$thisRule.checkOn" : "$ruleDesc") ruleDesc = (thisRule.checkOff ? "$ruleDesc Check OFF=$thisRule.checkOff" : "$ruleDesc") ruleDesc = (thisRule.wet ? "$ruleDesc Wet=$thisRule.wet" : "$ruleDesc") ruleDesc = (thisRule.piston ? "$ruleDesc Piston=$thisRule.piston" : "$ruleDesc") ruleDesc = (thisRule.actions ? "$ruleDesc Routines=$thisRule.actions" : "$ruleDesc") } if (thisRule.fromTimeType && thisRule.toTimeType) { def ruleFromTimeHHmm = (thisRule.fromTime ? format24hrTime(timeToday(thisRule.fromTime, location.timeZone)) : '') def ruleToTimeHHmm = (thisRule.toTime ? format24hrTime(timeToday(thisRule.toTime, location.timeZone)) : '') ruleDesc = (thisRule.fromTimeType == _timeTime ? "$ruleDesc From=$ruleFromTimeHHmm" : (thisRule.fromTimeType == _timeSunrise ? "$ruleDesc From=Sunrise" : "$ruleDesc From=Sunset")) ruleDesc = (thisRule.toTimeType == _timeTime ? "$ruleDesc To=$ruleToTimeHHmm" : (thisRule.toTimeType == _timeSunrise ? "$ruleDesc To=Sunrise" : "$ruleDesc To=Sunset")) } if (thisRule.type == _HRule) { ruleDesc = (thisRule.baseHumidity ? "$ruleDesc Baseline=$thisRule.baseHumidity" : "$ruleDesc") ruleDesc = (thisRule.humidity ? "$ruleDesc Heat=$thisRule.humidity" : "$ruleDesc") } else if (thisRule.type == _TRule) { ruleDesc = (thisRule.coolTemp ? "$ruleDesc Cool=$thisRule.coolTemp" : "$ruleDesc") ruleDesc = (thisRule.heatTemp ? "$ruleDesc Heat=$thisRule.heatTemp" : "$ruleDesc") } else { ruleDesc = (thisRule.switchesOn ? "$ruleDesc ON=$thisRule.switchesOn" : "$ruleDesc") ruleDesc = (thisRule.switchesOff ? "$ruleDesc OFF=$thisRule.switchesOff" : "$ruleDesc") ruleDesc = (thisRule.disabled ? "$ruleDesc Disabled=$thisRule.disabled" : "$ruleDesc") } href "pageRule", title: "$ruleDesc", params: [ruleNo: "$ruleNo"], required: false } else if (!emptyRule) emptyRule = i } } if (emptyRule) href "pageRule", title: "Create new rule", params: [ruleNo: emptyRule], required: false else paragraph "At max number of rules: 10" if (state.hT == _Hubitat && !hideAdvanced) href "pageRuleDelete", title: "DELETE rules?", description: "Selected rules will be DELETED on save. So DONT mark rules for deletion then edit them.", required: false } } } def pageRuleDelete() { def rulesList = parent.pageRuleDelete(getAllRules()) //log.debug rulesList ifDebug("rulesList: $rulesList") dynamicPage(name: "pageRuleDelete", title: "", install: false, uninstall: false) { section() { inputERMSDO('rulesToDelete', 'DELETE which rules?', false, true, false, null, rulesList) } } } def pageRule(params) { if (!state.passedOn && params) { state.passedOn = true state.passedParams = params } if (params?.ruleNo) state.pageRuleNo = params.ruleNo else if (state.passedParams) state.pageRuleNo = state.passedParams.ruleNo def ruleNo = state.pageRuleNo def ruleFromTimeType = settings["fromTimeType$ruleNo"] def ruleToTimeType = settings["toTimeType$ruleNo"] def ruleFromTimeHHmm = (settings["fromTime$ruleNo"] ? format24hrTime(timeToday(settings["fromTime$ruleNo"], location.timeZone)) : '') def ruleToTimeHHmm = (settings["toTime$ruleNo"] ? format24hrTime(timeToday(settings["toTime$ruleNo"], location.timeZone)) : '') def ruleFromTimeOffset = settings["fromTimeOffset$ruleNo"] def ruleToTimeOffset = settings["toTimeOffset$ruleNo"] def ruleTimerOverride = (settings["noMotion$ruleNo"] || settings["noMotionEngaged$ruleNo"] || settings["dimTimer$ruleNo"] || settings["noMotionAsleep$ruleNo"]) def ruleType = settings["type$ruleNo"] def levelOptions = [] if (autoColorTemperature || minLevel || maxLevel) levelOptions << [AL:"Auto Level (and color temperature)"]; for (def i = 1; i <= maxHolis; i++) { if (settings["holiName$i"] && settings["holiColorString$i"]) levelOptions << ["HL$i": settings["holiName$i"]] } [[1:"1%"], [5:"5%"], [10:"10%"], [15:"15%"], [20:"20%"], [25:"25%"], [30:"30%"], [40:"40%"], [50:"50%"], [60:"60%"], [70:"70%"], [80:"80%"], [90:"90%"], [99:"99%"], [100:"100%"]].each { levelOptions << it } def colorsList = colorsRGB.collect { [(it.key):it.value[1]] } boolean isFarenheit = (location.temperatureScale == 'F' ? true : false) def roomPresenceSensors = personsPresence.collect{ [(it.id): "${it.displayName}"] } def pR1 = 'What lux value?' def pR1x1 = 'Lux comparison?' def pR2 = 'What power value?' def pR2x1 = 'Power comparison?' def pR3 = 'Which presence sensors present?' def pR4 = 'What humidity range? (condition)' def pR5 = 'Cool to what temperature?' def pR6 = 'Heat to what temperature?' def pR7 = 'Within temperature range?' def pR8 = 'Fan on at temperature?' def pR9 = 'Fan speed with what temperature increments?' def pR10 = 'Keep dehumidifier on?' def pR11 = 'Keep humidifier on?' def pR12 = 'For how many minutes?' def pR13 = 'Water leak?' dynamicPage(name: "pageRule", title: "Edit Rule", install: false, uninstall: false) { section() { ifDebug("rule number page ${ruleNo}") paragraph "Rule number: $ruleNo" inputERMSDO("type$ruleNo", 'Rule type?', true, false, true, null, [[e:"Execution Rule"],[h:"Humidity Rule"],[t:"Temperature Rule"]]) input "name$ruleNo", "text", title: "Rule name?", required:false, capitalization: "none" inputBRDS("disabled$ruleNo", 'Rule disabled?', false, false) if (state.hT == _Hubitat) paragraph subHeaders("Rule triggers and conditions"); inputERMSDO("state$ruleNo", 'Which state?', false, true, false, null, [asleep, engaged, occupied, vacant]) input "mode$ruleNo", "mode", title: "Which mode?", required: false, multiple: true inputERMSDO("dayOfWeek$ruleNo", 'Which days of the week?', false, true, false, null, [[null:"All Days of Week"], [8:"Monday to Friday"], [9:"Saturday & Sunday"], [2:"Monday"], [3:"Tuesday"], [4:"Wednesday"], [5:"Thursday"], [6:"Friday"], [7:"Saturday"], [1:"Sunday"]]) if (!ruleType || ruleType == _ERule) { if (luxSensor) { inputNRDRS("luxThreshold$ruleNo", pR1, false, null, "0..*", true) if (settings["luxThreshold$ruleNo"]) inputERMSDO("luxCheck$ruleNo", pR1x1, true, false, false, false, [[false:"Less than or equals to"], [true:"Greater than"]]) else paragraph pR1x1 } else { paragraph pR1 + '\nset lux sensors in main settings to select' paragraph pR1x1 } if (powerDevice) { inputNRDRS("powerThreshold$ruleNo", pR2, false, null, "1..*", true) if (settings["powerThreshold$ruleNo"]) inputERMSDO("powerCheck$ruleNo", pR2x1, true, false, false, false, [[false:"Greater than or equals to"], [true:"Less than"]]) else paragraph pR2x1 } else { paragraph pR2 + '\nset power meter in main settings to select' paragraph pR2x1 } if (personsPresence && !presenceActionContinuous) inputERMSDO("presenceCheck$ruleNo", pR3, false, false, false, null, roomPresenceSensors) else paragraph pR3 + '\nset presence sensors in main settings to select' inputDRMS("checkOn$ruleNo", 'switch', 'Check switches are ON?', false, true) inputDRMS("checkOff$ruleNo", 'switch', 'Check switches are OFF?', false, true) if (waterSensors) inputBRDS("wet$ruleNo", pR13, false, false, false) else paragraph pR13 + '\nset water sensors in main settings to select' } } if ((!ruleType || ruleType == _ERule) && !hideAdvanced) { if (humiditySensor) section("") { href "pageHumidity", title: pR4, description: "${(settings["fromHumidity$ruleNo"] || settings["toHumidity$ruleNo"] ? settings["fromHumidity$ruleNo"] + ' - ' + settings["toHumidity$ruleNo"] : 'Add humidity range')}", params: [ruleNo: "$ruleNo"] } else section("") { paragraph pR4 + '\nset humidity sensor in main settings to select' } } if ((!ruleType || ruleType == _ERule) && !hideAdvanced) { section("") { href "pageRuleDate", title: "Date filter? (condition)", description: "${(settings["fromDate$ruleNo"] || settings["toDate$ruleNo"] ? settings["fromDate$ruleNo"] + ' - ' + settings["toDate$ruleNo"] : 'Add date filtering')}", params: [ruleNo: "$ruleNo"] } } section("") { href "pageRuleTime", title: "Time trigger?", description: "${(ruleFromTimeType || ruleToTimeType ? (ruleFromTimeType == _timeTime ? "$ruleFromTimeHHmm" : (ruleFromTimeType == _timeSunrise ? "Sunrise" : "Sunset") + (ruleFromTimeOffset ? " $ruleFromTimeOffset" : "")) + ' : ' + (ruleToTimeType == _timeTime ? "$ruleToTimeHHmm" : (ruleToTimeType == _timeSunrise ? "Sunrise" : "Sunset") + (ruleToTimeOffset ? " $ruleToTimeOffset" : "")) : 'Add time trigger')}", params: [ruleNo: "$ruleNo"] } if (!ruleType || ruleType == _ERule) { section("Lights and switches to turn ON:", hideable: false) { if (state.hT == _Hubitat) paragraph subHeaders("Switches to turn on and off"); inputDRMS("switchesOn$ruleNo", 'switch', 'Turn ON which switches?', false, true) inputERMSDO("setLevelTo$ruleNo", 'Set level?', false, false, true, null, levelOptions) inputERMSDO("setColorTo$ruleNo", 'Set color?', false, false, false, null, colorsList) if (settings["setLevelTo$ruleNo"] == 'AL' && autoColorTemperature) paragraph "Set color temperature?\ncannot set when level is set to 'AL'" else inputNRDRS("setColorTemperatureTo$ruleNo", "Set color temperature? (if light supports color & color is specified setting will be ignored.)", false, null, "1500..6500") inputDRMS("switchesOff$ruleNo", 'switch', 'Turn OFF which switches?', false, true) } if (!hideAdvanced) { section("") { if (state.hT == _Hubitat) paragraph subHeaders("Other execution options"); href "pageRuleCommands", title: "Device commands", description: "${(settings["device$ruleNo"] ? settings["device$ruleNo"].toString() + ' : ' + (settings["cmds$ruleNo"] ?: '') : "Tap to configure")}", params: [ruleNo: "$ruleNo"] } section("") { href "pageRuleOthers", title: "Routines/pistons and more", description: "${(settings["actions$ruleNo"] || settings["piston$ruleNo"] ? "Tap to change existing settings" : "Tap to configure")}", params: [ruleNo: "$ruleNo"] } section("") { href "pageRuleTimer", title: "Timer overrides", description: "${(ruleTimerOverride ? (settings["noMotion$ruleNo"] ?: '') + ', ' + (settings["noMotionEngaged$ruleNo"] ?: '') + ', ' + (settings["dimTimer$ruleNo"] ?: '') + ', ' + (settings["noMotionAsleep$ruleNo"] ?: '') : 'Add timer overrides')}", params: [ruleNo: "$ruleNo"] } } } else if (ruleType == _TRule) { section("Maintain room temperature?", hideable: false) { if (state.hT == _Hubitat) paragraph subHeaders("Maintain temperature settings"); if (['1', '3'].contains(maintainRoomTemp) && ((useThermostat && roomThermostat) || (!useThermostat && roomCoolSwitch))) input "coolTemp$ruleNo", "decimal", title:pR5, required: (!settings["fanOnTemp$ruleNo"] || maintainRoomTemp == '5' ? true : false), range: "${(isFarenheit ? '32..99' : '0..38')}", submitOnChange: true else paragraph pR5 + '\nset thermostat or cool switch to set' if (['2', '3'].contains(maintainRoomTemp) && ((useThermostat && roomThermostat) || (!useThermostat && roomHeatSwitch))) input "heatTemp$ruleNo", "decimal", title:pR6, required: true, range: "${(isFarenheit ? '32..99' : '0..38')}" else paragraph pR6 + '\nset thermostat or heat switch to set' if (['1', '2', '3'].contains(maintainRoomTemp) && ((useThermostat && roomThermostat) || (!useThermostat && (roomCoolSwitch || roomHeatSwitch)))) input "tempRange$ruleNo", "decimal", title:pR7, required: true, defaultValue: 0.4, trange: "0..3" else paragraph pR7 + '\nset thermostat or cool/heat switch to set' } if (!hideAdvanced) { section("Fan control?", hideable: false) { if (roomFanSwitch) { input "fanOnTemp$ruleNo", "decimal", title:pR8, required: false, defaultValue: null, range: "${(isFarenheit ? '32..99' : '0..38')}", submitOnChange: true input "fanSpeedIncTemp$ruleNo", "decimal", title:pR9, required: (settings["fanOnTemp$ruleNo"] ? true : false), defaultValue: 1, range: "1..5" } else { paragraph pR8 + '\nset fan switch to set' paragraph pR9 } } section("Vent control?", hideable: false) { if (useThermostat && roomVents) paragraph "Rooms vents will be automatically controlled with thermostat and room temperature." else paragraph "Enabled when using thermostat and room vents is set" } } } else { section("Maintain room humidity?", hideable: false) { if (state.hT == _Hubitat) paragraph subHeaders("Maintain humidity settings") if (roomDehumidifierSwitch && !settings["humiOn$ruleNo"]) inputBRDS("dehumiOn$ruleNo", pR10, true, false, true) else paragraph pR10 + '\nset dehumidifier switch to set' if (roomHumidifierSwitch && !settings["deHumiOn$ruleNo"]) inputBRDS("humiOn$ruleNo", pR11, true, false, true) else paragraph pR11 + '\nset humidifier switch to set' if (state.hT == _Hubitat) paragraph subHeaders("Maintain humidity settings"); inputERMSDO("humiCmp$ruleNo", 'Compare to?', false, false, true, null, [[0:"Value"], [1:"Room humidity when last VACANT"], [2:"Hourly average"], [3:"Daily average"], [4:"Weekly average"]]) if (settings["humiCmp$ruleNo"] == '0') input "humiValue$ruleNo", "decimal", title: "Humidity value?", required:true, range:"0.1..100" // else // if (settings["humiCmp$ruleNo"]) inputNRDRS("humiPercent$ruleNo", "Percentage ${(['0', '1'].contains(settings["humiCmp$ruleNo"]) ? 'change' : 'delta')} for trigger?", true, 5, "1..99") if (settings["state$ruleNo"]) inputNRDRS("humiMins$ruleNo", pR12, true, 0, "0..60") else paragraph pR12 + '\nselect state to set' inputNRDRS("humiMinRun$ruleNo", "Minimum run time?", false, null, "1..60", true) inputNRDRS("humiMaxRun$ruleNo", "Maximum run time?", false, null, "${(settings["humiMinRun$ruleNo"] ?: 1)}..60") } } } } private subHeaders(str) { if (str.size() > 50) str = str.substring(0, 50); return "
${str.toUpperCase().center(50)}
" } def pageHumidity(params) { if (params?.ruleNo) state.pageRuleNo = params.ruleNo; else if (state.passedParams) state.pageRuleNo = state.passedParams.ruleNo; def ruleNo = state.pageRuleNo def fHum = settings["fromHumidity$ruleNo"] def tHum = settings["toHumidity$ruleNo"] dynamicPage(name: "pageHumidity", title: "Edit Rule Humidity", install: false, uninstall: false) { section() { input "fromHumidity$ruleNo", "decimal", title: "From humidity?", required: (tHum ? true : false), defaultValue: null, range: "0.1..${(tHum ?: 100.0)}", submitOnChange: true input "toHumidity$ruleNo", "decimal", title: "To humidity?", required: (fHum ? true : false), defaultValue: null, range: "${(fHum ?: 0.1)}..100.0", submitOnChange: true } } } def pageRuleDate(params) { if (params?.ruleNo) state.pageRuleNo = params.ruleNo; else if (state.passedParams) state.pageRuleNo = state.passedParams.ruleNo; def ruleNo = state.pageRuleNo def ruleFromDate = settings["fromDate$ruleNo"] def ruleToDate = settings["toDate$ruleNo"] if (ruleFromDate && ruleToDate) { def rD = dateInputValid(ruleFromDate, ruleToDate) def fTime = rD[0] def tTime = rD[1] def fTime2 def tTime2 if (fTime && tTime) { fTime2 = new Date().parse("yyyy-MM-dd'T'HH:mm:ssZ", fTime) tTime2 = new Date().parse("yyyy-MM-dd'T'HH:mm:ssZ", tTime) } if (getHubType() == _SmartThings && ((fTime && !tTime) || (!fTime && tTime) || (fTime && tTime && tTime2 < fTime2))) sendNotification("Invalid date range!", [method: "push"]) } dynamicPage(name: "pageRuleDate", title: "Edit Rule Date Filter", install: false, uninstall: false) { section { paragraph 'NO WAY TO VALIDATE DATE FORMAT ON INPUT. If invalid date checking for date will be skipped.' paragraph 'Date formats below support following special values for year to enable dynamic date ranges:\n"yyyy" = this year\n"YYYY" = next year' input "fromDate$ruleNo", "text", title: "From date? (yyyy/MM/dd format)", required: (ruleToDate ? true : false), defaultValue: null, submitOnChange: true input "toDate$ruleNo", "text", title: "To date? (yyyy/MM/dd format)", required: (ruleFromDate ? true : false), defaultValue: null, submitOnChange: true } } } def pageRuleTime(params) { if (params?.ruleNo) state.pageRuleNo = params.ruleNo; else if (state.passedParams) state.pageRuleNo = state.passedParams.ruleNo; def ruleNo = state.pageRuleNo def ruleFromTimeType = settings["fromTimeType$ruleNo"] def ruleToTimeType = settings["toTimeType$ruleNo"] dynamicPage(name: "pageRuleTime", title: "Edit Rule Time Trigger", install: false, uninstall: false) { section() { inputERMSDO("fromTimeType$ruleNo", 'Choose from time type?', (ruleToTimeType ? true : false), false, true, null, [[1:"Sunrise"], [2:"Sunset"], [3:"Time"]]) if (ruleFromTimeType == '3') input "fromTime$ruleNo", "time", title: "From time?", required: true, defaultValue: null else if (ruleFromTimeType) inputNRDRS("fromTimeOffset$ruleNo", "Time offset?", false, 0, "-600..600") else paragraph "Choose from time type to select offset or time" inputERMSDO("toTimeType$ruleNo", 'Choose to time type?', (ruleFromTimeType ? true : false), false, true, null, [[1:"Sunrise"], [2:"Sunset"], [3:"Time"]]) if (ruleToTimeType == '3') input "toTime$ruleNo", "time", title: "To time?", required: true, defaultValue: null else if (ruleToTimeType) inputNRDRS("toTimeOffset$ruleNo", "Time offset?", false, 0, "-600..600") else paragraph "Choose to time type to select offset or time" } } } def pageRuleTimer(params) { if (params?.ruleNo) state.pageRuleNo = params.ruleNo; else if (state.passedParams) state.pageRuleNo = state.passedParams.ruleNo; def ruleNo = state.pageRuleNo dynamicPage(name: "pageRuleTimer", title: "Edit Rule Timer Overrides", install: false, uninstall: false) { section() { paragraph "These settings will temporarily replace the global settings when this rule is executed and reset back to the global settings when this rule no longer matches." inputNRDRS("noMotion$ruleNo", "Timeout after how many seconds when OCCUPIED?", false, null, "5..99999", true) inputNRDRS("noMotionEngaged$ruleNo", "Require motion within how many seconds when ENGAGED?", false, null, "5..99999") inputNRDRS("dimTimer$ruleNo", "CHECKING state timer in seconds?", false, null, "5..99999", true) inputNRDRS("noMotionAsleep$ruleNo", "Motion timeout for night switches when ASLEEP?", false, null, "5..99999") } } } def pageRuleCommands(params) { if (params?.ruleNo) state.pageRuleNo = params.ruleNo; else if (state.passedParams) state.pageRuleNo = state.passedParams.ruleNo; def ruleNo = state.pageRuleNo def deviceIs = (settings["device$ruleNo"] ?: settings["deviceSensor$ruleNo"]) def allCmds if (deviceIs) allCmds = deviceIs.getSupportedCommands().collect{ [(it):it.toString().capitalize()] } ifDebug("deviceIs: $deviceIs | allCmds: $allCmds") dynamicPage(name: "pageRuleCommands", title: "Edit Rule Execute", install: false, uninstall: false) { section("Device commands to issue:", hideable: false) { if (state.hT == _Hubitat || !settings["deviceSensor$ruleNo"]) inputDRMS("device$ruleNo", "${(state.hT == _Hubitat ? '*' : 'switch')}", 'Issue command to switch device?', false, false, true) else paragraph "Issue command to switch device?\nsensor device already selected" if (state.hT != _Hubitat) if (!settings["device$ruleNo"]) inputDRMS("deviceSensor$ruleNo", 'sensor', 'Issue command to sensor device?', false, false, true) else paragraph "Issue command to sensor device?\nswitch device already selected" inputERMSDO("cmds$ruleNo", 'Commands to call?', (deviceIs ? true : false), true, false, null, allCmds) } } } def pageRuleOthers(params) { if (params?.ruleNo) state.pageRuleNo = params.ruleNo; else if (state.passedParams) state.pageRuleNo = state.passedParams.ruleNo; def ruleNo = state.pageRuleNo def allActions = (state.hT == _SmartThings ? location.helloHome?.getPhrases()*.label : []) if (allActions) allActions.sort(); dynamicPage(name: "pageRuleOthers", title: "Edit Rule Execute", install: false, uninstall: false) { section("", hideable: false) { inputERMSDO("actions$ruleNo", 'Routines to execute?', false, true, false, null, allActions) inputERMSDO("piston$ruleNo", 'Piston to execute?', false, false, false, null, state.pList) // paragraph "Rules to execute?\nplaceholder for rule machine rule execution on HE" } } } private dateInputValid(dateInputStart, dateInputEnd) { if ((!dateInputStart || dateInputStart.size() < 8 || dateInputStart.size() > 10) || (!dateInputEnd || dateInputEnd.size() < 8 || dateInputEnd.size() > 10)) return [null, null] def dPS, dPE if (dateInputStart.toLowerCase().substring(0, 5) == 'yyyy/' || dateInputEnd.toLowerCase().substring(0, 5) == 'yyyy/') { def dateIS = yearTranslate(dateInputStart) def dIS = Date.parse("yyyy/M/d HH:mm:ss z", dateIS + ' 00:00:00 ' + location.timeZone.getDisplayName()) def dateIE = yearTranslate(dateInputEnd) def dIE = Date.parse("yyyy/M/d HH:mm:ss z", dateIE + ' 23:59:59 ' + location.timeZone.getDisplayName()) def cDate = new Date(now()) if (cDate > dIE) { Calendar c = Calendar.getInstance() c.setTime(dIS) c.add(Calendar.YEAR, 1) dIS = c.getTime() c.setTime(dIE) c.add(Calendar.YEAR, 1) dIE = c.getTime() } dPS = dIS.format("yyyy-MM-dd'T'HH:mm:ssZ") dPE = dIE.format("yyyy-MM-dd'T'HH:mm:ssZ") } else { def dIS = Date.parse("yyyy/M/d HH:mm:ss z", dateInputStart + ' 00:00:00 ' + location.timeZone.getDisplayName()) dPS = dIS.format("yyyy-MM-dd'T'HH:mm:ssZ") def dIE = Date.parse("yyyy/M/d HH:mm:ss z", dateInputEnd + ' 23:59:59 ' + location.timeZone.getDisplayName()) dPE = dIE.format("yyyy-MM-dd'T'HH:mm:ssZ") } return (!dPS || !dPE ? [null, null] : [dPS, dPE]) } private yearTranslate(dateP) { def rD def tY = (new Date(now())).getAt(Calendar.YEAR) def nY = tY + 1 rD = (dateP.substring(0,5) == 'yyyy/' ? tY + dateP.substring(4) : (dateP.substring(0,5) == 'YYYY/' ? nY + dateP.substring(4) : dateP)) return rD } def pageAsleepSettings() { def buttonNames = genericButtons def asleepButtonOptions = [:] if (asleepButton) { def buttonAttributes = asleepButton.supportedAttributes def attributeNameFound = false for (def att : buttonAttributes) { if (att.name == occupancy) buttonNames = occupancyButtons if (att.name == 'numberOfButtons') attributeNameFound = true } def numberOfButtons = asleepButton.currentNumberOfButtons if (attributeNameFound && numberOfButtons) { for (def i = 1; i <= numberOfButtons && i <= 16; i++) asleepButtonOptions << [(i.toString()):(buttonNames[i])] } else asleepButtonOptions << [null:"No buttons"] } def nightButtonOptions = [:] if (nightButton) { def nightButtonAttributes = nightButton.supportedAttributes def attributeNameFound = false for (def att : nightButtonAttributes) { if (att.name == occupancy) buttonNames = occupancyButtons; if (att.name == 'numberOfButtons') attributeNameFound = true; } def numberOfButtons = nightButton.currentNumberOfButtons if (attributeNameFound && numberOfButtons) { for (def i = 1; i <= numberOfButtons && i <= 16; i++) nightButtonOptions << [(i.toString()):(buttonNames[i])]; } else nightButtonOptions << [null:"No buttons"] } def roomMotionSensors = motionSensors.collect{ [(it.id): "${it.displayName}"] } def powerFromTimeHHmm = (powerFromTime ? format24hrTime(timeToday(powerFromTime, location.timeZone)) : '') def powerToTimeHHmm = (powerToTime ? format24hrTime(timeToday(powerToTime, location.timeZone)) : '') if (ht == _Hubitat) { if (asleepButton && !asleepButtonType) app.updateSetting("asleepButtonType", [type: "enum", value: "$pushAButton"]) if (nightButton && !nightButtonType) app.updateSetting("nightButtonType", [type: "enum", value: "$pushAButton"]) } def colorsList = colorsRGB.collect { [(it.key):it.value[1]] } def aTO = null if (asleepFromTime && asleepToTime) { def fTime = timeToday(asleepFromTime, location.timeZone) def tTime = timeToday(asleepToTime, location.timeZone) def xT = timeTodayA(fTime, tTime, location.timeZone) def iM = ((xT.getTime() - fTime.getTime()) % _SecondsInDay) aTO = ((iM / 60000f).trunc(0)).toInteger() } dynamicPage(name: "pageAsleepSettings", title: "Asleep Settings", install: false, uninstall: false) { section("Change room to ASLEEP when?", hideable: false) { inputDRMS('asleepSensor', 'sleepSensor', 'Sleep sensor to set room to ASLEEP?', false, true) input "asleepFromTime", "time", title: "Set room to asleep at time?", required: (asleepToTime ? true : false), defaultValue: null, submitOnChange: true input "asleepToTime", "time", title: "Wakeup room at time?", required: (asleepFromTime ? true : false), defaultValue: null, submitOnChange: true if (state.hT == _Hubitat) inputERMSDO('asleepButtonType', 'Button type?', (asleepButton ? true : false), false, true, null, [["$pushAButton":'Push button'], ["$holdAButton":'Hold button'], ["$doubleTapAButton":'Double tap button']]) if (state.hT == _SmartThings || asleepButtonType) inputDRMS('asleepButton', "${(state.hT == _SmartThings ? 'button' : asleepButtonType)}", 'Button to toggle ASLEEP?', false, false, true) else paragraph "Button to toggle ASLEEP?\nselect button type to set" if (asleepButton) { inputERMSDO('buttonIsAsleep', 'Button Number?', true, false, false, null, asleepButtonOptions) inputBRDS('buttonOnlySetsAsleep', 'Button only sets Asleep?', false, false) } else { paragraph "Button Number?\nselect button above to set" paragraph "Button only sets Asleep?" } inputDRMS('asleepSwitch', 'switch', 'If switch turns ON?', false, true) def pAS1 = 'Power value to set room to ASLEEP?' def pAS2 = 'Power time range?' def pAS3 = 'Power value triggers ASLEEP from VACANT?' def pAS4 = 'Power stays below for how many seconds to reset ASLEEP?' if (powerDevice && !powerValueEngaged && !powerValueLocked) { inputNRDRS("powerValueAsleep", pAS1, false, null, "0..99999", true) href "pagePowerTime", title:pAS2, description: "${(powerFromTimeType || powerToTimeType ? (powerFromTimeType == _timeTime ? "$powerFromTimeHHmm" : (powerFromTimeType == _timeSunrise ? "Sunrise" : "Sunset") + (powerFromTimeOffset ? " $powerFromTimeOffset" : "")) + ' : ' + (powerToTimeType == _timeTime ? "$powerToTimeHHmm" : (powerToTimeType == _timeSunrise ? "Sunrise" : "Sunset") + (powerToTimeOffset ? " $powerToTimeOffset" : "")) : 'Add power time range')}" inputBRDS('powerTriggerFromVacant', pAS3, false, true) inputNRDRS("powerStays", pAS4, (powerValueAsleep ? true : false), 30, "30..999") } else { paragraph pAS1 + '\nno power device or power value already used for another state' paragraph pAS2 paragraph pAS3 paragraph pAS4 } input "asleepMode", "mode", title: "ASLEEP/AWAKE with location mode change from/to?", required: false, multiple: false if (asleepFromTime && asleepToTime) inputNRDRS("noAsleep", "Timeout ASLEEP after how many minutes?", true, aTO, "$aTO..$aTO") else inputNRDRS("noAsleep", "Timeout ASLEEP after how many hours?", false, null, "1..99") inputBRDS('resetAsleepDirectly', 'When resetting room from ASLEEP directly move to VACANT?', false, false) if (contactSensor) inputERMSDO('resetAsleepWithContact', 'Reset ASLEEP when contact sensor open for?', false, false, false, null, [5:"5 mins", 10:"10 mins", 15:"15 mins", 30:"30 mins", 60:"60 mins"]) else paragraph "Reset ASLEEP when contact sensor open for?\nselect contact sensor in engaged setttings to set" inputBRDS('asleepOverrides', 'ASLEEP overrides state trigger devices for ENGAGED and OCCUPIED?', false, false) } section("${(state.hT != _Hubitat ? 'Night Lights:' : '')}", hideable: false) { if (state.hT == _Hubitat) paragraph subHeaders("Night Lights While Asleep"); if (motionSensors) { inputDRMS('nightSwitches', 'switch', 'Turn ON which night switches with motion?', false, true, true) inputERMSDO('nightMotionSensors', 'Use which room motion sensors?', false, true, false, null, roomMotionSensors) } else { paragraph "Turn ON which night switches with motion?\nselect motion sensor in ROOM DEVICES to set." paragraph "Use which room motion sensors?\nselect night switch above to set" } def pAS11 = 'Set level when turning ON?' def pAS12 = 'Set color temperature when turning ON?' def pAS13 = 'Set color?' def pAS14 = 'Timeout seconds for night lights?' def pAS15 = 'Turn on night lights when?' def pAS16 = 'Button to toggle night lights?' def pAS17 = 'Button Number?' def pAS18 = 'Button Action?' if (nightSwitches) { inputNRDRS('nightSetLevelTo', pAS11, false, null, "1..100") inputNRDRS('nightSetCT', pAS12, false, null, "1500..7500") inputERMSDO('nightSetColorTo', pAS13, false, false, false, null, colorsList) inputNRDRS("noMotionAsleep", pAS14, false, null, "5..99999") inputERMSDO('nightTurnOn', pAS15, true, true, false, null, [[1:"Motion in ASLEEP"], [2:"Changes to ASLEEP"], [3:"Changes away from ASLEEP"]]) if (state.hT == _Hubitat) inputERMSDO('nightButtonType', 'Button type?', (nightButton ? true : false), false, true, null, [["$pushAButton":'Push button'], ["$holdAButton":'Hold button'], ["$doubleTapAButton":'Double tap button']]) if (state.hT == _SmartThings || nightButtonType) inputDRMS('nightButton', "${(state.hT == _SmartThings ? 'button' : nightButtonType)}", pAS16, false, false, true) else paragraph pAS16 + '\nselect button type to set' if (nightButton) { inputERMSDO('nightButtonIs', pAS17, true, false, false, null, nightButtonOptions) inputERMSDO('nightButtonAction', pAS18, true, false, true, null, [[1:"Turn on"], [2:"Turn off"], [3:"Toggle"]]) } else { paragraph pAS17 + '\nselect button above to set' paragraph pAS18 } } else { paragraph pAS11 + '\nselect switches above to set' paragraph pAS12 paragraph pAS13 paragraph pAS14 paragraph pAS15 paragraph pAS16 paragraph pAS17 + '\nselect button above to set' paragraph pAS18 } } } } def pageLockedSettings() { def buttonNames = genericButtons def lockedButtonOptions = [:] state.lockedButtonIsOccupancy = false if (lockedButton) { def lockedButtonAttributes = lockedButton.supportedAttributes def attributeNameFound = false for (def att : lockedButtonAttributes) { if (att.name == occupancy) { buttonNames = occupancyButtons state.lockedButtonIsOccupancy = true } if (att.name == 'numberOfButtons') attributeNameFound = true } def numberOfButtons = lockedButton.currentNumberOfButtons if (attributeNameFound && numberOfButtons) { for (def i = 1; i <= numberOfButtons && i <= 16; i++) lockedButtonOptions << [(i.toString()):(buttonNames[i])] } else lockedButtonOptions << [null:"No buttons"] } def colorsList = colorsRGB.collect { [(it.key):it.value[1]] } def powerFromTimeHHmm = (powerFromTime ? format24hrTime(timeToday(powerFromTime, location.timeZone)) : '') def powerToTimeHHmm = (powerToTime ? format24hrTime(timeToday(powerToTime, location.timeZone)) : '') def pLS1 = 'If switch turns ON?' def pLS2 = 'When contact closes?' def pLS3 = 'Power value to set room to LOCKED?' def pLS4 = 'Power time range?' def pLS5 = 'Power value triggers LOCKED from VACANT?' def pLS6 = 'Power stays below for how many seconds to reset LOCKED?' def pLS7 = 'Button to set LOCKED?' def pLS8 = 'Button number?' def pLS9 = 'Button only sets LOCKED?' def pLS10 = 'Turn on switches when room changes to LOCKED?' def pLS11 = 'Turn off switches when room changes to LOCKED?' def pLS12 = 'Set level when turning ON?' def pLS13 = 'Set color temperature when turning ON?' def pLS14 = 'Set color?' def pLS15 = 'When LOCKED, process temperature rules as if ENGAGED?' dynamicPage(name: "pageLockedSettings", title: "Locked Settings", install: false, uninstall: false) { section("Change room to LOCKED when?", hideable:false) { inputDRMS('lockedSwitch', 'switch', 'Which switches?', false, true, true) if (lockedSwitch) inputBRDS('lockedSwitchCmd', pLS1 + ' (otherwise when switch turns OFF)', true, true) else paragraph pLS2 + '\nselect locked switch above to set' if (state.hT == _Hubitat) inputERMSDO('lockedButtonType', 'Button type?', (lockedButton ? true : false), false, true, null, [["$pushAButton":'Push button'], ["$holdAButton":'Hold button'], ["$doubleTapAButton":'Double tap button']]) if (state.hT == _SmartThings || lockedButtonType) inputDRMS('lockedButton', "${(state.hT == _SmartThings ? 'button' : lockedButtonType)}", pLS7, false, false, true) else paragraph pLS7 + '\nselect button type to set' if (lockedButton) { inputERMSDO('buttonIsLocked', pLS8, true, false, false, null, lockedButtonOptions) inputBRDS('buttonOnlySetsLocked', pLS9, false, false) } else { paragraph pLS8 + '\nselect button above to set' paragraph pLS9 } input "lockedModes", "mode", title: "Modes to set Room to LOCKED", required: false, multiple: true if (powerDevice && !powerValueEngaged && !powerValueAsleep) { inputNRDRS('powerValueLocked', pLS3, false, null, "0..99999", true) href "pagePowerTime", title: pLS4, description: "${(powerFromTimeType || powerToTimeType ? (powerFromTimeType == _timeTime ? "$powerFromTimeHHmm" : (powerFromTimeType == _timeSunrise ? "Sunrise" : "Sunset") + (powerFromTimeOffset ? " $powerFromTimeOffset" : "")) + ' : ' + (powerToTimeType == _timeTime ? "$powerToTimeHHmm" : (powerToTimeType == _timeSunrise ? "Sunrise" : "Sunset") + (powerToTimeOffset ? " $powerToTimeOffset" : "")) : 'Add power time range')}" inputBRDS('powerTriggerFromVacant', pLS5, false, true) inputNRDRS('powerStays', pLS6, (powerValueLocked ? true : false), 30, "30..999") } else { paragraph pLS3 + '\nno power device or power value already used for another state' paragraph pLS4 paragraph pLS5 paragraph pLS6 } inputDRMS('lockedContact', 'contactSensor', 'Which contact for LOCKED?', false, false, true) if (lockedContact) inputBRDS('lockedContactCmd', pLS2 + ' (otherwise when contact opens)', true, true) else paragraph pLS2 + '\nselect locked contact above to set' if (!lockedTurnOff) { inputBRDS('lockedTurnOn', pLS10, false, false, true) inputNRDRS('lockedSetLevelTo', pLS12, false, null, "1..100") inputNRDRS('lockedSetCT', pLS13, false, null, "1500..7500") inputERMSDO('lockedSetColorTo', pLS14, false, false, false, null, colorsList) } else paragraph pLS10 + '\nunselect locked turn off below to set' if (!lockedTurnOn) inputBRDS('lockedTurnOff', pLS11, false, false, true) else paragraph pLS11 + '\nunselect locked turn on above to set' inputBRDS('lockedProcessTemp', pLS15, false, false, false) inputNRDRS('unLocked', "Timeout LOCKED after how many hours?", false, null, "1..99") inputBRDS('lockedOverrides', 'LOCKED overrides trigger devices for other states?', false, false) } } } def pagePowerTime() { dynamicPage(name: "pagePowerTime", title: "Power Time Range", install: false, uninstall: false) { section() { inputERMSDO('powerFromTimeType', 'Choose from time type?', (powerToTimeType ? true : false), false, true, null, [[1:"Sunrise"], [2:"Sunset"], [3:"Time"]]) if (powerFromTimeType == '3') input "powerFromTime", "time", title: "From time?", required: true, defaultValue: null else if (powerFromTimeType) inputNRDRS('powerFromTimeOffset', "Time offset?", false, 0, "-600..600") else paragraph "Choose from time type to select offset or time" inputERMSDO('powerToTimeType', 'Choose to time type?', (powerFromTimeType ? true : false), false, true, null, [[1:"Sunrise"], [2:"Sunset"], [3:"Time"]]) if (powerToTimeType == '3') input "powerToTime", "time", title: "To time?", required: true, defaultValue: null else if (powerToTimeType) inputNRDRS('powerToTimeOffset', "Time offset?", false, 0, "-600..600") else paragraph "Choose to time type to select offset or time" } } } def pageRoomTemperature() { def validThermostat = true def otherRoom if (useThermostat && roomThermostat) { otherRoom = parent.checkThermostatValid(app.id, roomThermostat) ifDebug("otherRoom: $otherRoom") if (otherRoom) validThermostat = false } boolean isFarenheit = (location.temperatureScale == 'F' ? true : false) def pRT1 = 'Maintain room temperature?' def pRT2 = 'Check presence before maintaining temperature?' def pRT3 = 'Adjust temperature by ±0.5ºF?' def pRT4 = 'Room vents?' def pRT5 = 'Delay vents off by minutes?' def pRT6 = 'Fan switch?' def pRT7 = 'Check windows closed before cooling?' def pRT8 = 'Check windows closed before heating?' dynamicPage(name: "pageRoomTemperature", title: "Temperature Settings", install: false, uninstall: false) { if (validThermostat) { section("Maintain room temperature:", hideable: false) { if (tempSensors) inputERMSDO('maintainRoomTemp', pRT1, (tempSensors ? true : false), false, true, 4, [[1:"Cool"], [2:"Heat"], [3:"Both"], [5:"Only manage vents"], [4:"Neither"]]) else paragraph '\nselect temperature sensor to set' if (['1', '2', '3', '5'].contains(maintainRoomTemp)) { inputBRDS('useThermostat', 'Use thermostat? (otherwise uses room ac and/or heater)', true, false, true) if (useThermostat) { inputDRMS('roomThermostat', 'thermostat', 'Which thermostat?', true, false, true) inputNRDRS('thermoToTempSensor', "Delta (room - thermostat) temperature?", true, 0, "${(isFarenheit ? '-15..15' : '-9..9')}") } } if (!useThermostat && ['1', '3'].contains(maintainRoomTemp)) inputDRMS('roomCoolSwitch', 'switch', 'AC switch?', true, false) if (!useThermostat && ['2', '3'].contains(maintainRoomTemp)) inputDRMS('roomHeatSwitch', 'switch', 'Heater switch?', true, false) if (['1', '2', '3'].contains(maintainRoomTemp)) { if (personsPresence) inputBRDS('checkPresence', pRT2, true, false) else paragraph pRT2 + '\nselect presence sensors to set' if (contactSensorsRT) { inputBRDS("contactSensorsRTCheck", pRT7, true, false) inputBRDS("contactSensorsRTCheckHeat", pRT8, true, false) } else { paragraph pRT7 + '\nselect window contact sensors to set' paragraph pRT8 } // inputDRMS('contactSensorsRT', 'contactSensor', 'Check windows closed?', false, true) inputNRDRS('thermoOverride', "Allow thermostat or switch override for how many minutes?", true, 0, "1..30") paragraph "Remember to setup temperature rules for room cooling and/or heating." // TODO parametize the adjustment if (outTempSensor) inputBRDS('autoAdjustWithOutdoor', pRT3 + '\noutside temperature < 32ºF or > 90ºF', true, true) else paragraph pRT3 + '\nselect outdoor door temperature sensor to set' } } if (!hideAdvanced) { section("Room vents:", hideable: false) { if (useThermostat) { inputDRMS('roomVents', 'switch', pRT4, false, true, true) if (roomVents) inputNRDRS('delayedVentOff', pRT5, true, null, "1..600") else paragraph pRT5 + '\nselect room vents to set' } else { paragraph pRT4 + '\nenabled when using thermostat or manage vents' paragraph pRT5 } } section("Room fan:", hideable: false) { if (tempSensors) inputDRMS('roomFanSwitch', 'switch', pRT6, false, false) else paragraph pRT6 + '\nselect temperature sensor to set' } } } else { section("Warn About Thermostat", hideable: false) { paragraph "This thermostat has already been selected in room: $otherRoom. Please select another thermostat or clear the theromstat setting.\n\nSAVING THESE SETTINGS WITHOUT CHANGING OR CLEARING THE THERMOSTAT SELECTION WILL HAVE UNPREDICTABLE RESULTS." inputDRMS('roomThermostat', 'thermostat', 'Which thermostat?', true, false, true) } } } } def pageRoomHumidity() { def pRH1 = 'Maintain room humidity?' def pRH2 = 'Dehumidifier switch?' def pRH3 = 'Humidifier switch?' def pRH4 = 'Allow switch override for how many minutes?' dynamicPage(name: "pageRoomHumidity", title: "Humidity Settings", install: false, uninstall: false) { section("", hideable: false) { if (humiditySensor) { inputERMSDO('maintainRoomHumidity', pRH1, false, false, true, 4, [[1:"Dehumidify"], [2:"Humidify"], [3:"Both"], [4:"Neither"]]) if (['1', '3'].contains(maintainRoomHumidity)) inputDRMS('roomDehumidifierSwitch', 'switch', pRH2, true, false, true) else paragraph pRH2 + '\nselect humidity sensor and maintain humidity to set' if (['2', '3'].contains(maintainRoomHumidity)) inputDRMS('roomHumidifierSwitch', 'switch', pRH3, true, false, true) else paragraph pRH3 + '\nselect humidity sensor and maintain humidity to set' if (['1', '2', '3'].contains(maintainRoomHumidity)) inputNRDRS('humiOverride', pRH4, true, 0, "1..60") else paragraph pRH4 if (humiditySensor && ['1', '2', '3'].contains(maintainRoomHumidity)) paragraph "Remember to setup humidity rules for room humidification" } else { paragraph pRH1 + '\nselect humidity sensors to set' paragraph pRH2 + '\nselect humidity sensor and maintain humidity to set' paragraph pRH3 paragraph pRH4 } } } } def pageAdjacentRooms() { // def roomNames = parent.getRoomNames(app.id) def pAR1 = 'If motion in adjacent room check if person is still in this room?' def pAR2 = 'If moving through room turn on switches in adjacent rooms?' dynamicPage(name: "pageAdjacentRooms", title: "Adjacent Rooms Settings", install: false, uninstall: false) { section("Action when there is motion in ADJACENT rooms:", hideable: false) { inputERMSDO('adjRooms', 'Adjacent Rooms?', false, true, true, null, state.roomDevices) if (adjRooms) { inputBRDS('adjRoomsMotion', pAR1, false, false) inputBRDS('adjRoomsPathway', pAR2, false, false) } else { paragraph pAR1 + '\nselect adjacent rooms above to set' paragraph pAR2 } } } } def pageAnnouncementSettings() { def playerDevice = (speakerDevices || speechDevices || musicPlayers || (state.hT == _SmartThings && listOfMQs) ? true : false) def pAS1 = 'Announce in modes?' dynamicPage(name: "pageAnnouncementSettings", title: "Announcement Settings", install: false, uninstall: false) { section("") { href "pageAnnouncementSpeakerTimeSettings", title: "Spoken announcement settings", description: (playerDevice ? "Tap to change existing settings" : "Tap to configure") href "pageAnnouncementColorTimeSettings", title: "Color announcement settings", description: (announceSwitches ? "Tap to change existing settings" : "Tap to configure") if (playerDevice || announceSwitches) input "announceInModes", "mode", title:pAS1, required: false, multiple: true else paragraph pAS1 + '\nselect announce speaker or light to set' } section("") { href "pageAnnounceContacts", title: "Announcement contacts settings", description: (announceDoor || announceContact || announceContactRT ? "Tap to change existing settings" : "Tap to configure") } } } def pageAnnouncementSpeakerTimeSettings() { def playerDevice = (speakerDevices || speechDevices || musicPlayers || (state.hT == _SmartThings && listOfMQs) ? true : false) if (state.hT == _Hubitat) app.updateSetting("echoAccessCode", [type: "text", value: "${decrypt(settings['echoAccessCode'])}"]); def pASTS1 = 'Speaker volume?' def pASTS2 = 'Use variable volume?' def pASTS3 = 'Spoken announcements from hour?' def pASTS4 = 'Spoken announcements to hour?' dynamicPage(name: "pageAnnouncementSpeakerTimeSettings", title: "", install: false, uninstall: false) { section("Speakers for announcement:") { inputDRMS('speakerDevices', 'audioNotification', 'Which speakers?', false, true, true) inputDRMS('speechDevices', 'speechSynthesis', 'Which speech devices?', false, true, true) inputDRMS('musicPlayers', 'musicPlayer', 'Which media players?', false, true, true) if (state.hT == _SmartThings) inputERMSDO('listOfMQs', 'Select Ask Alexa Message Queues', true, false, false, null, state.askAlexaMQ) else input "echoAccessCode", "text", title: "Echo notify ACCESS CODE", required: false if (playerDevice) { inputNRDRS('speakerVolume', pASTS1, false, 33, "1..100") inputBRDS('useVariableVolume', pASTS2, true, false) } else { paragraph pASTS1 + '\nselect any speaker to set' paragraph pASTS2 } } section("Spoken announcement during hours:") { if (playerDevice) { inputNRDRS('startHH', pASTS3, true, 7, "0..${(endHH < 24 ? endHH : 23)}", true) inputNRDRS('endHH', pASTS4, true, 23, "${(startHH ? startHH + 1 : 0)}..24", true) } else { paragraph pASTS3 + '\nselect speaker to set' paragraph pASTS4 } } } } def pageAnnouncementColorTimeSettings() { def pACTS1 = 'Color announcements from hour?' def pACTS2 = 'Color announcements to hour?' dynamicPage(name: "pageAnnouncementColorTimeSettings", title: "", install: false, uninstall: false) { section("Lights for announcement with color:") { inputDRMS('announceSwitches', 'switch', 'Which switches?', false, true, true) } section("Color announcement during hours:") { if (announceSwitches) { inputNRDRS('startHHColor', pACTS1, true, 18, "0..${(endHHColor < 24 ? endHHColor : 23)}", true) inputNRDRS('endHHColor', pACTS2, true, 23, "${(startHHColor ? startHHColor + 1 : 0)}..24", true) } else { paragraph pACTS1 + '\nselect announce switches to set' paragraph pACTS2 } } } } def pageAnnounceContacts() { def playerDevice = (speakerDevices || speechDevices || musicPlayers || (state.hT == _SmartThings && listOfMQs) ? true : false) def colorsList = colorsRGB.collect { [(it.key):it.value[1]] } // colorsRGB.each { k, v -> colorsList << ["$k":"${v[1]}"] } def pAC1 = 'Announce when door opened or closed?' def pAC2 = 'Announce with speaker?' def pAC3 = 'Announce with color?' def pAC4 = 'Announce when door stays open?' def pAC5 = 'Announce with speaker?' def pAC6 = 'Announce with color?' def pAC7 = 'Announce when window opened or closed?' def pAC8 = 'Announce with speaker?' def pAC9 = 'Announce with color?' dynamicPage(name: "pageAnnounceContacts", title: "", install: false, uninstall: false) { section("Door announcements:") { if (contactSensor && (playerDevice || announceSwitches)) inputBRDS('announceDoor', pAC1, false, false, true) else paragraph pAC1 + '\nset contact sensor and announce devices to set' if (announceDoor && playerDevice) inputBRDS('announceDoorSpeaker', pAC2, true, false, true) else paragraph pAC2 + '\nselect door announcement and speaker to set' if (announceDoor && announceSwitches) inputERMSDO('announceDoorColor', pAC3, true, false, true, null, colorsList) else paragraph pAC3 + '\nselect door announcement and light to set' if (contactSensor && (playerDevice || announceSwitches)) inputERMSDO('announceContact', pAC4, false, false, false, null, [[1:"Every 1 minute"], [2:"Every 2 minutes"], [3:"Every 3 minutes"], [5:"Every 5 minutes"], [10:"Every 10 minutes"], [15:"Every 15 minutes"], [30:"Every 30 minutes"]]) else paragraph pAC4 + '\nset contact sensor and announce devices to set' if (announceContact && playerDevice) inputBRDS('announceContactSpeaker', pAC5, (announceContactColor ? false : true), false, true) else paragraph pAC5 + '\nselect door stays open announcement & speaker to set' if (announceContact && announceSwitches) inputERMSDO('announceContactColor', pAC6, (announceContactSpeaker ? false : true), false, true, null, colorsList) else paragraph pAC6 + '\nselect door stays open announcement & color bulb to set' } section("Window announcements:") { if (contactSensorsRT && (playerDevice || announceSwitches)) inputBRDS('announceContactRT', pAC7, false, false, true) else paragraph pAC7 + '\nset contact sensor and announce devices to set' if (announceContactRT && playerDevice) inputBRDS('announceContactRTSpeaker', pAC8, (announceContactRTColor ? false : true), false, true) else paragraph pAC8 + '\nselect window announcement & speaker to set' if (announceContactRT && announceSwitches) inputERMSDO('announceContactRTColor', pAC9, (announceContactRTSpeaker ? false : true), false, true, null, colorsList) else paragraph pAC9 + '\nselect window announcement & color bulb to set' } } } def pageGeneralSettings() { boolean isFarenheit = (location.temperatureScale == 'F' ? true : false) updateRulesToState() dynamicPage(name: "pageGeneralSettings", title: "General Settings", install: false, uninstall: false) { section("Mode settings for AWAY and PAUSE modes?", hideable: false) { input "awayModes", "mode", title: "Away modes to set Room to VACANT?", required: false, multiple: true input "pauseModes", "mode", title: "Modes in which to pause automation?", required: false, multiple: true } section("Run on which days of the week?\n(when blank runs on all days)", hideable: false) { inputERMSDO('dayOfWeek', 'Which days of the week?', false, true, false, null, [[null:"All Days of Week"], [8:"Monday to Friday"], [9:"Saturday & Sunday"], [2:"Monday"], [3:"Tuesday"], [4:"Wednesday"], [5:"Thursday"], [6:"Friday"], [7:"Saturday"], [1:"Sunday"]]) } section("Temperature Scale", hideable: false) { inputBRDS('useCelsius', 'Use Celsius?', false, !isFarenheit) } section("Turn off all switches when no rule matches?", hideable: false) { if (state.vacant) { if (allSwitchesOff) app.updateSetting('allSwitchesOff', [type: 'bool', value: false]); paragraph 'Turn OFF option disabled when there is a rule for vacant state' } else inputBRDS('allSwitchesOff', 'Turn OFF?', false, true) } section("Optimize device commands based on device state?", hideable: false) { inputBRDS('cmdOpt', 'Command optimization?', true, false) } if (state.hT == _Hubitat) { section("When switching off lights dim to off?", hideable: false) { inputERMSDO('dimOver', 'Dim over seconds?', false, false, false, [], [[[]:"No dimming"], [3:"3 seconds"], [5:"5 seconds"], [10:"10 seconds"], [15:"15 seconds"], [30:"30 seconds"], [45:"45 seconds"], [60:"60 seconds"], [90:"90 seconds"]]) } section("Publish Occupancy Icons to use with Dashboard?", hideable: false) { inputBRDS('pubOccIcons', 'Publish?', false, false) } } if (!hideAdvanced) { section("Process execution rules only on state change?", hideable: false) { inputBRDS('onlyOnStateChange', 'Only on state change?', false, false) inputERMSDO("butNotInStates", 'But not in these states?', false, true, false, null, [asleep, engaged, occupied, vacant]) } section("Debug logging?", hideable: false) { inputBRDS('debugLogging', 'Turn on?', false, false) } } if (!hideAdvanced) { section("When room device switch capability turned on programatically (rooms_device.on()) set room to?\n(note: when room device switch is tuned off room state is set to VACANT.)", hideable: false) { inputERMSDO('roomDeviceSwitchOn', 'Which room state?', false, false, false, ['occupied'], ['occupied', 'engaged', 'locked', 'asleep']) } section("Icon URL to use for this room?\nfor best results please use image of type 'png' and size 1024x1024 pixels. image url needs to be publicly accessible for ST to access.", hideable: false) { input "iconURL", "text", title: "Icon URL?", required: false, defaultValue: "https://cdn.rawgit.com/adey/bangali/master/resources/icons/roomOccupancySettings.png" } } } } private inputBRDS(var, tex, req, dfv, sub = false) { return "${input "$var", 'bool', title: tex, required: req, submitOnChange: sub, defaultValue: dfv}" } private inputDRMS(var, capa, tex, req, mul, sub = false) { return "${input "$var", "capability.$capa", title: tex, required: req, multiple: mul, submitOnChange: sub}" } private inputERMSDO(var, tex, req, mul, sub, dfv, opt) { return "${input "$var", 'enum', title: tex, required: req, multiple: mul, submitOnChange: sub, defaultValue: dfv, options: opt}" } private inputNRDRS(var, tex, req, dfv, ran, sub = false) { return "${input "$var", 'number', title: tex, required: req, defaultValue: dfv, description: ran, range: ran, submitOnChange: sub}" } def pageAllSettings() { def allRules = getAllRules() dynamicPage(name: "pageAllSettings", title: "View All Settings", install: false, uninstall: false) { section("") { inputBRDS('onlyHas', 'Only show settings with value?', false, false, true) inputBRDS('anonIt', "Anonymize settings?${(state.hT == _Hubitat ? '\t\t' : '')}", false, false, true) } section("", hideable: false) { paragraph "${parent.pageAllSettings(settings, allRules, childCreated(), onlyHas, anonIt)}" } } } def installed() {} def updated() { initialize() getHubType() def pVer = parent.version() def ver = version() if (pVer != ver) { ifDebug("Rooms Child app verion does not match Rooms Manager app version. Parent version is $pVer and this app version is ${ver}. Please update app code and save${(state.hT == _SmartThings ? '/publish' : '')} before trying again.", 'error') return } def nowTime = now() ifDebug("updated", 'info') if (!childCreated()) spawnChildDevice(app.label); if (checkDriverVersion()) return; deleteRules() updateRoom() if (parent) parent.handleAdjRooms(); def child = getChildDevice(getRoom()) def childLabel = child.getLabel() if (childLabel != app.label) child.setLabel(app.label); child.setOnStateC(roomDeviceSwitchOn) /// child."${(isRoomEngaged() ? engaged : vacant)}"() "${(isRoomEngaged() ? engaged : vacant)}"() log.debug "\t$app.label updated: ${now() - nowTime} ms" } private updateRoom() { log.info "updateRoom $app.label" if (debugLogging) state.debugOffTime = now() + _SecondsInDay; boolean isFarenheit = (location.temperatureScale == 'F' ? true : false) state.prvMode = location.currentMode.toString() subscribe(location, "mode", modeEventHandler) state.motionTraffic = 0 state.noMotion = ((noMotionOccupied && noMotionOccupied >= 5) ? noMotionOccupied : null) state.noMotionEngaged = ((noMotionEngaged && noMotionEngaged >= 5) ? noMotionEngaged : null) if (motionSensors) { subscribe(motionSensors, "motion.active", motionActiveEventHandler) subscribe(motionSensors, "motion.inactive", motionInactiveEventHandler) } if (accelSensors) { subscribe(accelSensors, "acceleration.active", motionActiveEventHandler) subscribe(accelSensors, "acceleration.inactive", motionInactiveEventHandler) } // if (roomButton) for (def i = 0; i <= maxButtons; i++) { if ((state.hT == _SmartThings || settings["roomButtonType$i"]) && settings["roomButton$i"] && settings["roomButtonNumber$i"]) subscribe(settings["roomButton$i"], (state.hT == _Hubitat ? "${heCapToAttrMap[settings["roomButtonType$i"]]}.${settings["roomButtonNumber$i"]}" : 'button'), roomButtonPushedEventHandler) } if (occupiedButton) subscribe(occupiedButton, (state.hT == _Hubitat ? "${heCapToAttrMap[occupiedButtonType]}.$buttonIsOccupied" : 'button'), buttonPushedOccupiedEventHandler) if (occSwitches) subscribe(occSwitches, "switch", occupiedSwitchEventHandler) ifDebug("updateRoom 2", 'info') state.switchesHasLevel = [:] state.switchesHasColor = [:] state.switchesHasColorTemperature = [:] state.dimTimer = ((dimTimer && dimTimer >= 5) ? dimTimer as Integer : 5) state.dimByLevel = ((state.dimTimer && dimByLevel) ? dimByLevel : null) state.dimToLevel = ((state.dimTimer && dimToLevel) ? dimToLevel : null) if (engagedSwitch) subscribe(engagedSwitch, "switch", engagedSwitchEventHandler) if (contactSensor) { subscribe(contactSensor, (contactSensorOutsideDoor ? "contact.closed" : "contact.open"), contactOpenEventHandler) subscribe(contactSensor, (contactSensorOutsideDoor ? "contact.open" : "contact.closed"), contactClosedEventHandler) } if (musicDevice && musicEngaged) { subscribe(musicDevice, "status.playing", musicPlayingEventHandler) subscribe(musicDevice, "status.paused", musicStoppedEventHandler) subscribe(musicDevice, "status.stopped", musicStoppedEventHandler) } state.busyCheck = (busyCheck && busyCheck.isInteger() ? busyCheck as Integer : null) if (engagedButton) subscribe(engagedButton, (state.hT == _Hubitat ? "${heCapToAttrMap[engagedButtonType]}.$buttonIs" : 'button'), buttonPushedEventHandler) if (personsPresence) subscribe(personsPresence, "presence", presenceEventHandler) ifDebug("updateRoom 3", 'info') state.holidays = [:] for (def i = 1; i <= maxHolis; i++) { if (!settings["holiName$i"] || !settings["holiColorString$i"]) continue; def holiColors = [] def holiHues = [:] def holiColorCount = 0 def str = settings["holiColorString$i"].split(',') // str.each { for (def itc : str) { holiColors << itc def hue = convertRGBToHueSaturation(itc) holiHues << [(holiColorCount):hue] holiColorCount = holiColorCount + 1 } //state.holidays << [(i):[name: settings["holiName$i"], count: holiColorCount, hues: holiHues, colors: holiColors, style: settings["holiStyle$i"], seconds: settings["holiSeconds$i"], level: settings["holiLevel$i"]]] state.holidays << [(i):[name: settings["holiName$i"], count: holiColorCount, hues: holiHues, colors: holiColors, seconds: settings["holiSeconds$i"], level: settings["holiLevel$i"]]] } if (anotherRoomEngaged) { // parent.subscribeChildrenToEngaged(app.id, anotherRoomEngaged) // subsribe(anotherRoomEngaged, "occupancy", anotherRoomEventHandler) // anotherRoomEngaged.each { for (def rom : anotherRoomEngaged) { def roomDeviceObject = parent.getChildRoomOccupancyDeviceObject(rom) if (roomDeviceObject) { if (state.hT == _SmartThings) subscribe(roomDeviceObject, "button", anotherRoomEngagedButtonPushedEventHandler); else { subscribe(roomDeviceObject, "pushed.8", anotherRoomEngagedButtonPushedEventHandler); // asleep subscribe(roomDeviceObject, "pushed.9", anotherRoomEngagedButtonPushedEventHandler); // engaged } } } } if (vacantButton) subscribe(vacantButton, (state.hT == _Hubitat ? "${heCapToAttrMap[vacantButtonType]}.$buttonIsVacant" : 'button'), buttonPushedVacantEventHandler) if (vacantSwitches) subscribe(vacantSwitches, "switch.off", vacantSwitchOffEventHandler); ifDebug("updateRoom 4", 'info') if (luxSensor) subscribe(luxSensor, "illuminance", luxEventHandler) if (powerDevice) { subscribe(powerDevice, "power", powerEventHandler) state.previousPower = getIntfromStr((String) (powerDevice instanceof List ? powerDevice.currentPower.max() : powerDevice.currentPower)) } else state.previousPower = null state.powerLastMins = [:] if (asleepSensor) subscribe(asleepSensor, "sleeping", asleepEventHandler) if (asleepButton) subscribe(asleepButton, (state.hT == _Hubitat ? "${heCapToAttrMap[asleepButtonType]}.$buttonIsAsleep" : 'button'), buttonPushedAsleepEventHandler) if (asleepSwitch) subscribe(asleepSwitch, "switch", asleepEventHandler) if (nightButton) subscribe(nightButton, (state.hT == _Hubitat ? "${heCapToAttrMap[nightButtonType]}.$nightButtonIs" : 'button'), nightButtonPushedEventHandler) state.noMotionAsleep = ((noMotionAsleep && noMotionAsleep >= 5) ? noMotionAsleep : null) state.nightSetLevelTo = (nightSetLevelTo ? nightSetLevelTo : null) state.nightSetCT = (nightSetCT ? nightSetCT as Integer : null) state.nightSetColorTo = (nightSetColorTo ?: null) state.nightSetHueTo = (state.nightSetColorTo && colorsRGB[state.nightSetColorTo] ? convertRGBToHueSaturation(colorsRGB[state.nightSetColorTo][1]) : []) state.noAsleep = (asleepFromTime && asleepToTime ? ((noAsleep && noAsleep >= 1) ? (noAsleep * 60) : null) : ((noAsleep && noAsleep >= 1) ? (noAsleep * 60 * 60) : null)) ifDebug("updateRoom 5", 'info') if (lockedButton) subscribe(lockedButton, (state.hT == _Hubitat ? "${heCapToAttrMap[lockedButtonType]}.$buttonIsLocked" : 'button'), lockedButtonPushedEventHandler) if (lockedContact) subscribe(lockedContact, "contact", lockedEventHandler) if (lockedSwitch) subscribe(lockedSwitch, "switch", lockedEventHandler) if (lockedTurnOn) { state.lockedSetLevelTo = (lockedSetLevelTo ?: null) state.lockedSetCT = (lockedSetCT ? lockedSetCT as Integer : null) state.lockedSetColorTo = (lockedSetColorTo ?: null) state.lockedSetHueTo = (state.lockedSetColorTo && colorsRGB[state.lockedSetColorTo] ? convertRGBToHueSaturation(colorsRGB[state.lockedSetColorTo][1]) : []) } else state.lockedSetLevelTo = state.lockedSetCT = state.lockedSetColorTo = state.lockedSetHueTo = null if (thermoOverride) { if (roomThermostat) subscribe(roomThermostat, "thermostat", roomThermostatEventHandler) if (roomCoolSwitch) { subscribe(roomCoolSwitch, "switch.on", roomCoolSwitchOnEventHandler) subscribe(roomCoolSwitch, "switch.off", roomCoolHeatSwitchOffEventHandler) } if (roomHeatSwitch) { subscribe(roomHeatSwitch, "switch.on", roomHeatSwitchOnEventHandler) subscribe(roomHeatSwitch, "switch.off", roomCoolHeatSwitchOffEventHandler) } } state.roomThermoTurnedOn = false state.roomCoolTurnedOn = false state.roomHeatTurnedOn = false state.thermoOverride = false if (roomFanSwitch) { subscribe(roomFanSwitch, "switch", updateFanIndP) subscribe(roomFanSwitch, "level", updateFanIndP) } state.unLocked = ((unLocked && unLocked >= 1) ? (unLocked * 60 * 60) : null) state.dayOfWeek = [] if (dayOfWeek) { for (def dOW : dayOfWeek) switch(dOW) { case '1': case '2': case '3': case '4': case '5': case '6': case '7': state.dayOfWeek << dOW break case '8': state.dayOfWeek = state.dayOfWeek + [1,2,3,4,5] break case '9': state.dayOfWeek = state.dayOfWeek + [6,7] break // default: state.dayOfWeek = null; break } } // else // state.dayOfWeek = null ifDebug("updateRoom 6", 'info') if (contactSensorsRT) subscribe(contactSensorsRT, "contact", contactsRTEventHandler) if (waterSensors) subscribe(waterSensors, "water", waterEventHandler) if (tempSensors) subscribe(tempSensors, "temperature", temperatureEventHandler) if (outTempSensor) subscribe(outTempSensor, "temperature", updateOutTempIndP) if (roomVents) { subscribe(roomVents, "switch", updateVentIndP) subscribe(roomVents, "switchLevel", updateVentIndP) subscribe(roomVents, "level", updateVentIndP) } updateRulesToState() updateSwitchAttributesToStateAndSubscribe() if (humiditySensor) { subscribe(humiditySensor, "humidity", humidityEventHandler, [filterEvents:false]) state.previousHumidity = getAvgHumidity() } else state.previousHumidity = null if (humiOverride) { if (roomDehumidifierSwitch) { subscribe(roomDehumidifierSwitch, "switch.on", roomDehumidifierSwitchOnEventHandler) subscribe(roomDehumidifierSwitch, "switch.off", roomDehumiAndHumiSwitchOffEventHandler) } if (roomHumidifierSwitch) { subscribe(roomHumidifierSwitch, "switch.on", roomHumidifierSwitchOnEventHandler) subscribe(roomHumidifierSwitch, "switch.off", roomDehumiAndHumiSwitchOffEventHandler) } } state.humidity = [dehumidifierTurnedOn:false, humidifierTurnedOn:false, override:false, offAt:false, offIn:false, forceOffIn:false, lastState:null, lastStateAt:0, hour:99, previous:false, lastRule:null, minsInState:0, lastReading:0] humidityTimer() if (state.hT == _SmartThings) { subscribe(location, "askAlexaMQ", askAlexaMQHandler) // app.removeSetting("echoAccessCode"); // app.updateSetting("echoAccessCode", [type: "text", value: ""]); } else { state.askAlexaMQ = [] if (echoAccessCode) app.updateSetting("echoAccessCode", [type: "text", value: "${encrypt(settings['echoAccessCode'])}"]); } // def child = getChildDevice(getRoom()) // subscribe(child, "occupancy", occupancyHandler) ifDebug("updateRoom final", 'info') state.toUpdateTimeouts = false if (state.hT != _Hubitat) updateIndicators(); state.processChild = (((new Date(now())).format("mm", location.timeZone).toInteger() + new Random().nextInt(60)) % 60) subscribe(location, "sunrise", runEvery5) subscribe(location, "sunset", runEvery5) state.scheduled = ['isScheduled':false, 'toRun':'', 'timers':[:], 'isExecuting':false] timerNext() runEvery5Minutes(runEvery5) state.runEvery5 = now() // state.nextScheduleFromToTimes = null // state.lastScheduleFromToTimes = 0 runIn(0, runEvery5) // runIn(2, processCoolHeat) // processHumidity() } def runEvery5(evt = [:]) { scheduleRunEvery5() // def nowDate = new Date(nowTime) // def doM = nowDate.getAt(Calendar.DAY_OF_MONTH) // if (state.dayOfMonth != doM) { // state.dayOfMonth = doM state.scheduled.isExecuting = false if (asleepFromTime && !state.scheduled.timers["asleep"]) { def aTime = timeToday(asleepFromTime, location.timeZone).getTime() def nowTime = now() if (aTime > nowTime && !state.scheduled.timers["asleep"]) { ////log.debug "nowTime: $nowTime | aTime: $aTime" addSchedule("asleep", ((aTime - nowTime) / 1000).toInteger(), true) } } // } /// def executed = false // if (!state.nextScheduleFromToTimes || ((nowTime - state.nextScheduleFromToTimes) > 300000) || ((nowTime - (state.lastScheduleFromToTimes ?: 0)) > 300000)) /// executed = scheduleFromToTimes() scheduleFromToTimes() /// if (!executed) executeScheduled(); executeScheduled() } private scheduleRunEvery5() { def nowTime = now() if ((nowTime - (state?.runEvery5 ?: 0)) > 450000) { subscribe(location, "sunrise", runEvery5) subscribe(location, "sunset", runEvery5) runEvery5Minutes(runEvery5) // unsubscribe("scheduleFromToTimes") } state.runEvery5 = nowTime } def updateRoomAdjMS(adjMotionSensors) { ifDebug("updateRoomAdjMS", 'info') // state.adjMotionSensorsID = adjMotionSensors.collect{ it.id } //log.debug adjMotionSensors state.adjMotionSensorsID = [] if (adjMotionSensors && (adjRoomsMotion || adjRoomsPathway)) { for (def adjMS : adjMotionSensors) { state.adjMotionSensorsID << adjMS.id; subscribe(adjMS, "motion.active", adjMotionActiveEventHandler) subscribe(adjMS, "motion.inactive", adjMotionInactiveEventHandler) } } if (motionSensors) { state.hasGetLastActivity = true for (def mS : motionSensors) { def allCmds = mS.getSupportedCommands().collect{ it.toString() } if (!allCmds.contains('geLastActivity')) { state.hasGetLastActivity = false break } } } } def initialize() { unsubscribe() unschedule() sendLocationEvent(name: "AskAlexaMQRefresh", isStateChange: true) state.remove("pList") state.colorsRotating = false state.colorNotificationColor = null state.colorNotificationColorStack = [] state.hasGetLastActivity = false state.roomDevices = [:] state.scheduled = ['isScheduled':false, 'toRun':'', 'timers':[:], 'isExecuting':false] } def askAlexaMQHandler(evt) { if (evt) switch (evt.value) { case "refresh": (state.askAlexaMQ = evt.jsonData && evt.jsonData?.queues ? evt.jsonData.queues : []); break; } } private deleteRules() { if (state.hT != _Hubitat || !rulesToDelete) return; def ruleVars = ['name', 'disabled', 'state', 'mode', 'dayOfWeek', 'luxThreshold', 'luxCheck', 'powerThreshold', 'powerCheck', 'presenceCheck', 'checkOn', 'checkOff', 'wet', 'fromHumidity', 'toHumidity', 'fromDate', 'toDate', 'fromTimeType', 'fromTime', 'fromTimeOffset', 'toTimeType', 'toTime', 'toTimeOffset', 'type', 'coolTemp', 'heatTemp', 'tempRange', 'fanOnTemp', 'fanSpeedIncTemp', 'device', 'cmds', 'piston', 'actions', 'switchesOn', 'setLevelTo', 'setColorTo', 'setColorTemperatureTo', 'switchesOff', 'noMotion', 'noMotionEngaged', 'dimTimer', 'noMotionAsleep'] rulesToDelete.each { i -> ruleVars.each { app.removeSetting("$it$i") } } app.removeSetting("rulesToDelete") updateRulesToState() } def occupied(vM = false) { cSs(occupied, vM); switchOnOff(true) } def checking(vM = false) { cSs(checking, vM) } def vacant(vM = false) { cSs(vacant, vM); switchOnOff(false) } def donotdisturb(vM = false) { cSs(donotdisturb, vM); switchOnOff(false) } def reserved(vM = false) { cSs(reserved, vM); switchOnOff(false) } def asleep(vM = false) { cSs(asleep, vM); switchOnOff(true) } def locked(vM = false) { cSs(locked, vM); switchOnOff(false) } def engaged(vM = false) { cSs(engaged, vM); switchOnOff(true) } def kaput(vM = false) { cSs(kaput, vM); switchOnOff(false) } private switchOnOff(on) { def child = getChildDevice(getRoom()) if (!child) return; def switchState = (on ? 'on' : 'off') child.sendEvent(name: "switch", value: "$switchState", descriptionText: "$child.displayName is $switchState", displayed: true) } private cSs(nSt, vM) { def rSt = getChildDevice(getRoom())?.currentValue(occupancy) handleSwitches(rSt, nSt, vM) updateOccupancy(nSt) } private updateOccupancy(newOcc) { newOcc = newOcc?.toLowerCase() def child = getChildDevice(getRoom()) if (!child) return; //log.debug 'updateOccupancy' def buttonMap = ['occupied':1, 'locked':4, 'vacant':3, 'reserved':5, 'checking':2, 'kaput':6, 'donotdisturb':7, 'asleep':8, 'engaged':9] if (!newOcc || !(buttonMap.containsKey(newOcc))) { ifDebug("Missing or invalid parameter room occupancy: $newOcc") return } child.sendEvent(name: "occupancy", value: newOcc, descriptionText: "$child.displayName changed to $newOcc", displayed: true) if (state.hT == _Hubitat && pubOccIcons) { def img = "https://cdn.rawgit.com/adey/bangali/master/resources/icons/rooms${newOcc?.capitalize()}State.png" child.sendEvent(name: "occupancyIconS", value: "", descriptionText: "$child.displayName $newOcc icon small", displayed: true) child.sendEvent(name: "occupancyIconM", value: "", descriptionText: "$child.displayName $newOcc icon medium", displayed: true) child.sendEvent(name: "occupancyIconL", value: "", descriptionText: "$child.displayName $newOcc icon large", displayed: true) child.sendEvent(name: "occupancyIconXL", value: "", descriptionText: "$child.displayName $newOcc icon extra large", displayed: true) child.sendEvent(name: "occupancyIconXXL", value: "", descriptionText: "$child.displayName $newOcc icon extra extra large", displayed: true) child.sendEvent(name: "occupancyIconURL", value: img, descriptionText: "$child.displayName $newOcc icon URL", displayed: true) } def buton = buttonMap[newOcc] if (state.hT == _SmartThings) { //log.debug "updateOccupancy sendEvent button $newOcc $buton" child.sendEvent(name: "button", value: "pushed", data:["buttonNumber":buton], descriptionText: "$child.displayName button $buton was pushed.", isStateChange:true, displayed: true) } else child.sendEvent(name:"pushableButton", value:buton, descriptionText: "$child.displayName button $buton was pushed.") // if (hT != _Hubitat()) // sendEvent(name: "button", value: "pushed", data: [buttonNumber: "$buton"], descriptionText: "$device.displayName button $buton was pushed", displayed: true) // else // sendEvent(name: "pushableButton", value: buton, descriptionText: "$device.displayName button $buton was pushed", displayed: true) child.sendEvent(name: newOcc, value: newOcc, descriptionText: "$child.displayName reset tile $newOcc", displayed: true) def formatter = new java.text.SimpleDateFormat("EEE, MMM d yyyy @ h:mm:ss a z") formatter.setTimeZone(location.timeZone) state.statusMsg = formatter.format(now()) child.sendEvent(name: "status", value: state.statusMsg, displayed: true) // for new app child.sendEvent(name: "lastUpdated", value: state.statusMsg, displayed: true) } def updateIndicators() { ifDebug("updateIndicators", 'info') def child = getChildDevice(getRoom()) if (!child) return; def ind = -1 if (motionSensors || accelSensors) if (motionSensors && motionSensors.currentMotion.contains('active')) ind = 1 else if (accelSensors && accelSensors.currentMotion.contains('active')) ind = 1 else ind = 0 updateMotionInd(ind) updateLuxInd(getAvgLux()) updateHumidityInd(getAvgHumidity()) updateContactInd((contactSensor ? (contactSensor.currentContact.contains('closed') ? 1 : 0) : -1), false) updatePresenceInd((personsPresence ? (personsPresence.currentPresence.contains(present) ? 1 : 0) : -1)) updatePresenceActionInd((personsPresence ? presenceAction : -1)) updateDoWInd((dayOfWeek ?: -1)) updateTemperatureInd(getAvgTemperature()) // for new app updateRulesInd() updateLastRuleInd(-1) def power = (powerDevice ? getIntfromStr((String) (powerDevice instanceof List ? powerDevice.currentPower.max() : powerDevice.currentPower)) : -1) updatePowerInd(power) if (pauseModes) { ind = '' for (def mod : pauseModes) { ind = ind + (ind.size() > 0 ? ', ' : '') + mod } } else ind = -1 child.sendEvent(name: 'pauseInd', value: (ind == -1 ? '--' : ind), descriptionText: (ind == -1 ? "indicate no pause modes" : "indicate pause modes")) for (def x : ['', 'A', 'E', 'L', 'N', 'O']) { updateSwitchInd("isAny${x}SwitchOn"(), x) } updatePresenceEngagedInd((personsPresence ? (presenceActionContinuous ? 'Yes' : 'No') : -1)) updateBusyEngagedInd((busyCheck ? (busyCheck == lightTraffic ? 'Light' : (busyCheck == mediumTraffic ? 'Medium' : 'Heavy')) : -1)) updateTimeouts() child.sendEvent(name: 'turnAllOffInd', value: (allSwitchesOff ? 'Yes' : 'No'), descriptionText: "indicate if all switches should be turned off when no rules match") def dimBy = (state.dimByLevel ?: -1) def dimTo = (state.dimToLevel ?: -1) child.sendEvent(name: 'dimByLevelInd', value: (dimBy == -1 && dimTo == -1 ? '-- / --' : "${(dimBy == -1 ? '--' : dimBy + '%')} /\n${(dimTo == -1 ? '--' : dimTo + '% ')}"), descriptionText: (dimBy == -1 && dimTo == -1 ? "indicate no dimming" : "indicate dimming by / to level")) updateWattsInd(powerValueEngaged ?: -1, 'E') updateWattsInd(powerValueAsleep ?: -1, 'A') updateContactInd((contactSensorsRT ? (contactSensorsRT.currentContact.contains('closed') ? 1 : 0) : -1), true) def aRooms = (adjRooms ? adjRooms.size() : -1) child.sendEvent(name: 'aRoomInd', value: (aRooms == -1 ? '--' : aRooms + '\nrooms'), descriptionText: (aRooms == -1 ? "indicate no adjacent rooms" : "indicate how many adjacent rooms")) updateAdjMotionInd(-1) updateThermostatIndP() updateThermoOverrideIndP() updateFanIndP() updateOutTempIndP() updateVentIndP() } private updateSwitchInd(switchOn, switchType) { if (state.hT == _Hubitat) return; def child = getChildDevice(getRoom()) if (!child) return; def sN = '' switch(switchType) { case 'A': sN = 'asleep '; break case 'E': sN = 'engaged '; break case 'L': sN = 'locked '; break case 'N': sN = 'night '; break case 'O': sN = 'occupied '; break default: break } def vV = '--' def dD = "indicate no $sN switch" switch(switchOn) { case 1: vV = 'on'; dD = "indicate $sN switch is on"; break case 0: vV = 'off'; dD = "indicate $sN switch is off"; break } def ind = "${switchType}SwitchInd" ind = ind[0].toLowerCase() + ind.substring(1) child.sendEvent(name: ind, value: vV, descriptionText: dD) } private updateTemperatureInd(temp) { if (state.hT == _Hubitat) return; def child = getChildDevice(getRoom()) if (!child) return; def tS = (location.temperatureScale ?: 'F') child.sendEvent(name: 'temperatureInd', value: (temp == -1 ? '--' : temp + '°' + tS), unit: tS, descriptionText: (temp == -1 ? "indicate no temperature sensor" : "indicate temperature value")) // for new app child.sendEvent(name: 'temperature', value: (temp == -1 ? '--' : temp), unit: tS, descriptionText: (temp == -1 ? "indicate no maintain temperature" : "indicate maintain temperature value")) } private getAvgLux() { int countLuxSensors = (luxSensor ? (luxSensor instanceof List ? luxSensor.size() : 1) : 0) if (countLuxSensors < 1) return -1; def luxes = luxSensor.currentIlluminance def lux = 0.0f for (def lx : luxes) lux = lux + lx; return (lux / countLuxSensors).round(0).toInteger() } private getAvgTemperature() { int countTempSensors = (tempSensors ? (tempSensors instanceof List ? tempSensors.size() : 1) : 0) if (countTempSensors < 1) return -1; def temperatures = tempSensors.currentTemperature def temperature = 0.0f for (def tm : temperatures) temperature = temperature + tm; return (temperature / countTempSensors).round(1) } def updateOutTempIndP(evt = null) { if (state.hT == _Hubitat) return; def child = getChildDevice(getRoom()) if (!child) return; def tS = (location.temperatureScale ?: 'F') def temp = (outTempSensor ? outTempSensor.currentTemperature : -1) child.sendEvent(name: 'outTempInd', value: (temp == -1 ? '--' : temp + '°' + tS), unit: tS, descriptionText: (temp == -1 ? "indicate no temperature sensor" : "indicate temperature value")) } private updatePresenceActionInd(presenceAction) { if (state.hT == _Hubitat) return; def child = getChildDevice(getRoom()) if (!child) return; def vV = '--' def dD = "indicate no presence sensor" switch(presenceAction) { case '1': vV = 'Arrival'; dD = "indicate arrival action when present"; break case '2': vV = 'Departure'; dD = "indicate departure action when not present"; break case '3': vV = 'Both'; dD = "indicate both arrival and depature action with presence"; break case '4': vV = 'Neither'; dD = "indicate no action with with present"; break } child.sendEvent(name: 'presenceActionInd', value: vV, descriptionText: dD) } private updatePresenceInd(presencePresent) { if (state.hT == _Hubitat) return; def child = getChildDevice(getRoom()) if (!child) return; def vV = 'none' // for new app def vX = 'none' def dD = "indicate no presence sensor" switch(presencePresent) { case 1: vV = 'present'; vX = 'present'; dD = "indicate presence present"; break case 0: vV = 'absent'; vX = 'not present'; dD = "indicate presence not present"; break } child.sendEvent(name: 'presenceInd', value: vV, descriptionText: dD) // for new app child.sendEvent(name: 'presence', value: vX, descriptionText: dD) } private updateDoWInd(dow) { if (state.hT == _Hubitat) return; def child = getChildDevice(getRoom()) if (!child) return; def vV switch(dow) { case '1': vV = 'Monday'; break case '2': vV = 'Tuesday'; break case '3': vV = 'Wednesday'; break case '4': vV = 'Thursday'; break case '5': vV = 'Friday'; break case '6': vV = 'Saturday'; break case '7': vV = 'Sunday'; break case '8': vV = 'M - F'; break case '9': vV = 'S & S'; break default: vV = 'Everyday'; break } child.sendEvent(name: 'dowInd', value: vV, descriptionText: "indicate run on only these days of the week: $vV") } // for new app private updateRulesInd() { if (state.hT == _Hubitat) return; def child = getChildDevice(getRoom()) if (!child) return; // for new app def rules = (state.rules ? state.rules.size() : -1) child.sendEvent(name: 'rulesInd', value: (rules == -1 ? '0' : rules), descriptionText: (rules == -1 ? "indicate no rules" : "indicate rules count")) // for new app def total = 0 for (def i = 1; i <= maxRules; i++) { def thisRule = getRule(String.valueOf(i), '*', false) if (thisRule) total = total + 1 } updateRulesCard(child, total, 't') updateRulesCard(child, rules, 'c') } private updatePowerInd(power) { if (state.hT == _Hubitat) return; def child = getChildDevice(getRoom()) if (!child) return; child.sendEvent(name: 'powerInd', value: (power == -1 ? '--' : (power <= 100 ? power : formatNumber(power))), descriptionText: (power == -1 ? "indicate no power sensor" : "indicate power value")) // for new app child.sendEvent(name: 'power', value: (power == -1 ? '--' : power), descriptionText: (power == -1 ? "indicate no power sensor" : "indicate power value")) } private updateWattsInd(watts, wType) { if (state.hT == _Hubitat) return; def child = getChildDevice(getRoom()) if (!child) return; def dD = (wType = 'E' ? 'engaged' : 'asleep') child.sendEvent(name: (wType = 'E' ? 'eWattsInd' : 'aWattsInd'), value: (watts == -1 ? '--' : (watts <= 100 ? watts : formatNumber(watts))), descriptionText: (watts == -1 ? "indicate no $dD watts" : "indicate $dD watts value")) } private updateLastRuleInd(rule) { if (state.hT == _Hubitat) return; def child = getChildDevice(getRoom()) if (!child) return; child.sendEvent(name: 'lastRuleInd', value: (rule == -1 ? '--' : rule), descriptionText: (rule == -1 ? "indicate no rule executed" : "indicate rule number last executed")) // for new app updateRulesCard(child, rule, 'l') } // for new app private updateRulesCard(child, v, t) { if (!state.rulesCard) state.rulesCard = ['total':0, 'count':0, 'last':-1] if (t == 't') state.rulesCard.total = v else if (t == 'c') state.rulesCard.count = v else if (t == 'l') state.rulesCard.last = v def vV = 'Total: ' + (state.rulesCard.total == -1 ? '0' : state.rulesCard.total) if (state.rulesCard.total != 0) { if (state.rulesCard.count == 0) vV = vV + ' & active: 0' else { vV = vV + ', active: ' + (state.rulesCard.count == -1 ? '0' : state.rulesCard.count) if (state.rulesCard.last != -1) vV = vV + ' & last run: ' + state.rulesCard.last } } child.sendEvent(name: 'rules', value: vV, descriptionText: "rules card update") } private updateLuxInd(lux) { if (state.hT == _Hubitat) return; def child = getChildDevice(getRoom()) if (!child) return; child.sendEvent(name: 'luxInd', value: (lux == -1 ? '--' : (lux <= 100 ? lux : formatNumber(lux))), descriptionText: (lux == -1 ? "indicate no lux sensor" : "indicate lux value")) // for new app child.sendEvent(name: 'illuminance', value: (lux == -1 ? '--' : lux), descriptionText: (lux == -1 ? "indicate no lux sensor" : "indicate lux value")) } private updateHumidityInd(humidity) { if (state.hT == _Hubitat) return; def child = getChildDevice(getRoom()) if (!child) return; child.sendEvent(name: 'humidityInd', value: (humidity == -1 ? '--' : humidity.toString() + '%'), descriptionText: (humidity == -1 ? "indicate no humidity sensor" : "indicate humidity value")) // for new app child.sendEvent(name: 'humidity', value: (humidity == -1 ? '--' : humidity), descriptionText: (humidity == -1 ? "indicate no humidity sensor" : "indicate humidity value")) } private updateContactInd(contactClosed, window) { if (state.hT == _Hubitat) return; def child = getChildDevice(getRoom()) if (!child) return; def vV = 'none' def dD = "indicate no contact sensor" switch(contactClosed) { case 1: vV = 'closed'; dD = "indicate contact closed"; break case 0: vV = 'open'; dD = "indicate contact open"; break } child.sendEvent(name: (window ? 'contactRTInd' : 'contactInd'), value: vV, descriptionText: dD) // for new app vV = (contactSensor && contactSensorsRT ? (contactSensor.currentContact.contains('open') || contactSensorsRT.currentContact.contains('open') ? 'open' : 'closed') : (contactSensor ? (contactSensor.currentContact.contains('open') ? 'open' : 'closed') : (contactSensorsRT ? (contactSensorsRT.currentContact.contains('open') ? 'open' : 'closed') : 'none'))) child.sendEvent(name: 'contact', value: vV, descriptionText: dD) } private updatePresenceEngagedInd(presenceEngaged) { if (state.hT == _Hubitat) return; def child = getChildDevice(getRoom()) if (!child) return; child.sendEvent(name: 'presenceEngagedInd', value: (presenceEngaged == -1 ? '--' : presenceEngaged), descriptionText: (presenceEngaged == -1 ? "indicate no presence sensor" : "indicate if presence action continuous")) } private updateBusyEngagedInd(busyEngaged) { if (state.hT == _Hubitat) return; def child = getChildDevice(getRoom()) if (!child) return; child.sendEvent(name: 'busyEngagedInd', value: (busyEngaged == -1 ? '--' : "$busyEngaged\ntraffic"), descriptionText: (busyEngaged == -1 ? "indicate no presence sensor" : "indicate traffic check")) } private updateAdjMotionInd(motionOn) { if (state.hT == _Hubitat) return; def child = getChildDevice(getRoom()) if (!child) return; def vV = 'none' def dD = "indicate no adjacent motion sensor" switch(motionOn) { case 1: vV = 'active'; dD = "indicate adjacent motion active"; break case 0: vV = 'inactive'; dD = "indicate adjacent motion inactive"; break } child.sendEvent(name: 'aMotionInd', value: vV, descriptionText: dD) } private getAvgHumidity() { int countHumiditySensors = (humiditySensor ? (humiditySensor instanceof List ? humiditySensor.size() : 1) : 0) if (countHumiditySensors < 1) return -1; def humidities = humiditySensor.currentHumidity def humidity = 0.0f for (def hm : humidities) humidity = humidity + hm return (humidity / countHumiditySensors).round(1) } private isAnySwitchOn() { def ind = -1 for (def i = 1; i <= maxRules; i++) { def ruleNo = String.valueOf(i) def thisRule = getNextRuleSwitches(ruleNo) if (thisRule.ruleNo == 'EOR') break; i = thisRule.ruleNo as Integer if (thisRule.switchesOn?.currentSwitch?.contains('on')) { ind = 1 break } else ind = 0 } return ind } private isAnyOSwitchOn() { return (occSwitches ? (occSwitches.currentSwitch.contains('on') ? 1 : 0) : -1) } private isAnyESwitchOn() { return (engagedSwitch ? (engagedSwitch.currentSwitch.contains('on') ? 1 : 0) : -1) } private isAnyASwitchOn() { return (asleepSwitch ? (asleepSwitch.currentSwitch.contains('on') ? 1 : 0) : -1) } private isAnyNSwitchOn() { return (nightSwitches ? (nightSwitches.currentSwitch.contains('on') ? 1 : 0) : -1) } private isAnyLSwitchOn() { return (lockedSwitch ? (lockedSwitch.currentSwitch.contains('on') ? 1 : 0) : -1) } def updateRulesToState() { state.timeCheck = false state.ruleHasAL = false state.ruleHasHL = false state.vacant = false state.powerCheck = false state.execute = false state.maintainRoomTemp = false state.maintainRoomHumi = false state.rules = false for (def i = 1; i <= maxRules; i++) { def ruleNo = String.valueOf(i) def thisRule = getRule(ruleNo, '*', false) if (thisRule && !thisRule.disabled) { state.maxRuleNo = i if (!state.rules) state.rules = [:]; state.rules << ["$ruleNo":[isRule:true]] if (!thisRule.type || thisRule.type == _ERule) { state.execute = true if (thisRule.state && thisRule.state.contains('vacant')) state.vacant = true; if (thisRule.powerThreshold) state.powerCheck = true; } else if (thisRule.type == _TRule) state.maintainRoomTemp = true; else if (thisRule.type == _HRule) state.maintainRoomHumi = true; if (thisRule.level == 'AL') state.ruleHasAL = true; else if (thisRule.level?.startsWith('HL')) state.ruleHasHL = true; if ((thisRule.fromTimeType && (thisRule.fromTimeType != _timeTime || thisRule.fromTime)) && (thisRule.toTimeType && (thisRule.toTimeType != _timeTime || thisRule.toTime))) state.timeCheck = true } } } def updateSwitchAttributesToStateAndSubscribe() { ifDebug("updateSwitchAttributesToStateAndSubscribe", 'info') def sOn = [] def sOff = [] def sID = [] def checkSwitches = [] def cID = [] for (def i = 1; i <= state.maxRuleNo; i++) { def ruleNo = String.valueOf(i) def thisRule = getNextRule(ruleNo, _ERule, false, false) if (thisRule.ruleNo == 'EOR') break; for (def swt : thisRule.switchesOn) { def itID = swt.getId() if (!sID.contains(itID)) { sOn << swt sID << itID if (swt.hasCommand("setLevel")) state.switchesHasLevel << ["$itID":true]; if (swt.hasCommand("setColor")) state.switchesHasColor << ["$itID":true]; if (swt.hasCommand("setColorTemperature")) state.switchesHasColorTemperature << ["$itID":true]; } } for (def swt : thisRule.switchesOff) { def itID = swt.getId() if (!sID.contains(itID)) { sOff << swt sID << itID if (swt.hasCommand("setLevel")) state.switchesHasLevel << ["$itID":true]; if (swt.hasCommand("setColor")) state.switchesHasColor << ["$itID":true]; if (swt.hasCommand("setColorTemperature")) state.switchesHasColorTemperature << ["$itID":true]; } } for (def swt : thisRule.checkOn) { def itID = swt.getId() if (!cID.contains(itID)) { checkSwitches << swt; cID << itID } } for (def swt : thisRule.checkOff) { def itID = swt.getId() if (!cID.contains(itID)) { checkSwitches << swt; cID << itID } } } for (def swt : sOn) subscribe(swt, "switch", switchEventHandler); for (def swt : checkSwitches) subscribe(swt, "switch", checkSwitchEventHandler); for (def swt : nightSwitches) { def itID = swt.getId() if (!sID.contains(itID)) { sID << itID if (swt.hasCommand("setLevel")) state.switchesHasLevel << ["$itID":true]; if (swt.hasCommand("setColor")) state.switchesHasColor << ["$itID":true]; if (swt.hasCommand("setColorTemperature")) state.switchesHasColorTemperature << ["$itID":true]; } } } private getAllRules() { def allRules = [] def thisRule for (def i = 1; i <= maxRules; i++) { thisRule = getRule(String.valueOf(i), '*', false, false) if (!thisRule) continue; allRules << thisRule i = thisRule.ruleNo as Integer } return allRules } private getNextRule(ruleNo, ruleType = '*', checkState = true, getConditionsOnly = false) { def thisRule for (def i = ruleNo as Integer; i <= maxRules; i++) { if (checkState && i > state.maxRuleNo) break; thisRule = getRule(String.valueOf(i), ruleType, checkState, getConditionsOnly) if (thisRule && !thisRule.disabled) return thisRule; } return [ruleNo:'EOR'] } private getRule(ruleNo, ruleTypeP = '*', checkState = true, getConditionsOnly = false) { def nowTime = now() if (!ruleNo) return null; if (checkState && (!state.rules || !state.rules[ruleNo.toString()])) return null; if (ruleTypeP == _ERule) ruleTypeP = null; if (checkState && ((!ruleTypeP && !state.execute) || (ruleTypeP == _TRule && !state.maintainRoomTemp) || (ruleTypeP == _HRule && !state.maintainRoomHumi))) return null def ruleType = settings["type$ruleNo"] if (ruleType == _ERule) ruleType = null; if (ruleTypeP != '*' && ruleType != ruleTypeP) return null; def ruleName = settings["name$ruleNo"] def ruleDisabled = settings["disabled$ruleNo"] def ruleMode = settings["mode$ruleNo"] def ruleState = settings["state$ruleNo"] def ruleDayOfWeek = [] if (settings["dayOfWeek$ruleNo"]) for (def dOW : settings["dayOfWeek$ruleNo"]) switch(dOW) { case '1': case '2': case '3': case '4': case '5': case '6': case '7': ruleDayOfWeek << dOW break case '8': ruleDayOfWeek = ruleDayOfWeek + [1,2,3,4,5] break case '9': ruleDayOfWeek = ruleDayOfWeek + [6,7] break } def ruleLuxThreshold = settings["luxThreshold$ruleNo"] def ruleLuxCheck = settings["luxCheck$ruleNo"] def rulePowerThreshold = settings["powerThreshold$ruleNo"] def rulePowerCheck = settings["powerCheck$ruleNo"] def rulePresenceCheck = settings["presenceCheck$ruleNo"] def ruleCheckOn = settings["checkOn$ruleNo"] def ruleCheckOff = settings["checkOff$ruleNo"] def ruleWet = settings["wet$ruleNo"] def ruleFromHumidity = settings["fromHumidity$ruleNo"] def ruleToHumidity = settings["toHumidity$ruleNo"] def rD = dateInputValid(settings["fromDate$ruleNo"], settings["toDate$ruleNo"]) def ruleFromDate = (!ruleType || ruleType == _ERule ? rD[0] : null) def ruleToDate = (!ruleType || ruleType == _ERule ? rD[1] : null) def ruleFromTimeType = settings["fromTimeType$ruleNo"] def ruleFromTimeOffset = settings["fromTimeOffset$ruleNo"] def ruleFromTime = settings["fromTime$ruleNo"] def ruleToTimeType = settings["toTimeType$ruleNo"] def ruleToTimeOffset = settings["toTimeOffset$ruleNo"] def ruleToTime = settings["toTime$ruleNo"] def ret if (ruleType == _HRule) { def ruleDehumiOn = settings["dehumiOn$ruleNo"] def ruleHumiOn = settings["humiOn$ruleNo"] def ruleHumiCmp = settings["humiCmp$ruleNo"] def ruleHumiValue = settings["humiValue$ruleNo"] def ruleHumiPercent = settings["humiPercent$ruleNo"] def ruleHumiMins = settings["humiMins$ruleNo"] def ruleHumiMinRun = settings["humiMinRun$ruleNo"] def ruleHumiMaxRun = settings["humiMaxRun$ruleNo"] def ruleHumiPrvState = settings["humiPrvState$ruleNo"] if (!(ruleName || ruleDisabled || ruleMode || ruleState || ruleDayOfWeek || ruleFromTimeType || ruleToTimeType || ruleDehumiOn || ruleHumiOn || ruleHumiCmp || ruleHumiValue || ruleHumiPercent)) ret = null else ret = [ruleNo:ruleNo, type:ruleType, name:ruleName, disabled:ruleDisabled, mode:ruleMode, state:ruleState, dayOfWeek:ruleDayOfWeek, fromTimeType:ruleFromTimeType, fromTimeOffset:ruleFromTimeOffset, fromTime:ruleFromTime, toTimeType:ruleToTimeType, toTimeOffset:ruleToTimeOffset, toTime:ruleToTime, deHumiOn:ruleDehumiOn, humiOn:ruleHumiOn, humiCmp:ruleHumiCmp, humiValue:ruleHumiValue, humiPercent:ruleHumiPercent, humiMins:ruleHumiMins, humiMinRun:ruleHumiMinRun, humiMaxRun:ruleHumiMaxRun, humiPrvState: ruleHumiPrvState] } else if (ruleType == _TRule) { def ruleRoomCoolTemp = settings["coolTemp$ruleNo"] def ruleRoomHeatTemp = settings["heatTemp$ruleNo"] def ruleTempRange = settings["tempRange$ruleNo"] def ruleFanOnTemp = settings["fanOnTemp$ruleNo"] def ruleFanSpeedIncTemp = settings["fanSpeedIncTemp$ruleNo"] if (!(ruleName || ruleDisabled || ruleMode || ruleState || ruleDayOfWeek || ruleFromTimeType || ruleToTimeType || ruleRoomCoolTemp || ruleRoomHeatTemp || ruleFanOnTemp)) ret = null else ret = [ruleNo:ruleNo, type:ruleType, name:ruleName, disabled:ruleDisabled, mode:ruleMode, state:ruleState, dayOfWeek:ruleDayOfWeek, fromTimeType:ruleFromTimeType, fromTimeOffset:ruleFromTimeOffset, fromTime:ruleFromTime, toTimeType:ruleToTimeType, toTimeOffset:ruleToTimeOffset, toTime:ruleToTime, coolTemp:ruleRoomCoolTemp, heatTemp:ruleRoomHeatTemp, tempRange:ruleTempRange, fanOnTemp:ruleFanOnTemp, fanSpeedIncTemp:ruleFanSpeedIncTemp] } else { if (getConditionsOnly) { if (!(ruleName || ruleDisabled || ruleMode || ruleState || ruleDayOfWeek || ruleLuxThreshold != null || rulePowerThreshold || rulePresenceCheck || ruleCheckOn || ruleCheckOff || ruleWet || (ruleFromHumidity && ruleToHumidity) || ruleFromDate || ruleToDate || ruleFromTimeType || ruleToTimeType)) ret = null else ret = [ruleNo:ruleNo, type:ruleType, name:ruleName, disabled:ruleDisabled, mode:ruleMode, state:ruleState, dayOfWeek:ruleDayOfWeek, luxThreshold:ruleLuxThreshold, luxCheck:ruleLuxCheck, powerThreshold:rulePowerThreshold, powerCheck:rulePowerCheck, presence:rulePresenceCheck, checkOn:ruleCheckOn, checkOff:ruleCheckOff, wet:ruleWet, fromHumidity:ruleFromHumidity, toHumidity:ruleToHumidity, fromDate:ruleFromDate, toDate:ruleToDate, fromTimeType:ruleFromTimeType, fromTimeOffset:ruleFromTimeOffset, fromTime:ruleFromTime, toTimeType:ruleToTimeType, toTimeOffset:ruleToTimeOffset, toTime:ruleToTime] } else { def ruleDevice = (settings["device$ruleNo"] ?: settings["deviceSensor$ruleNo"]) def ruleDeviceCmds = settings["cmds$ruleNo"] def rulePiston = settings["piston$ruleNo"] def ruleActions = settings["actions$ruleNo"] def ruleSwitchesOn = settings["switchesOn$ruleNo"] def ruleSetLevelTo = settings["setLevelTo$ruleNo"] def ruleSetColorTo = settings["setColorTo$ruleNo"] def ruleSetHueTo = (ruleSetColorTo && colorsRGB[ruleSetColorTo] ? convertRGBToHueSaturation(colorsRGB[ruleSetColorTo][1]) : []) def ruleSetColorTemperatureTo = settings["setColorTemperatureTo$ruleNo"] def ruleSwitchesOff = settings["switchesOff$ruleNo"] def ruleNoMotion = settings["noMotion$ruleNo"] def ruleNoMotionEngaged = settings["noMotionEngaged$ruleNo"] def ruleDimTimer = settings["dimTimer$ruleNo"] def ruleNoMotionAsleep = settings["noMotionAsleep$ruleNo"] if (!(ruleName || ruleDisabled || ruleMode || ruleState || ruleDayOfWeek || ruleLuxThreshold != null || rulePowerThreshold || rulePresenceCheck || ruleCheckOn || ruleCheckOff || ruleWet || (ruleFromHumidity && ruleToHumidity) || ruleFromDate || ruleToDate || ruleFromTimeType || ruleToTimeType || ruleDevice || ruleDeviceCmds || rulePiston || ruleActions || ruleSwitchesOn || ruleSetLevelTo || ruleSetColorTo || ruleSetColorTemperatureTo || ruleSwitchesOff || ruleNoMotion || ruleNoMotionEngaged || ruleDimTimer || ruleNoMotionAsleep)) ret = null else ret = [ruleNo:ruleNo, type:ruleType, name:ruleName, disabled:ruleDisabled, mode:ruleMode, state:ruleState, dayOfWeek:ruleDayOfWeek, luxThreshold:ruleLuxThreshold, luxCheck:ruleLuxCheck, powerThreshold:rulePowerThreshold, powerCheck:rulePowerCheck, presence:rulePresenceCheck, checkOn:ruleCheckOn, checkOff:ruleCheckOff, wet:ruleWet, fromHumidity:ruleFromHumidity, toHumidity:ruleToHumidity, fromDate:ruleFromDate, toDate:ruleToDate, fromTimeType:ruleFromTimeType, fromTimeOffset:ruleFromTimeOffset, fromTime:ruleFromTime, toTimeType:ruleToTimeType, toTimeOffset:ruleToTimeOffset, toTime:ruleToTime, device:ruleDevice, commands:ruleDeviceCmds, piston:rulePiston, actions:ruleActions, switchesOn:ruleSwitchesOn, level:ruleSetLevelTo, color:ruleSetColorTo, hue:ruleSetHueTo, colorTemperature:ruleSetColorTemperatureTo, switchesOff:ruleSwitchesOff, noMotion:ruleNoMotion, noMotionEngaged:ruleNoMotionEngaged, dimTimer:ruleDimTimer, noMotionAsleep:ruleNoMotionAsleep] } } return ret } private getNextRuleSwitches(ruleNo, checkState = true) { def ret = [ruleNo:'EOR'] if (!ruleNo || (checkState && (ruleNo.toInteger() > state.maxRuleNo || !state.execute || !state.rules))) return ret; for (def i = ruleNo as Integer; i <= maxRules; i++) { def rN = String.valueOf(i) if (checkState && !state.rules[rN]) continue; def ruleType = settings["type$rN"] if (ruleType == _ERule) ruleType = null if (ruleType || settings["disabled$rN"]) continue; // def rN = String.valueOf(i) def ruleSwitchesOn = settings["switchesOn$rN"] def ruleSwitchesOff = settings["switchesOff$rN"] if (ruleSwitchesOn || ruleSwitchesOff) { ret = [ruleNo:rN, type:ruleType, switchesOn:ruleSwitchesOn, switchesOff:ruleSwitchesOff] break } } return ret } def modeEventHandler(evt) { ifDebug("modeEventHandler", 'info') if (!state.dayOfWeek || checkRunDay()) { def rSt = getChildDevice(getRoom())?.currentValue(occupancy) if (awayModes && awayModes.contains(evt.value)) roomVacant(true) else if (pauseModes && pauseModes.contains(evt.value)) unscheduleAll("mode handler") else if (asleepMode && (asleepMode.contains(evt.value) || asleepMode.contains(state.prvMode))) asleepEventHandler([:]) else if (lockedModes && lockedModes.contains(evt.value)) { if (rSt != locked) locked(); } else if (!onlyOnStateChange || (butNotInStates && butNotInStates.contains(rSt))) switchesOnOrOff() } state.prvMode = location.currentMode.toString() scheduleRunEvery5() updateRulesInd() } def motionActiveEventHandler(evt) { ifDebug("motionActiveEventHandler", 'info') def nowTime = now() def child = getChildDevice(getRoom()) updateMotionInd(1) //scheduleRunEvery5() if (!checkPauseModesAndDoW()) return; def rSt = child?.currentValue(occupancy) if (evt.name == 'motion') { if (rSt == vacant && triggerMotionSensors && !triggerMotionSensors.contains(evt.deviceId.toString())) return; if (rSt == asleep) { if (nightMotionSensors && !nightMotionSensors.contains(evt.deviceId.toString())) return; if (nightSwitches && nightTurnOn.contains('1')) { dimNightLights() if (state.noMotionAsleep && whichNoMotion != lastMotionInactive) { addSchedule("nightSwitchesOff", state.noMotionAsleep, state.scheduled.isExecuting) } } return } } if (rSt == vacant && allMotionAndAccel) { if (triggerMotionSensors) { for (def m : motionSensors) if (triggerMotionSensors.contains(m.deviceId.toString()) && m.currentMotion == inactive) return } else if (motionSensors && motionSensors.currentMotion.contains(inactive)) return if (accelSensors && accelSensors.currentAcceleration.contains(inactive)) return } unscheduleAll("motion active handler") def motionActive = (evt.name == 'motion' ? whichNoMotion == lastMotionActive : true) if (rSt == engaged) { if (state.noMotionEngaged) { refreshEngagedTimer() checkContactStaysOpen() } return } if (state.busyCheck && repeatedMotion && rSt == occupied) { state.motionTraffic = state.motionTraffic + 1 if (state.motionTraffic >= state.busyCheck) state.isBusy = true } if (state.isBusy && ['occupied', 'checking', 'vacant'].contains(rSt)) { turnOffIsBusy() engaged() return } if (['checking', 'vacant'].contains(rSt)) { // if (rSt == checking && state.stateStack[1]['state'] == asleep) { // removeSchedule("resetAsleep", state.scheduled.isExecuting) // } // else if (isRoomEngaged()) { engaged() checkContactStaysOpen() } else { if (powerDevice) { def cPwr = getIntfromStr((String) (powerDevice instanceof List ? powerDevice.currentPower.max() : powerDevice.currentPower)) if (powerValueAsleep && cPwr >= powerValueAsleep && (powerTriggerFromVacant || rSt != vacant)) asleep() else if (powerValueLocked && cPwr >= powerValueLocked && (powerTriggerFromVacant || rSt != vacant)) locked() else occupied() } else occupied() } } else if (rSt == occupied) { if (isRoomEngaged()) { engaged() checkContactStaysOpen() } else if (motionActive && state.noMotion) refreshOccupiedTimer() } } private checkContactStaysOpen() { if (!contactSensor) return; def cV = contactSensor.currentContact.contains(open) if (resetEngagedWithContact && ((!contactSensorOutsideDoor && cV) || (contactSensorOutsideDoor && !cV))) addSchedule("resetEngaged", (resetEngagedWithContact as Integer) * 60, state.scheduled.isExecuting) } def motionInactiveEventHandler(evt) { ifDebug("motionInactiveEventHandler", 'info') def nowTime = now() updateMotionInd(((evt.name == 'motion' ? motionSensors.currentMotion : accelSensors.currentAcceleration).contains(active) ? 1 : 0)) if (!checkPauseModesAndDoW()) return; def child = getChildDevice(getRoom()) def rSt = child?.currentValue(occupancy) def motionInactive = (evt.name == 'motion' ? !motionSensors.currentMotion.contains(active) && whichNoMotion == lastMotionInactive : !accelSensors.currentAcceleration.contains(active)) if (rSt == engaged) { if (motionInactive && state.noMotionEngaged) refreshEngagedTimer() } else if (rSt == occupied) { if (motionInactive && state.noMotion) refreshOccupiedTimer() } else if (rSt == asleep && nightSwitches && nightTurnOn.contains('1')) { if (!nightMotionSensors || nightMotionSensors.contains(evt.deviceId.toString())) if (motionInactive) addSchedule("nightSwitchesOff", (state.noMotionAsleep ?: 1), state.scheduled.isExecuting) } } private updateMotionInd(motionOn) { if (state.hT == _Hubitat) return; def child = getChildDevice(getRoom()) if (!child) return; def vV = 'none' def dD = "indicate no motion sensor" switch(motionOn) { case 1: vV = 'active'; dD = "indicate motion active"; break case 0: vV = 'inactive'; dD = "indicate motion inactive"; break } child.sendEvent(name: 'motionInd', value: vV, descriptionText: dD) // for new app child.sendEvent(name: 'motion', value: vV, descriptionText: dD) } def adjMotionActiveEventHandler(evt) { ifDebug("adjMotionActiveEventHandler", 'info') if (!state.adjMotionSensorsID || !state.adjMotionSensorsID.contains(evt.device.id.toString())) return; updateAdjMotionInd(1) if (!checkPauseModesAndDoW()) return; def child = getChildDevice(getRoom()) def rSt = child?.currentValue(occupancy) if (adjRoomsMotion && rSt == occupied) { def mV = motionSensors?.currentMotion.contains(active) def mD = (state.hasGetLastActivity ? motionSensors?.getLastActivity().max() : null) if (!(mV && (!mD || mD > evt.date))) checking(); return } if (adjRoomsPathway && rSt == vacant) for (def adj : adjRooms) { def currentState = parent.getCurrentState(adj) if (currentState == vacant) { checking() return } } } def adjMotionInactiveEventHandler(evt) { ifDebug("adjMotionInactiveEventHandler", 'info') if (!state.adjMotionSensorsID || !state.adjMotionSensorsID.contains(evt.device.id.toString())) return updateAdjMotionInd(0) } def roomButtonPushedEventHandler(evt) { ifDebug("roomButtonPushedEventHandler: $evt.name | $evt.value", 'info') if (!checkPauseModesAndDoW()) return; if (state.hT == _SmartThings) { if (!evt.data) return; def eD = new groovy.json.JsonSlurper().parseText(evt.data) assert eD instanceof Map if (!eD || !eD['buttonNumber']) return; def buttonForRoom = false for (def i = 1; i <= maxButtons; i++) { if (settings["roomButton$i"] && settings["roomButtonNumber$i"] && evt.deviceId == settings["roomButton$i"].id && eD['buttonNumber'] == settings["roomButtonNumber$i"] as Integer) { buttonForRoom = true break } } ifDebug("buttonForRoom: $buttonForRoom") if (!buttonForRoom) return; } def child = getChildDevice(getRoom()) def rSt = child?.currentValue(occupancy) def nRSt = engaged def nextState = false for (def btn : roomButtonStates) { if (nextState) { nRSt = btn nextState = false } if (btn == rSt) nextState = true } "$nRSt"() } def buttonPushedOccupiedEventHandler(evt) { ifDebug("buttonPushedOccupiedEventHandler", 'info') if (!checkPauseModesAndDoW()) return; if (state.hT == _SmartThings) { if (!evt.data) return; def eD = new groovy.json.JsonSlurper().parseText(evt.data) assert eD instanceof Map if (!eD || (buttonIsOccupied && eD['buttonNumber'] && eD['buttonNumber'] != buttonIsOccupied as Integer)) return; } def child = getChildDevice(getRoom()) def rSt = child?.currentValue(occupancy) if (rSt == occupied) { if (!buttonOnlySetsOccupied) checking(); } else occupied() } def occupiedSwitchEventHandler(evt) { ifDebug("occupiedSwitchEventHandler", 'info') updateSwitchInd(isAnyOSwitchOn(), 'O') def child = getChildDevice(getRoom()) def rSt = child?.currentValue(occupancy) if (!checkPauseModesAndDoW() || (rSt == locked && lockedOverrides) || (rSt == asleep && asleepOverrides) || (rSt == engaged && engagedOverrides) || !['vacant','occupied','checking', 'engaged'].contains(rSt)) return def isOccupied = isRoomOccupied() if (['vacant', 'checking'].contains(rSt)) { if (isOccupied) occupied() else if (isRoomEngaged()) engaged() } else if (rSt == occupied) (isOccupied ? (state.noMotion ? refreshOccupiedTimer() : unscheduleAll("occupiedSwitchEventHandler")) : roomVacant()) } private isRoomOccupied() { if (occSwitches && occSwitches.currentSwitch.contains(on)) return true; if ((motionSensors && whichNoMotion == lastMotionInactive && motionSensors.currentMotion.contains(active)) || (accelSensors && accelSensors.currentAcceleration.contains(active))) return true else return false } def switchEventHandler(evt) { ifDebug("switchEventHandler", 'info') if (state.hT == _SmartThings) updateSwitchInd(isAnySwitchOn(), ''); } def buttonPushedEventHandler(evt) { ifDebug("buttonPushedEventHandler", 'info') if (!checkPauseModesAndDoW()) return; if (state.hT == _SmartThings) { if (!evt.data) return; def eD = new groovy.json.JsonSlurper().parseText(evt.data) assert eD instanceof Map if (!eD || (buttonIs && eD['buttonNumber'] != buttonIs as Integer)) return; } def child = getChildDevice(getRoom()) def rSt = child?.currentValue(occupancy) if (rSt == engaged) { if (!buttonOnlySetsEngaged) /// child."${(resetEngagedDirectly ? vacant : checking)}"(); // "${(resetEngagedDirectly ? vacant : checking)}"() roomVacant() } else engaged() } def lockedButtonPushedEventHandler(evt) { ifDebug("lockedButtonPushedEventHandler", 'info') if (!checkPauseModesAndDoW()) return; def bIO = false if (state.hT == _SmartThings) { if (!evt.data) return; def eD = new groovy.json.JsonSlurper().parseText(evt.data) assert eD instanceof Map if (!eD) return; //log.debug "${eD['buttonNumber']} | $buttonIsLocked" if (buttonIsLocked && eD['buttonNumber'] != buttonIsLocked as Integer) if (state.lockedButtonIsOccupancy) bIO = true else return } def child = getChildDevice(getRoom()) def rSt = child?.currentValue(occupancy) //log.debug "setLocked | $bIO" if (rSt == locked || bIO) { if (!buttonOnlySetsLocked && rSt != vacant) /// child."${(resetEngagedDirectly ? vacant : checking)}"(); // "${(resetEngagedDirectly ? vacant : checking)}"() roomVacant() } else locked() } def buttonPushedVacantEventHandler(evt) { ifDebug("buttonPushedVacantEventHandler", 'info') if (!checkPauseModesAndDoW()) return; if (state.hT == _SmartThings) { if (!evt.data) return; def eD = new groovy.json.JsonSlurper().parseText(evt.data) assert eD instanceof Map if (!eD || (buttonIsVacant && eD['buttonNumber'] && eD['buttonNumber'] != buttonIsVacant as Integer)) return; } if (getChildDevice(getRoom())?.currentValue(occupancy) != vacant) vacant(); } def vacantSwitchOffEventHandler(evt) { ifDebug("vacantSwitchOffEventHandler", 'info') if (!checkPauseModesAndDoW()) return; if (['engaged', 'occupied', 'checking'].contains(getChildDevice(getRoom())?.currentValue(occupancy))) vacant() } def buttonPushedAsleepEventHandler(evt) { ifDebug("buttonPushedAsleepEventHandler", 'info') if (!checkPauseModesAndDoW()) return; if (state.hT == _SmartThings) { if (!evt.data) return; def eD = new groovy.json.JsonSlurper().parseText(evt.data) assert eD instanceof Map if (!eD || (buttonIsAsleep && eD['buttonNumber'] != buttonIsAsleep as Integer)) return; } if (getChildDevice(getRoom())?.currentValue(occupancy) == asleep) { if (!buttonOnlySetsAsleep) roomAwake() } else asleep() } def anotherRoomEngagedButtonPushedEventHandler(evt) { ifDebug("anotherRoomEngagedButtonPushedEventHandler", 'info') if (!checkPauseModesAndDoW()) return; if (personsPresence && presenceActionContinuous && personsPresence.currentPresence.contains(present)) return; if (state.hT == _SmartThings) { if (!evt.data) return; def eD = new groovy.json.JsonSlurper().parseText(evt.data) assert eD instanceof Map if (!eD || eD['buttonNumber'] != 9) return; } if (getChildDevice(getRoom())?.currentValue(occupancy) == engaged) roomVacant(); } def presenceEventHandler(evt) { ifDebug("presenceEventHandler", 'info') def departed = (evt.value == 'not present' ? true : false) if (departed && personsPresence.currentPresence.contains(present)) return; def child = getChildDevice(getRoom()) updatePresenceInd(0) scheduleRunEvery5() if (!checkPauseModesAndDoW()) return; def rSt = child?.currentValue(occupancy) processCoolHeat((rSt == locked && lockedProcessTemp ? engaged : null)) if ((rSt == locked && lockedOverrides) || (rSt == asleep && asleepOverrides)) return; if (departed) { if (presenceActionDeparture() && ['asleep', 'engaged', 'occupied'].contains(rSt)) roomVacant(); state.thermoOverride = false updateThermoOverrideIndP() } else if (presenceActionArrival() && ['occupied', 'checking', 'vacant'].contains(rSt)) engaged(); processHumidity() } def engagedSwitchEventHandler(evt) { ifDebug("engagedSwitchEventHandler", 'info') updateSwitchInd(isAnyESwitchOn(), 'E'); if (!checkPauseModesAndDoW()) return; def child = getChildDevice(getRoom()) def rSt = child?.currentValue(occupancy) if ((rSt == locked && lockedOverrides) || (rSt == asleep && asleepOverrides)) return; if (isRoomEngaged()) (rSt != engaged ? engaged() : refreshEngagedTimer()) else if (rSt == engaged) roomVacant() } private isRoomEngaged() { ifDebug("isRoomEngaged", 'info') if (personsPresence && presenceActionContinuous && personsPresence.currentPresence.contains(present)) return true; /* add check to look back at power value for powerStaysAbove for is engaged check with power */ if (engagedSwitch && engagedSwitch.currentSwitch.contains('on')) return true; if (contactSensor && !contactSensorNotTriggersEngaged) { def cV = contactSensor.currentContact.contains(open) if ((!contactSensorOutsideDoor && !cV) || (contactSensorOutsideDoor && cV)) return true; } if (powerDevice && powerValueEngaged && (powerKeepsEngaged || getChildDevice(getRoom())?.currentValue(occupancy) != engaged)) { def currentPower = (powerDevice instanceof List ? powerDevice.currentPower.max() : powerDevice.currentPower) if (getIntfromStr((String) currentPower) >= powerValueEngaged) { if (powerStaysAbove && state.powerLastMins) { def nowTime = now() def backTime = nowTime - (powerStaysAbove * 1000l) def foundValue = false def pVE = powerValueEngaged.toInteger() for (def pwr : state.powerLastMins) if (Long.valueOf(pwr.key) >= backTime && pwr.value.toInteger() < pVE) { foundValue = true break } if (!foundValue) return true; } else return true } } if (musicDevice && musicEngaged && musicDevice.currentStatus == 'playing') return true; return false } private refreshOccupiedTimer() { if (state.noMotion) addSchedule("roomVacant", state.noMotion, state.scheduled.isExecuting) } private refreshEngagedTimer() { if (state.noMotionEngaged && (!presenceActionContinuous || !personsPresence.currentPresence.contains(present))) addSchedule("roomVacant", state.noMotionEngaged, state.scheduled.isExecuting) } def contactOpenEventHandler(evt) { ifDebug("contactOpenEventHandler", 'info') def cV = (contactSensor ? contactSensor.currentContact : '') contactAnnounce(evt.device, cV, (contactSensorOutsideDoor ? false : true)) def child = getChildDevice(getRoom()) updateContactInd(contactSensorOutsideDoor ? (cV.contains(open) ? 0 : 1) : 0, false) scheduleRunEvery5() if (!checkPauseModesAndDoW()) return; def rSt = child?.currentValue(occupancy) if ((rSt == locked && lockedOverrides) || (rSt == asleep && asleepOverrides)) return; if (rSt == engaged) { if (contactEngagedRequiresMotion || contactSensorOutsideDoor) checking() else if (isRoomEngaged()) contactEngaged(true) else if (!contactSensorNotTriggersEngaged && (!contactSensorOutsideDoor || !cV.contains(open))) roomVacant() } else if (rSt == asleep) { if (resetAsleepWithContact) { // def rAwC = (resetAsleepWithContact as Integer) * 60 addSchedule("resetAsleep", ((resetAsleepWithContact as Integer) * 60), state.scheduled.isExecuting) } } else if (rSt == vacant) { if (hasOccupiedDevice()) roomVacant() } else if (rSt == occupied) { def motionActive = ((!motionSensors || (whichNoMotion == lastMotionInactive && motionSensors.currentMotion.contains(active))) || (!accelSensors || accelSensors.currentAcceleration.contains(active))) if (!motionActive || contactEngagedRequiresMotion) checking() } else if (rSt == checking) addSchedule("roomVacant", state.dimTimer, state.scheduled.isExecuting) } def contactClosedEventHandler(evt) { ifDebug("contactClosedEventHandler", 'info') def cV = (contactSensor ? contactSensor.currentContact : '') contactAnnounce(evt.device, cV, (contactSensorOutsideDoor ? true : false)) def child = getChildDevice(getRoom()) updateContactInd(contactSensorOutsideDoor ? 0 : (cV.contains(open) ? 0 : 1), false) if (!checkPauseModesAndDoW()) return; def rSt = child?.currentValue(occupancy) if ((rSt == locked && lockedOverrides) || (rSt == asleep && asleepOverrides)) return; if (rSt == engaged) { if (contactEngagedRequiresMotion) checking() else if (isRoomEngaged()) contactEngaged(false) else if (!contactSensorNotTriggersEngaged) refreshEngagedTimer() } else if (rSt == asleep) { //log.debug "resetAsleepWithContact: $resetAsleepWithContact | cV.contains(open): ${!cV.contains(open)}" if (resetAsleepWithContact && !cV.contains(open)) removeSchedule("resetAsleep", state.scheduled.isExecuting) } else if (['checking', 'vacant'].contains(rSt)) { if (hasOccupiedDevice()) "${(isRoomOccupied() ? occupied : checking)}"() else if (((!contactEngagedRequiresMotion || !motionSensors) && (contactSensorOutsideDoor && cV.contains(open)) || (!contactSensorOutsideDoor && !cV.contains(open))) && !contactEngagedRequiresMotion) engaged() else if (rSt == vacant) checking() else addSchedule("roomVacant", state.dimTimer, state.scheduled.isExecuting) } else if (rSt == occupied) { if (!contactSensorNotTriggersEngaged && (contactSensorOutsideDoor || !cV.contains(open))) { def motionActive = ((!motionSensors || (whichNoMotion == lastMotionInactive && motionSensors.currentMotion.contains(active))) || (!accelSensors || accelSensors.currentAcceleration.contains(active))) if (motionActive && !contactEngagedRequiresMotion) engaged(); } } } private contactEngaged(fromOpened) { ifDebug("contactEngaged", 'info') if (personsPresence && presenceActionContinuous && personsPresence.currentPresence.contains(present)) unscheduleAll("contact closed handler") else if (resetEngagedWithContact) { if (fromOpened) addSchedule("resetEngaged", ((resetEngagedWithContact as Integer) * 60), state.scheduled.isExecuting) else removeSchedule("resetEngaged", state.scheduled.isExecuting) } else refreshEngagedTimer() } private contactAnnounce(dev, cV, opened) { ifDebug("contactAnnounce", 'info') if (announceDoor) { if (announceDoorColor) setupColorNotification(convertRGBToHueSaturation(colorsRGB[announceDoorColor][1])) if (announceDoorSpeaker) speakIt(dev.displayName + (opened ? ' opened. ' : ' closed. ')) } if (announceContact) if (cV.contains(open)) addSchedule("contactStaysOpen", ((announceContact as Integer) * 60), state.scheduled.isExecuting) else removeSchedule("contactStaysOpen", state.scheduled.isExecuting) } def contactStaysOpen() { ifDebug("contactStaysOpen", 'info') if (contactSensor.currentContact.contains(open)) { def cO = '', cOCount = 0 for (def sen : contactSensor) if (sen.currentContact == open) { cO = (cO.size() > 0 ? ', ' : '') + sen.displayName cOCount = cOCount + 1 } if (cOCount > 0 && announceContact) { cO = addAnd(cO) if (announceContactColor) setupColorNotification(convertRGBToHueSaturation(colorsRGB[announceContactColor][1])) if (announceContactSpeaker) speakIt('Contacts ' + cO + " ${(cOCount == 1 ? 'is' : 'are')} open. ") } addSchedule("contactStaysOpen", announceContact.toInteger() * 60, state.scheduled.isExecuting) } } private addAnd(str) { def lio = str.lastIndexOf(',') return (lio == -1 ? str : (str.substring(0, lio) + " and " + str.substring(lio + 1))) } def resetEngaged() { if (getChildDevice(getRoom())?.currentValue(occupancy) == engaged) roomVacant() } private resetAsleep() { if (getChildDevice(getRoom())?.currentValue(occupancy) == asleep) roomAwake() } def contactsRTEventHandler(evt) { ifDebug("contactsRTEventHandler", 'info') def child = getChildDevice(getRoom()) updateContactInd(contactSensorsRT.currentContact.contains(open) ? 0 : 1, true) if (announceContactRT) { if (announceContactRTColor) { setupColorNotification(convertRGBToHueSaturation(colorsRGB[announceContactRTColor][1])) } if (announceContactRTSpeaker) speakIt(evt.device.displayName + ' ' + evt.value.toString() + '.'); } def rSt = child?.currentValue(occupancy) if (checkPauseModesAndDoW()) processCoolHeat((rSt == locked && lockedProcessTemp ? engaged : null)); } def waterEventHandler(evt) { ifDebug("waterEventHandler", 'info') def child = getChildDevice(getRoom()) // int currentLux = getIntfromStr((String) evt.value) // updateLuxInd(currentLux) scheduleRunEvery5() if (!checkPauseModesAndDoW()) return; def rSt = child?.currentValue(occupancy) if (!onlyOnStateChange || (butNotInStates && butNotInStates.contains(rSt))) switchesOnOrOff() } def musicPlayingEventHandler(evt) { ifDebug("musicPlayingEventHandler", 'info') def child = getChildDevice(getRoom()) scheduleRunEvery5() if (!checkPauseModesAndDoW()) return; def rSt = child?.currentValue(occupancy) if ((rSt == locked && lockedOverrides) || (rSt == asleep && asleepOverrides)) return; if (rSt == engaged) refreshEngagedTimer() else if (isRoomEngaged()) engaged() else if (hasOccupiedDevice() && isRoomOccupied()) "${(state.dimTimer ? occupied : checking)}"() else checking() } def musicStoppedEventHandler(evt) { ifDebug("musicStoppedEventHandler", 'info') def child = getChildDevice(getRoom()) if (!checkPauseModesAndDoW()) return; if (isRoomEngaged()) return; def rSt = child?.currentValue(occupancy) if ((rSt == locked && lockedOverrides) || (rSt == asleep && asleepOverrides)) return; if (rSt == engaged && !isRoomEngaged()) "${(resetEngagedDirectly ? vacant : checking)}"() else checking() } def temperatureEventHandler(evt) { def temperature = getAvgTemperature() // boolean isFarenheit = (location.temperatureScale == 'F' ? true : false) updateTemperatureInd(temperature); // scheduleRunEvery5() //log.debug "temperatureEventHandler" //log.debug checkPauseModesAndDoW() def child = getChildDevice(getRoom()) def rSt = child?.currentValue(occupancy) if (checkPauseModesAndDoW() && state.maintainRoomTemp) processCoolHeat((rSt == locked && lockedProcessTemp ? engaged : null), true); } def lockedEventHandler(evt) { ifDebug("lockedSwitchOnEventHandler", 'info') updateSwitchInd(isAnyLSwitchOn(), 'L'); if (!checkPauseModesAndDoW()) return; def child = getChildDevice(getRoom()) if (!child) return; def evtVal = (evt.name == 'switch' ? (lockedSwitchCmd ? lockedSwitch.currentSwitch.contains(on) : !lockedSwitch.currentSwitch.contains(on)) : (lockedContactCmd ? lockedContact.currentContact.contains(closed) : !lockedSwitch.currentSwitch.contains(closed))) // if (evt.name == 'switch') // evtVal = (lockedSwitchCmd ? lockedSwitch.currentSwitch.contains(on) : !lockedSwitch.currentSwitch.contains(on)) // else // evtVal = (lockedContactCmd ? lockedContact.currentContact.contains(closed) : !lockedSwitch.currentSwitch.contains(closed)) def rSt = child.currentValue(occupancy) if (evtVal) { if (rSt != locked) locked(); } else if (rSt == locked) { def motionActive = ((motionSensors && whichNoMotion == lastMotionInactive && motionSensors.currentMotion.contains(active)) || (accelSensors && accelSensors.currentAcceleration.contains(active))) "${(isRoomEngaged() ? engaged : (motionActive ? occupied : (state.dimTimer ? checking : vacant)))}"() } } def checkSwitchEventHandler(evt) { def rSt = getChildDevice(getRoom())?.currentValue(occupancy) if (!onlyOnStateChange || (butNotInStates && butNotInStates.contains(rSt))) switchesOnOrOff() } def roomThermostatEventHandler(evt) { if (!state.roomThermoTurnedOn) { state.thermoOverride = true addSchedule("thermoUnOverride", thermoOverride * 60, state.scheduled.isExecuting) updateThermoOverrideIndP() } else state.roomThermoTurnedOn = false } def roomCoolSwitchOnEventHandler(evt) { if (!state.roomCoolTurnedOn) { state.thermoOverride = true addSchedule("thermoUnOverride", thermoOverride * 60, state.scheduled.isExecuting) updateThermoOverrideIndP() } else state.roomCoolTurnedOn = false } def roomHeatSwitchOnEventHandler(evt) { if (!state.roomHeatTurnedOn) { state.thermoOverride = true addSchedule("thermoUnOverride", thermoOverride * 60, state.scheduled.isExecuting) updateThermoOverrideIndP() } else state.roomHeatTurnedOn = false } def roomCoolHeatSwitchOffEventHandler(evt) { removeSchedule("thermoUnOverride", state.scheduled.isExecuting) state.thermoOverride = false updateThermoOverrideIndP() updateCoolHeatInd(getAvgTemperature(), null, [:]) } private thermoUnOverride() { state.thermoOverride = false def rSt = getChildDevice(getRoom())?.currentValue(occupancy) processCoolHeat((rSt == locked && lockedProcessTemp ? engaged : null)) updateThermoOverrideIndP() } private updateThermoOverrideIndP() { if (state.hT == _Hubitat) return; def child = getChildDevice(getRoom()) if (child) child.sendEvent(name: 'thermoOverrideInd', value: (state.processCoolHeat ? (state.thermoOverride ? on : off) : '--'), descriptionText: "indicate thermo override minutes") } def processCoolHeat(rSt = null, canSkip = false) { ifDebug("processCoolHeat", 'info') if (!state.maintainRoomTemp || state.thermoOverride) return; def nowTime = now() //log.debug "$canSkip | $nowTime | $state.lastProcessedCH | $_ProcessCHEvery" if (canSkip && ((nowTime - (state.lastProcessedCH ?: 0L)) < _ProcessCHEvery)) return; state.lastProcessedCH = nowTime // def child = getChildDevice(getRoom()) if (!rSt) rSt = getChildDevice(getRoom())?.currentValue(occupancy); // ifDebug("rSt: $rSt") def temperature = getAvgTemperature() // def updateMaintainInd = true def turnOn = null def thisRule = [:] if (['engaged', 'occupied', 'asleep', 'vacant'].contains(rSt) && (!checkPresence || (personsPresence ? personsPresence.currentPresence.contains(present) : false))) { turnOn = checkForRules(_TRule, rSt) if (turnOn) thisRule = getRule(turnOn, _TRule) } //log.debug "temperature turnOn: $turnOn" ifDebug("processCoolHeat: rule: $turnOn") def turnOff = true if (turnOn && maintainRoomTemp != '4') { //log.debug "processCoolHeat: rule: $turnOn | maintainRoomTemp: $maintainRoomTemp | useThermostat: $useThermostat | roomThermostat: $roomThermostat | roomCoolSwitch: $roomCoolSwitch | roomHeatSwitch: $roomHeatSwitch | contactSensorsRTCheck: $contactSensorsRTCheck | contactSensorsRT: $contactSensorsRT | contactSensorsRT.currentContact: $contactSensorsRT?.currentContact" if (thisRule.coolTemp && thisRule.heatTemp && Math.abs(thisRule.coolTemp - thisRule.heatTemp) < 2.0) { ifDebug("Cool($thisRule.coolTemp) and heat($thisRule.heatTemp) temperature less than 2º apart. Not processing cooling and heating rule # ${thisRule.ruleNo}.", "error") log.debug "Cool($thisRule.coolTemp) and heat($thisRule.heatTemp) temperature less than 2º apart. Not processing cooling and heating rule # ${thisRule.ruleNo}." } else { def cooling = false def heating = false if (['1', '3', '5'].contains(maintainRoomTemp) && ((useThermostat && roomThermostat) || (!useThermostat && roomCoolSwitch)) && (!contactSensorsRTCheck || (!contactSensorsRT || !contactSensorsRT.currentContact.contains(open)))) { //log.debug "cool" cooling = coolIt(thisRule, temperature) //turnOff = false } if (['2', '3', '5'].contains(maintainRoomTemp) && ((useThermostat && roomThermostat) || (!useThermostat && roomHeatSwitch)) && (!contactSensorsRTCheckHeat || (!contactSensorsRT || !contactSensorsRT.currentContact.contains(open)))) { //log.debug "heat" heating = heatIt(thisRule, temperature) //turnOff = false } turnOff = !(cooling || heating) //log.debug "turnOff: $turnOff" } } if (turnOff) { state.roomThermoTurnedOn = false state.roomCoolTurnedOn = false state.roomHeatTurnedOn = false if (useThermostat) { if (['1', '2', '3'].contains(maintainRoomTemp)) { if (roomThermostat) roomThermostat.auto() if (roomVents) delayedVentOff() } } else { if (roomCoolSwitch && (!cmdOpt || roomCoolSwitch.currentSwitch == on)) roomCoolSwitch.off() if (roomHeatSwitch && (!cmdOpt || roomHeatSwitch.currentSwitch == on)) roomHeatSwitch.off() } } if (roomFanSwitch) { def fanOff = true if (turnOn && thisRule.fanOnTemp) { def fanLowTemp = (thisRule.fanOnTemp + 0f).round(1) def fanMediumTemp = (thisRule.fanOnTemp + (thisRule.fanSpeedIncTemp * 1f)).round(1) def fanHighTemp = (thisRule.fanOnTemp + (thisRule.fanSpeedIncTemp * 2f)).round(1) // ifDebug("temperature: $temperature | fanOnTemp: $thisRule.fanOnTemp | fanLowTemp: $fanLowTemp | fanMediumTemp: $fanMediumTemp | fanHighTemp: $fanHighTemp") if (temperature >= fanLowTemp) { fanOff = false if (!cmdOpt || roomFanSwitch.currentSwitch == off) roomFanSwitch.on() if (roomFanSwitch.hasCommand("setLevel")) roomFanSwitch.setLevel((temperature >= fanHighTemp ? fanHigh : (temperature >= fanMediumTemp ? fanMedium : fanLow))) } // else // if (!cmdOpt || roomFanSwitch.currentSwitch == on) // roomFanSwitch.off() } // else if (fanOff && (!cmdOpt || roomFanSwitch.currentSwitch == on)) roomFanSwitch.off() } updateCoolHeatInd(temperature, turnOn, thisRule); //log.debug "\tperf processCoolHeat: ${now() - nowTime} ms" } // processCoolHeat private coolIt(thisRule, temperature) { def coolHigh = thisRule.coolTemp + (thisRule.tempRange / 2f).round(1) def coolLow = thisRule.coolTemp - (thisRule.tempRange / 2f).round(1) if (outTempSensor && autoAdjustWithOutdoor) { def outTemp = outTempSensor.currentTemperature boolean isFarenheit = (location.temperatureScale == 'F' ? true : false) if (outTemp > (isFarenheit ? 90 : 26.7)) { coolHigh = coolHigh - (isFarenheit ? 0.5 : 0.28) coolLow = coolLow - (isFarenheit ? 0.5 : 0.28) } } if (temperature >= coolHigh) { //log.debug "$temperature >= $coolHigh" if (useThermostat && maintainRoomTemp != '5') { state.roomThermoTurnedOn = true roomThermostat.setCoolingSetpoint(thisRule.coolTemp - thermoToTempSensor) roomThermostat.fanAuto() roomThermostat.cool() } else if (roomCoolSwitch) { //log.debug "turn on $roomCoolSwitch.currentSwitch" state.roomCoolTurnedOn = true if (!cmdOpt || roomCoolSwitch.currentSwitch == off) roomCoolSwitch.on(); } } else if (temperature <= coolLow && ((useThermostat && maintainRoomTemp != '5') || roomCoolSwitch)) { state.roomThermoTurnedOn = false state.roomCoolTurnedOn = false if (useThermostat) roomThermostat.auto() else if (!cmdOpt || roomCoolSwitch.currentSwitch == on) roomCoolSwitch.off() } if (useThermostat && roomVents) { if (roomThermostat.currentThermostatOperatingState == 'cooling') { def ventLevel = (((temperature - coolLow) * 100) / (coolHigh - coolLow)).round(0) ventLevel = (ventLevel > 100 ? 100 : (ventLevel > 0 ?: 0)) ventsOn(ventLevel) } else delayedVentOff() } return (state.roomThermoTurnedOn || state.roomCoolTurnedOn) } private heatIt(thisRule, temperature) { def heatHigh = thisRule.heatTemp + (thisRule.tempRange / 2f).round(1) def heatLow = thisRule.heatTemp - (thisRule.tempRange / 2f).round(1) if (outTempSensor && autoAdjustWithOutdoor) { def outTemp = outTempSensor.currentTemperature boolean isFarenheit = (location.temperatureScale == 'F' ? true : false) if (outTemp < (isFarenheit ? 32 : 0)) { heatHigh = heatHigh + (isFarenheit ? 0.5 : 0.28) heatLow = heatLow + (isFarenheit ? 0.5 : 0.28) } } if (temperature >= heatHigh && ((useThermostat && maintainRoomTemp != '5') || roomHeatSwitch)) { state.roomThermoTurnedOn = false state.roomHeatTurnedOn = false if (useThermostat) roomThermostat.auto() else if (!cmdOpt || roomHeatSwitch.currentSwitch == on) roomHeatSwitch.off() } else if (temperature <= heatLow) { if (useThermostat && maintainRoomTemp != '5') { state.roomThermoTurnedOn = true roomThermostat.setHeatingSetpoint(thisRule.heatTemp - thermoToTempSensor) roomThermostat.fanAuto() roomThermostat.heat() } else if (roomHeatSwitch) { state.roomHeatTurnedOn = true if (!cmdOpt || roomHeatSwitch.currentSwitch == off) roomHeatSwitch.on(); } } if (useThermostat && roomVents) { if (roomThermostat.currentThermostatOperatingState == 'heating') { def ventLevel = (((temperature - heatLow) * 100 ) / (heatHigh - heatLow)).round(0) ventLevel = (ventLevel > 100 ? 100 : (ventLevel > 0 ?: 0)) ventsOn(ventLevel) } else delayedVentOff() } return (state.roomThermoTurnedOn || state.roomHeatTurnedOn) } private updateCoolHeatInd(temperature, turnOn, thisRule) { if (state.hT == _Hubitat) return; def child = getChildDevice(getRoom()) if (!child) return; if (turnOn) { def temp = 999 boolean isFarenheit = (location.temperatureScale == 'F' ? true : false) def coolTemp = thisRule?.coolTemp def heatTemp = thisRule?.heatTemp if (outTempSensor && autoAdjustWithOutdoor) { def outTemp = outTempSensor?.currentTemperature if (outTemp > (isFarenheit ? 90 : 26.7)) coolTemp = coolTemp - (isFarenheit ? 0.5 : 0.28); if (outTemp < (isFarenheit ? 32 : 0)) heatTemp = heatTemp + (isFarenheit ? 0.5 : 0.28); } if (maintainRoomTemp == '1') temp = coolTemp else if (maintainRoomTemp == '2') temp = heatTemp else if (maintainRoomTemp == '3' || maintainRoomTemp == '5') temp = (Math.abs(temperature - coolTemp) >= Math.abs(temperature - heatTemp) ? heatTemp : coolTemp) else temp = -1 if (temp != 999) { def tS = (location.temperatureScale ?: 'F') child.sendEvent(name: 'maintainInd', value: (temp == -1 ? '--' : temp + '°' + tS), unit: tS, descriptionText: (temp == -1 ? "indicate no maintain temperature" : "indicate maintain temperature value")) } } updateThermostatIndP() } private updateThermostatIndP() { if (state.hT == _Hubitat) return; def child = getChildDevice(getRoom()) if (!child) return; def currentPresence = (personsPresence ? personsPresence.currentPresence : '') def currentThermostatOperatingState = roomThermostat?.currentThermostatOperatingState def thermo = 9 def isHere = currentPresence.contains('present') if ((useThermostat && currentThermostatOperatingState == 'cooling') || (!useThermostat && roomCoolSwitch?.currentSwitch == on)) thermo = 4 else if ((useThermostat && currentThermostatOperatingState == 'heating') || (!useThermostat && roomHeatSwitch?.currentSwitch == on)) thermo = 5 else if (!isHere && ['1', '2', '3'].contains(maintainRoomTemp)) thermo = 0 else if (maintainRoomTemp == '3') thermo = 1 else if (maintainRoomTemp == '1') thermo = 2 else if (maintainRoomTemp == '2') thermo = 3 ifDebug("updateThermostatIndC: thermo: $thermo") def vV = '--' def dD = "indicate no thermostat setting" switch(thermo) { case 0: vV = 'off'; dD = "indicate thermostat not auto"; break case 1: vV = 'auto'; dD = "indicate thermostat auto"; break case 2: vV = 'autoCool'; dD = "indicate thermostat auto cool"; break case 3: vV = 'autoHeat'; dD = "indicate thermostat auto heat"; break case 4: vV = 'cooling'; dD = "indicate thermostat cooling"; break case 5: vV = 'heating'; dD = "indicate thermostat heating"; break } child.sendEvent(name: 'thermostatInd', value: vV, descriptionText: dD) } def updateFanIndP(evt = [:]) { if (state.hT == _Hubitat) return; def child = getChildDevice(getRoom()) if (!child) return; def cL = roomFanSwitch?.currentLevel def vV = '--' def dD = "indicate no fan setting" switch((!roomFanSwitch ? -1 : (roomFanSwitch.currentSwitch == 'off' ? 0 : (cL <= fanLow ? 1 : (cL <= fanMedium ? 2 : 3))))) { case 0: vV = 'off'; dD = "indicate fan off"; break case 1: vV = 'low'; dD = "indicate fan on at low speed"; break case 2: vV = 'medium'; dD = "indicate fan on at medium speed"; break case 3: vV = 'high'; dD = "indicate fan on at high speed"; break } child.sendEvent(name: 'fanInd', value: vV, descriptionText: dD) } def updateVentIndP(evt = [:]) { if (state.hT == _Hubitat) return; def child = getChildDevice(getRoom()) if (!child) return; def vent = (!roomVents ? -1 : (roomVents.currentSwitch.contains(on) ? 1 : 0)) def vL, dD switch(vent) { case -1: vL = 'none'; dD = "indicate no vents"; break case 0: vL = 'closed'; dD = "indicate vents closed"; break default: vL = 'open'; dD = "indicate vent open"; break } child.sendEvent(name: 'ventInd', value: vL, descriptionText: dD) } private delayedVentOff() { if (roomVents) (delayedVentOff ? addSchedule("ventsOff", delayedVentOff * 60, state.scheduled.isExecuting) : ventsOff()) } private ventsOff() { if (!cmdOpt || roomVents.currentSwitch == on) roomVents.off() } private ventsOn(lvl) { removeSchedule("ventsOff", state.scheduled.isExecuting) roomVents.setLevel(lvl) } private checkForRules(ruleType, rSt = null) { def turnOn = null def turnOnRules = [:] def thisRule = [:] def child = getChildDevice(getRoom()) if (!rSt) rSt = child?.currentValue(occupancy); if (state.rules) { def nowTime = now() + 1000 def nowDate = new Date(nowTime) def sunRiseAndSet = sunRiseAndSet() if (!sunRiseAndSet) return; def timedRulesOnly = false for (def i = 1; i <= maxRules; i++) { def ruleHasTime = false def ruleNo = String.valueOf(i) // if (turnOnRules[(ruleNo)]) thisRule = turnOnRules[(ruleNo)]; // else thisRule = getNextRule(ruleNo, ruleType, true, true); thisRule = (turnOnRules[(ruleNo)] ? turnOnRules[(ruleNo)] : getNextRule(ruleNo, ruleType, true, true)) if (thisRule.ruleNo == 'EOR') break; if (!turnOnRules[(thisRule.ruleNo)]) turnOnRules << [(thisRule.ruleNo):thisRule]; i = thisRule.ruleNo as Integer if (thisRule.mode && !thisRule.mode.contains(location.currentMode.toString())) continue; if (thisRule.state && !thisRule.state.contains(rSt)) continue; if (thisRule.dayOfWeek && !(checkRunDay(thisRule.dayOfWeek))) continue; if (state.timeCheck && (thisRule.fromTimeType && (thisRule.fromTimeType != _timeTime || thisRule.fromTime)) && (thisRule.toTimeType && (thisRule.toTimeType != _timeTime || thisRule.toTime))) { def x = compareRuleTime(sunRiseAndSet, thisRule) if (!x.inBetween) continue; if (!timedRulesOnly) { turnOn = null timedRulesOnly = true i = 0 continue } ruleHasTime = true } if (timedRulesOnly && !ruleHasTime) continue; turnOn = thisRule.ruleNo } } return turnOn } def humidityEventHandler(evt) { ifDebug('humidityEventHandler', 'info') def nowTime = now() def child = getChildDevice(getRoom()) def avgHumidity = getAvgHumidity() state.humidity.lastReading = avgHumidity updateHumidityInd(avgHumidity) if (!state.avgHumidity) state.avgHumidity = [:] def thisDay = (new Date(now())).getDay() def intCurrentHH = (new Date(now())).format("HH", location.timeZone) as Integer def mapKey = String.format("%03d", ((thisDay.toInteger() * 100) + intCurrentHH)) state.avgHumidity[mapKey] = (state.avgHumidity[mapKey] ? ((state.avgHumidity[mapKey] + avgHumidity) / 2f).round(1) : avgHumidity) if (state.avgHumidity.size() > 2) { def rAvgHumidity def y = state.avgHumidity?.findAll { !it.key.startsWith('rAH') }?.collect { it.value } if (y) { rAvgHumidity = ((y.sum() / y.size()) * 1f).round(1) state.avgHumidity['rAH'] = rAvgHumidity } if (state.humidity.hour != intCurrentHH) { for (def i = 0; i < 7; i++) { def j = i * 100 def k = state.avgHumidity.findAll { it.key.isInteger() && it.key.toInteger() >= j && it.key.toInteger() <= (j + 23) } if (k && k.size() > 2) { rAvgHumidity = ((k.collect { it.value }.sum() / k.size()) * 1f).round(1) mapKey = String.format("%02dd", i) state.avgHumidity["rAH$mapKey"] = rAvgHumidity } } for (def i = 0; i < 24; i++) { def k = [] for (def j = 0; j < 7; j++) { mapKey = String.format("%03d", ((j * 100) + i)) if (state.avgHumidity[mapKey]) k << state.avgHumidity[mapKey]; } if (k && k.size() > 2) { rAvgHumidity = ((k.sum() / k.size()) * 1f).round(1) mapKey = String.format("%02dh", i) state.avgHumidity["rAH$mapKey"] = rAvgHumidity } } state.humidity.hour = intCurrentHH } } // scheduleRunEvery5() if (!checkPauseModesAndDoW()) return; def rSt = child?.currentValue(occupancy) if (!onlyOnStateChange || (butNotInStates && butNotInStates.contains(rSt))) switchesOnOrOff() processHumidity() } def processHumidity(rSt = null) { ifDebug("processHumidity", 'info') if (!state.maintainRoomHumi || state.humidity.override || state.humidity.offIn) return; def nowTime = now() if (!rSt) rSt = getChildDevice(getRoom())?.currentValue(occupancy) if (rSt == checking) return; def humidity = getAvgHumidity() if (state.humidity.offAt) { def humiOffAt if (!(state.humidity.offAt instanceof String)) humiOffAt = state.humidity.offAt else { if (state.humidity.offAt == 'HAV') mapKey = String.format("%02dh", intCurrentHH) else if (state.humidity.offAt == 'DAV') mapKey = String.format("%02dd", thisDay) else if (state.humidity.offAt == 'WAV') mapKey = '' humiOffAt = (state.avgHumidity["rAH$mapKey"] ?: (state.avgHumidity["rAH"] ?: humidity)) } if ((roomDehumidifierSwitch?.currentSwitch == on && humidity <= humiOffAt) || (roomHumidifierSwitch?.currentSwitch == on && humidity >= humiOffAt)) state.humidity.offAt = false if (state.humidity.offAt) return; } def thisRule = [:] def turnOn = null def postProcessing = false if ([engaged, occupied, asleep, vacant].contains(rSt)) { turnOn = checkForRules(_HRule, rSt) if (turnOn) { thisRule = getRule(turnOn, _HRule) // if (turnOn && thisRule.state) { if (thisRule.state) { // if (thisRule.state.contains(engaged) || thisRule.state.contains(asleep) || (thisRule.state.contains(occupied) && state.humidity.lastState == vacant)) if ([engaged, asleep, occupied].contains(thisRule.state) && state.humidity.lastState == vacant) state.humidity.lastRule = null } } } // ifDebug("$state.humidity.lastRule | $state.humidity.lastState | $rSt | $state.humidity.minsInState") if (state.humidity.lastRule && state.humidity.lastState != rSt) { def getOldRule = true if (state.humidity.minsInState > 0) { def tDiff = (((now() - (state.humidity.lastStateAt ?: 0)) % _SecondsInDay) / 60000f).trunc(0) // ifDebug("state.humidity.minsInState: $state.humidity.minsInState | tDiff: $tDiff") if (tDiff < state.humidity.minsInState) getOldRule = false } if (getOldRule) { turnOn = state.humidity.lastRule thisRule = getRule(turnOn, _HRule) if (thisRule.humiCmp || thisRule.humiMinRun || thisRule.humiMaxRun) postProcessing = true } } state.humidity.lastRule = turnOn state.humidity.lastState = rSt state.humidity.lastStateAt = now() state.humidity.minsInState = (thisRule.humiMins ?: 0) if (!turnOn) { turnOffHumiSwitches() //log.debug "\tperf processHumidity 1: ${now() - nowTime} ms" return } // ifDebug("processHumidity: rule: $turnOn | $thisRule.humiMinRun | $thisRule.humiMaxRun | $rSt") if (!postProcessing) if (thisRule.deHumiOn) { turnOnRoomDehumidifier() return } else if (thisRule.humiOn) { turnOnRoomHumidifier() return } def thisDay = (new Date(now())).getDay() def intCurrentHH = (new Date(now())).format("HH", location.timeZone) as Integer def cmpHumidity = getHumiCmp(thisRule) ifDebug("cmpHumidity: $cmpHumidity | $rSt | $state.humidity.lastState") def turnOff = true state.humidity.forceOffIn = false if (thisRule.humiMinRun) state.humidity.offIn = thisRule.humiMinRun if (thisRule.humiMaxRun) state.humidity.forceOffIn = thisRule.humiMaxRun if (cmpHumidity) { def humiPercented = ((cmpHumidity * thisRule.humiPercent) / 100f).round(1) def humiIncrease = cmpHumidity + humiPercented def humiDecrease = cmpHumidity - humiPercented if (humidity >= humiIncrease && roomDehumidifierSwitch) { turnOnRoomDehumidifier() state.humidity.offAt = cmpHumidity as float turnOff = false } else if (humidity <= humiDecrease && roomHumidifierSwitch) { turnOnRoomHumidifier() state.humidity.offAt = cmpHumidity as float turnOff == false } } ifDebug("$state.humidity.offIn | $state.humidity.forceOffIn") if (turnOff) turnOffHumiSwitches() if (state.humidity.offIn || state.humidity.forceOffIn) humidityTimer() //log.debug "\tperf processHumidity: ${now() - nowTime} ms" } private getHumiCmp(thisRule) { ifDebug("getHumiCmp", 'info') def cmpHumidity = false switch(thisRule.humiCmp) { case '0': cmpHumidity = thisRule.humiValue as float break case '1': cmpHumidity = (state.humidity.previous ?: (state.avgHumidity["rAH"] ?: false)) state.humidity.previous = false break case '2': cmpHumidity = (state.avgHumidity["rAH${String.format("%02dh", intCurrentHH)}"] ?: (state.avgHumidity["rAH"] ?: (state.humidity.previous ?: false))) break case '3': cmpHumidity = (state.avgHumidity["rAH${String.format("%02dd", thisDay)}"] ?: (state.avgHumidity["rAH"] ?: (state.humidity.previous ?: false))) break case '4': cmpHumidity = (state.avgHumidity["rAH"] ?: (state.humidity.previous ?: false)) break } return cmpHumidity } private turnOnRoomDehumidifier() { ifDebug("turnOnRoomDehumidifier", 'info') if (roomHumidifierSwitch && (!cmdOpt || roomHumidifierSwitch.currentSwitch == on)) roomHumidifierSwitch.off() if (roomDehumidifierSwitch && roomDehumidifierSwitch.currentSwitch == off) { state.humidity.dehumidifierTurnedOn = true if (!cmdOpt || roomDehumidifierSwitch.currentSwitch == off) roomDehumidifierSwitch.on() } } private turnOnRoomHumidifier() { ifDebug("turnOnRoomHumidifier", 'info') if (roomDehumidifierSwitch && (!cmdOpt || roomDehumidifierSwitch.currentSwitch == on)) roomDehumidifierSwitch.off() if (roomHumidifierSwitch && roomHumidifierSwitch.currentSwitch == off) { state.humidity.humidifierTurnedOn = true if (!cmdOpt || roomHumidifierSwitch.currentSwitch == off) roomHumidifierSwitch.on() } } private turnOffHumiSwitches() { ifDebug("turnOffHumiSwitches", 'info') if (roomDehumidifierSwitch && (!cmdOpt || roomDehumidifierSwitch.currentSwitch == on)) roomDehumidifierSwitch.off() if (roomHumidifierSwitch && (!cmdOpt || roomHumidifierSwitch.currentSwitch == on)) roomHumidifierSwitch.off() removeSchedule(["humidityTimer2", "humidityTimer3"], state.scheduled.isExecuting) state.humidity.offIn = state.humidity.forceOffIn = state.humidity.offAt = false state.humidity.lastRule = null } private humidityTimer() { ifDebug("humidityTimer", 'info') removeSchedule(["humidityTimer2", "humidityTimer3"], state.scheduled.isExecuting) if (roomDehumidifierSwitch?.currentSwitch == on || roomHumidifierSwitch?.currentSwitch == on) { if (state.humidity.offIn) addSchedule("humidityTimer2", state.humidity.offIn * 60, state.scheduled.isExecuting) else if (state.humidity.forceOffIn) humidityTimer2() else if (!state.humidity.offAt) turnOffHumiSwitches() } else turnOffHumiSwitches() } def humidityTimer2() { ifDebug("humidityTimer2", 'info') removeSchedule("humidityTimer3", state.scheduled.isExecuting) if (roomDehumidifierSwitch?.currentSwitch == on || roomHumidifierSwitch?.currentSwitch == on) { def offIn = state.humidity.offIn state.humidity.offIn = false state.humidity.lastRule = null processHumidity() if (state.humidity.forceOffIn && (roomDehumidifierSwitch?.currentSwitch == on || roomHumidifierSwitch?.currentSwitch == on)) addSchedule("humidityTimer3", (state.humidity.forceOffIn - (offIn ?: 0)) * 60, state.scheduled.isExecuting) } else { state.humidity.offIn = state.humidity.offAt = false } state.humidity.forceOffIn = false } def humidityTimer3() { ifDebug("humidityTimer3", 'info') turnOffHumiSwitches() } def roomDehumidifierSwitchOnEventHandler(evt) { ifDebug("roomDehumidifierSwitchOnEventHandler $state.humidity.dehumidifierTurnedOn", 'info') if (state.humidity.dehumidifierTurnedOn) state.humidity.dehumidifierTurnedOn = state.humidity.override = false else resetHumiAuto() } def roomDehumiAndHumiSwitchOffEventHandler(evt) { ifDebug("roomDehumiAndHumiSwitchOffEventHandler", 'info') removeSchedule("humiUnOverride", state.scheduled.isExecuting) state.humidity.override = false } def roomHumidifierSwitchOnEventHandler(evt) { ifDebug("roomHumidifierSwitchOnEventHandler $state.roomHumidifierTurnedOn", 'info') if (state.humidity.humidifierTurnedOn) state.humidity.humidifierTurnedOn = state.humidity.override = false else resetHumiAuto() } private resetHumiAuto() { state.humidity.override = true removeSchedule(["humidityTimer2", "humidityTimer3"], state.scheduled.isExecuting) state.humidity.offIn = state.humidity.forceOffIn = state.humidity.offAt = false state.humidity.lastRule = null state.humidity.minsInState = state.humidity.lastStateAt = 0 addSchedule("humiUnOverride", humiOverride * 60, state.scheduled.isExecuting) } def humiUnOverride() { ifDebug("humiUnOverride", 'info') state.humidity.override = false processHumidity() } def luxEventHandler(evt) { ifDebug("luxEventHandler", 'info') def child = getChildDevice(getRoom()) int currentLux = getIntfromStr((String) evt.value) updateLuxInd(currentLux) //scheduleRunEvery5() if (!checkPauseModesAndDoW()) return; def rSt = child?.currentValue(occupancy) if (!onlyOnStateChange || (butNotInStates && butNotInStates.contains(rSt))) switchesOnOrOff() } private getIntfromStr(String str) { // int intValue = 0 // if (!str) // intValue = 0 // else if (str.indexOf('.') >= 0) // intValue = str.substring(0, str.indexOf('.')) as Integer // else // intValue = str.toInteger(); return (!str ? 0 : (str.indexOf('.') >= 0 ? str.substring(0, str.indexOf('.')) as Integer : str.toInteger())) } private formatNumber(num) { return String.format("%,d", num as Integer) } def powerEventHandler(evt) { ifDebug("powerEventHandler", 'info') def nowTime = now() def child = getChildDevice(getRoom()) def currentPower = getIntfromStr((String) (powerDevice instanceof List ? powerDevice.currentPower.max() : powerDevice.currentPower)) updatePowerInd(currentPower) // scheduleRunEvery5() storePowerForLastMins(currentPower) if (!checkPauseModesAndDoW()) return; if (state.previousPower == currentPower) return; def rSt = child?.currentValue(occupancy) if ((rSt == locked && lockedOverrides && !powerValueLocked) || (rSt == asleep && asleepOverrides && !powerValueAsleep) || (rSt == engaged && engagedOverrides && !powerValueEngaged)) return def timeOK = (powerValueEngaged || powerValueAsleep || powerValueLocked ? true : false) if (timeOK && (powerFromTimeType && (powerFromTimeType != _timeTime || powerFromTime)) && (powerToTimeType && (powerToTimeType != _timeTime || powerToTime))) { def sunRiseAndSet = sunRiseAndSet() if (!sunRiseAndSet) return; def x = compareRuleTime(sunRiseAndSet, [ruleNo:'power', fromTimeType:powerFromTimeType, fromTime:powerFromTime, fromTimeOffset:powerFromTimeOffset, toTimeType:powerToTimeType, toTime:powerToTime, toTimeOffset:powerToTimeOffset]) if (!x.inBetween) timeOK = false } if (timeOK) { if (powerValueEngaged) { if (currentPower >= powerValueEngaged && state.previousPower < powerValueEngaged && (powerTriggerFromVacant ? (powerTriggerFromOccupied ? ['engaged', 'occupied', 'checking', 'vacant'].contains(rSt) : ['engaged', 'checking', 'vacant'].contains(rSt)) : (powerTriggerFromOccupied ? ['engaged', 'occupied', 'checking'].contains(rSt) : ['engaged', 'checking'].contains(rSt)))) { removeSchedule("powerStaysBelowEngaged", state.scheduled.isExecuting) if (powerStaysAbove) addSchedule("powerStaysAboveEngaged", powerStaysAbove, state.scheduled.isExecuting) else engaged() } else if (currentPower < powerValueEngaged && state.previousPower >= powerValueEngaged && rSt == engaged) { removeSchedule("powerStaysAboveEngaged", true) addSchedule("powerStaysBelowEngaged", powerStays, state.scheduled.isExecuting) } } else if (powerValueAsleep) { if (currentPower >= powerValueAsleep && state.previousPower < powerValueAsleep && ['engaged', 'occupied', 'checking', 'vacant'].contains(rSt) && (powerTriggerFromVacant || rSt != vacant)) { removeSchedule("powerStaysBelowAsleep", state.scheduled.isExecuting) asleep() } else if (currentPower < powerValueAsleep && state.previousPower >= powerValueAsleep && rSt == asleep) addSchedule("powerStaysBelowAsleep", powerStays, state.scheduled.isExecuting) } else if (powerValueLocked) { if (currentPower >= powerValueLocked && state.previousPower < powerValueLocked && ['engaged', 'occupied', 'checking', 'vacant'].contains(rSt) && (powerTriggerFromVacant || rSt != vacant)) { removeSchedule("powerStaysBelowLocked", state.scheduled.isExecuting) locked() } else if (currentPower < powerValueLocked && state.previousPower >= powerValueLocked && rSt == locked) addSchedule("powerStaysBelowLocked", powerStays, state.scheduled.isExecuting) } } if (state.powerCheck && (!onlyOnStateChange || (butNotInStates && butNotInStates.contains(rSt)))) switchesOnOrOff() state.previousPower = currentPower } private powerStaysAboveEngaged() { if (getChildDevice(getRoom())?.currentValue(occupancy) != engaged && isRoomEngaged()) engaged() } private powerStaysBelowEngaged() { if (getChildDevice(getRoom())?.currentValue(occupancy) == engaged && !isRoomEngaged()) roomVacant() } private powerStaysBelowAsleep() { if (getChildDevice(getRoom())?.currentValue(occupancy) == asleep) roomAwake() } private powerStaysBelowLocked() { if (getChildDevice(getRoom())?.currentValue(occupancy) == locked) roomVacant() } private storePowerForLastMins(cPwr) { // if (!powerStaysAbove) return; def nowTime = now() if (!state?.powerLastMins) state.powerLastMins = [:] else { def nowTime2 = nowTime - ((powerStaysAbove ?: 0).toInteger() * 1000l) def rmvPwr = [] for (def pwr : state.powerLastMins) if (Long.valueOf(pwr.key) < nowTime2) rmvPwr << pwr.key for (def rP : rmvPwr) state.powerLastMins.remove(rP) } state.powerLastMins << [(nowTime):cPwr.toInteger()] } def roomVacant(forceVacant = false) { ifDebug("roomVacant", 'info') def newState = vacant if (!forceVacant) { def rSt = getChildDevice(getRoom())?.currentValue(occupancy) def motionActive = ((motionSensors && whichNoMotion == lastMotionInactive && motionSensors.currentMotion.contains(active)) || (accelSensors && accelSensors.currentAcceleration.contains(active))) if (['engaged', 'occupied', 'checking', 'vacant', 'asleep'].contains(rSt) && motionActive) newState = (isRoomEngaged() ? engaged : occupied) else if ((rSt != engaged || !resetEngagedDirectly) && rSt != checking) newState = (state.dimTimer ? checking : vacant) } "$newState"() } private roomAwake() { ifDebug("roomAwake", 'info') removeSchedule("roomAwake", state.scheduled.isExecuting) if (getChildDevice(getRoom())?.currentValue(occupancy) != asleep) return; def nS = vacant if (isRoomEngaged()) nS = engaged else if ((motionSensors && whichNoMotion == lastMotionInactive && motionSensors.currentMotion.contains(active)) || (accelSensors && accelSensors.currentAcceleration.contains(active))) nS = occupied else if (!resetAsleepDirectly && state.dimTimer) nS = checking "$nS"() /// "${(isRoomEngaged() ? engaged : (((motionSensors && whichNoMotion == lastMotionInactive && motionSensors.currentMotion.contains(active)) || (accelSensors && accelSensors.currentAcceleration.contains(active))) ? occupied : (!resetAsleepDirectly && state.dimTimer ? checking : vacant)))}"() } private roomUnlocked() { ifDebug("roomUnlocked", 'info') if (getChildDevice(getRoom())?.currentValue(occupancy) == locked) roomVacant() } //def handleSwitches(oldState, newState, returnTimer = false, vacationMode = false) { private handleSwitches(oldState, newState, vacationMode = false) { ifDebug("${app.label} handleSwitches: room state - old: ${oldState} new: ${newState}", 'info') def nowTime = now() if (debugLogging && state.debugOffTime < nowTime) app.updateSetting('debugLogging', [type: 'bool', value: false]); if (oldState == newState) return false; previousStateStack(['state':newState, 'date':nowTime]) state.motiontraffic = 0 if (!vacationMode && !checkPauseModesAndDoW()) return; def child = getChildDevice(getRoom()) if (oldState == vacant) { if (nightSwitches && nightTurnOn && nightTurnOn.contains('3')) removeSchedule("nightSwitchesOff", state.scheduled.isExecuting) if (state.maintainRoomHumi && !state.humidity.previous) state.humidity.previous = getAvgHumidity(); } // def timer = null if (oldState == asleep) { removeSchedule(["roomAwake", "resetAsleep", "nightSwitchesOff"], state.scheduled.isExecuting) if (nightSwitches && !nightTurnOn.contains('3')) nightSwitchesOff(); // state changed away from asleep and night switches to turn off } else if (oldState == locked) removeSchedule("roomUnlocked", state.scheduled.isExecuting) else { unscheduleAll("handle switches") if (oldState == checking) unDimLights(newState); } if (['engaged', 'occupied', 'asleep', 'vacant'].contains(newState)) { if (newState != vacant || state.vacant) processRules(newState, false, vacationMode) else { switches2Off() if (musicDevice && turnOffMusic && musicDevice.currentStatus == 'playing') musicDevice.pause() } if (newState == asleep) { if (nightSwitches) { if ((motionSensors && motionSensors.currentMotion.contains(active)) || nightTurnOn.contains('2')) { dimNightLights() if (state.noMotionAsleep && ((motionSensors && !motionSensors.currentMotion.contains(active)) || whichNoMotion != lastMotionInactive)) addSchedule("nightSwitchesOff", state.noMotionAsleep, state.scheduled.isExecuting) } else nightSwitchesOff() } if (state.noAsleep) addSchedule("roomAwake", state.noAsleep, state.scheduled.isExecuting) if (resetAsleepWithContact && (contactSensor ? contactSensor.currentContact : '').contains(open)) // def cV = (contactSensor ? contactSensor.currentContact : '') // if ((contactSensor ? contactSensor.currentContact : '').contains(open)) addSchedule("resetAsleep", (resetAsleepWithContact as Integer) * 60, state.scheduled.isExecuting) // } } else if (newState == engaged) { if (state.noMotionEngaged) refreshEngagedTimer() } else if (newState == occupied) { state.motionTraffic = 1 def motionInactive = ((!motionSensors || whichNoMotion == lastMotionActive || (whichNoMotion == lastMotionInactive && !motionSensors.currentMotion.contains(active))) || (!accelSensors || !accelSensors.currentAcceleration.contains(active))) if (state.noMotion && motionInactive) refreshOccupiedTimer() } } else if (newState == checking) { dimLights() addSchedule("roomVacant", state.dimTimer, state.scheduled.isExecuting) } else if (newState == locked) { if (lockedTurnOn) { if (!state.rules) state.rules = [:]; state.rules << ['LK':[:]] def lockedSwitches = whichSwitchesAreOn(true) turnSwitchesOn([ruleNo:'LK', type:'e', name:'locked switches', disabled:false, switchesOn:lockedSwitches, level:state.lockedSetLevelTo, color:state.lockedSetColorTo, hue:state.lockedSetHueTo, colorTemperature:state.lockedSetCT]) state.rules.remove('LK') } else if (lockedTurnOff) switches2Off() if (state.unLocked) addSchedule("roomUnlocked", state.unLocked, state.scheduled.isExecuting) } ////log.debug "\tperf handleSwitches mid: ${now() - nowTime} ms" if (oldState == asleep && newState == vacant && nightSwitches && nightTurnOn.contains('3')) { dimNightLights() if (state.noMotionAsleep) addSchedule("nightSwitchesOff", state.noMotionAsleep, state.scheduled.isExecuting) } if (!vacationMode && ['engaged', 'occupied', 'asleep', 'vacant'].contains(newState)) { processCoolHeat((newState == locked && lockedProcessTemp ? engaged : newState)) processHumidity(newState) } ////log.debug "\tperf handleSwitches end: ${now() - nowTime} ms" } def switchesOnOrOff(switchesOnly = false) { ifDebug("switchesOnOrOff", 'info') def rSt = getChildDevice(getRoom())?.currentValue(occupancy) if (rSt && ['engaged', 'occupied', 'asleep', 'vacant'].contains(rSt)) { def turnedOn = processRules(rSt, switchesOnly) //log.debug "turnedOn: $turnedOn" if (!turnedOn && allSwitchesOff) { switches2Off() if (musicDevice && turnOffMusic && musicDevice.currentStatus == 'playing') musicDevice.pause() } } } private processRules(pRSt = null, switchesOnly = false, vacationMode = false) { ifDebug("processRules", 'info') if (!state.execute || !state.rules) return false; def nowTime = now() + 1000 if (state.toUpdateTimeouts) { state.noMotion = ((noMotionOccupied && noMotionOccupied >= 5) ? noMotionOccupied as Integer : null) state.noMotionEngaged = ((noMotionEngaged && noMotionEngaged >= 5) ? noMotionEngaged as Integer : null) state.dimTimer = ((dimTimer && dimTimer >= 5) ? dimTimer as Integer : 5) // forces minimum of 5 seconds to allow for checking state state.noMotionAsleep = ((noMotionAsleep && noMotionAsleep >= 5) ? noMotionAsleep as Integer : null) updateTimeouts() state.toUpdateTimeouts = false } def child = getChildDevice(getRoom()) def rSt = (pRSt ?: child?.currentValue(occupancy)) def sunRiseAndSet = sunRiseAndSet() if (!sunRiseAndSet) return; def nowDate = new Date(nowTime) def turnOn = [] def turnOnRules = [:] def previousRule = [] def previousRuleLux = null def previousRuleLuxCheck = null def previousRulePower = false state.lastRule = null def thisRule = [:] def timedRulesOnly = false for (def i = 1; i <= maxRules; i++) { def ruleHasTime = false def ruleNo = String.valueOf(i) //// thisRule = (turnOnRules[(ruleNo)] ?: getNextRule(ruleNo, _ERule, true, false)) thisRule = (turnOnRules[(ruleNo)] ?: getNextRule(ruleNo, _ERule, true, true)) // if (turnOnRules[(ruleNo)]) thisRule = turnOnRules[(ruleNo)]; // else thisRule = getNextRule(ruleNo, _ERule, true, false); if (thisRule.ruleNo == 'EOR') break; if (!turnOnRules[(thisRule.ruleNo)]) turnOnRules << [(thisRule.ruleNo):thisRule] i = thisRule.ruleNo as Integer if (thisRule.mode && !vacationMode && !thisRule.mode.contains(location.currentMode.toString())) continue; if (thisRule.state && !thisRule.state.contains(rSt)) continue; if (thisRule.dayOfWeek && !(checkRunDay(thisRule.dayOfWeek))) continue; if (thisRule.fromDate && thisRule.toDate) { if (nowDate.before(new Date().parse("yyyy-MM-dd'T'HH:mm:ssZ", thisRule.fromDate))) continue; if (nowDate.after(new Date().parse("yyyy-MM-dd'T'HH:mm:ssZ", thisRule.toDate))) continue; } if (thisRule.luxThreshold != null) { def lux = getAvgLux() def luxThreshold = thisRule.luxThreshold.toInteger() boolean luxC = (thisRule.luxCheck == 'true' ? true : false) //log.debug "ruleNo: $i | lux: $lux | thisRule.luxThreshold: $thisRule.luxThreshold | thisRule.luxCheck: $thisRule.luxCheck" //log.debug "ruleNo: $i | lux: $lux | luxThreshold: $luxThreshold | thisRule.luxCheck: $thisRule.luxCheck" // if (luxC) { // if (lux <= luxThreshold) continue; //log.debug "luxCheck: true | ${(lux <= luxThreshold)}" // } // else { // if (lux > luxThreshold) continue; //log.debug "luxCheck: false | ${(lux > luxThreshold)}" // } if ((!luxC && lux > luxThreshold) || (luxC && lux <= luxThreshold)) continue; //log.debug "${(lux > thisRule.luxThreshold)} | ${(lux <= thisRule.luxThreshold)}" } if (state.powerCheck) { boolean powerC = (thisRule.powerCheck == 'true' ? true : false) if (powerC) { if ((previousRulePower && !thisRule.powerThreshold) || (thisRule.powerThreshold && getIntfromStr((String) (powerDevice instanceof List ? powerDevice.currentPower.max() : powerDevice.currentPower)) >= thisRule.powerThreshold)) continue } else { if ((previousRulePower && !thisRule.powerThreshold) || (thisRule.powerThreshold && getIntfromStr((String) (powerDevice instanceof List ? powerDevice.currentPower.max() : powerDevice.currentPower)) < thisRule.powerThreshold)) continue } } if (thisRule.presence && !vacationMode && !personsPresence.findAll { thisRule.presence.contains(it.id) && it.currentPresence == present }) continue if (thisRule.fromHumidity && thisRule.toHumidity) { def humidity = getAvgHumidity() if (humidity < thisRule.fromHumidity || humidity > thisRule.toHumidity) continue; } if (thisRule.checkOn && !thisRule.checkOn.currentSwitch.contains(on)) continue; if (thisRule.checkOff && thisRule.checkOff.currentSwitch.contains(on)) continue; if (thisRule.wet && waterSensors && !waterSensors.currentWater.contains('wet')) continue; if (state.timeCheck && (thisRule.fromTimeType && (thisRule.fromTimeType != _timeTime || thisRule.fromTime)) && (thisRule.toTimeType && (thisRule.toTimeType != _timeTime || thisRule.toTime))) { def x = compareRuleTime(sunRiseAndSet, thisRule) if (!x.inBetween) continue; if (!timedRulesOnly) { turnOn = [] previousRule = [] previousRuleLux = null timedRulesOnly = true i = 0 continue } ruleHasTime = true } if (timedRulesOnly && !ruleHasTime) continue; if (state.powerCheck && thisRule.powerThreshold && !previousRulePower) { previousRulePower = true turnOn = [] previousRule = [] previousRuleLux = null i = 0 continue } if (thisRule.luxThreshold != null) { if (previousRuleLux == thisRule.luxThreshold && previousRuleLuxCheck == thisRule.luxCheck) { turnOn << thisRule.ruleNo previousRule << thisRule.ruleNo } else if (!previousRuleLux || ((!thisRule.luxCheck && !previousRuleLuxCheck && thisRule.luxThreshold < previousRuleLux) || (thisRule.luxCheck && previousRuleLuxCheck && thisRule.luxThreshold >= previousRuleLux))) { for (def prv : previousRule) turnOn.remove(prv); turnOn << thisRule.ruleNo previousRule << thisRule.ruleNo previousRuleLux = thisRule.luxThreshold previousRuleLuxCheck = thisRule.luxCheck } } else turnOn << thisRule.ruleNo } ifDebug("processRules: rules to execute: $turnOn") //log.debug "\tperf processRules after rules: ${now() - nowTime + 1000} ms" def ret if (state.holidayLights) if (state.holiRuleNo && (!turnOn || !turnOn.contains(state.holiRuleNo))) { def d = settings["switchesOn$state.holiRuleNo"] if (d && (!cmdOpt || d.currentSwitch.contains(on))) d.off() state.holidayLights = false } if (turnOn) { def turnOnRulesFull = [:] state.switchesPreventToggle = [] for (def trn : turnOn) { //// thisRule = turnOnRules[(trn)] //log.debug "trn: $trn | turnOnRules[(trn)].ruleNo: ${turnOnRules[(trn)].ruleNo}" thisRule = getRule(trn, _ERule, true, false) turnOnRulesFull << [(thisRule.ruleNo):thisRule] for (def tR : thisRule.switchesOn) { def itID = tR.getId() if (!state.switchesPreventToggle.contains(itID)) state.switchesPreventToggle << itID; } } //log.debug "\tperf processRules pre execute: ${now() - nowTime + 1000} ms" for (def trn : turnOn) executeRule(turnOnRulesFull[(trn)], switchesOnly) state.switchesPreventToggle = [] ret = true } else { updateLastRuleInd(-1); ret = false } ////log.debug "\tperf processRules: ${now() - nowTime + 1000} ms" return ret } private compareRuleTime(sunRiseAndSet, thisRule) { def nowDate = new Date(now()) def fTime, tTime if (thisRule.fromTimeType == _timeSunrise) fTime = new Date(sunRiseAndSet.rise.getTime() + ((thisRule.fromTimeOffset ?: 0) * 60000L)) else if (thisRule.fromTimeType == _timeSunset) fTime = new Date(sunRiseAndSet.set.getTime() + ((thisRule.fromTimeOffset ?: 0) * 60000L)) else fTime = timeToday(thisRule.fromTime, location.timeZone) if (thisRule.toTimeType == _timeSunrise) tTime = new Date(sunRiseAndSet.rise.getTime() + ((thisRule.toTimeOffset ?: 0) * 60000L)) else if (thisRule.toTimeType == _timeSunset) tTime = new Date(sunRiseAndSet.set.getTime() + ((thisRule.toTimeOffset ?: 0) * 60000L)) else tTime = timeToday(thisRule.toTime, location.timeZone) def fTimeX = fTime def tTimeX = tTime while (fTime > tTime) tTime = tTime.plus(1); while (tTime - fTime > _SecondsInDay) tTime = tTime.minus(1); while (tTime.getTime() - nowDate.getTime() > _SecondsInDay) { fTime = fTime.minus(1) tTime = tTime.minus(1) } def x = nowDate.after(fTime) && nowDate.before(tTime) return [inBetween:x, fTime:fTime, tTime:tTime] } private executeRule(thisRule, switchesOnly = false) { turnSwitchesOnAndOff(thisRule) if (!switchesOnly) { runDeviceCmds(thisRule) runActions(thisRule) executePiston(thisRule) } if (thisRule.noMotion && thisRule.noMotion >= 5) { state.noMotion = thisRule.noMotion as Integer state.toUpdateTimeouts = true } if (thisRule.noMotionEngaged && thisRule.noMotionEngaged >= 5) { state.noMotionEngaged = thisRule.noMotionEngaged as Integer state.toUpdateTimeouts = true } if (thisRule.dimTimer && thisRule.dimTimer >= 5) { state.dimTimer = thisRule.dimTimer as Integer state.toUpdateTimeouts = true } if (thisRule.noMotionAsleep && thisRule.noMotionAsleep >= 5) { state.noMotionAsleep = thisRule.noMotionAsleep as Integer state.toUpdateTimeouts = true } if (state.toUpdateTimeouts) updateTimeouts() } private turnSwitchesOnAndOff(thisRule) { def nowTime = now() ifDebug("turnSwitchesOnAndOff", 'info') state.lastRule = (state.lastRule ? state.lastRule + ',' : '') + thisRule.ruleNo updateLastRuleInd(state.lastRule); if (thisRule.switchesOn) { if (thisRule.level?.startsWith('HL')) { if (!state.holidayLights || state.holiRuleNo != thisRule.ruleNo) { state.holiRuleNo = thisRule.ruleNo def i = thisRule.level.substring(2) state.holiHues = state.holidays[i].hues //state.holiStyle = state.holidays[i].style state.holiSeconds = state.holidays[i].seconds state.holiLevel = state.holidays[i].level state.holiColorCount = state.holidays[i].count state.holiColorIndex = -1 state.holiLastTW = [:] state.holidayLights = true holidayLights() } } else turnSwitchesOn(thisRule) } //log.debug "\tperf turnSwitchesOnAndOff before off: ${now() - nowTime} ms" /* for (def swt : thisRule.switchesOff) { def itID = swt.getId() if (!state.switchesPreventToggle.contains(itID)) if (dimOver && state.switchesHasLevel[itID] && state.hT == _Hubitat) swt.setLevel(0, dimOver.toInteger()) else if (!cmdOpt || swt.currentSwitch == on) swt.off() } */ for (def swt : thisRule.switchesOff) { //def delay = false def itID = swt.getId() if (!state.switchesPreventToggle.contains(itID)) if ((state.hT == _SmartThings || swt.currentLevel == 0 || !state.switchesHasLevel[(itID)] || !dimOver)) { if (!cmdOpt || swt.currentSwitch == on) swt.off() } else { //if (delay) //pauseExecution(10l) if (!cmdOpt || swt.currentSwitch == on) swt.setLevel(0, dimOver.toInteger()) //deviceCmdDelay(swt, 'setLevel', delay, 0, dimOver.toInteger()) //delay = true } } //log.debug "\tperf turnSwitchesOnAndOff: ${now() - nowTime} ms" } private turnSwitchesOn(thisRule) { def colorTemperature = null def level = null for (def swt : thisRule.switchesOn) { /// def turnOn = true def itID = swt.getId() if (thisRule.color && state.switchesHasColor[itID]) { swt.setColor(thisRule.hue) /// turnOn = false } else if ((thisRule.colorTemperature || (thisRule.level == 'AL' && autoColorTemperature)) && state.switchesHasColorTemperature[itID]) { if (!colorTemperature) colorTemperature = (thisRule.level == 'AL' ? calculateLevelOrKelvin(true) : thisRule.colorTemperature) as Integer if (colorTemperature) { swt.setColorTemperature(colorTemperature) /// turnOn = false } } //log.debug thisRule.level if (thisRule.level && state.switchesHasLevel[itID]) { if (!level) level = (thisRule.level == 'AL' ? calculateLevelOrKelvin(false) : thisRule.level) as Integer if (level) { (state.hT == _Hubitat ? (dimOver ? swt.setLevel(level, dimOver.toInteger()) : swt.setLevel(level, 0)) : swt.setLevel(level)) /// turnOn = false } } /// if (turnOn && (!cmdOpt || swt.currentSwitch == off)) if (!cmdOpt || swt.currentSwitch == off) swt.on() } } def holidayLights() { ifDebug('holidayLights', 'info') if (!state.holidayLights) return; def switchesOn = settings["switchesOn$state.holiRuleNo"] //if (state.holiStyle == 'RO') { state.holiColorIndex = (state.holiColorIndex < (state.holiColorCount -1) ? state.holiColorIndex + 1 : 0) holidayLightsRotate(switchesOn) //} //else if (state.holiStyle == 'TW') { //state.holiLastTW = (state.holiTW ?: [:]) //holidayLightsTwinkle(switchesOn) //} addSchedule("holidayLights", state.holiSeconds, state.scheduled.isExecuting) } private holidayLightsRotate(switchesOn) { ifDebug('holidayLightsRotate', 'info') def cI = state.holiColorIndex for (def swt : switchesOn) { def holiColor = state.holiHues."$cI" if (!cmdOpt || swt.currentSwitch == off) swt.on() swt.setColor(holiColor) if (state.holiLevel) swt.setLevel(state.holiLevel) cI = (cI < (state.holiColorCount -1) ? cI + 1 : 0) } } /* private holidayLightsTwinkle(switchesOn) { ifDebug('holidayLightsTwinkle', 'info') (state.hT == _Hubitat ? switchesOn.setLevel(0,0) : switchesOn.setLevel(0)) def noSwitches = switchesOn.size() def noColors = state.holiHues.size() int cI def randomFound Random rand = new Random() state.holiTW = [:] for (def i = 0; i < noSwitches; i++) { randomFound = false for (def j = 0; j < (noColors * 3); j++) { cI = rand.nextInt(noColors) if (state.holiLastTW."$i" != cI) { state.holiTW."$i" = cI randomFound = true break } } if (!randomFound) state.holiTW."$i" = cI; } cI = 0 for (def swt : switchesOn) { def tw = state.holiTW."$cI" swt.setColor(state.holiHues."$tw") if (state.holiLevel) (state.hT == _Hubitat ? swt.setLevel(state.holiLevel, 1) : swt.setLevel(state.holiLevel)); cI = cI + 1 } } */ private runDeviceCmds(thisRule) { if (thisRule.device && thisRule.commands) for (def cmds : thisRule.commands) thisRule.device."$cmds"() } private runActions(thisRule) { for (def act : thisRule.actions) location.helloHome?.execute(act) } private executePiston(thisRule) { if (thisRule.piston) webCoRE_execute(thisRule.piston) } private calculateLevelOrKelvin(kelvin = false) { // only calculate level and kelvin every 10 mins def nowTime = now() int x if (!state.calculateLK) state.calculateLK = [:]; if (kelvin) { if (state.calculateLK && state.calculateLK['kelvin'] && (nowTime - state.calculateLK.kelvin.time) < 180000) x = state.calculateLK.kelvin.value else { x = calculateLK(minKelvin, maxKelvin, fadeCTWake, fadeKWakeBefore, fadeKWakeAfter, fadeCTSleep, fadeKSleepBefore, fadeKSleepAfter) state.calculateLK['kelvin'] = [time:nowTime, value:x] } } else { if (state.calculateLK && state.calculateLK['level'] && (nowTime - state.calculateLK.level.time) < 180000) x = state.calculateLK.level.value else { x = calculateLK(minLevel, maxLevel, fadeLevelWake, fadeWakeBefore, fadeWakeAfter, fadeLevelSleep, fadeSleepBefore, fadeSleepAfter) state.calculateLK['level'] = [time:nowTime, value:x] } } ifDebug("${(kelvin ? 'kelvin' : 'level')}: $x") //log.debug "\tperf: calculateLevelOrKelvin: ${now() - nowTime} ms" return x } private calculateLK(min, max, fadeW, fadeWB, fadeWA, fadeS, fadeSB, fadeSA) { long timeNow = now() def dateNow = new Date(timeNow) def useInput = (wakeupTime && sleepTime ? true : false) def wTime, sTime wTime = timeToday((useInput ? wakeupTime : "7:00"), location.timeZone) sTime = timeToday((useInput ? sleepTime : "23:00"), location.timeZone) while (wTime > sTime) sTime = sTime.plus(1); //log.debug "wTime: $wTime | sTime: $sTime" long maxMinDiff = max - min if (fadeW) { def wTimeBefore = new Date((wTime.getTime() - (fadeWB * 3600000L))) def wTimeAfter = new Date((wTime.getTime() + (fadeWA * 3600000L))) if (timeOfDayIsBetween(wTimeBefore, wTimeAfter, dateNow, location.timeZone)) { double cDD = ((dateNow.getTime() - wTimeBefore.getTime()) / (wTimeAfter.getTime() - wTimeBefore.getTime())) cDD = cDD * maxMinDiff int cD = cDD + min cD = (cD > max ? max : cD) return cD } } if (fadeS) { def sTimeBefore = new Date((sTime.getTime() - (fadeSB * 3600000L))) def sTimeAfter = new Date((sTime.getTime() + (fadeSA * 3600000L))) if (timeOfDayIsBetween(sTimeBefore, sTimeAfter, dateNow, location.timeZone)) { double cDD = ((sTimeAfter.getTime() - dateNow.getTime()) / (sTimeAfter.getTime() - sTimeBefore.getTime())) cDD = cDD * maxMinDiff int cD = cDD + min cD = (cD > max ? max : cD) return cD } } //log.debug "(timeOfDayIsBetween($wTime, $sTime, $dateNow, $location.timeZone) ? ($fadeW ? $max : null) : ($fadeS ? $min : null))" def x = (timeOfDayIsBetween(wTime, sTime, dateNow, location.timeZone) ? (fadeW ? max : null) : (fadeS ? min : null)) return x } // since hubitat does not support timeTodayAfter(...) 2018-04-08 private timeTodayA(Date whichDate, Date thisDate, timeZone) { //log.debug "whichDate: $whichDate | thisDate: $thisDate | timeZone: $timeZone" //if (thisDate instanceof Date) log.debug "is date"; return (thisDate.before(whichDate) ? thisDate.plus(((whichDate.getTime() - thisDate.getTime()) / _SecondsInDay).intValue() + 1) : thisDate) } private whichSwitchesAreOn(returnAllSwitches = false) { def switches = getAllSwitches() if (returnAllSwitches) return switches def switchesOn = [] for (def swt : switches) if (swt.currentSwitch == on) switchesOn << swt return switchesOn } def dimLights() { ifDebug("dimLights", 'info') state.preDimLevel = [:] if (!state.dimTimer || (!state.dimByLevel && !state.dimToLevel)) return; def switchesThatAreOn = whichSwitchesAreOn() if (switchesThatAreOn && state.dimByLevel) { for (def swt : switchesThatAreOn) { if (swt.currentSwitch == 'on') { def itID = swt.getId() if (state.switchesHasLevel[itID]) { def currentLevel = swt.currentLevel state.preDimLevel << [(swt.getId()):currentLevel] def dimByLevel = state.dimByLevel.toInteger() def newLevel = (currentLevel > dimByLevel ? currentLevel - dimByLevel : 1) (state.hT == _Hubitat ? (dimOver ? swt.setLevel(newLevel, dimOver.toInteger()) : swt.setLevel(newLevel, 0)) : swt.setLevel(newLevel)) } } } } else { int lux if (luxCheckingDimTo && luxSensor) lux = getAvgLux(); if (!luxCheckingDimTo || lux <= luxCheckingDimTo) { def allSwitches = whichSwitchesAreOn(true) if (allSwitches && state.dimToLevel) { for (def swt : allSwitches) { def itID = swt.getId() if (state.switchesHasLevel[itID]) { state.preDimLevel << [(swt.getId()):swt.currentLevel] (state.hT == _Hubitat ? (dimOver ? swt.setLevel(state.dimToLevel, dimOver.toInteger()) : swt.setLevel(state.dimToLevel, 0)) : swt.setLevel(state.dimToLevel)) } } } } } } private unDimLights(rSt) { ifDebug("unDimLights", 'info') if (!state.dimTimer || (!state.dimByLevel && !state.dimToLevel) || !state.preDimLevel) return; if (!notRestoreLL || rSt != vacant) { for (def swt : whichSwitchesAreOn()) { def itID = swt.getId() if (swt.currentSwitch == 'on' && state.switchesHasLevel[itID]) { def newLevel = state.preDimLevel[itID] if (newLevel > 0) (state.hT == _SmartThings ? swt.setLevel(newLevel) : swt.setLevel(newLevel, (dimOver ? dimOver.toInteger() : 0))) } } } state.preDimLevel = [:] } def switches2Off() { if (state.holidayLights) return; //def delay = false def switches = whichSwitchesAreOn(true) for (def swt : switches) { if ((state.hT == _SmartThings || swt.currentLevel == 0 || !state.switchesHasLevel[(swt.getId())] || !dimOver)) { if (!cmdOpt || swt.currentSwitch == on) swt.off() } else { //if (delay) //pauseExecution(10l) if (!cmdOpt || swt.currentSwitch == on) swt.setLevel(0, dimOver.toInteger()) //deviceCmdDelay(swt, 'setLevel', delay, 0, dimOver.toInteger()) //delay = true } } } // app:2802019-12-02 03:29:20.313 pm errorgroovy.lang.MissingMethodException: No signature of method: groovy.json.internal.LazyMap.setLevel() is applicable for argument types: (java.lang.Integer, java.lang.Integer) values: [0, 3] on line 4991 (runDeviceCmd) private deviceCmdDelay(dev, command, delayMS, param1 = null, param2 = null) { if (param2 != null) { log.debug "dev: $dev | cmd: $command | p1: $param1 | p2: $param2" runInMillis(delayMS, runDeviceCmd, [data: [dev: dev, cmd: command, param1: param1, param2: param2]]) } else if (param1 != null) runInMillis(delayMS, runDeviceCmd, [data: [dev: dev, cmd: command, param1: param1]]) else runInMillis(delayMS, runDeviceCmd, [data: [dev: dev, cmd: command]]) } def runDeviceCmd(data) { def dev = data.dev def cmd = data.cmd def p1 = data?.param1 def p2 = data?.param2 if (p2 != null) dev."$cmd"(p1, p2) else if (p1 != null) dev."$cmd"(p1) else dev."$cmd"() } private previousStateStack(previousState) { ifDebug("previousStateStack", 'info') def i def timeIs = now() def factor = (state.busyCheck ?: 10) def removeHowOld = (state.noMotion ? (((state.noMotion as Integer) + (state.dimTimer as Integer)) * factor) : (180 * factor)) def howMany int gapBetween turnOffIsBusy() if (state.stateStack) for (i = 9; i > 0; i--) { if (state.stateStack[String.valueOf(i-1)]) state.stateStack[String.valueOf(i)] = state.stateStack[String.valueOf(i-1)] } else state.stateStack = [:] state.stateStack << ['0':previousState] if (state.busyCheck) { howMany = 0 gapBetween = 0 for (i = 9; i > 0; i--) { def s = String.valueOf(i) if (!state.stateStack[s]) continue; gapBetween = ((timeIs - (state.stateStack[s])['date']) / 1000) if (gapBetween > removeHowOld) continue; def sM = String.valueOf(i-1) if (['occupied', 'checking', 'vacant'].contains((state.stateStack[s])['state']) && ['occupied', 'checking', 'vacant'].contains((state.stateStack[sM])['state'])) { howMany++ gapBetween += (((state.stateStack[sM])['date'] - (state.stateStack[s])['date']) / 1000) } } if (howMany >= state.busyCheck) { ifDebug("busy on") state.isBusy = true state.stateStack = [:] addSchedule("turnOffIsBusy", removeHowOld, state.scheduled.isExecuting) } } } def turnOffIsBusy() { state.motionTraffic = 0 state.isBusy = false } private spawnChildDevice(roomName) { ifDebug("spawnChildDevice") app.updateLabel(app.label) if (!childCreated()) addChildDevice("bangali", "rooms occupancy", getRoom(), null, [name: getRoom(), label: roomName, completedSetup: true]) } private checkDriverVersion() { def ver = version() def cVer = getChildDevice(getRoom())?.version() def ret = false if (ver != cVer) { def driver = (state.hT == _SmartThings ? 'DTH' : 'driver') ifDebug("Rooms Child app verion does not match Rooms Occupancy $driver version. App version is $ver and $driver version is ${cVer}. Please update $driver code and save${(state.hT == _SmartThings ? '/publish' : '')} before trying again.", 'error') ret = true } return ret } private childCreated() { return (getChildDevice(getRoom()) ? true : false) } private getRoom() { return "rm_${app.id}" } def uninstalled() { ifDebug("uninstalled") // parent.unsubscribeChild(app.id) // parent.unsubscribeChildDevice(app) unschedule() unsubscribe() getAllChildDevices().each { ifDebug("deleting $app.label virtual device $it.label") parent.unsubscribeChildRoomDevice(it) deleteChildDevice(it.deviceNetworkId) } } def childUninstalled() { ifDebug("uninstalled room device ${app.label}") } private convertRGBToHueSaturation(setColorTo) { def str = setColorTo.replaceAll("\\s","").toLowerCase() def rgb = (colorsRGB[str][0] ?: colorsRGB['white'][0]) //log.debug "convertRGBToHueSaturation: $str | $rgb" float r = rgb[0] / 255 float g = rgb[1] / 255 float b = rgb[2] / 255 float max = Math.max(Math.max(r, g), b) float min = Math.min(Math.min(r, g), b) float h, s, l = (max + min) / 2 if (max == min) h = s = 0 // achromatic else { float d = max - min s = (l > 0.5 ? d / (2 - max - min) : d / (max + min)) switch (max) { case r: h = (g - b) / d + (g < b ? 6 : 0); break case g: h = (b - r) / d + 2; break case b: h = (r - g) / d + 4; break } h /= 6 } //log.debug "hue: $h | sat: $s | lvl: $l" return [hue: Math.round(h * 100), saturation: Math.round(s * 100), level: Math.round(l * 100)] } private unscheduleAll(classNameCalledFrom) { ifDebug("${app.label} unschedule calling class: $classNameCalledFrom") def rS = ["roomVacant", "resetEngaged"] if (powerDevice) if (powerValueEngaged) { rS << "powerStaysAboveEngaged" rS << "powerStaysBelowEngaged" } else if (powerValueAsleep) rS << "powerStaysBelowAsleep" else if (powerValueLocked) rS << "powerStaysBelowLocked" removeSchedule(rS, state.scheduled.isExecuting) } private addSchedule(String fnc, timer, nest = false) { def time = now() + (timer * 1000) if (!state.scheduled) state.scheduled = ['isScheduled':false, 'toRun':'', 'timers':[:], 'isExecuting':false] if (state.scheduled.timers[(fnc)]) state.scheduled.timers[(fnc)] = time else state.scheduled.timers << [(fnc):time] if (!nest) executeScheduled() } private executeScheduled(nest = false) { // if (!state.scheduled.timers) return; if (state.scheduled.timers) { def now = now() //log.debug "${state.scheduled.timers.sort { a, b -> a.value <=> b.value }}" state.scheduled.isExecuting = true def timers = state.scheduled.timers.sort { a, b -> a.value <=> b.value } //log.debug "timers: $timers" for (def t : timers) { //log.debug "key: $t.key | value: $t.value | now: $now" if (t.value < (now + 1000l)) { removeSchedule("$t.key", true) "$t.key"() } else break } state.scheduled.isExecuting = false } // if (state.scheduled.toRun) { // unschedule('scheduled') // state.scheduled.toRun = '' // } if (state.scheduled.timers) { timerNext() def t = state.scheduled.timers.min { it.value } state.scheduled.toRun = t.key runOnce(new Date(t.value), scheduled) state.scheduled.isScheduled = true } else { state.scheduled.toRun = '' if (state.scheduled.isScheduled) { unschedule('scheduled') state.scheduled.isScheduled = false } } } def scheduled() { if (state.scheduled.toRun) { def toRun = state.scheduled.toRun removeSchedule(toRun, true) state.scheduled.toRun = '' state.scheduled.isScheduled = false "$toRun"() } executeScheduled() } private removeSchedule(fnc, nest = false) { if (!state.scheduled.timers) return; def fncList = [] (fnc instanceof List ? fncList = fnc : fncList << fnc) for (def f : fncList) if (state.scheduled.timers[(f)]) state.scheduled.timers.remove(f) // state.scheduled.timers = state.scheduled.timers.findAll { it.key != f } if (!nest) executeScheduled() } private timerNext() { def now = now() def nS = (state.scheduled.timers ? state.scheduled.timers.findAll{ !["timerNext", "holidayLights", "setupColorNotification", "notifyWithColor", "setAnnounceSwitches", "checkAndTurnOnOffSwitchesC", "asleep", "humidityTimer2", "humidityTimer3", "powerStaysAboveEngaged", "powerStaysBelowEngaged", "powerStaysBelowAsleep", "powerStaysBelowLocked", "turnOffIsBusy"].contains(it.key) }.min{ it.value } : [:]) def timerLeft = (nS ? (nS.value && now < nS.value ? ((nS.value - now) / 1000).toInteger() : 0) : 0) def child = getChildDevice(getRoom()) if (child) { def timerInd = (timerLeft > 3600 ? (timerLeft / 3600f).round(1) + 'h' : (timerLeft > 60 ? (timerLeft / 60f).round(1) + 'm' : timerLeft + 's')).replace(".0","") // for new app if (state.hT == _SmartThings) { child.sendEvent(name: "timer", value: (timerInd ?: '--'), displayed: true) // for new app child.sendEvent(name: "countDown", value: (timerInd ?: '--'), displayed: true) } else child.sendEvent(name: "countdown", value: timerInd, descriptionText: "countdown timer: $timerInd", displayed: true) } if (timerLeft > 0) { int timerUpdate = (timerLeft > 3600 ? 300 : (timerLeft > 300 ? 60 : (timerLeft > 30 ? 30 : (timerLeft < 5 ? timerLeft : 5)))) def timerMod = timerLeft % timerUpdate addSchedule("timerNext", (timerMod ?: timerUpdate), true); } } private scheduleFromToTimes() { //log.debug "evt: $evt" // if (!state?.runEvery5) { //scheduleRunEvery5() // unsubscribe("scheduleFromToTimes") // } def nowTime = now() // state.nextScheduleFromToTimes = null // state.lastScheduleFromToTimes = nowTime // def rSt = getChildDevice(getRoom())?.currentValue(occupancy) // def nowDate = new Date(nowTime) // def cHH = nowDate.format("HH", location.timeZone).toInteger() // def cMM = nowDate.format("mm", location.timeZone).toInteger() // cMM = cMM + (state.processChild > cMM ? (((state.processChild - cMM) % 5) ?: 5) : (5 - ((cMM - state.processChild) % 5))) // if (cMM > 59) { // cMM = cMM % 60 // cHH = (cHH > 22 ? 0 : cHH + 1) // } //log.debug "cHH: $cHH | cMM: $cMM" // def pTime = timeTodayA(nowDate, timeToday(String.format("%02d:%02d", cHH, cMM), location.timeZone), location.timeZone) // def process = (checkPauseModesAndDoW() && (!onlyOnStateChange || (butNotInStates && butNotInStates.contains(rSt)))) def runTime = new Date(nowTime + 300000l) // def nextTime = new Date(nowTime + 300000l) def time = false if (state.rules && state.timeCheck) { def fromTime = scheduleFromTime() if (fromTime) { def toTime = scheduleToTime() if (toTime) { if (fromTime <= toTime) { // if (runTime.equals(fromTime)) // process = true // else if (runTime.after(fromTime)) { if (runTime.after(fromTime)) { time = true runTime = fromTime } } else { // if (runTime.equals(toTime)) // process = true // else if (runTime.after(toTime)) { if (runTime.after(toTime)) { time = true runTime = toTime } } } } updateTimeFromToInd() } // def opt = [] // if (process) opt << 'process' // if (time) opt << 'time' // state.nextScheduleFromToTimes = runTime.getTime() // def oldOpt = state?.nextScheduleOpt // state.nextScheduleOpt = opt // if ((!oldOpt || oldOpt.contains('time') || oldOpt.contains('process')) && state.nextScheduleFromToTimes > nowTime) ////log.debug "\tperf scheduleFromToTimes mid: ${(now() - nowTime)} ms" // if (process) checkAndTurnOnOffSwitchesC() if (time) addSchedule("checkAndTurnOnOffSwitchesC", ((runTime.getTime() + new Random().nextInt(3000) - nowTime) / 1000).toInteger(), true) // return true // } // else // return false ////log.debug "\tperf scheduleFromToTimes end: ${(now() - nowTime)} ms" } private scheduleFromTime() { ifDebug("scheduleFromTime", 'info') if (!state.rules || !state.timeCheck) return false; def nextTime = scheduleTime(true) state.fTime = nextTime // updateTimeFromToInd() return nextTime } private scheduleToTime() { ifDebug("scheduleToTime", 'info') if (!state.rules || !state.timeCheck) return false; def nextTime = scheduleTime(false) state.tTime = nextTime // updateTimeFromToInd() return nextTime } private scheduleTime(fromTime) { def nowTime = now() def nowDate = new Date(nowTime) // def sunriseTime, sunsetTime def sunRiseAndSet = sunRiseAndSet() if (!sunRiseAndSet) return; // sunriseTime = sunRiseAndSet.rise // sunsetTime = sunRiseAndSet.set def nextTime = null // def sunriseTimeWithOff, sunsetTimeWithOff for (def i = 1; i <= maxRules; i++) { def ruleNo = String.valueOf(i) def thisRule = getNextRule(ruleNo, _ERule, true, true) //log.debug thisRule if (thisRule.ruleNo == 'EOR') break; i = thisRule.ruleNo as Integer if (thisRule.fromDate && thisRule.toDate) { def fTime = new Date().parse("yyyy-MM-dd'T'HH:mm:ssZ", thisRule.fromDate) def tTime = new Date().parse("yyyy-MM-dd'T'HH:mm:ssZ", thisRule.toDate) //log.debug "nowDate: $nowDate | nextTime: $nextTime | fTime: $fTime | $tTime" if (nowDate > tTime) continue; if ((!nextTime && nowDate >= fTime && nowDate <= tTime) || (nextTime && (fromTime ? fTime : tTime) >= nowDate && (fromTime ? fTime : tTime) < nextTime)) nextTime = (fromTime ? fTime : tTime) //log.debug nextTime } //log.debug "$thisRule.fromTimeType | $thisRule.fromTime | $thisRule.toTimeType | $thisRule.toTime" if (!state.timeCheck || (!thisRule.fromTimeType || (thisRule.fromTimeType == _timeTime && !thisRule.fromTime)) || (!thisRule.toTimeType || (thisRule.toTimeType == _timeTime && !thisRule.toTime))) continue; def cTime if ((fromTime ? thisRule.fromTimeType : thisRule.toTimeType) == _timeSunrise) cTime = ((fromTime ? thisRule.fromTimeOffset : thisRule.toTimeOffset) ? new Date(sunRiseAndSet.rise.getTime() + ((fromTime ? thisRule.fromTimeOffset : thisRule.toTimeOffset) * 60000L)) : sunRiseAndSet.rise) else if ((fromTime ? thisRule.fromTimeType : thisRule.toTimeType) == _timeSunset) cTime = ((fromTime ? thisRule.fromTimeOffset : thisRule.toTimeOffset) ? new Date(sunRiseAndSet.set.getTime() + ((fromTime ? thisRule.fromTimeOffset : thisRule.toTimeOffset) * 60000L)) : sunRiseAndSet.set) else cTime = timeToday((fromTime ? thisRule.fromTime : thisRule.toTime), location.timeZone) cTime = timeTodayA(nowDate, cTime, location.timeZone) if (!nextTime || timeOfDayIsBetween(nowDate, nextTime, cTime, location.timeZone)) nextTime = cTime; } ////log.debug "\tperf scheduleTime: ${(now() - nowTime)} ms" return nextTime } private checkAndTurnOnOffSwitchesC() { //def nowTime = now() def rSt = getChildDevice(getRoom())?.currentValue(occupancy) if ((onlyOnStateChange && (!butNotInStates || !butNotInStates.contains(rSt))) || (awayModes && awayModes.contains(location.currentMode.toString())) || !checkPauseModesAndDoW()) return false if (state.stateStack && ((now() - state.stateStack['0'].date) / 1000f) < 150) return false switchesOnOrOff(true) ////log.debug "\tperf checkAndTurnOnOffSwitchesC: ${(now() - nowTime)} ms" } private sunRiseAndSet() { def sunriseAndSunset = getSunriseAndSunset() if (!sunriseAndSunset.sunrise || !sunriseAndSunset.sunset) { ifDebug("Please set location for the hub for rules to be processed.", "error") return false } return [rise:sunriseAndSunset.sunrise, set:sunriseAndSunset.sunset] } private updateTimeFromToInd() { state.timeFromTo = format24hrTime(state.fTime) + " " + format24hrTime(state.tTime) if (state.hT == _SmartThings) getChildDevice(getRoom())?.sendEvent(name: 'timeInd', value: state.timeFromTo, descriptionText: "indicate time from to") } private updateTimeouts() { if (state.hT != _SmartThings) return; def child = getChildDevice(getRoom()) if (!child) return; child.sendEvent(name: 'noMotionInd', value: (state.noMotion ? formatNumber(state.noMotion) : '--'), descriptionText: (state.noMotion ? "indicate motion timer for occupied state" : "indicate no motion timer for occupied state")) child.sendEvent(name: 'dimTimerInd', value: (state.dimTimer ? formatNumber(state.dimTimer) : '--'), descriptionText: (state.dimTimer ? "indicate timer for checking state" : "indicate no timer for checking state")) child.sendEvent(name: 'noMotionEngagedInd', value: (state.noMotionEngaged ? formatNumber(state.noMotionEngaged) : '--'), descriptionText: (state.noMotionEngaged ? "indicate motion timer for engaged state" : "indicate no motion timer for engaged state")) child.sendEvent(name: 'noMotionAsleepInd', value: (state.noMotionAsleep ? formatNumber(state.noMotionAsleep) : '--'), descriptionText: (state.noMotionAsleep ? "indicate motion timer for asleep state" : "indicate no motion timer for asleep state")) } private format24hrTime(timeToFormat, format = "HH:mm") { return (timeToFormat ? timeToFormat.format("HH:mm", location.timeZone) : '') } def getAdjMotionSensors() { return motionSensors } def getAdjRoomsSetting() { return adjRooms } def getLastStateChild() { def addRoom = state.stateStack[0] addRoom << ['room':app.label] return addRoom } def getChildRoomOccupancyDeviceC() { return getChildDevice(getRoom()) } def getChildRoomThermostat() { return (useThermostat && roomThermostat ? [name: app.label, thermostat: roomThermostat] : null) } private checkPauseModesAndDoW() { if ((pauseModes && pauseModes.contains(location.currentMode.toString())) || (state.dayOfWeek && !(checkRunDay()))) return false else return true } private checkRunDay(dayOfWeek = null) { long timestamp = now() def thisDay = ((new Date(timestamp + location.timeZone.getOffset(timestamp))).day ?: 7) return (dayOfWeek ?: state.dayOfWeek).contains(thisDay) } private speakIt(str) { ifDebug("speakIt", 'info') if (announceInModes && !announceInModes.contains(location.currentMode.toString())) return false; def nowDate = new Date(now()) def intCurrentHH = nowDate.format("HH", location.timeZone) as Integer def intCurrentMM = nowDate.format("mm", location.timeZone) as Integer if (intCurrentHH < startHH || (intCurrentHH > endHH || (intCurrentHH == endHH && intCurrentMM != 0))) return; def vol = speakerVolume if (useVariableVolume) { int x = startHH + ((endHH - startHH) / 4) int y = endHH - ((endHH - startHH) / 3) if (intCurrentHH <= x || intCurrentHH >= y) vol = (speakerVolume - (speakerVolume / 3)).toInteger(); } if (speakerDevices) { def currentVolume = speakerDevices.currentLevel def isMuteOn = speakerDevices.currentMute.contains("muted") if (isMuteOn) speakerDevices.unmute(); speakerDevices.playTextAndResume(str, vol); if (currentVolume != vol) speakerDevices.setLevel(currentVolume); if (isMuteOn) musicPlayers.mute(); } if (speechDevices) speechDevices.speak(str); if (musicPlayers) { def currentVolume = musicPlayers.currentLevel def isMuteOn = musicPlayers.currentMute.contains("muted") if (isMuteOn) musicPlayers.unmute(); musicPlayers.playTrackAndResume(str, vol) if (currentVolume != vol) musicPlayers.setLevel(currentVolume) if (isMuteOn) musicPlayers.mute(); } if (state.hT == _SmartThings && listOfMQs) sendLocationEvent(name: "AskAlexaMsgQueue", value: "Rooms Occupancy", isStateChange: true, descriptionText: "$str", data:[queues:listOfMQs, expires: 30, notifyOnly: true, suppressTimeDate: true]) else if (state.hT == _Hubitat && echoAccessCode) { def uri = "https://api.notifymyecho.com/v1/NotifyMe?notification=${URLEncoder.encode(str, 'UTF-8')}&accessCode=${decrypt(echoAccessCode)}" httpPost([uri: uri, requestContentType: 'application/json']) { response -> if (response?.status != 200) ifDebug("echo notify response $response"); } } } private setupColorNotification(color = null) { ifDebug("setupColorNotification", 'info') def nowDate = new Date(now()) def intCurrentHH = nowDate.format("HH", location.timeZone) as Integer def intCurrentMM = nowDate.format("mm", location.timeZone) as Integer if ((!announceSwitches || (announceInModes && !(announceInModes.contains(location.currentMode.toString())))) || (intCurrentHH < startHHColor || (intCurrentHH > endHHColor || (intCurrentHH == endHHColor && intCurrentMM != 0)))) { state.colorNotificationColorStack = [] return false } if (!state.colorsRotating && state.colorNotifyTimes <= 0) { state.colorNotifyTimes = (announceSwitches?.hasCommand("setLevel") ? 9 : 3) saveAnnounceSwitches() if (!color) { if (state.colorNotificationColorStack) { color = state.colorNotificationColorStack[0] state.colorNotificationColorStack.remove(0) } } if (color) { state.colorNotificationColor = color notifyWithColor() } } else { if (color) state.colorNotificationColorStack << color addSchedule("setupColorNotification", 10, state.scheduled.isExecuting) } } private notifyWithColor() { if (state.colorNotifyTimes % 2) { if (!cmdOpt || announceSwitches.currentSwitch.contains(off)) announceSwitches.on(); for (def swt : announceSwitches) if (swt.hasCommand("setColor")) swt.setColor(state.colorNotificationColor); } else if (!cmdOpt || announceSwitches.currentSwitch == on) announceSwitches.off() removeSchedule("notifyWithColor", state.scheduled.isExecuting) state.colorNotifyTimes = state.colorNotifyTimes - 1 if (state.colorNotifyTimes > 0) addSchedule("notifyWithColor", 1, state.scheduled.isExecuting) else { restoreAnnounceSwitches() if (state.colorNotificationColorStack) addSchedule("setupColorNotification", (state.hT == _SmartThings ? 1 : 5), state.scheduled.isExecuting) } } private saveAnnounceSwitches() { removeSchedule("setAnnounceSwitches", state.scheduled.isExecuting) state.colorSwitchSave = [] state.colorColorSave = [] state.colorColorTemperatureSave = [] state.colorColorTemperatureTrueSave = [] for (def swt : announceSwitches) { state.colorSwitchSave << swt.currentSwitch state.colorColorSave << [hue: swt.currentHue, saturation: swt.currentSaturation, level: swt.currentLevel] def evts = swt.events(max: 250) def foundValue = false def keepSearching = true for (def evt : evts) { if (!foundValue && keepSearching) { if (evt.value == 'setColorTemperature') foundValue = true else if (['hue', 'saturation'].contains(evt.name)) keepSearching = false } else break } state.colorColorTemperatureTrueSave << (foundValue ? true : false) state.colorColorTemperatureSave << swt.currentColorTemperature } } private restoreAnnounceSwitches() { def i = 0 for (def swt : announceSwitches) { ifDebug("$swt | ${state.colorColorSave[i]} | ${state.colorSwitchSave[(i)]} | ${state.colorColorTemperatureTrueSave[i]} | ${state.colorColorTemperatureSave[i]}") if (state.colorColorTemperatureTrueSave[(i)] == true) { if (swt.hasCommand("setColorTemperature")) swt.setColorTemperature(state.colorColorTemperatureSave[(i)]) } else { if (swt.hasCommand("setColor")) swt.setColor(state.colorColorSave[(i)]); } if (swt.hasCommand("setLevel")) swt.setLevel(state.colorColorSave[(i)].level); i = i + 1 } addSchedule("setAnnounceSwitches", (state.hT == _SmartThings ? 1 : 3), state.scheduled.isExecuting) } def setAnnounceSwitches() { def i = 0 for (def swt : announceSwitches) swt."${(state.colorSwitchSave[(i++)] == off ? off : on)}"() } private presenceActionArrival() { return (presenceAction == '1' || presenceAction == '3') } private presenceActionDeparture() { return (presenceAction == '2' || presenceAction == '3') } private ifDebug(msg = null, level = null) { if (msg && (isDebug() || level == 'error')) log."${level ?: 'debug'}" " $app.label: " + msg } private hasOccupiedDevice() { return (motionSensors || accelSensors || occupiedButton || occSwitches) } // only called from device handler def turnSwitchesAllOnOrOff(turnOn) { def switches = getAllSwitches() if (switches) { def action = (turnOn ? on : off) for (def swt : switches) if (swt.currentSwitch != action) swt."$action"() } } def roomDeviceSwitchOnP() { return roomDeviceSwitchOn } private getAllSwitches() { def switches = [] def switchesID = [] for (def i = 1; i <= maxRules; i++) { def ruleNo = String.valueOf(i) def thisRule = getNextRuleSwitches(ruleNo) if (thisRule.ruleNo == 'EOR') break; i = thisRule.ruleNo as Integer for (def swt : thisRule.switchesOn) { def itID = swt.getId() if (!switchesID.contains(itID)) { switches << swt switchesID << itID } } } return switches } def asleepEventHandler(evt) { ifDebug("asleepEventHandler", 'info') def child = getChildDevice(getRoom()) if (state.hT != _Hubitat && child) updateSwitchInd(isAnyASwitchOn(), 'A') if (!checkPauseModesAndDoW()) return; def rSt = child?.currentValue(occupancy) if (rSt == locked && lockedOverrides) return; if (isRoomAsleep()) { if (rSt != asleep) asleep() } else if (rSt == asleep) roomAwake() } private isRoomAsleep() { if ((asleepSwitch && asleepSwitch.currentSwitch.contains('on')) || (asleepSensor && asleepSensor.currentSleeping.contains('sleeping')) || (asleepMode && asleepMode.contains(location.currentMode.toString())) || (powerDevice && powerValueAsleep && getIntfromStr((String) (powerDevice instanceof List ? powerDevice.currentPower.max() : powerDevice.currentPower)) >= powerValueAsleep)) return true else return false } //------------------------------------------------------Night option------------------------------------------------------// def nightButtonPushedEventHandler(evt) { ifDebug("nightButtonPushedEventHandler", 'info') if (!checkPauseModesAndDoW()) return; if (state.hT == _SmartThings) { if (!evt.data) return; def nM = new groovy.json.JsonSlurper().parseText(evt.data) assert nM instanceof Map if (!nM || (nightButtonIs && nM['buttonNumber'] != nightButtonIs as Integer)) return; } def rSt = getChildDevice(getRoom())?.currentValue(occupancy) if (nightSwitches && rSt == 'asleep') { unscheduleAll("night button pushed handler") def nS = nightSwitches.currentSwitch if (nightButtonAction == '1' || (nightButtonAction == '3' && !ns.contains(on))) dimNightLights(); else if ((nightButtonAction == "2" || nightButtonAction == '3') && nS.contains(on)) nightSwitchesOff(); } } def dimNightLights() { ifDebug("dimNightLights", 'info') removeSchedule("dimNightLights", state.scheduled.isExecuting) if (nightSwitches) { if (!state.rules) state.rules = [:]; state.rules << ['NL':[:]] turnSwitchesOn([ruleNo:'NL', type:'e', name:'night lights', disabled:false, switchesOn:nightSwitches, level:state.nightSetLevelTo, color:state.nightSetColorTo, hue:state.nightSetHueTo, colorTemperature:state.nightSetCT]) state.rules.remove('NL') updateSwitchInd(1, 'N') } } def nightSwitchesOff() { removeSchedule("nightSwitchesOff", state.scheduled.isExecuting) if (nightSwitches) { if (!cmdOpt || nightSwitches.currentSwitch.contains(on)) nightSwitches.off(); updateSwitchInd(0, 'N'); } if (resetAsleepWithContact && (contactSensor ? contactSensor.currentContact : '').contains(open)) addSchedule("resetAsleep", (resetAsleepWithContact as Integer) * 60, state.scheduled.isExecuting) } //------------------------------------------------------------------------------------------------------------------------// /*************************************************************************/ /* webCoRE Connector v0.2 */ /*************************************************************************/ /* Copyright 2016 Adrian Caramaliu */ /* */ /* This program is free software: you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ /* the Free Software Foundation, either version 3 of the License, or */ /* (at your option) any later version. */ /* */ /* This program is distributed in the hope that it will be useful, */ /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ /* GNU General Public License for more details. */ /* */ /* You should have received a copy of the GNU General Public License */ /* along with this program. If not, see .*/ /*************************************************************************/ private webCoRE_handle() { return 'webCoRE' } private webCoRE_init(pistonExecutedCbk) { ifDebug("webCoRE_init", 'info') state.webCoRE = (state.webCoRE instanceof Map ? state.webCoRE:[:]) + (pistonExecutedCbk ? [cbk:pistonExecutedCbk] : [:]) subscribe(location, "${webCoRE_handle()}.pistonList", webCoRE_handler) if (pistonExecutedCbk) subscribe(location, "${webCoRE_handle()}.pistonExecuted", webCoRE_handler); // webCoRE_poll() sendLocationEvent([name: webCoRE_handle(), value:'poll', isStateChange:true, displayed:false]) } /* private webCoRE_poll() { ifDebug("webCoRE_poll") sendLocationEvent([name: webCoRE_handle(), value:'poll', isStateChange:true, displayed:false]) } */ public webCoRE_execute(pistonIdOrName, Map data=[:]) { ifDebug("webCoRE_execute", 'info') def i = (state.webCoRE?.pistons ?: []).find{(it.name == pistonIdOrName) || (it.id == pistonIdOrName)}?.id; if (i) sendLocationEvent([name:i, value:app.label, isStateChange:true, displayed:false, data:data]); } public webCoRE_list(mode) { ifDebug("webCoRE_list", 'info') def p = state.webCoRE?.pistons; if (p) p.collect{mode == 'id' ? it.id : (mode == 'name' ? it.name : [id:it.id, name:it.name]) // log.debug "Reading piston: ${it}" } return p } public webCoRE_handler(evt) { ifDebug("webCoRE_handler", 'info') switch(evt.value) { case 'pistonList': List p = state.webCoRE?.pistons ?: [] Map d = evt.jsonData ?: [:] if (d.id && d.pistons && (d.pistons instanceof List)) { p.removeAll{it.iid == d.id} p += d.pistons.collect{[iid:d.id]+it}.sort{it.name} state.webCoRE = [updated:now(), pistons:p] } break case 'pistonExecuted': def cbk = state.webCoRE?.cbk if (cbk && evt.jsonData) "$cbk"(evt.jsonData); break } } //------------------------------------------------------------------------------------------------------------------------// /* @Field final Map colorsRGB = [ black: [[0, 0, 0], 'Black'], gray: [[128, 128, 128], 'Gray'], silver: [[192, 192, 192], 'Silver'], white: [[255, 255, 255], 'White'], maroon: [[128, 0, 0], 'Maroon'], red: [[255, 0, 0], 'Red'], lightsalmon: [[255, 160, 122], 'Orange'], saddlebrown: [[139, 69, 19], 'Saddle Brown'], chocolate: [[210, 105, 30], 'Chocolate'], peru: [[205, 133, 63], 'Peru'], darkorange: [[255, 140, 0], 'Dark Orange'], antiquewhite: [[250, 235, 215], 'Antique White'], orange: [[255, 165, 0], 'Orange'], gold: [[255, 215, 0], 'Gold'], olive: [[128, 128, 0], 'Olive'], yellow: [[255, 255, 0], 'Yellow'], darkgreen: [[0, 100, 0], 'Dark Green'], green: [[0, 128, 0], 'Green'], lime: [[0, 255, 0], 'Lime'], teal: [[0, 128, 128], 'Teal'], aqua: [[0, 255, 255], 'Aqua'], darkturquoise: [[0, 206, 209], 'Dark Turquoise'], lightskyblue: [[135, 206, 250], 'Light Sky Blue'], dodgerblue: [[30, 144, 255], 'Dodger Blue'], navy: [[0, 0, 128], 'Navy'], blue: [[0, 0, 255], 'Blue'], indigo: [[75, 0, 130], 'Indigo'], darkviolet: [[148, 0, 211], 'Dark Violet'], purple: [[128, 0, 128], 'Purple'], fuchsia: [[255, 0, 255], 'Fuchsia'], hotpink: [[255, 105, 180], 'Hot Pink'], crimson: [[220, 20, 60], 'Crimson'], pink: [[255, 192, 203], 'Pink'] ] */ @Field final Map colorsRGB = [ aliceblue: [[240, 248, 255], 'Alice Blue'], antiquewhite: [[250, 235, 215], 'Antique White'], aqua: [[0, 255, 255], 'Aqua'], aquamarine: [[127, 255, 212], 'Aquamarine'], azure: [[240, 255, 255], 'Azure'], beige: [[245, 245, 220], 'Beige'], bisque: [[255, 228, 196], 'Bisque'], black: [[0, 0, 0], 'Black'], blanchedalmond: [[255, 235, 205], 'Blanched Almond'], blue: [[0, 0, 255], 'Blue'], blueviolet: [[138, 43, 226], 'Blue Violet'], brown: [[165, 42, 42], 'Brown'], burlywood: [[222, 184, 135], 'Burly Wood'], cadetblue: [[95, 158, 160], 'Cadet Blue'], chartreuse: [[127, 255, 0], 'Chartreuse'], chocolate: [[210, 105, 30], 'Chocolate'], coral: [[255, 127, 80], 'Coral'], cornflowerblue: [[100, 149, 237], 'Corn Flower Blue'], cornsilk: [[255, 248, 220], 'Corn Silk'], crimson: [[220, 20, 60], 'Crimson'], cyan: [[0, 255, 255], 'Cyan'], darkblue: [[0, 0, 139], 'Dark Blue'], darkcyan: [[0, 139, 139], 'Dark Cyan'], darkgoldenrod: [[184, 134, 11], 'Dark Golden Rod'], darkgray: [[169, 169, 169], 'Dark Gray'], darkgreen: [[0, 100, 0], 'Dark Green'], darkgrey: [[169, 169, 169], 'Dark Grey'], darkkhaki: [[189, 183, 107], 'Dark Khaki'], darkmagenta: [[139, 0, 139], 'Dark Magenta'], darkolivegreen: [[85, 107, 47], 'Dark Olive Green'], darkorange: [[255, 140, 0], 'Dark Orange'], darkorchid: [[153, 50, 204], 'Dark Orchid'], darkred: [[139, 0, 0], 'Dark Red'], darksalmon: [[233, 150, 122], 'Dark Salmon'], darkseagreen: [[143, 188, 143], 'Dark Sea Green'], darkslateblue: [[72, 61, 139], 'Dark Slate Blue'], darkslategray: [[47, 79, 79], 'Dark Slate Gray'], darkslategrey: [[47, 79, 79], 'Dark Slate Grey'], darkturquoise: [[0, 206, 209], 'Dark Turquoise'], darkviolet: [[148, 0, 211], 'Dark Violet'], deeppink: [[255, 20, 147], 'Deep Pink'], deepskyblue: [[0, 191, 255], 'Deep Sky Blue'], dimgray: [[105, 105, 105], 'Dim Gray'], dimgrey: [[105, 105, 105], 'Dim Grey'], dodgerblue: [[30, 144, 255], 'Dodger Blue'], firebrick: [[178, 34, 34], 'Fire Brick'], floralwhite: [[255, 250, 240], 'Floral White'], forestgreen: [[34, 139, 34], 'Forest Green'], fuchsia: [[255, 0, 255], 'Fuchsia'], gainsboro: [[220, 220, 220], 'Gainsboro'], ghostwhite: [[248, 248, 255], 'Ghost White'], gold: [[255, 215, 0], 'Gold'], goldenrod: [[218, 165, 32], 'Golden Rod'], gray: [[128, 128, 128], 'Gray'], green: [[0, 128, 0], 'Green'], greenyellow: [[173, 255, 47], 'Green Yellow'], grey: [[128, 128, 128], 'Grey'], honeydew: [[240, 255, 240], 'Honey Dew'], hotpink: [[255, 105, 180], 'Hot Pink'], indianred: [[205, 92, 92], 'Indian Red'], indigo: [[75, 0, 130], 'Indigo'], ivory: [[255, 255, 240], 'Ivory'], khaki: [[240, 230, 140], 'Khaki'], lavender: [[230, 230, 250], 'Lavender'], lavenderblush: [[255, 240, 245], 'Lavender Blush'], lawngreen: [[124, 252, 0], 'Lawn Green'], lemonchiffon: [[255, 250, 205], 'Lemon Chiffon'], lightblue: [[173, 216, 230], 'Light Blue'], lightcoral: [[240, 128, 128], 'Light Coral'], lightcyan: [[224, 255, 255], 'Light Cyan'], lightgoldenrodyellow: [[250, 250, 210], 'Light Golden Rod Yellow'], lightgray: [[211, 211, 211], 'Light Gray'], lightgreen: [[144, 238, 144], 'Light Green'], lightgrey: [[211, 211, 211], 'Light Grey'], lightpink: [[255, 182, 193], 'Light Pink'], lightsalmon: [[255, 160, 122], 'Light Salmon'], lightseagreen: [[32, 178, 170], 'Light Sea Green'], lightskyblue: [[135, 206, 250], 'Light Sky Blue'], lightslategray: [[119, 136, 153], 'Light Slate Gray'], lightslategrey: [[119, 136, 153], 'Light Slate Grey'], lightsteelblue: [[176, 196, 222], 'Ligth Steel Blue'], lightyellow: [[255, 255, 224], 'Light Yellow'], lime: [[0, 255, 0], 'Lime'], limegreen: [[50, 205, 50], 'Lime Green'], linen: [[250, 240, 230], 'Linen'], magenta: [[255, 0, 255], 'Magenta'], maroon: [[128, 0, 0], 'Maroon'], mediumaquamarine: [[102, 205, 170], 'Medium Aquamarine'], mediumblue: [[0, 0, 205], 'Medium Blue'], mediumorchid: [[186, 85, 211], 'Medium Orchid'], mediumpurple: [[147, 112, 219], 'Medium Purple'], mediumseagreen: [[60, 179, 113], 'Medium Sea Green'], mediumslateblue: [[123, 104, 238], 'Medium Slate Blue'], mediumspringgreen: [[0, 250, 154], 'Medium Spring Green'], mediumturquoise: [[72, 209, 204], 'Medium Turquoise'], mediumvioletred: [[199, 21, 133], 'Medium Violet Red'], midnightblue: [[25, 25, 112], 'Medium Blue'], mintcream: [[245, 255, 250], 'Mint Cream'], mistyrose: [[255, 228, 225], 'Misty Rose'], moccasin: [[255, 228, 181], 'Moccasin'], navajowhite: [[255, 222, 173], 'Navajo White'], navy: [[0, 0, 128], 'Navy'], oldlace: [[253, 245, 230], 'Old Lace'], olive: [[128, 128, 0], 'Olive'], olivedrab: [[107, 142, 35], 'Olive Drab'], orange: [[255, 165, 0], 'Orange'], orangered: [[255, 69, 0], 'Orange Red'], orchid: [[218, 112, 214], 'Orchid'], palegoldenrod: [[238, 232, 170], 'Pale Golden Rod'], palegreen: [[152, 251, 152], 'Pale Green'], paleturquoise: [[175, 238, 238], 'Pale Turquoise'], palevioletred: [[219, 112, 147], 'Pale Violet Red'], papayawhip: [[255, 239, 213], 'Papaya Whip'], peachpuff: [[255, 218, 185], 'Peach Cuff'], peru: [[205, 133, 63], 'Peru'], pink: [[255, 192, 203], 'Pink'], plum: [[221, 160, 221], 'Plum'], powderblue: [[176, 224, 230], 'Powder Blue'], purple: [[128, 0, 128], 'Purple'], rebeccapurple: [[102, 51, 153], 'Rebecca Purple'], red: [[255, 0, 0], 'Red'], rosybrown: [[188, 143, 143], 'Rosy Brown'], royalblue: [[65, 105, 225], 'Royal Blue'], saddlebrown: [[139, 69, 19], 'Saddle Brown'], salmon: [[250, 128, 114], 'Salmon'], sandybrown: [[244, 164, 96], 'Sandy Brown'], seagreen: [[46, 139, 87], 'Sea Green'], seashell: [[255, 245, 238], 'Sea Shell'], sienna: [[160, 82, 45], 'Sienna'], silver: [[192, 192, 192], 'Silver'], skyblue: [[135, 206, 235], 'Sky Blue'], slateblue: [[106, 90, 205], 'Slate Blue'], slategray: [[112, 128, 144], 'Slate Gray'], slategrey: [[112, 128, 144], 'Slate Grey'], snow: [[255, 250, 250], 'Snow'], springgreen: [[0, 255, 127], 'Spring Green'], steelblue: [[70, 130, 180], 'Steel Blue'], tan: [[210, 180, 140], 'Tan'], teal: [[0, 128, 128], 'Teal'], thistle: [[216, 191, 216], 'Thistle'], tomato: [[255, 99, 71], 'Tomato'], turquoise: [[64, 224, 208], 'Turquoise'], violet: [[238, 130, 238], 'Violet'], wheat: [[245, 222, 179], 'Wheat'], white: [[255, 255, 255], 'White'], whitesmoke: [[245, 245, 245], 'White Smoke'], yellow: [[255, 255, 0], 'Yellow'], yellowgreen: [[154, 205, 50], 'Yellow Green'] ] @Field final String _RIimage = 'roomOccupancySettings.png' @Field final String _OPimage = 'roomsOnePage.png' @Field final String _REimage = 'roomsEasy.png' @Field final String _HAimage = 'roomsHideAdvanced.png' @Field final String _ODimage = 'roomsOtherDevices.png' @Field final String _OCimage = 'roomsOccupied.png' @Field final String _ENimage = 'roomsEngaged.png' @Field final String _CHimage = 'roomsChecking.png' @Field final String _VAimage = 'roomsVacant.png' @Field final String _ASimage = 'roomsAsleep.png' @Field final String _LOimage = 'roomsLocked.png' @Field final String _ALimage = 'roomsLightLevel.png' @Field final String _HLimage = 'roomsHolidayLights3.png' @Field final String _RTimage = 'roomsTemperature.png' @Field final String _RHimage = 'roomsHumidity.png' @Field final String _RUimage = 'roomsRules.png' @Field final String _ARimage = 'roomsAdjacent5.png' @Field final String _GEimage = 'roomsSettings.png' @Field final String _ANimage = 'roomsAnnouncement.png' @Field final String _VIimage = 'roomsViewAll.png' @Field final String _GHimage = 'roomOccupancySettings.png' @Field final String _gitREADME = 'https://github.com/adey/bangali/blob/master/README.md'