diff --git a/smartapps/bangali/rooms-child-app.src/rooms-child-app.groovy b/smartapps/bangali/rooms-child-app.src/rooms-child-app.groovy index f47c4a4..c021b75 100644 --- a/smartapps/bangali/rooms-child-app.src/rooms-child-app.groovy +++ b/smartapps/bangali/rooms-child-app.src/rooms-child-app.groovy @@ -1241,18 +1241,21 @@ private pageRoomTemperature() { input "maintainRoomTemp", "enum", title: "Maintain room temperature?", required: false, multiple: false, defaultValue: 4, options: [[1:"Cool"], [2:"Heat"], [3:"Both"], [4:"Neither"]], submitOnChange: true if (['1', '2', '3'].contains(maintainRoomTemp)) { + if (personsPresence) + input "checkPresence", "bool", title: "Check presence before maintaining temperature?", required: true, multiple: false, defaultValue: false + else + paragraph "Check presence before maintaining temperature?\nselect presence sensor(s) to set" input "useThermostat", "bool", title: "Use thermostat? (otherwise uses room ac and/or heater)", required: true, multiple: false, defaultValue: false, submitOnChange: true - if (useThermostat) { + //input "outTempSensor", "capability.temperatureMeasurement", title: "Which outdoor temperature sensor?", required: false, multiple: false + if (useThermostat) { input "roomThermostat", "capability.thermostat", title: "Which thermostat?", required: true, multiple: false - input "thermoToTempSensor", "number", title: "Delta (room temperature - thermostat temperature)?", - description: "if room sensor reads 2° lower than thermostat set this to -2 and so on.", - required: false, multiple: false, defaultValue: 0, range: "-15..15" + input "thermoToTempSensor", "number", title: "Room sensor temperature - thermostat temperature = ? (typical delta)", + description: "Use to compensate for differences ", + required: true, multiple: false, defaultValue: 0, range: "-15..15" + input "thermostatOffset", "number", title: "Temporary extra thermostat offset to force HVAC activation?", + description: "Adds/subtracts to/from thermostat setting", + required: true, multiple: false, range: "0..9", defaultValue: 3 } - if (personsPresence) - input "checkPresence", "bool", title: "Check presence before maintaining temperature?", required: true, multiple: false, defaultValue: false - else - paragraph "Check presence before maintaining temperature?\nselect presence sensor(s) to set" - input "outTempSensor", "capability.temperatureMeasurement", title: "Which outdoor temperature sensor?", required: false, multiple: false } if (!useThermostat && ['1', '3'].contains(maintainRoomTemp)) { input "roomCoolSwitch", "capability.switch", title: "Which switch to turn on AC?", required: true, multiple: false, range: "32..99" @@ -2410,27 +2413,165 @@ def processCoolHeat() { def processCoolHeat() { ifDebug("processCoolHeat") - def temp = -1 - def child = getChildDevice(getRoom()) + + //Need to stop things even if nobody's in def isHere = (personsPresence ? personsPresence.currentValue("presence").contains('present') : false) if ((checkPresence && !isHere) || maintainRoomTemp == '4') { - if (checkPresence && !isHere) { - if (['1', '3'].contains(maintainRoomTemp)) - (useThermostat ? roomThermostat.off() : roomCoolSwitch.off()) - if (['2', '3'].contains(maintainRoomTemp)) - (useThermostat ? roomThermostat.off() : roomHeatSwitch.off()) - } updateMaintainIndP(temp) updateThermostatIndP(isHere) - return } - def roomState = child?.currentValue('occupancy') + if (maintainRoomTemp == '4') + return + def temperature = getAvgTemperature() def updateMaintainIndicator = true - def turnOn = null - def thisRule = [:] - if (state.rules) { + + def turnOn = getActiveTemperatureRule() + ifDebug("processCoolHeat: rule: $turnOn") + if (turnOn) { + def thisRule = getRule(turnOn, 't') + def tempRange = thisRule.tempRange + def setpoint + //reduced the off commands and fan commands to prevent spamming the likes of Nest, which have an API rate limiter + + if (['1', '3'].contains(maintainRoomTemp)) { + def coolHigh = thisRule.coolTemp + tempRange + def coolLow = thisRule.coolTemp - tempRange + ifDebug ("Temperature: ${temperature} Lower limit: ${coolLow}, Upper Limit: ${coolHigh}, cooling: ${state.coolingActive}") + if (temperature >= coolHigh && (!checkPresence || (checkPresence && isHere)) && (!state.coolingActive)) + setTemp("coolon", coolLow) + else + if (temperature <= coolLow && state.coolingActive) + setTemp("cooloff") + } + + if (['2', '3'].contains(maintainRoomTemp)) { + def heatHigh = thisRule.heatTemp + tempRange + def heatLow = thisRule.heatTemp - tempRange + ifDebug ("Temperature: ${temperature} Lower limit: ${heatLow}, Upper Limit: ${heatHigh}, heating: ${state.heatingActive}") + if (temperature >= heatHigh && state.heatingActive) + setTemp("heatoff") + else + if (temperature <= heatLow && (!checkPresence || (checkPresence && isHere)) && !(state.heatingActive)) + setTemp("heaton", heatHigh) + } + + updateThermostatIndP(isHere) + if (updateMaintainIndicator) { + if (maintainRoomTemp == '1') + updateMaintainIndP(thisRule.coolTemp) + else if (maintainRoomTemp == '2') + updateMaintainIndP(thisRule.heatTemp) + else if (maintainRoomTemp == '3') { + def x = Math.abs(temperature - thisRule.coolTemp) + def y = Math.abs(temperature - thisRule.heatTemp) + if (x >= y) + updateMaintainIndP(thisRule.heatTemp) + else + updateMaintainIndP(thisRule.coolTemp) + } + } + } + + else if (state.heatingActive) + setTemp("heatoff") + else if (state.coolingActive) + setTemp("cooloff") +} + +private setTemp(command, temp = null) +{ + def setpoint + switch (command) + { + case "heaton": + if (useThermostat) { + if (thermostatActive()) + break + setpoint = temp - thermoToTempSensor + thermostatOffset + ifDebug("On - New setpoint: ${setpoint}") + roomThermostat.setHeatingSetpoint(setpoint) + //roomThermostat.fanAuto() + roomThermostat.heat() + } + else + if (roomHeatSwitch.currentValue("switch") == 'off') { + roomHeatSwitch.on() + updateMaintainIndP(roomHeatTemp) + updateMaintainIndicator = false + } + state.heatingActive = true + break + + case "heatoff": + state.heatingActive = false + if (useThermostat) { + currentTemp = roomThermostat.currentTemperature + if (roomThermostat.currentHeatingSetpoint <= currentTemp) + break + roomThermostat.setHeatingSetpoint(currentTemp) + ifDebug("Off - New setpoint: $currentTemp") + } + else + roomHeatSwitch.off() + break + + case "coolon": + ifDebug("Cooling on") + if (useThermostat) { + if (thermostatActive()) + break + setpoint = temp - thermoToTempSensor - thermostatOffset + roomThermostat.setCoolingSetpoint(setpoint) + //roomThermostat.setThermostatFanMode() + roomThermostat.cool() + } + else + if (roomCoolSwitch.currentValue("switch") == 'off') { + roomCoolSwitch.on() + updateMaintainIndP(roomCoolTemp) + updateMaintainIndicator = false + } + state.coolingActive = true + break + + case "cooloff": + ifDebug("Cooling off") + state.coolingActive = false + if (useThermostat) { + currentTemp = roomThermostat.currentTemperature + if (roomThermostat.currentCoolingSetpoint >= currentTemp) + break + roomThermostat.setCoolingSetpoint(currentTemp) + } + else + roomCoolSwitch.off() + break + } +} + +private thermostatActive() +{ + def status = roomThermostat.currentThermostatOperatingState + ifDebug("Thermostat status: $status") + if (status == "heating" || status == "cooling") + return true + return false +} + +def getStateValue(key) { + return state[key] +} + +private getActiveTemperatureRule() +{ + if (state.rules) + { + def thisRule = [:] + def result = null def currentMode = String.valueOf(location.currentMode) + def child = getChildDevice(getRoom()) + def roomState = child?.currentValue('occupancy') def nowTime = now() + 1000 def nowDate = new Date(nowTime) def sunriseAndSunset = getSunriseAndSunset() @@ -2439,7 +2580,9 @@ def processCoolHeat() { def timedRulesOnly = false def sunriseTimeWithOff, sunsetTimeWithOff def i = 1 - for (; i < 11; i++) { + + for (; i < 11; i++) + { def ruleHasTime = false def ruleNo = String.valueOf(i) thisRule = getNextRule(ruleNo, 't', true) @@ -2448,23 +2591,24 @@ def processCoolHeat() { if (thisRule.mode && !thisRule.mode.contains(currentMode)) continue; if (thisRule.state && !thisRule.state.contains(roomState)) continue; if (thisRule.dayOfWeek && !(checkRunDay(thisRule.dayOfWeek))) continue; -// saved old time comparison while adding offset to sunrise / sunset -/* if ((thisRule.fromTimeType && (thisRule.fromTimeType != timeTime() || thisRule.fromTime)) && - (thisRule.toTimeType && (thisRule.toTimeType != timeTime() || thisRule.toTime))) { - def fTime = ( thisRule.fromTimeType == timeSunrise() ? sunriseTime : ( thisRule.fromTimeType == timeSunset() ? sunsetTime : timeToday(thisRule.fromTime, location.timeZone))) - def tTime = ( thisRule.toTimeType == timeSunrise() ? sunriseTime : ( thisRule.toTimeType == timeSunset() ? sunsetTime : timeToday(thisRule.toTime, location.timeZone))) -// ifDebug("ruleNo: $ruleNo | fTime: $fTime | tTime: $tTime | nowDate: $nowDate | timeOfDayIsBetween: ${timeOfDayIsBetween(fTime, tTime, nowDate, location.timeZone)}") - if (!(timeOfDayIsBetween(fTime, tTime, nowDate, location.timeZone))) continue; - if (!timedRulesOnly) { - turnOn = null - timedRulesOnly = true - i = 0 - continue - } - ruleHasTime = true + // saved old time comparison while adding offset to sunrise / sunset + /* if ((thisRule.fromTimeType && (thisRule.fromTimeType != timeTime() || thisRule.fromTime)) && + (thisRule.toTimeType && (thisRule.toTimeType != timeTime() || thisRule.toTime))) { + def fTime = ( thisRule.fromTimeType == timeSunrise() ? sunriseTime : ( thisRule.fromTimeType == timeSunset() ? sunsetTime : timeToday(thisRule.fromTime, location.timeZone))) + def tTime = ( thisRule.toTimeType == timeSunrise() ? sunriseTime : ( thisRule.toTimeType == timeSunset() ? sunsetTime : timeToday(thisRule.toTime, location.timeZone))) + // ifDebug("ruleNo: $ruleNo | fTime: $fTime | tTime: $tTime | nowDate: $nowDate | timeOfDayIsBetween: ${timeOfDayIsBetween(fTime, tTime, nowDate, location.timeZone)}") + if (!(timeOfDayIsBetween(fTime, tTime, nowDate, location.timeZone))) continue; + if (!timedRulesOnly) { + turnOn = null + timedRulesOnly = true + i = 0 + continue + } + ruleHasTime = true }*/ if ((thisRule.fromTimeType && (thisRule.fromTimeType != timeTime() || thisRule.fromTime)) && - (thisRule.toTimeType && (thisRule.toTimeType != timeTime() || thisRule.toTime))) { + (thisRule.toTimeType && (thisRule.toTimeType != timeTime() || thisRule.toTime))) + { if (thisRule.fromTimeType == timeSunrise()) sunriseTimeWithOff = (thisRule.fromTimeOffset ? new Date(sunriseTime.getTime() + (thisRule.fromTimeOffset * 60000L)) : sunriseTime) else if (thisRule.fromTimeType == timeSunset()) @@ -2475,83 +2619,24 @@ def processCoolHeat() { else if (thisRule.toTimeType == timeSunset()) sunsetTimeWithOff = (thisRule.toTimeOffset ? new Date(sunsetTime.getTime() + (thisRule.toTimeOffset * 60000L)) : sunsetTime) def tTime = ( thisRule.toTimeType == timeSunrise() ? sunriseTimeWithOff : ( thisRule.toTimeType == timeSunset() ? sunsetTimeWithOff : timeToday(thisRule.toTime, location.timeZone))) -// ifDebug("ruleNo: $ruleNo | fTime: $fTime | tTime: $tTime | nowDate: $nowDate | timeOfDayIsBetween: ${timeOfDayIsBetween(fTime, tTime, nowDate, location.timeZone)}") + // ifDebug("ruleNo: $ruleNo | fTime: $fTime | tTime: $tTime | nowDate: $nowDate | timeOfDayIsBetween: ${timeOfDayIsBetween(fTime, tTime, nowDate, location.timeZone)}") if (!(timeOfDayIsBetween(fTime, tTime, nowDate, location.timeZone))) continue; if (!timedRulesOnly) { - turnOn = null + result = null timedRulesOnly = true i = 0 continue } ruleHasTime = true } -// ifDebug("ruleNo: $thisRule.ruleNo | thisRule.luxThreshold: $thisRule.luxThreshold | turnOn: $turnOn | previousRuleLux: $previousRuleLux") -// ifDebug("timedRulesOnly: $timedRulesOnly | ruleHasTime: $ruleHasTime") + ifDebug("${i} ${thisRule.ruleNo}") + // ifDebug("ruleNo: $thisRule.ruleNo | thisRule.luxThreshold: $thisRule.luxThreshold | turnOn: $turnOn | previousRuleLux: $previousRuleLux") + // ifDebug("timedRulesOnly: $timedRulesOnly | ruleHasTime: $ruleHasTime") if (timedRulesOnly && !ruleHasTime) continue; - turnOn = thisRule.ruleNo - } - } - ifDebug("processCoolHeat: rule: $turnOn") - if (turnOn) { - thisRule = getRule(turnOn, 't') - def tempRange = thisRule.tempRange - if (['1', '3'].contains(maintainRoomTemp)) { - def coolHigh = thisRule.coolTemp + (tempRange / 2f).round(1) - def coolLow = thisRule.coolTemp - (tempRange / 2f).round(1) - if (temperature >= coolHigh) { - if (useThermostat) { - roomThermostat.setCoolingSetpoint(thisRule.coolTemp - thermoToTempSensor) - roomThermostat.setThermostatFanMode() - roomThermostat.cool() - } - else { - if (roomCoolSwitch.currentValue("switch") == 'off') { - roomCoolSwitch.on() - updateMaintainIndP(roomCoolTemp) - updateMaintainIndicator = false - } - } - } - else if (temperature <= coolLow) - (useThermostat ? roomThermostat.off() : roomCoolSwitch.off()) - } - if (['2', '3'].contains(maintainRoomTemp)) { - def heatHigh = thisRule.heatTemp + (tempRange / 2f).round(1) - def heatLow = thisRule.heatTemp - (tempRange / 2f).round(1) - if (temperature >= heatHigh) - (useThermostat ? roomThermostat.off() : roomHeatSwitch.off()) - else { - if (temperature <= heatLow) { - if (useThermostat) { - roomThermostat.setHeatingSetpoint(thisRule.heatTemp - thermoToTempSensor) - roomThermostat.fanAuto() - roomThermostat.heat() - } - else { - if (roomHeatSwitch.currentValue("switch") == 'off') { - roomHeatSwitch.on() - updateMaintainIndP(roomHeatTemp) - updateMaintainIndicator = false - } - } - } - } - } - updateThermostatIndP(isHere) - if (updateMaintainIndicator) { - if (maintainRoomTemp == '1') - updateMaintainIndP(thisRule.coolTemp) - else if (maintainRoomTemp == '2') - updateMaintainIndP(thisRule.heatTemp) - else if (maintainRoomTemp == '3') { - def x = Math.abs(temperature - thisRule.coolTemp) - def y = Math.abs(temperature - thisRule.heatTemp) - if (x >= y) - updateMaintainIndP(thisRule.heatTemp) - else - updateMaintainIndP(thisRule.coolTemp) - } + ifDebug("${i} ${thisRule.ruleNo}") + result = thisRule.ruleNo } + return result } } diff --git a/smartapps/bangali/rooms-manager.src/rooms-manager.groovy b/smartapps/bangali/rooms-manager.src/rooms-manager.groovy index 0c55136..1bd6311 100644 --- a/smartapps/bangali/rooms-manager.src/rooms-manager.groovy +++ b/smartapps/bangali/rooms-manager.src/rooms-manager.groovy @@ -381,19 +381,25 @@ def pageSpeakerSettings() { def i = (presenceSensors ? presenceSensors.size() : 0) def str = (presenceNames ? presenceNames.split(',') : []) def j = str.size() - if (i != j) sendNotification("Count of presense sensors and names do not match!", [method: "push"]); - dynamicPage(name: "pageSpeakerSettings", title: "Speaker Settings", install: true, uninstall: true) { + if (presenceNames && i != j) sendNotification("Count of presense sensors and names do not match!", [method: "push"]); + dynamicPage(name: "pageSpeakerSettings", title: "Presence & Announcement Settings", install: true, uninstall: true) { section { - input "speakerDevices", "capability.audioNotification", title: "Which speakers?", required: false, multiple: true, submitOnChange: true + input "presenceSensors", "capability.presenceSensor", title: "Which presence sensors?", required: true, multiple: true + input "speakerDevices", "capability.audioNotification", title: "Which speakers?", required: false, multiple: true, submitOnChange: true + input "speechSynthesizers", "capability.speechSynthesis", title: "Which synthesizers?", required: false, multiple: true, submitOnChange: true + if (speakerDevices) { - input "speakerVolume", "number", title: "Speaker volume?", required: false, multiple: false, defaultValue: 33, range: "1..100" - input "speakerAnnounce", "bool", title: "Announce when presence sensors arrive or depart?", required: false, multiple: false, defaultValue: false, submitOnChange: true + input "speakerVolume", "number", title: "Speaker volume?", required: false, multiple: false, defaultValue: 33, range: "1..100" } else { paragraph "Speaker volume?\nselect speaker(s) to set." - paragraph "Announce when presence sensors arrive or depart?\nselect speaker(s) to set." } - if (speakerDevices && speakerAnnounce) { + if (speakerDevices || speechSynthesizers) + input "speakerAnnounce", "bool", title: "Announce when presence sensors arrive or depart?", required: false, multiple: false, defaultValue: false, submitOnChange: true + else + paragraph "Announce when presence sensors arrive or depart?\nselect speaker/synths to set." + + if ((speakerDevices || speechSynthesizers) && speakerAnnounce) { input "presenceSensors", "capability.presenceSensor", title: "Which presence snesors?", required: true, multiple: true input "presenceNames", "text", title: "Comma delmited names? (in sequence of presence sensors)", required: true, multiple: false, submitOnChange: true input "contactSensors", "capability.contactSensor", title: "Which contact sensors? (welcome home greeting is played after this contact sensor closes.)", required: true, multiple: true @@ -411,11 +417,11 @@ def pageSpeakerSettings() { paragraph "Seconds after?\nselect announce to set." paragraph "Left home announcement?\nselect announce to set." } - if (speakerDevices) + if (speakerDevices || speechSynthesizers) input "timeAnnounce", "enum", title: "Announce time?", required: false, multiple: false, defaultValue: 4, options: [[1:"Every 15 minutes"], [2:"Every 30 minutes"], [3:"Every hour"], [4:"No"]], submitOnChange: true else - paragraph "Announce time?\nselect speaker devices to set." + paragraph "Announce time?\nselect speakers/synths to set." } section { if (speakerAnnounce || ['1', '2', '3'].contains(timeAnnounce)) { @@ -531,7 +537,12 @@ def contactClosedEventHandler(evt = null) { def nowDate = new Date(now()) def intCurrentHH = nowDate.format("HH", location.timeZone) as Integer if (intCurrentHH >= startHH && intCurrentHH <= endHH) - speakerDevices.playTextAndResume(persons, speakerVolume) + { + if (speakerDevices) + speakerDevices.playTextAndResume(persons, speakerVolume) + if (speechSynthesizers) + speechSynthesizers.speak(persons) + } if (evt) state.whoCameHome.personsIn = []; else state.whoCameHome.personsOut = []; } @@ -723,7 +734,10 @@ def tellTime() { // speakerDevices.playTrackAndResume("http://s3.amazonaws.com/smartapp-media/sonos/bell1.mp3", // (intCurrentMM == 0 ? 10 : (intCurrentMM == 15 ? 4 : (intCurrentMM == 30 ? 6 : 8))), speakerVolume) // pause(1000) - speakerDevices.playTextAndResume(timeString, speakerVolume) + if (speakerDevices) + speakerDevices.playTextAndResume(timeString, speakerVolume) + if (speechSynthesizers) + speechSynthesizers.speak(timeString) } }