ChatGPT Wrote a Working Basic Hubitat App

Ok so I just tried again the ChatGPT ability to code Groovy for Hubitat and I'm very impressed with the results, this is getting scary, will we be out of a job/hobby soon?

So I asked him to make a Hubitat app that will turn on a light light at a certain time and turn it off at another time. This is what it spit out with no modifications from my part and on the first try...

definition(
    name: "Scheduled Light Control",
    namespace: "yournamespace",
    author: "Your Name",
    description: "Turns on and off a light at specified times each day",
    category: "Convenience",
    iconUrl: "https://example.com/icon.png", // Optional: Set an icon
    iconX2Url: "https://example.com/icon@2x.png" // Optional: Set a 2x icon
)

preferences {
    section("Select Light") {
        input "light", "capability.switch", title: "Which light?", required: true
    }
    section("Set On Time") {
        input "onTime", "time", title: "At what time do you want to turn on the light?", required: true
    }
    section("Set Off Time") {
        input "offTime", "time", title: "At what time do you want to turn off the light?", required: true
    }
}

def installed() {
    log.debug "Installed with settings: ${settings}"
    scheduleLightActions()
}

def updated() {
    log.debug "Updated with settings: ${settings}"
    unschedule() // Unschedule any previous schedules
    scheduleLightActions() // Re-schedule with updated times
}

def scheduleLightActions() {
    // Scheduling the light to turn on at the specified on time
    schedule(settings.onTime, turnOnLight)

    // Scheduling the light to turn off at the specified off time
    schedule(settings.offTime, turnOffLight)
}

def turnOnLight() {
    log.debug "Turning on the light..."
    light.on()
}

def turnOffLight() {
    log.debug "Turning off the light..."
    light.off()
}

All looked good, so I pasted to Hubitat app code, changed namespace / Author and hit save. Then installed it and tested on a virtual switch and scheduling appeared in the app "cog wheel" page and functioned as expected.

7 Likes

Do you have a copy of the specific text you used as a prompt?

1 Like

Something like I wrote above...

Just tried again and here is the exact word for word I asked...

can you write an app for Hubitat in Groovy for turning on a light at specific time and to turn if off at another time

And here is again what it spit out, some differences from the code above but not much, mostly this time it does not have any comments in the code. Logs are sent as info instead of debug, but it's basically the same result.

definition(
    name: "Scheduled Light Control",
    namespace: "yournamespace",
    author: "Your Name",
    description: "Turns a light on and off at specific times",
    category: "Convenience",
    iconUrl: "",
    iconX2Url: ""
)

preferences {
    section("Select Light to Control") {
        input "light", "capability.switch", title: "Select a light", required: true
    }
    section("Set Schedule") {
        input "onTime", "time", title: "Turn On Time", required: true
        input "offTime", "time", title: "Turn Off Time", required: true
    }
}

def installed() {
    initialize()
}

def updated() {
    unschedule()
    initialize()
}

def initialize() {
    schedule(onTime, turnOnLight)
    schedule(offTime, turnOffLight)
}

def turnOnLight() {
    light.on()
    log.info "Light turned on at ${onTime}"
}

def turnOffLight() {
    light.off()
    log.info "Light turned off at ${offTime}"
}

Try it out, it even explains what some of the code does and tells you how to add it to the apps code section...

1 Like

I've been using it for a little bit to help with little things. I used it to modify a community driver in order to add a transitiontime setting allowing for smoother changes between Philips Hue scenes. I also used it to write a small custom app that automatically modifies this setting when a switch turns on. Finally, I've been testing ChatGPT to help write an Integration app. This has been a little bit hit or miss, but I'm on the free plan. When I'm on the main model, it works well. When I use up my "free" time, things go significantly slower.

Lot's of trial and error since I have zero knowledge of coding, but it has helped fill in little gaps.

4 Likes

Just did the same with Copilot free and got the same result. Amazing!

The other absolutely killer one is to use GitHub Copilot in VS Code for Groovy.

3 Likes

I need to research this, did not know that there was a VSCode extension

1 Like

So this is pulling from what; working code it has seen and learned from? Or a basic understanding of translating a given task into Groovy?

I guess I'm left wondering what/how/or even if, your words "app for Hubitat" in fact change the outcome....and if it does, where the heck is it scraping examples or framework to build that knowledge-base?

Seeking to understand.

It’s :magic_wand:Magic !

2 Likes

I wonder if this is the way that some of the abandoned but still useful apps can be easily updated and maintained.

2 Likes

It probably has access to Hubitat’s GitHub repo, other GitHub repos with Groovy code that pertains to Hubitat, maybe this Discourse community.

Don’t know for sure, those just seem like likely possibilities.

So you'd be exposing/teaching/training on "what changed" in Hubitat-land?

Still waiting to hear "who let's these dogs out and where" for such smarts to be built.

Almost, coders are quickly adapting to using AI to do first draft code to then adapt and edit. Doing a lot of the base grunt work and opening up time to be creative. The coders toolbox just got bigger.

Of course it will change the outcome, how will it know that the Groovy code I want is specific ti Hubitat, Groovy is not an HE only language,

ChatGPT learns just like we do, by looking at example of code and figuring out what each part does, the more examples of code it looks at, the better it will get at writing code. All the code snippets it looks at are publicly available, search the web for hubitat app code groovy and you will get a very large list of code, just in these forums are a bunch of them.

Yeah, OK...but...it would have to discern what works and what doesn't right? What's good and complete vs a half baked test. Did it once train on an app (or component of) that did exactly what you asked or is it building a style & rule guide for HE apps from which it strings together code based on logic it KNOWS would work.

I can't believe this all happens w/o some supervised training.

In other words, is ChatGPT out there randomly scraping all the code it can find and looking at structural attributes to know what platform it is written for and deciding on it's own how to categorize what it sees into a knowledge base of thousands of different platforms/systems? Please tell me there is more deliberate training involved.

That's why it's called AI training, it does exactly like humans do, it trains by looking at the world it can see and looks for repetition to understand what it sees, when looking at Groovy and seeing the work Hubitat al around, it will just put things together and understand that this Groovy code is for Hubitat. What else can I say. If you don't believe it, well, don't.

1 Like

Just going to end on this note. Back before this was all called "AI" it was called what it is rooted in, Neural Networks. The early problems that this mathematics was unleashed on benefited from training datasets were not random walks through the Internet (which barely existed).

Still today, for highly exacting applications, training (albeit strengthened by outliers) is an important path to reliable outcomes. Instead of ChatGPT worming through code examples of varying quality some supervised training in the right HE space could result in a significant boost in quality outcomes.

Having first been introduced to Neural Networks three decades ago I am indeed in amazement how well the random walk resolves to a product that is sooo readily usable.

So yeah, take that "I can't believe" as colloquialism and no, I didn't write this with the assistance of ChatGPT.

:rofl: well placed :wink:

2 Likes

Spot on. My understanding of GitHub Copilot and others from Claude AI etc. is that they specialize the 'training' on the LLM with higher quality inputs (code samples) to get a better result to you point, instead of scraping bad code or code that doesn't work well.

1 Like

Okay, I've decided to give ChatGPT a go as well.

I love Room Lighting, but there is an issue that has bugged me, maybe I just don't know how to use Room Lighting properly for this feature, where the "light levels" are not updating correctly for modes that have the "activation" option unticked for certain modes, all the lights are activated at a previous mode level.

Anyway, I've played around with ChatGPT and after some trial and error, I've got the code below.

  • activation based on motion sensors or manual switch
  • lights off after specified motion delay for both cases
  • Modes can be selected for manual activation, so not activated by motion
  • And, I believe it keeps all light levels as specified for all modes.
    image

Will test for the next few days and see how well it works for my use case!

definition( 
    name: "Mode-Based Lighting",
    namespace: "iEnam-ChatGPT",
    author: "iEnam-ChatGPT",
    description: "Lighting control with manual activation override for specific modes.",
    category: "Convenience",
    iconUrl: "",
    iconX2Url: ""
)

preferences {
    page(name: "mainPage", title: "Configure Mode-Based Lighting", install: true, uninstall: true) {
        section("App Name") {
            input "appName", "text", title: "Enter a custom app name", required: true
        }
        section("Select Lights") {
            input "lights", "capability.switchLevel", title: "Select lights to control", multiple: true, required: true
        }
        section("Motion Sensors (optional)") {
            input "motionSensors", "capability.motionSensor", title: "Select motion sensors to activate lights", multiple: true, required: false
        }
        section("Lighting Levels by Mode") {
            location.modes.each { mode ->
                input "level_${mode}", "number", title: "Light level for ${mode} mode (0-100)", required: false, range: "0..100"
            }
        }
        section("Manual Activation Modes") {
            input "manualModes", "mode", title: "Select modes for manual activation", multiple: true, required: false
        }
        section("Options") {
            input "turnOffDelay", "number", title: "Turn off delay (in seconds) after motion stops", required: false, defaultValue: 300
        }
    }
}

def installed() {
    initialize()
}

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

def initialize() {
    state.lightActive = false // Initialize light active status
    updateAppLabel() // Update app label with the initial status

    if (motionSensors) {
        subscribe(motionSensors, "motion.active", motionActiveHandler)
        subscribe(motionSensors, "motion.inactive", motionInactiveHandler)
    }
    subscribe(lights, "switch.on", manualLightHandler)
    subscribe(lights, "switch.on", lightOnHandler) // Monitor lights turned on externally
    subscribe(location, "mode", modeChangeHandler)
}

def modeChangeHandler(evt) {
    log.debug "Mode changed to ${evt.value}"
    adjustLightsForMode()
}

def motionActiveHandler(evt) {
    def currentMode = location.mode
    log.debug "Motion detected on ${evt.device.displayName} in mode: ${currentMode}"

    if (manualModes?.contains(currentMode)) {
        log.debug "Mode '${currentMode}' is set for manual activation. Skipping motion-triggered light activation."
        return
    }

    activateLightsForMode()
}

def motionInactiveHandler(evt) {
    log.debug "Motion stopped on ${evt.device.displayName}"
    
    // Check if all motion sensors are inactive
    def allInactive = motionSensors?.every { sensor -> sensor.currentMotion == "inactive" }
    
    if (allInactive) {
        log.debug "All motion sensors are inactive. Starting turn-off delay."
        if (turnOffDelay) {
            runIn(turnOffDelay, turnOffLights) // Delay in seconds
        } else {
            turnOffLights()
        }
    } else {
        log.debug "Some motion sensors are still active. Turn-off delay not started."
    }
}

def manualLightHandler(evt) {
    def currentMode = location.mode
    log.debug "Light manually turned on: ${evt.device.displayName}"

    def lightLevel = settings["level_${currentMode}"]
    if (lightLevel) {
        log.debug "Adjusting manual light to level ${lightLevel} for mode ${currentMode}"
        evt.device.setLevel(lightLevel.toInteger())
        state.lightActive = true
        updateAppLabel() // Update the app label when light status changes
    }
}

def lightOnHandler(evt) {
    // Handle when a light is turned on manually or externally
    def currentMode = location.mode
    log.debug "Light turned on externally: ${evt.device.displayName}"

    def lightLevel = settings["level_${currentMode}"]
    if (lightLevel) {
        log.debug "Adjusting light to level ${lightLevel} for mode ${currentMode}"
        evt.device.setLevel(lightLevel.toInteger())
    }

    // When a light is turned on externally, check motion sensors' state
    checkMotionAndDelayTurnOff(evt.device)
}

def checkMotionAndDelayTurnOff(light) {
    // Check if all motion sensors are inactive
    def allInactive = motionSensors?.every { sensor -> sensor.currentMotion == "inactive" }

    if (allInactive) {
        log.debug "No motion detected. Scheduling light off after delay."
        if (turnOffDelay) {
            runIn(turnOffDelay, turnOffLights) // Delay in seconds
        } else {
            turnOffLights()
        }
    } else {
        log.debug "Motion detected, not turning off light."
    }
}

def adjustLightsForMode() {
    def currentMode = location.mode
    def lightLevel = settings["level_${currentMode}"]

    if (lightLevel) {
        lights.each { light ->
            if (light.currentValue("switch") == "on") { // Adjust only if the light is already on
                log.debug "Adjusting ${light.displayName} to level ${lightLevel} for mode ${currentMode}"
                light.setLevel(lightLevel.toInteger())
            } else {
                log.debug "${light.displayName} is off. Skipping adjustment."
            }
        }
    }
}

def activateLightsForMode() {
    def currentMode = location.mode
    def lightLevel = settings["level_${currentMode}"]

    if (lightLevel) {
        lights.each { light ->
            log.debug "Activating ${light.displayName} to level ${lightLevel} for mode ${currentMode}"
            light.setLevel(lightLevel.toInteger())
            light.on()
        }
        state.lightActive = true
    }
    updateAppLabel() // Update the app label when light status changes
}

def turnOffLights() {
    log.debug "Turning off lights"
    lights.each { light -> 
        light.off() 
    }
    state.lightActive = false
    updateAppLabel() // Update the app label when light status changes
    log.debug "Light status set to: INACTIVE"
}

def updateAppLabel() {
    def status = state.lightActive ? "ACTIVE" : "INACTIVE"
    def statusColor = state.lightActive ? "#008000" : "#808080" // Green for active, grey for inactive
    def displayName = settings.appName ?: "Mode-Based Lighting" // Use custom name or fallback to default
    app.updateLabel("<font color='${statusColor}'>${displayName} [${status}]</font>")
}

// --- BUTTON ACTIONS ---

def activeLights() {
    log.debug "Manually setting lights to active mode"
    activateLightsForMode()
}

def turnOff() {
    log.debug "Manually turning off lights"
    turnOffLights()
}

def updateSettings() {
    log.debug "Manually updating settings and adjusting lights"
    adjustLightsForMode()
}

// Button Methods
def mainPage() {
    dynamicPage(name: "mainPage", title: "Control Mode-Based Lighting", install: true, uninstall: true) {
        section("Controls") {
            paragraph "Use the buttons below to control lights manually:"
            input name: "activeButton", type: "button", title: "Active"
            input name: "turnOffButton", type: "button", title: "Turn Off"
            input name: "updateButton", type: "button", title: "Update"
        }
    }
}