/** * Local Ecobee Helpers (parent app) * * Offline helper framework for the local Ecobee HAP Thermostat. Add as many * child helpers as you need (Room Vent, Open-Contact Pause, Humidity). No cloud. * Inspired by SANdood Ecobee Suite helpers, rebuilt from scratch. * * Namespace: RamSet * * Author: RamSet * Version: 1.0.0 (2026-06-24) * Version history: * 1.0.0 - Initial release. Parent framework for Room Vent, Open-Contact Pause and Humidity helpers. * * DISCLAIMER: Provided as-is, without warranty of any kind. You are solely * responsible for the safe operation of your HVAC system and connected devices. * Use at your own risk. */ definition( name: "Local Ecobee Helpers", namespace: "RamSet", author: "RamSet", description: "Offline helper apps for the local Ecobee HAP Thermostat (vents, open-contact pause, humidity).", category: "Convenience", iconUrl: "", iconX2Url: "", importUrl: "https://raw.githubusercontent.com/RamSet/hubitat/main/apps/ecobee-hap-helpers/ecobee-hap-helpers-parent.groovy", singleInstance: false ) preferences { page(name: "mainPage") } def mainPage() { dynamicPage(name: "mainPage", title: "Local Ecobee Helpers", install: true, uninstall: true) { section("Thermostat") { input "thermostat", "capability.thermostat", title: "Local Ecobee HAP Thermostat", required: true, submitOnChange: true if (thermostat) { paragraph "State: ${thermostat.currentValue('thermostatOperatingState')} | " + "mode: ${thermostat.currentValue('thermostatMode')} | " + "heat sp: ${thermostat.currentValue('heatingSetpoint')}° | " + "cool sp: ${thermostat.currentValue('coolingSetpoint')}° | " + "humidity: ${thermostat.currentValue('humidity')}% (target ${thermostat.currentValue('humiditySetpoint')}%)" } } section("Helpers — add as many as you need") { app(name: "roomVents", appName: "Local Ecobee Room Vent", namespace: "RamSet", title: "Add a Room Vent…", multiple: true) app(name: "contactPause", appName: "Local Ecobee Open-Contact Pause", namespace: "RamSet", title: "Add an Open-Contact Pause…", multiple: true) app(name: "humidity", appName: "Local Ecobee Humidity", namespace: "RamSet", title: "Add a Humidity helper…", multiple: true) } section("System-wide airflow safety") { def rpt = belowFloorReport() if (rpt.below) { paragraph "WARNING: ${rpt.below.size()} of ${rpt.vents} vent(s) are set below the 10% safe floor: ${rpt.below.join(', ')}. " + "Verify your air handler tolerates this — too many closed vents at once can damage the system." } else if (rpt.vents) { paragraph "All ${rpt.vents} room vent(s) are at or above the 10% minimum floor." } else { paragraph "No room vents configured yet." } } } } def installed() { log.info "Local Ecobee Helpers installed" } def updated() { log.info "Local Ecobee Helpers updated" } // shared accessor used by child apps (settings.thermostat avoids getter self-recursion) def getThermostat() { return settings.thermostat } // aggregate the per-vent floors reported by Room Vent children Map belowFloorReport() { def below = [] int vents = 0 getChildApps().each { ch -> try { def r = ch.ventReport() // only Room Vent children return a map if (r) { vents += (r.ventCount ?: 0) as int if (r.floor != null && (r.floor as int) < 10) below << "${r.name} (${r.floor}%)" } } catch (ignored) { /* not a Room Vent child */ } } return [below: below, vents: vents] }