Got my Condensation Prediction App working!

Hi so I have been trying for weeks to get my Condensation Prediction App working with no luck. (I have minimum to no coding experience, but I can follow along somewhat. I have assembled a mash of code from all over the place but I cant get it working.)

I am sure someone will be able to look at it and suggest fixes in 10 seconds!

Issues...

  1. I dont think the device can see the setting in the app, leading to "java.lang.NullPointerException: Cannot get property 'outsideTempDevice' on null object on line 37 (method pollSensors)" error
  2. The goal is to be able to show the prediction of the device in the dashboard.

Anyone fancy helping me out?

---Here is the device code----

definition(
name: "Condensation Prediction App",
namespace: "KHR1", // Replace with your desired namespace
author: "Your Name", // Replace with your name
description: "Predicts condensation based on temperature and humidity",
category: "Convenience",
iconUrl: "",
iconX2Url: ""
)

preferences {
section("Sensors") {
input "outsideTempDevice","capability.temperatureMeasurement", title: "Outside Temperature Sensor", multiple: false, required: true
input "insideTempDevice", "capability.temperatureMeasurement", title: "Inside Temperature Sensor", multiple: false, required: true
input "insideHumidityDevice", "capability.relativeHumidityMeasurement", title: "Inside Humidity Sensor", multiple: false, required: true
}
}
def installed() {
initialize()
}

def updated() {
unsubscribe()
initialize()
}

def initialize() {
// Create or update the virtual device
createOrAdjustVirtualDevice()
}

def createOrAdjustVirtualDevice() {
def deviceName = "VirtualCondensationPrediction2"
def deviceNamespace = "KHR1"
def deviceNetworkId = "d5dafb13"

def existingDevice = getChildDevices().find { device.deviceNetworkId == deviceNetworkId }
if (existingDevice) {
    existingDevice.deviceNetworkId = deviceNetworkId
    existingDevice.updateDataValue("outsideTempDevice", deviceSettings.outsideTempDevice)
    existingDevice.updateDataValue("insideTempDevice", deviceSettings.insideTempDevice)
    existingDevice.updateDataValue("insideHumidityDevice", deviceSettings.insideHumidityDevice)
} else {
    addChildDevice(deviceNamespace, deviceName, deviceNetworkId)
    sendEvent(name: "outsideTempDevice", value: deviceSettings.outsideTempDevice)
    sendEvent(name: "insideTempDevice", value: deviceSettings.insideTempDevice)
    sendEvent(name: "insideHumidityDevice", value: deviceSettings.insideHumidityDevice)
}

}

---Here is the device code-----
metadata {
definition (name: "VirtualCondensationPrediction2", namespace: "KHR1", author: "Your Name"){
capability "Sensor"
command "setcondensationPrediction", ["STRING"]
attribute "condensationPrediction", "String"
command "setoutsideTemp", ["NUMBER"]
attribute "outsideTemp", "Number"
command "setinsideTemp", ["NUMBER"]
attribute "insideTemp", "Number"
command "setinsideHumidity", ["NUMBER"]
attribute "insideHumidity", "Number"
}
}

def installed() {
initialize()
}

def updated() {
unsubscribe()
initialize()
}

def initialize() {
// Assuming the deviceNetworkId is set correctly in the app
def deviceNetworkId = "d5dafb13"

// Fetch device settings from the app using deviceNetworkId
def outsideTempDevice = getChildDevice(deviceNetworkId).getDataValue("outsideTempDevice")
def insideTempDevice = getChildDevice(deviceNetworkId).getDataValue("insideTempDevice")
def insideHumidityDevice = getChildDevice(deviceNetworkId).getDataValue("insideHumidityDevice")

runEvery15Minutes(pollSensors)

}

def pollSensors() {
def outsideTemp = getTemperature(deviceSettings.outsideTempDevice)
def insideTemp = getTemperature(deviceSettings.insideTempDevice)
def insideHumidity = getHumidity(deviceSettings.insideHumidityDevice)

def prediction = predictCondensation(outsideTemp, insideTemp, insideHumidity)

setAttribute("outsideTemp", outsideTemp)
setAttribute("insideTemp", insideTemp)
setAttribute("insideHumidity", insideHumidity)
setAttribute("condensationPrediction", prediction)

}

def calculateDewPoint(temperature, relativeHumidity) {
def a = 17.625
def b = 243.04
def alpha = (a * temperature) / (b + temperature) + Math.log10(relativeHumidity / 100)
return (b * alpha) / (a - alpha)
}

def predictCondensation(outsideTemp, insideTemp, insideHumidity) {
def windowSurfaceTemp = (insideTemp + outsideTemp) / 2
def dewPoint = calculateDewPoint(insideTemp, insideHumidity)

if (windowSurfaceTemp <= dewPoint) {
    return "Condensation is likely to occur."
} else {
    return "Condensation is unlikely."
}

}

// Helper methods to get sensor values using capability identifiers
def getTemperature(device) {
return device.currentValue("temperature")
}

def getHumidity(device) {
return device.currentValue("humidity")
}

thanks in advance!

got it working perfectly now with the help of OPENAI. Been a god send

Here is the app

definition(
    name: "Condensation Prediction App",
    namespace: "KHR1",
    author: "Your Name",
    description: "Predicts condensation based on temperature and humidity",
    category: "Convenience",
    iconUrl: "",
    iconX2Url: ""
)

preferences {
    section("Sensors") {
        input "outsideTempDevice", "capability.temperatureMeasurement", title: "Outside Temperature Sensor", required: true
        input "insideTempDevice", "capability.temperatureMeasurement", title: "Inside Temperature Sensor", required: true
        input "insideHumidityDevice", "capability.relativeHumidityMeasurement", title: "Inside Humidity Sensor", required: true
    }
}

def installed() {
    initialize()
}

def updated() {
    unsubscribe()
    initialize()
}

def initialize() {
    createOrUpdateVirtualDevice()
    subscribe(outsideTempDevice, "temperature", sensorUpdateHandler)
    subscribe(insideTempDevice, "temperature", sensorUpdateHandler)
    subscribe(insideHumidityDevice, "humidity", sensorUpdateHandler)
}

def createOrUpdateVirtualDevice() {
    def childDevice = getChildDevice("VirtualCondensationPrediction2")
    if (!childDevice) {
        addChildDevice("KHR1", "VirtualCondensationPrediction2", "VirtualCondensationPrediction2", [label: "Condensation Prediction Device", isComponent: false])
    }
}

def sensorUpdateHandler(evt) {
    def childDevice = getChildDevice("VirtualCondensationPrediction2")
    if (childDevice) {
        childDevice.pollSensors(outsideTempDevice.currentTemperature, insideTempDevice.currentTemperature, insideHumidityDevice.currentHumidity)
    }
}

and here is the device

metadata {
    definition(name: "VirtualCondensationPrediction2", namespace: "KHR1", author: "Your Name") {
        capability "Sensor"
        attribute "outsideTemp", "number"
        attribute "insideTemp", "number"
        attribute "insideHumidity", "number"
        attribute "condensationPrediction", "string"
        command "pollSensors", ["number", "number", "number"]
    }
}

def installed() {
    initialize()
}

def updated() {
    initialize()
}

def initialize() {
    // Initialize attributes with default values
    sendEvent(name: "outsideTemp", value: 0)
    sendEvent(name: "insideTemp", value: 0)
    sendEvent(name: "insideHumidity", value: 0)
    sendEvent(name: "condensationPrediction", value: "Unknown")

    // Schedule polling every 15 minutes
    unschedule() // Clear any existing schedules
    runEvery15Minutes(fetchSensorData)

    log.debug "Device initialized and scheduled for polling."
}

def fetchSensorData() {
    log.debug "Starting fetchSensorData"

    // Get the associated app's parent sensors
    def outsideTempDevice = getParent()?.outsideTempDevice
    def insideTempDevice = getParent()?.insideTempDevice
    def insideHumidityDevice = getParent()?.insideHumidityDevice

    if (outsideTempDevice && insideTempDevice && insideHumidityDevice) {
        def outsideTemp = outsideTempDevice.currentTemperature
        def insideTemp = insideTempDevice.currentTemperature
        def insideHumidity = insideHumidityDevice.currentHumidity

        log.debug "Fetched Sensor Data: Outside Temp: ${outsideTemp}, Inside Temp: ${insideTemp}, Inside Humidity: ${insideHumidity}"

        if (outsideTemp == null || insideTemp == null || insideHumidity == null) {
            log.warn "Incomplete sensor data. Skipping prediction update."
            return
        }

        pollSensors(outsideTemp, insideTemp, insideHumidity)
    } else {
        log.error "Unable to fetch sensor data: Sensors not properly linked."
    }
}

def pollSensors(outsideTemp, insideTemp, insideHumidity) {
    log.debug "pollSensors called with Outside Temp: ${outsideTemp}, Inside Temp: ${insideTemp}, Inside Humidity: ${insideHumidity}"

    // Validate data types
    if (!(outsideTemp instanceof Number) || !(insideTemp instanceof Number) || !(insideHumidity instanceof Number)) {
        log.error "Invalid data types: Outside Temp: ${outsideTemp}, Inside Temp: ${insideTemp}, Inside Humidity: ${insideHumidity}"
        return
    }

    // Update attributes
    sendEvent(name: "outsideTemp", value: outsideTemp, isStateChange: true)
    sendEvent(name: "insideTemp", value: insideTemp, isStateChange: true)
    sendEvent(name: "insideHumidity", value: insideHumidity, isStateChange: true)

    // Calculate prediction
    def prediction = predictCondensation(outsideTemp, insideTemp, insideHumidity)
    log.debug "Condensation Prediction: ${prediction}"
    sendEvent(name: "condensationPrediction", value: prediction, isStateChange: true)
}

def predictCondensation(outsideTemp, insideTemp, insideHumidity) {
    log.debug "predictCondensation called with Outside Temp: ${outsideTemp}, Inside Temp: ${insideTemp}, Inside Humidity: ${insideHumidity}"

    if (outsideTemp == null || insideTemp == null || insideHumidity == null) {
        log.warn "Invalid input data for prediction. Returning 'Unknown'."
        return "Unknown"
    }

    def windowSurfaceTemp = (insideTemp + outsideTemp) / 2
    def dewPoint = calculateDewPoint(insideTemp, insideHumidity)

    log.debug "Calculated Window Surface Temp: ${windowSurfaceTemp}, Dew Point: ${dewPoint}"

    if (windowSurfaceTemp <= dewPoint) {
        return "Condensation is likely to occur."
    } else {
        return "Condensation is unlikely."
    }
}

def calculateDewPoint(temperature, relativeHumidity) {
    log.debug "Calculating Dew Point for Temp: ${temperature}, Humidity: ${relativeHumidity}"

    if (relativeHumidity <= 0 || relativeHumidity > 100) {
        log.error "Invalid relative humidity: ${relativeHumidity}. Must be between 0 and 100."
        return null
    }

    def a = 17.625
    def b = 243.04
    def alpha = (a * temperature) / (b + temperature) + Math.log(relativeHumidity / 100)

    def dewPoint = (b * alpha) / (a - alpha)
    log.debug "Dew Point Calculated: ${dewPoint}"
    return dewPoint
}

1 Like

This looks cool. Can you elaborate a bit more on how you are using this? Would you put it on HPM?

+1

I do something similar and an curious how you're approaching the problem.

Thanks for the questions! I wanted to display on my dashboard whether the windows in one of my rooms are likely to have condensation in the morning. This way, I can proactively use the dehumidifier. If condensation is predicted, I'll turn on the dehumidifier; if not, I'll leave it off. Unfortunately, my dehumidifier can't be linked to Habitat, so I can't fully automate this process. My next step is to remove the calculated dew point and call an API from a weather service for my area.

1 Like

Also just updated the device to add more logging