/***********************************************************************************************************************
*
*  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 <http://www.gnu.org/licenses/>.
*
*  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 ? "<img src=$url height=$_ImgSize width=$_ImgSize>  " : '')
}

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 "<div style='text-align:center;background-color:#bbbbbb;color:#ffffff;'>${str.toUpperCase().center(50)}</div>"
}

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: "<img src=$img height=25 width=25>", descriptionText: "$child.displayName $newOcc icon small", displayed: true)
		child.sendEvent(name: "occupancyIconM", value: "<img src=$img height=50 width=50>", descriptionText: "$child.displayName $newOcc icon medium", displayed: true)
		child.sendEvent(name: "occupancyIconL", value: "<img src=$img height=75 width=75>", descriptionText: "$child.displayName $newOcc icon large", displayed: true)
		child.sendEvent(name: "occupancyIconXL", value: "<img src=$img height=100 width=100>", descriptionText: "$child.displayName $newOcc icon extra large", displayed: true)
		child.sendEvent(name: "occupancyIconXXL", value: "<img src=$img height=150 width=150>", 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 <ady624(at)gmail.com>                */
/*                                                                       */
/*  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 <http://www.gnu.org/licenses/>.*/
/*************************************************************************/
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'
