Smart Vent Recommendations

None of them make large enough ones for the vents in my Texas sized home, with Texas sized vents either. And putting in trim plates that restrict flow (in some of the larger cases) isn't OK with me.

2 Likes

I have 1 6X20 vent that I was thinking a 6x8 and a6x10 with some sort of bracket would work. But then I remember my semi unhappiness worth the reliability of the vents...

For those who have used smart vents, I'm curious....

If my primary use-case is to close off the flow of air in overly-conditioned rooms for the purpose of improving the temperatures in under-conditioned rooms, then can I essentially test the would-be effectiveness of smart vents by manually closing my existing dumb vents in the under-conditioned rooms to see if I get the impact I'm hoping for? Then, if I do, I'll happily shell out the $$$ to buy smart vents for most of the rooms in my home. But if it doesn't have the desired impact (~3-5 degree F swing) in the under-conditioned rooms, it just ain't worth it.

Opinions please?

That sounds like it should work, but youā€™d need either sensors or temp gauges and have to monitor the individual rooms over varying periods of time and also consider outdoor temp impacts on certain rooms (ie. Certain upstairs rooms get bombarded with direct sunlight, etc.)

I find the need for smart vents really situation dependent. For example:

  1. we retrofitted our old house for a new heating/AC system. This house wasnā€™t designed for AC ductwork so space for it is limited. Also, return air in some rooms is non-existent so circulation is poor.
  2. quoted to add one zone (upstairs and downstairs) was $4,000. Wouldnā€™t provide individual room-by-room zone control and would also require an additional thermostat to be run.
  3. Our house has cathedral ceilings throughout and also had an older poorly performing roof and leaking skylights (since replaced with new lighter-colored shingles and solar-powered skylights under US tax solar credit.)
  4. installed ceiling fans where applicable to help recirculate trapped air in cathedral ceilings. This helped a lot.
  5. temps would fluctuate wildlyā€”not even close even with ecobee and Ecobee room sensors being averaged. Some rooms were 10-degrees hotter/cooler than others.
  6. after installing Keen vents in almost all rooms, average temps between rooms started to balance themselves out. Noticeable improvement within a few degrees.)
  7. set AC fan to recirculate almost all the time. Itā€™s debatable whether this impacts electric bill, but your motor is not cycling as much and could actually save wear and tear and money. The positive is that it helps balance room temps out even more while improving air filtration (assuming you have an air filter.) UPDATE: several users have pointed out that this is a bad idea in colder months due to increased indoor humidity levels.
  8. ditch the Keen app. I found Keenect to be more responsive and plus youā€™re not reliant on Keen/Ecobee outages which were quite common and would frequently overheat/overcook rooms when disconnected.
  9. I will say that out of 8+ vents, 2 of them acted up. Keen reluctantly replaced one of them. The other they told me was out of warranty...
  10. some people also claim that these may harm your AC system by backflow. Keen says that they prevent that from happening Which I think means that it never fully closes all of them. Letā€™s say it does, but it might only shave off a few months at most off of your blower motor. It would be a small price to pay.
  11. Iā€™d also add that Iā€™m fiddling with this system more than Iā€™d like to. Itā€™s not a perfect science and Iā€™m continually trying to dial this in.

The positive of the system is more room by room control and accuracy once set up, but the negative is vent reliability. I personally think Iā€™ve already made our money back when it comes to electricity after two years as well as provided additional comfort. Just wish the vents were more reliable. That being said, if one of them broke Iā€™d likely purchase a replacement although I wouldnā€™t be too happy. Without these vents or when they fail, we notice it almost immediately.

1 Like

Depending on where you live, this could be bad advice. This reduces the air conditioner's effectiveness at removing humidity. Basically, you cause the water that has condensed on the coil when the compressor turns off to re-evaporate into conditioned space. Of course, if you live in the desert, then humidity is no problem.

3 Likes

I agree with @reid.a.baldwin - for most of the US, this is not a good idea. Not only will it increase indoor humidity quite dramatically, it will also result in conditions that promote mold growth.

John Proctor, P.E. published a terrific technical article in Home Energy called "Smart and Cool - the Art of Air Conditioning", available here, on this subject that is well worth a read.

1 Like

Iā€™m in the NE. I just assumed when it recirculates that it kept everything off aside from the fan and all it really did was recirculate the air through the supply and returnsā€”nothing to do with the condenser/evaporator coils unless the AC itself was set to run. I suppose in the winter this may be okay but in the summer it should be reduced according to what youā€™ve said.

Iā€™m going to reduce the fan runtime (no auto option with Ecobee) and see what happens to the humidity levels as it has been rather high.

Thanks for that excellent article.

1 Like

So you guys just changed my life for the better. I put my fan setting to 15 mins. auto and itā€™s already dropped the humidity 6 points on one of the most humid days weā€™ve had. Iā€™m considering putting it to zero but I wonder at what cost (less balance and less filtration vs lower humidity?)

1 Like

Thank you for the excellent and comprehensive advice. Think I'm going to take the plunge. Will let y'all know how it goes!

0 is better than 15.

Why donā€™t you use a rule to run the fan for 20 mins only when the temp difference in various rooms of your house exceeds a set amount - say 3 degrees Fahrenheit?

2 Likes

Thatā€™s actually an excellent idea, although if I ever get hit by a car my wife is totally screwed by the smart home. I think I actually disconnected the Ecobee from being controlled by HEā€”too many moving parts for my pea-brain to handle but may have to look into that again.

As I understand the Auto 15 setting, it ensures that the fan runs at least 15 minutes of each hour. So, if your AC is running for 20 minutes each hour, there will be no additional fan time. If your AC runs 10 minutes in an hour, it will run the fan for 5 minutes. In most cases, that 5 minutes will not be right after an AC run, so it will have only a minor effect on humidity.

1 Like

As far as smart vents go, I've had both Flair and Keen.

Flair

Pros:

  • Hardware is solid
  • Customer service (during pandemic shutdown) was decent and responsive.
    Cons:
  • Battery life was terrible
  • 30 seconds to a full minute before they would move after a command was sent
  • Internet dependent. They all just open up fully if you lose internet, with no local control.
  • Only 3 opening levels available, 0%, 50% and 100%.
  • Whatever communication protocol they use between the pucks and vents sucks. They were constantly losing connection.

In the end I sent them all back. It would have been a really nice system having a little dial thermostat in each zone so people could set their own comfort level, but with their proprietary communications protocol, internet dependence, and latency it was doomed from the start.

Keen

Pros:

  • Hardware is solid
  • Works with Hubitat without their hub, you only have to buy the vents.
  • They use regular Zigbee protocol so the vents integrate right in.
  • Battery life, I just recently had to go around and change all the batteries. They lasted about 6-7 months.
    Cons:
  • Customer service as of last month is un-responsive. When I went to change the batteries, a couple of the vents had batteries that corroded. Cleaning off the contacts was enough for most of the vents, but one had corroded so badly that it ate the contact off the circuit board and ate through one of the springs. I've been trying to get an answer from them about an RMA, but no response.

I haven't tried any other vents, and it would cost a small fortune to do professionally installed dampers in our house. Right now I have 8 Keen vents, a Centralite Pearl thermostat (which I don't recommend and am switching out for a Honeywell T6), and 6 Samsung door sensors that I use for temperature sensors in each zone. (had to change the batteries in all of those at the same time as the Keen vents) We also have a duct booster fan that we had to install because the previous owners built a sizeable add-on to the house, and just extended the supply ducts without considering the reduced airflow.

With fresh batteries and a little tweaking here and there I've managed to keep our whole house within a 3 degree range, for the most part. There are exceptions like when one of the kids leaves the door into the garage open for more than 15-20 minutes, but it works.

For software I'm using the KeenectLite App, but I have made several modifications to it to handle the booster fan and automatically switch the Centralite Pearl thermostat from heating to cooling based on the outside temperature since it doesn't have an "Auto" mode. I also added code to take into consideration the outside temperature when using a heat pump and made it switch to "Auxiliary Heat" when it's cold enough outside that the heat pump becomes less efficient. Aux Heat or Emergency Heat switches from the heat pump to using natural gas.

With all of that we're saving about 20-30% on our electric bill, mainly because we don't have to run space heaters in the winter and window air conditioners in the summer upstairs anymore.

If anyone has any questions about Flair, Keen, the Centralite Pearl, or the KeenectLite app for Hubitat I'm happy to help.

1 Like

Your experience with battery life is not the norm; at least from other posts here about Keen vents.

1 Like

I take it the norm is less than that? It could be the driver polling to often, which was mentioned in another thread I saw earlier. I'm using this one, and it seems to do pretty well.

metadata {
definition (name: "Keen Home Smart Vent - lgk V2", namespace: "lgkapps", author: "Keen Home, lgkahn") {
    capability "Switch Level"
    capability "Switch"
    capability "Configuration"
    capability "Refresh"
    capability "Sensor"
    capability "Temperature Measurement"
    capability "Battery"

   // lgk modifications to add door and contact control, so you can open/close in alexa and view status as open/closed in contacts
   // also add label/words open closed when viewing from list of devices in room
   // lgk 10/20 had to modify by calling this function to get the corrected description map in Hubitat.. This was not necesary in Snmarthings so something is different in the underlying achitecture.
   // zigbee.parseDescriptionAsMap(description) 
    
	capability "Door Control"
    capability "Contact Sensor"

    command "getLevel"
    command "getOnOff"
    command "getPressure"
    command "getBattery"
    command "getTemperature"
    command "setZigBeeIdTile"
    command "clearObstruction"

    fingerprint endpoint: "1",
    profileId: "0104",
    inClusters: "0000,0001,0003,0004,0005,0006,0008,0020,0402,0403,0B05,FC01,FC02",
    outClusters: "0019"
}


preferences {
    input("TempOffset", "number", title: "Temperature Offset/Adjustment -10 to +10 in Degrees?",range: "-10..10", description: "If your temperature is innacurate this will offset/adjust it by this many degrees.", defaultValue: 0, required: false)
    input("debug", "bool", title: "Enable logging?", required: true, defaultValue: false)

}

// simulator metadata
simulator {
    // status messages
    status "on": "on/off: 1"
    status "off": "on/off: 0"

    // reply messages
    reply "zcl on-off on": "on/off: 1"
    reply "zcl on-off off": "on/off: 0"
}

}

/**** PARSE METHODS ****/
def parse(String description)
{
   if (debug) log.debug "in parse"
   if (debug) log.debug "description: $description"

    Map map = [:]
    if (description?.startsWith('catchall:'))
    {
        map = parseCatchAllMessage(description)
    }
    else if (description?.startsWith('read attr -'))
    {
        map = parseReportAttributeMessage(description)
    }
    else if (description?.startsWith('temperature: ') || description?.startsWith('humidity: ')) 
    {
        map = parseCustomMessage(description)
    }
    else if (description?.startsWith('on/off: '))
    {
        map = parseOnOffMessage(description)
    }

    if (debug) log.debug "Parse returned $map"
    return map ? createEvent(map) : null
}

private Map parseCatchAllMessage(String description) {
   if (debug)  log.debug "parseCatchAllMessage"

    def cluster = zigbee.parse(description)
  if (debug)  log.debug "cluster: ${cluster}"
    if (shouldProcessMessage(cluster)) {
   
    switch(cluster.clusterId) {
        case 0x0001:
            return makeBatteryResult(cluster.data.last())
            break

        case 0x0402:
            // temp is last 2 data values. reverse to swap endian
            String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
            def value = convertTemperatureHex(temp)
            return makeTemperatureResult(value)
            break

        case 0x0006:
            return makeOnOffResult(cluster.data[-1])
            break
        }
    }

    return [:]
}

private boolean shouldProcessMessage(cluster) {
    // 0x0B is default response indicating message got through
    // 0x07 is bind message
    if (cluster.profileId != 0x0104 ||
        cluster.command == 0x0B ||
        cluster.command == 0x07 ||
        (cluster.data.size() > 0 && cluster.data.first() == 0x3e)) {
        return false
    }

    return true
}

private Map parseReportAttributeMessage(String description) {
    if (debug) log.debug "parseReportAttributeMessage"

    Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
        def nameAndValue = param.split(":")
        map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
    }
   if (debug) log.debug "Desc Map: $descMap"

    if (descMap.cluster == "0006" && descMap.attrId == "0000") {
        if (debug) {
            log.debug "in cluster 0006 case "
            log.debug "map = $descMap"
            log.debug "value = $descMap.value"
        }
            return makeOnOffResult(Integer.parseInt(descMap.value));  
   
    }
    else if (descMap.cluster == "0008" && descMap.attrId == "0000") {
        return makeLevelResult(descMap.value)
    }
    else if (descMap.cluster == "0402" && descMap.attrId == "0000") {
        if (debug) log.debug "in 0402 case"
        if (debug) log.debug "orig map = $descMap"
        
         // lgk need to call this in hubitat to get correct converted map
        def descMapNew = zigbee.parseDescriptionAsMap(description) 
        if (debug) log.debug "Corrected map = $descMapNew"
   
        def value = convertTemperatureHex(descMapNew.value)
        return makeTemperatureResult(value)
    }
    else if (descMap.cluster == "0001" && descMap.attrId == "0021") {
        return makeBatteryResult(Integer.parseInt(descMap.value, 16))
    }
    else if (descMap.cluster == "0403" && descMap.attrId == "0020") {
        if (debug) log.debug "***************************in 0403 case"
        if (debug) log.debug "orig map = $descMap"
   
        // lgk fix here as well 
        def descMapNew = zigbee.parseDescriptionAsMap(description) 
        if (debug) log.debug "new map = $descMapNew"  
        return makePressureResult(Integer.parseInt(descMapNew.value, 16))
    }
    else if (descMap.cluster == "0000" && descMap.attrId == "0006") {
        return makeSerialResult(new String(descMap.value.decodeHex()))
    }

    // shouldn't get here
    return [:]

}

private Map parseCustomMessage(String description) {
    Map resultMap = [:]
    if (description?.startsWith('temperature: ')) {
         log.debug "${description}"
         def oldvalue = zigbee.parseHATemperatureValue(description, "temperature: ", 
getTemperatureScale())
      
        log.debug "split: " + description.split(": ")
        def value = Double.parseDouble(description.split(": ")[1])
       log.debug "${value}" 
        resultMap = makeTemperatureResult(convertTemperature(value))
    }
    return resultMap
}

private Map parseOnOffMessage(String description) {
    if (debug) log.debug "in parse on off"
    Map resultMap = [:]
    if (description?.startsWith('on/off: ')) {
        def value = Integer.parseInt(description - "on/off: ")
        resultMap = makeOnOffResult(value)
    }
    return resultMap
}

private Map makeOnOffResult(rawValue) {
   if (debug) log.debug "makeOnOffResult: ${rawValue}"
    def linkText = getLinkText(device)
    def value = rawValue == 1 ? "on" : "off"
    return [
        name: "switch",
        value: value,
        descriptionText: "${linkText} is ${value}"
    ]
}

private Map makeLevelResult(rawValue) {
    def linkText = getLinkText(device)
    def value = Integer.parseInt(rawValue, 16)
    def rangeMax = 254

    // catch obstruction level
    if (value == 255) {
        log.debug "${linkText} is obstructed"
        // Just return here. Once the vent is power cycled
        // it will go back to the previous level before obstruction.
        // Therefore, no need to update level on the display.
        return [
            name: "switch",
            value: "obstructed",
            descriptionText: "${linkText} is obstructed. Please power cycle."
        ]
    }

    value = Math.floor(value / rangeMax * 100)

    return [
        name: "level",
        value: value,
        descriptionText: "${linkText} level is ${value}%"
    ]
}

private Map makePressureResult(rawValue) {
    if (debug) log.debug 'makePressureResut'
    def linkText = getLinkText(device)

    def pascals = rawValue / 10
    def result = [
        name: 'pressure',
        descriptionText: "${linkText} pressure is ${pascals}Pa",
        value: pascals
    ]

    return result
}

private Map makeBatteryResult(rawValue) {
    // log.debug 'makeBatteryResult'
    def linkText = getLinkText(device)
    def intValue = rawValue as Integer
    // log.debug
    [
        name: 'battery',
        value: intValue,
        descriptionText: "${linkText} battery is at ${intValue}%"
    ]
}

private Map makeTemperatureResult(value) {

     if (debug) log.debug 'makeTemperatureResult'

    def linkText = getLinkText(device)

    if (settings.TempOffset != null) {
        def offset = settings.TempOffset as BigDecimal
  
        if (debug) log.debug "raw temp value = $value"
        def v = value + offset as BigDecimal
   
       value = v + offset
  
       def dispval =  String.format("%5.1f", v)
   
       def finalval = dispval as BigDecimal
   
       value = finalval
       if (debug) log.debug "value:= $value"
      
    }

    return [
        name: 'temperature',
        value: "" + value,
        descriptionText: "${linkText} is ${value}Ā°${temperatureScale}",
    ]
}

/**** HELPER METHODS ****/
private def convertTemperatureHex(value) {
  //  log.debug "in coverttemphex calling new fx"

    if (debug)
    {
        log.debug "convertTemperatureHex(${value})"
        log.debug "scale = $getTemperatureScale()"
    }

    def celsius = Integer.parseInt(value, 16).shortValue() / 100

    return convertTemperature(celsius)
}

private def convertTemperature(celsius) {
    if (debug) log.debug "convertTemperature()"

    if(getTemperatureScale() == "C"){
        return celsius
    } else {
        def fahrenheit = Math.round(celsiusToFahrenheit(celsius) * 100) /100
       if (debug)  log.debug "converted to F: ${fahrenheit}"
        return fahrenheit
    }
}

private def makeSerialResult(serial) {
   if (debug) log.debug "makeSerialResult: " + serial

    def linkText = getLinkText(device)
    sendEvent([
        name: "serial",
        value: serial,
        descriptionText: "${linkText} has serial ${serial}" ])
    return [
        name: "serial",
        value: serial,
        descriptionText: "${linkText} has serial ${serial}" ]
}

// takes a level from 0 to 100 and translates it to a ZigBee move to level with on/off command
private def makeLevelCommand(level) {
    def rangeMax = 254
    def scaledLevel = Math.round(level * rangeMax / 100)
    log.debug "scaled level for ${level}%: ${scaledLevel}"

    // convert to hex string and pad to two digits
    def hexLevel = new BigInteger(scaledLevel.toString()).toString(16).padLeft(2, '0')

    "st cmd 0x${device.deviceNetworkId} 1 8 4 {${hexLevel} 0000}"
}

/**** COMMAND METHODS ****/
def on() {
    def linkText = getLinkText(device)
    log.debug "open ${linkText}"

    // only change the state if the vent is not obstructed
    if (device.currentValue("switch") == "obstructed") {
        log.error("cannot open because ${linkText} is obstructed")
        return
    }

  // LGK
    sendEvent(name: "contact", value: "open")
    sendEvent(name: "door", value: "open")
    sendEvent(name: "level", value: 100)

    sendEvent(makeOnOffResult(1))
    "st cmd 0x${device.deviceNetworkId} 1 6 1 {}"
    setLevel(100)
}

def off() {
    def linkText = getLinkText(device)
    log.debug "close ${linkText}"

    // only change the state if the vent is not obstructed
    if (device.currentValue("switch") == "obstructed") {
        log.error("cannot close because ${linkText} is obstructed")
        return
    }

    // lgk
    sendEvent(name: "contact", value: "closed")
    sendEvent(name: "door", value: "closed") 
    sendEvent(name: "level", value: 0)

sendEvent(makeOnOffResult(0))
    "st cmd 0x${device.deviceNetworkId} 1 6 0 {}"
    setLevel(0)

}

def clearObstruction() {
    def linkText = getLinkText(device)
    log.debug "attempting to clear ${linkText} obstruction"

    sendEvent([
        name: "switch",
        value: "clearing",
        descriptionText: "${linkText} is clearing obstruction"
    ])

    // send a move command to ensure level attribute gets reset for old, buggy firmware
    // then send a reset to factory defaults
    // finally re-configure to ensure reports and binding is still properly set after the rtfd
    [
        makeLevelCommand(device.currentValue("level")), "delay 500",
        "st cmd 0x${device.deviceNetworkId} 1 0 0 {}", "delay 5000"
    ] + configure()
}

def setLevel(value) {
    log.debug "setting level: ${value}"
    def linkText = getLinkText(device)

    // only change the level if the vent is not obstructed
    def currentState = device.currentValue("switch")

    if (currentState == "obstructed") {
        log.error("cannot set level because ${linkText} is obstructed")
        return
    }

    sendEvent(name: "level", value: value)
    if (value > 0) {
        sendEvent(name: "switch", value: "on", descriptionText: "${linkText} is on by setting a level")
        sendEvent(name: "contact", value: "open")
        sendEvent(name: "door", value: "open")  
    }
    else {
        sendEvent(name: "switch", value: "off", descriptionText: "${linkText} is off by setting level to 0")
        sendEvent(name: "contact", value: "closed")
        sendEvent(name: "door", value: "closed")
}

    makeLevelCommand(value)
}

def getOnOff() {
    log.debug "getOnOff()"

    // disallow on/off updates while vent is obstructed
    if (device.currentValue("switch") == "obstructed") {
        log.error("cannot update open/close status because ${getLinkText(device)} is obstructed")
        return []
    }

    ["st rattr 0x${device.deviceNetworkId} 1 0x0006 0"]
}


 // lgk open close


def open()
{
    on()
}

def close()
{
    off()
}


def getPressure() {
    log.debug "getPressure()"

    // using a Keen Home specific attribute in the pressure measurement cluster
    [
        "zcl mfg-code 0x115B", "delay 200",
        "zcl global read 0x0403 0x20", "delay 200",
        "send 0x${device.deviceNetworkId} 1 1", "delay 200"
    ]
}

def getLevel() {
    log.debug "getLevel()"

    // disallow level updates while vent is obstructed
    if (device.currentValue("switch") == "obstructed") {
        log.error("cannot update level status because ${getLinkText(device)} is obstructed")
        return []
    }

    ["st rattr 0x${device.deviceNetworkId} 1 0x0008 0x0000"]
}

def getTemperature() {
    log.debug "getTemperature()"

    ["st rattr 0x${device.deviceNetworkId} 1 0x0402 0"]
}

def getBattery() {
    log.debug "getBattery()"

    ["st rattr 0x${device.deviceNetworkId} 1 0x0001 0x0021"]
}

def setZigBeeIdTile() {
    log.debug "setZigBeeIdTile() - ${device.zigbeeId}"

    def linkText = getLinkText(device)

    sendEvent([
        name: "zigbeeId",
        value: device.zigbeeId,
        descriptionText: "${linkText} has zigbeeId ${device.zigbeeId}" ])
   return [
       name: "zigbeeId",
       value: device.zigbeeId,
       descriptionText: "${linkText} has zigbeeId ${device.zigbeeId}" ]
     }

def refresh() {
getOnOff() +
    getLevel() +
    getTemperature() +
    getPressure() +
    getBattery()
}

def configure() {
    log.debug "CONFIGURE"

    // get ZigBee ID by hidden tile because that's the only way we can do it
    setZigBeeIdTile()

    // def configCmds = [
        // bind reporting clusters to hub
       // "zdo bind 0x${device.deviceNetworkId} 1 1 0x0006 {${device.zigbeeId}} {}", "delay 500",
       // "zdo bind 0x${device.deviceNetworkId} 1 1 0x0008 {${device.zigbeeId}} {}", "delay 500",
       // "zdo bind 0x${device.deviceNetworkId} 1 1 0x0402 {${device.zigbeeId}} {}", "delay 500",
       // "zdo bind 0x${device.deviceNetworkId} 1 1 0x0403 {${device.zigbeeId}} {}", "delay 500",
       // "zdo bind 0x${device.deviceNetworkId} 1 1 0x0001 {${device.zigbeeId}} {}", "delay 500"

    def configCmds = [
        // configure report commands
        // [cluster] [attr] [type] [min-interval] [max-interval] [min-change]

        // mike 2015/06/22: preconfigured; see tech spec
        // vent on/off state - type: boolean, change: 1
        // "zcl global send-me-a-report 6 0 0x10 5 60 {01}", "delay 200",
        // "send 0x${device.deviceNetworkId} 1 1", "delay 1500",

        // mike 2015/06/22: preconfigured; see tech spec
        // vent level - type: int8u, change: 1
        // "zcl global send-me-a-report 8 0 0x20 5 60 {01}", "delay 200",
        // "send 0x${device.deviceNetworkId} 1 1", "delay 1500",

        // Yves Racine 2015/09/10: temp and pressure reports are preconfigured, but
        //   we'd like to override their settings for our own purposes
        // temperature - type: int16s, change: 0xA = 10 = 0.1C, 0x32=50=0.5C
        "zcl global send-me-a-report 0x0402 0 0x29 300 600 {3200}", "delay 200",
        "send 0x${device.deviceNetworkId} 1 1", "delay 1500",

        // Yves Racine 2015/09/10: use new custom pressure attribute
        // pressure - type: int32u, change: 1 = 0.1Pa, 500=50 PA
        "zcl mfg-code 0x115B", "delay 200",
        "zcl global send-me-a-report 0x0403 0x20 0x22 300 600 {01F400}", "delay 200",
        "send 0x${device.deviceNetworkId} 1 1", "delay 1500",

        // mike 2015/06/2: preconfigured; see tech spec
        // battery - type: int8u, change: 1
        // "zcl global send-me-a-report 1 0x21 0x20 60 3600 {01}", "delay 200",
        // "send 0x${device.deviceNetworkId} 1 1", "delay 1500",

        // binding commands
        "zdo bind 0x${device.deviceNetworkId} 1 1 0x0006 {${device.zigbeeId}} {}", "delay 500",
        "zdo bind 0x${device.deviceNetworkId} 1 1 0x0008 {${device.zigbeeId}} {}", "delay 500",
        "zdo bind 0x${device.deviceNetworkId} 1 1 0x0402 {${device.zigbeeId}} {}", "delay 500",
        "zdo bind 0x${device.deviceNetworkId} 1 1 0x0403 {${device.zigbeeId}} {}", "delay 500",
        "zdo bind 0x${device.deviceNetworkId} 1 1 0x0001 {${device.zigbeeId}} {}", "delay 500"
    ]

    return configCmds + refresh()
}

I only have a single keen vent but using the stock driver, I'm also getting 6 mo or so out of a set of batteries. I'm not seeing the battery drain issue some of the other users are seeing.

My vent was purchased 2 years or so ago.

Iā€™m not seeing any battery drain and have owned them since 2017, but migrated to HE 2 years ago. This past year I switched over the Ecobee Suite Manager which controls my vents and itā€™s been working very well.

1 Like

Start by averaging all of your temp sensors in your house. Use this virtual device to turn on/off your HVAC.

Use the same averaging app for each separate room in your house, which can be monitored like this.

You can "one-time manually" (I know dirty word around here) set the vents to increase/decrease each room to desired "comfort" once it's set leave it and forget.

Once you have the comfort defined you can then operate your thermostat on/off based upon the whole house temperature, at which you will see your hvac will actually run longer duration to achieve 1 degree "whole house" difference then 1 degree using a single thermostat operating point (or even an ecobee average with a tiny few sensors added.

My A/C kicks on when the whole "house average temp" reaches 73.50 degrees and turns off 0.75 degrees less at 72.75 "unless" the humidity in the house is above 47% then it won't shut off until 72.00 degrees. By running it down to 72 I've found that is the point humidity will generally stay below the 47% range which to me is the optimal comfort.


You can do this all on the hub via rule machine, I just use nodered as I find it easier to "tweek" if need be. By your "one time manually" adjusted vents your desired comfort should stay there season long as that rooms temp sensors should increase/decrease faster or slower based upon how much they are open, and thereby increase/decrease the "whole house average temp" overall at the desired preference.

As long as your not averaging 15 sensors in one room and only 2 in the other.

1 Like

I can believe that this is effective in some houses (apparently in yours). However, in many houses, there are particular rooms that are much more sensitive to solar gain than other rooms. Those rooms will tend to be too warm on sunny days and too cold at night unless there is some type of zoned control. Also, occasionally used things like fireplaces will impact some rooms more than others.

1 Like