Are there any examples or documentation of using DYNAMIC_ENUM

You meant in the getPrograms method? If so I still get the same error

EDIT:
I tried in the section and got the same error.

comment out the preference input for now, save the driver, write the attribute value from the app, or add a default value from the installed method in the driver, then debug from there.

I have the listOfPrograms listed in the "Current States" but the editor doesn't let me save the driver. Any workaround?

Please post the driver code

This is the driver. I've put dummy values just for testing. If you uncomment the line this.currentValue("listOfPrograms") you will not be able to save it.

import groovy.transform.Field
import groovy.json.JsonSlurper

@Field Utils = Utils_create();
@Field List<String> LOG_LEVELS = ["error", "warn", "info", "debug", "trace"]
@Field String DEFAULT_LOG_LEVEL = LOG_LEVELS[1]
@Field static final Integer eventStreamDisconnectGracePeriod = 30
@Field static ArrayList programsList = [
            "Dishcare.Dishwasher.Program.Auto1",
            "Dishcare.Dishwasher.Program.Auto2",
            "Dishcare.Dishwasher.Program.Auto3",
            "Dishcare.Dishwasher.Program.Eco50",
            "Dishcare.Dishwasher.Program.Quick45",
            "Dishcare.Dishwasher.Program.Intensiv70",
            "Dishcare.Dishwasher.Program.Normal65",
            "Dishcare.Dishwasher.Program.Glas40",
            "Dishcare.Dishwasher.Program.GlassCare",
            "Dishcare.Dishwasher.Program.NightWash",
            "Dishcare.Dishwasher.Program.Quick65",
            "Dishcare.Dishwasher.Program.Normal45",
            "Dishcare.Dishwasher.Program.Intensiv45",
            "Dishcare.Dishwasher.Program.AutoHalfLoad",
            "Dishcare.Dishwasher.Program.IntensivPower",
            "Dishcare.Dishwasher.Program.MagicDaily",
            "Dishcare.Dishwasher.Program.Super60",
            "Dishcare.Dishwasher.Program.Kurz60",
            "Dishcare.Dishwasher.Program.ExpressSparkle65",
            "Dishcare.Dishwasher.Program.MachineCare",
            "Dishcare.Dishwasher.Program.SteamFresh",
            "Dishcare.Dishwasher.Program.MaximumCleaning"
        ]

metadata {
    definition(name: "Home Connect Dishwasher", namespace: "wattos.homeconnect", author: "wattos@gmail.com") {
        capability "Sensor"
        capability "Initialize"
        capability "Switch"
        
        //command "setProgram", [[name:"Program*", "type":"ENUM", "description":"Program to set", "constraints":getPrograms()]]

        //attribute "Programs",  "string"
        command "deviceLog", [[name: "Level*", type:"STRING", description: "Level of the message"], 
                              [name: "Message*", type:"STRING", description: "Message"]] 
        command "connectEventStream"
        command "disconnectEventStream"
        command "reset"

        attribute "listOfPrograms", "enum"

        // BSH.Common.Status.RemoteControlActive
        // This status indicates whether the allowance for remote controlling is enabled.
        attribute "RemoteControlActive", "enum", ["on", "off"]

        // BSH.Common.Status.RemoteControlStartAllowed
        // This status indicates whether the remote program start is enabled. 
        // This can happen due to a programmatic change (only disabling), 
        // or manually by the user changing the flag locally on the home appliance, 
        // or automatically after a certain duration - usually 24 hours.
        attribute "RemoteControlStartAllowed", "enum", ["on", "off"]

        // BSH.Common.Status.OperationState
        // This status describes the operation state of the home appliance. 
        attribute "OperationState", "enum", [
            // Key: BSH.Common.EnumType.OperationState.Inactive
            // Description: Home appliance is inactive. It could be switched off or in standby.
            "Inactive",

            // Key: BSH.Common.EnumType.OperationState.Ready
            // Description: Home appliance is switched on. No program is active.
            "Ready",

            // Key: BSH.Common.EnumType.OperationState.DelayedStart
            // Description: A program has been activated but has not started yet.
            "DelayedStart",

            // Key: BSH.Common.EnumType.OperationState.Run
            // Description: A program is currently active.
            "Run",

            // Key: BSH.Common.EnumType.OperationState.Pause
            // Description: The active program has been paused.
            "Pause",

            // Key: BSH.Common.EnumType.OperationState.ActionRequired
            // Description: The active program requires a user interaction.
            "ActionRequired",

            // Key: BSH.Common.EnumType.OperationState.Finished
            // Description: The active program has finished or has been aborted successfully.
            "Finished",

            // Key: BSH.Common.EnumType.OperationState.Error
            // Description: The home appliance is in an error state.
            "Error",

            // Key: BSH.Common.EnumType.OperationState.Aborting
            // Description: The active program is currently aborting.
            "Aborting",
        ]

        // BSH.Common.Status.DoorState
        // This status describes the state of the door of the home appliance. 
        // A change of that status is either triggered by the user operating 
        // the home appliance locally (i.e. opening/closing door) or 
        // automatically by the home appliance (i.e. locking the door).
        //
        // Please note that the door state of coffee machines is currently 
        // only available for American coffee machines. 
        // All other coffee machines will be supported soon.
        attribute "DoorState", "enum", [
            //  Key: BSH.Common.EnumType.DoorState.Open
            // Description: The door of the home appliance is open.
            "Open",

            // Key: BSH.Common.EnumType.DoorState.Closed
            // Description: The door of the home appliance is closed but not locked.
            "Closed",

            //  Key: BSH.Common.EnumType.DoorState.Locked
            // Description: The door of the home appliance is locked.
            "Locked",
        ]

        attribute "ActiveProgram", "string"

        attribute "PowerState", "enum", [
            // Key: BSH.Common.EnumType.PowerState.Off
            // Description: The home appliance switched to off state but can 
            // be switched on by writing the value BSH.Common.EnumType.PowerState.
            // On to this setting.
            "Off",

            // Key: BSH.Common.EnumType.PowerState.On
            // Description: The home appliance switched to on state. 
            // You can switch it off by writing the value BSH.Common.EnumType.PowerState.Off 
            // or BSH.Common.EnumType.PowerState.Standby depending on what is supported by the appliance.
            "On",

            //  Key: BSH.Common.EnumType.PowerState.Standby
            // Description: The home appliance went to standby mode.
            // You can switch it on or off by changing the value of this setting appropriately.
            "Standby"
        ]

        attribute "EventPresentState", "enum", [
            // Key: BSH.Common.EnumType.EventPresentState.Present
            // Description: The event occurred and is present.
            "Event active",

            // Key: BSH.Common.EnumType.EventPresentState.Off
            // Description: The event is off.
            "Off",

            //  Key: BSH.Common.EnumType.EventPresentState.Confirmed
            // Description: The event has been confirmed by the user.
            "Confirmed"
        ]

        attribute "EventStreamStatus", "enum", ["connected", "disconnected"]
    }
    
    preferences {
        section { // General
            //if (this.currentValue("listOfPrograms") != null) {
                input name:"selectedProgram", type:"enum", title: "select program", options:getPrograms()
            //}
            input name: "logLevel", title: "Log Level", type: "enum", options: LOG_LEVELS, defaultValue: DEFAULT_LOG_LEVEL, required: false
        }
    }
}

void setProgram(program) {
    Utils.toLogger("info", "setProgram() ${program}")

    def haId = device.deviceNetworkId
    parent.getHomeConnectAPI().getAvailablePrograms(haId) { activeProgram ->
        Utils.toLogger("info", "getAvailablePrograms received: ${activeProgram}")
    }
}

void initialize() {
    Utils.toLogger("info", "initialize()")
    intializeStatus();
    //runEvery1Minute("intializeStatus")
}

void installed() {
    Utils.toLogger("info", "installed()")
    sendEvent(name:"listOfPrograms", value: ["1", "2", "3"])
    intializeStatus();
}

void updated() {
    Utils.toLogger("info", "updated()")
}

void uninstalled() {
    disconnectEventStream()
}

void reset() {
    Utils.toLogger("debug", "reset")
    unschedule()
    sendEvent(name: "EventStreamStatus", value: "disconnected", displayed: true, isStateChange: true)
    disconnectEventStream()
}

def getPrograms() {
    //return parseJson(device.currentValue("listOfPrograms"))
    //Utils.toLogger("info", "getPrograms ${currentValue("listOfPrograms")}")
    //if (this.currentValue("listOfPrograms") == null) {return ["1"]}
    //return parseJson(this.currentValue("listOfPrograms"))
    return ["A", "B"]
}

def on() {
    parent.setPowertate(device, true)
}

def off() {
    parent.setPowertate(device, false)
}

void intializeStatus() {
    Utils.toLogger("info", "Initializing the status of the device")

    parent.intializeStatus(device)
    
    try {
        disconnectEventStream()
        connectEventStream()
    } catch (Exception e) {
        Utils.toLogger("error", "intializeStatus() failed: ${e.message}")
        setEventStreamStatusToDisconnected()
    }
}

void connectEventStream() {
    Utils.toLogger("debug", "connectEventStream()")
    def haId = device.deviceNetworkId
    parent.getHomeConnectAPI().connectDeviceEvents(haId, interfaces);
}

void reconnectEventStream(Boolean notIfAlreadyConnected = true) {
    Utils.toLogger("debug", "reconnectEventStream(notIfAlreadyConnected=$notIfAlreadyConnected)")
    
    if (device.currentValue("EventStreamStatus") == "connected" && notIfAlreadyConnected) {
        Utils.toLogger("debug", "already connected; skipping reconnection")
    } else {
        //disconnectEventStream()
        connectEventStream()
    }
}

void disconnectEventStream() {
    Utils.toLogger("debug", "disconnectEventStream()")
    def haId = device.deviceNetworkId
    parent.getHomeConnectAPI().disconnectDeviceEvents(haId, interfaces);
}

void setEventStreamStatusToConnected() {
    Utils.toLogger("debug", "setEventStreamStatusToConnected()")
    unschedule("setEventStreamStatusToDisconnected")
    if (device.currentValue("EventStreamStatus") == "disconnected") { 
        sendEvent(name: "EventStreamStatus", value: "connected", displayed: true, isStateChange: true)
    }
    state.connectionRetryTime = 15
}

void setEventStreamStatusToDisconnected() {
    Utils.toLogger("debug", "setEventStreamStatusToDisconnected()")
    sendEvent(name: "EventStreamStatus", value: "disconnected", displayed: true, isStateChange: true)
    if (state.connectionRetryTime) {
       state.connectionRetryTime *= 2
       if (state.connectionRetryTime > 900) {
          state.connectionRetryTime = 900 // cap retry time at 15 minutes
       }
    } else {
       state.connectionRetryTime = 15
    }
    Utils.toLogger("debug", "reconnecting EventStream in ${state.connectionRetryTime} seconds")
    runIn(state.connectionRetryTime, "reconnectEventStream")
}

void eventStreamStatus(String text) {
    Utils.toLogger("debug", "Received eventstream status message: ${text}")
    def (String type, String message) = text.split(':', 2)
    switch (type) {    
        case 'START':
            setEventStreamStatusToConnected()
            //Utils.toLogger("info", "Event Stream connected")
            break
        
        case 'STOP':
            Utils.toLogger("debug", "eventStreamDisconnectGracePeriod: ${eventStreamDisconnectGracePeriod}")
            runIn(eventStreamDisconnectGracePeriod, "setEventStreamStatusToDisconnected")
            //Utils.toLogger("info", "Event Stream disconnected")
            break

        default:
            Utils.toLogger("error", "Received unhandled Event Stream status message: ${text}")
            runIn(eventStreamDisconnectGracePeriod, "setEventStreamStatusToDisconnected")
            break
    }
}

void parse(String text) {
    Utils.toLogger("debug", "Received eventstream message: ${text}")  
    parent.processMessage(device, text)
}

def deviceLog(level, msg) {
    Utils.toLogger(level, msg)
}

/**
 * Simple utilities for manipulation
 */

def Utils_create() {
    def instance = [:];
    
    instance.toLogger = { level, msg ->
        if (level && msg) {
            Integer levelIdx = LOG_LEVELS.indexOf(level);
            Integer setLevelIdx = LOG_LEVELS.indexOf(logLevel);
            if (setLevelIdx < 0) {
                setLevelIdx = LOG_LEVELS.indexOf(DEFAULT_LOG_LEVEL);
            }
            if (levelIdx <= setLevelIdx) {
                log."${level}" "${device.displayName} ${msg}";
            }
        }
    }

    return instance;
}

In the app I do this:

        def homeConnectDevice = state.foundDevices.find({it.haId == homeConnectDeviceId})
        switch(homeConnectDevice.type) {
            case "Dishwasher":
                device = addChildDevice('wattos.homeconnect', 'Home Connect Dishwasher', hubitatDeviceId);
            break
        }
        
        device.sendEvent(name:"listOfPrograms", value: ["1", "2", "3"])

working code below:

import groovy.transform.Field
import groovy.json.JsonSlurper

@Field Utils = Utils_create();
@Field List<String> LOG_LEVELS = ["error", "warn", "info", "debug", "trace"]
@Field String DEFAULT_LOG_LEVEL = LOG_LEVELS[1]
@Field static final Integer eventStreamDisconnectGracePeriod = 30
@Field static ArrayList programsList = [
            "Dishcare.Dishwasher.Program.Auto1",
            "Dishcare.Dishwasher.Program.Auto2",
            "Dishcare.Dishwasher.Program.Auto3",
            "Dishcare.Dishwasher.Program.Eco50",
            "Dishcare.Dishwasher.Program.Quick45",
            "Dishcare.Dishwasher.Program.Intensiv70",
            "Dishcare.Dishwasher.Program.Normal65",
            "Dishcare.Dishwasher.Program.Glas40",
            "Dishcare.Dishwasher.Program.GlassCare",
            "Dishcare.Dishwasher.Program.NightWash",
            "Dishcare.Dishwasher.Program.Quick65",
            "Dishcare.Dishwasher.Program.Normal45",
            "Dishcare.Dishwasher.Program.Intensiv45",
            "Dishcare.Dishwasher.Program.AutoHalfLoad",
            "Dishcare.Dishwasher.Program.IntensivPower",
            "Dishcare.Dishwasher.Program.MagicDaily",
            "Dishcare.Dishwasher.Program.Super60",
            "Dishcare.Dishwasher.Program.Kurz60",
            "Dishcare.Dishwasher.Program.ExpressSparkle65",
            "Dishcare.Dishwasher.Program.MachineCare",
            "Dishcare.Dishwasher.Program.SteamFresh",
            "Dishcare.Dishwasher.Program.MaximumCleaning"
        ]

metadata {
    definition(name: "Home Connect Dishwasher", namespace: "wattos.homeconnect", author: "wattos@gmail.com") {
        capability "Sensor"
        capability "Initialize"
        capability "Switch"
        
        //command "setProgram", [[name:"Program*", "type":"ENUM", "description":"Program to set", "constraints":getPrograms()]]

        //attribute "Programs",  "string"
        command "deviceLog", [[name: "Level*", type:"STRING", description: "Level of the message"], 
                              [name: "Message*", type:"STRING", description: "Message"]] 
        command "connectEventStream"
        command "disconnectEventStream"
        command "reset"

        //added for testing event write
        command "testWriteProgramEvent"
        //change this to JSON_OBJECT as enum is not an attribute data type
        attribute "listOfPrograms", "JSON_OBJECT"

        // BSH.Common.Status.RemoteControlActive
        // This status indicates whether the allowance for remote controlling is enabled.
        attribute "RemoteControlActive", "enum", ["on", "off"]

        // BSH.Common.Status.RemoteControlStartAllowed
        // This status indicates whether the remote program start is enabled. 
        // This can happen due to a programmatic change (only disabling), 
        // or manually by the user changing the flag locally on the home appliance, 
        // or automatically after a certain duration - usually 24 hours.
        attribute "RemoteControlStartAllowed", "enum", ["on", "off"]

        // BSH.Common.Status.OperationState
        // This status describes the operation state of the home appliance. 
        attribute "OperationState", "enum", [
            // Key: BSH.Common.EnumType.OperationState.Inactive
            // Description: Home appliance is inactive. It could be switched off or in standby.
            "Inactive",

            // Key: BSH.Common.EnumType.OperationState.Ready
            // Description: Home appliance is switched on. No program is active.
            "Ready",

            // Key: BSH.Common.EnumType.OperationState.DelayedStart
            // Description: A program has been activated but has not started yet.
            "DelayedStart",

            // Key: BSH.Common.EnumType.OperationState.Run
            // Description: A program is currently active.
            "Run",

            // Key: BSH.Common.EnumType.OperationState.Pause
            // Description: The active program has been paused.
            "Pause",

            // Key: BSH.Common.EnumType.OperationState.ActionRequired
            // Description: The active program requires a user interaction.
            "ActionRequired",

            // Key: BSH.Common.EnumType.OperationState.Finished
            // Description: The active program has finished or has been aborted successfully.
            "Finished",

            // Key: BSH.Common.EnumType.OperationState.Error
            // Description: The home appliance is in an error state.
            "Error",

            // Key: BSH.Common.EnumType.OperationState.Aborting
            // Description: The active program is currently aborting.
            "Aborting",
        ]

        // BSH.Common.Status.DoorState
        // This status describes the state of the door of the home appliance. 
        // A change of that status is either triggered by the user operating 
        // the home appliance locally (i.e. opening/closing door) or 
        // automatically by the home appliance (i.e. locking the door).
        //
        // Please note that the door state of coffee machines is currently 
        // only available for American coffee machines. 
        // All other coffee machines will be supported soon.
        attribute "DoorState", "enum", [
            //  Key: BSH.Common.EnumType.DoorState.Open
            // Description: The door of the home appliance is open.
            "Open",

            // Key: BSH.Common.EnumType.DoorState.Closed
            // Description: The door of the home appliance is closed but not locked.
            "Closed",

            //  Key: BSH.Common.EnumType.DoorState.Locked
            // Description: The door of the home appliance is locked.
            "Locked",
        ]

        attribute "ActiveProgram", "string"

        attribute "PowerState", "enum", [
            // Key: BSH.Common.EnumType.PowerState.Off
            // Description: The home appliance switched to off state but can 
            // be switched on by writing the value BSH.Common.EnumType.PowerState.
            // On to this setting.
            "Off",

            // Key: BSH.Common.EnumType.PowerState.On
            // Description: The home appliance switched to on state. 
            // You can switch it off by writing the value BSH.Common.EnumType.PowerState.Off 
            // or BSH.Common.EnumType.PowerState.Standby depending on what is supported by the appliance.
            "On",

            //  Key: BSH.Common.EnumType.PowerState.Standby
            // Description: The home appliance went to standby mode.
            // You can switch it on or off by changing the value of this setting appropriately.
            "Standby"
        ]

        attribute "EventPresentState", "enum", [
            // Key: BSH.Common.EnumType.EventPresentState.Present
            // Description: The event occurred and is present.
            "Event active",

            // Key: BSH.Common.EnumType.EventPresentState.Off
            // Description: The event is off.
            "Off",

            //  Key: BSH.Common.EnumType.EventPresentState.Confirmed
            // Description: The event has been confirmed by the user.
            "Confirmed"
        ]

        attribute "EventStreamStatus", "enum", ["connected", "disconnected"]
    }
    
    preferences {
        section { // General
            input name:"selectedProgram", type:"enum", title: "select program", options:getPrograms()
            input name: "logLevel", title: "Log Level", type: "enum", options: LOG_LEVELS, defaultValue: DEFAULT_LOG_LEVEL, required: false
        }
    }
}
//added for testing
void testWriteProgramEvent(){
    //NOTE: the attribute value must be written using json builder
    sendEvent(name:"listOfPrograms", value: new groovy.json.JsonBuilder(["1", "2", "3","5","7"]))
}

void setProgram(program) {
    Utils.toLogger("info", "setProgram() ${program}")

    def haId = device.deviceNetworkId
    parent.getHomeConnectAPI().getAvailablePrograms(haId) { activeProgram ->
        Utils.toLogger("info", "getAvailablePrograms received: ${activeProgram}")
    }
}

void initialize() {
    Utils.toLogger("info", "initialize()")
    intializeStatus();
    //runEvery1Minute("intializeStatus")
}

void installed() {
    Utils.toLogger("info", "installed()")
    intializeStatus();
}

void updated() {
    Utils.toLogger("info", "updated()")
}

void uninstalled() {
    disconnectEventStream()
}

void reset() {
    Utils.toLogger("debug", "reset")
    unschedule()
    sendEvent(name: "EventStreamStatus", value: "disconnected", displayed: true, isStateChange: true)
    disconnectEventStream()
}

List<String> getPrograms() {
    //updated
    String json = device?.currentValue("listOfPrograms")
    if (json != null) return parseJson(json)
    else []
}

def on() {
    parent.setPowertate(device, true)
}

def off() {
    parent.setPowertate(device, false)
}

void intializeStatus() {
    Utils.toLogger("info", "Initializing the status of the device")

    parent.intializeStatus(device)
    
    try {
        disconnectEventStream()
        connectEventStream()
    } catch (Exception e) {
        Utils.toLogger("error", "intializeStatus() failed: ${e.message}")
        setEventStreamStatusToDisconnected()
    }
}

void connectEventStream() {
    Utils.toLogger("debug", "connectEventStream()")
    def haId = device.deviceNetworkId
    parent.getHomeConnectAPI().connectDeviceEvents(haId, interfaces);
}

void reconnectEventStream(Boolean notIfAlreadyConnected = true) {
    Utils.toLogger("debug", "reconnectEventStream(notIfAlreadyConnected=$notIfAlreadyConnected)")
    
    if (device.currentValue("EventStreamStatus") == "connected" && notIfAlreadyConnected) {
        Utils.toLogger("debug", "already connected; skipping reconnection")
    } else {
        //disconnectEventStream()
        connectEventStream()
    }
}

void disconnectEventStream() {
    Utils.toLogger("debug", "disconnectEventStream()")
    def haId = device.deviceNetworkId
    parent.getHomeConnectAPI().disconnectDeviceEvents(haId, interfaces);
}

void setEventStreamStatusToConnected() {
    Utils.toLogger("debug", "setEventStreamStatusToConnected()")
    unschedule("setEventStreamStatusToDisconnected")
    if (device.currentValue("EventStreamStatus") == "disconnected") { 
        sendEvent(name: "EventStreamStatus", value: "connected", displayed: true, isStateChange: true)
    }
    state.connectionRetryTime = 15
}

void setEventStreamStatusToDisconnected() {
    Utils.toLogger("debug", "setEventStreamStatusToDisconnected()")
    sendEvent(name: "EventStreamStatus", value: "disconnected", displayed: true, isStateChange: true)
    if (state.connectionRetryTime) {
       state.connectionRetryTime *= 2
       if (state.connectionRetryTime > 900) {
          state.connectionRetryTime = 900 // cap retry time at 15 minutes
       }
    } else {
       state.connectionRetryTime = 15
    }
    Utils.toLogger("debug", "reconnecting EventStream in ${state.connectionRetryTime} seconds")
    runIn(state.connectionRetryTime, "reconnectEventStream")
}

void eventStreamStatus(String text) {
    Utils.toLogger("debug", "Received eventstream status message: ${text}")
    def (String type, String message) = text.split(':', 2)
    switch (type) {    
        case 'START':
            setEventStreamStatusToConnected()
            //Utils.toLogger("info", "Event Stream connected")
            break
        
        case 'STOP':
            Utils.toLogger("debug", "eventStreamDisconnectGracePeriod: ${eventStreamDisconnectGracePeriod}")
            runIn(eventStreamDisconnectGracePeriod, "setEventStreamStatusToDisconnected")
            //Utils.toLogger("info", "Event Stream disconnected")
            break

        default:
            Utils.toLogger("error", "Received unhandled Event Stream status message: ${text}")
            runIn(eventStreamDisconnectGracePeriod, "setEventStreamStatusToDisconnected")
            break
    }
}

void parse(String text) {
    Utils.toLogger("debug", "Received eventstream message: ${text}")  
    parent.processMessage(device, text)
}

def deviceLog(level, msg) {
    Utils.toLogger(level, msg)
}

/**
 * Simple utilities for manipulation
 */

def Utils_create() {
    def instance = [:];
    
    instance.toLogger = { level, msg ->
        if (level && msg) {
            Integer levelIdx = LOG_LEVELS.indexOf(level);
            Integer setLevelIdx = LOG_LEVELS.indexOf(logLevel);
            if (setLevelIdx < 0) {
                setLevelIdx = LOG_LEVELS.indexOf(DEFAULT_LOG_LEVEL);
            }
            if (levelIdx <= setLevelIdx) {
                log."${level}" "${device.displayName} ${msg}";
            }
        }
    }

    return instance;
}
2 Likes

Thanks a lot. It finally worked :slight_smile:

1 Like

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.