Working with Device presentation

Continuing the discussion from Custom Capability and CLI Developer Preview:

Hi, @TAustin. I open this new post to continue our conversation.

Which icon are you using?

Can you share your device configuration, please? I’d like to see the config of all the capabilities you’re using so I can replicate the behavior you mention.

Regarding Icon - I don’t specify, so it’s the stock switch icon.

Here’s all my json:

DASHBOARD ACTION CUSTOM CAPABILITY (SWITCH):

{
    "id": "partyvoice23922.dscdashswitch",
    "version": 1,
    "status": "proposed",
    "name": "dscdashswitch",
    "attributes": {
        "switch": {
            "schema": {
                "type": "object",
                "properties": {
                    "value": {
                        "type": "string"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "value"
                ]
            },
            "setter": "setSwitch",
            "enumCommands": []
        }
    },
    "commands": {
        "setSwitch": {
            "name": "setSwitch",
            "arguments": [
                {
                    "name": "value",
                    "optional": false,
                    "schema": {
                        "type": "string"
                    }
                }
            ]
        }
    }
}

DASHBOARD STATE CUSTOM CAPABILITY (LABEL):

{
    "id": "partyvoice23922.partitionStatus",
    "version": 1,
    "status": "proposed",
    "name": "Partition Status",
    "attributes": {
        "partStatus": {
            "schema": {
                "type": "object",
                "properties": {
                    "value": {
                        "type": "string",
                        "maxLength": 16
                    }
                },
                "additionalProperties": false,
                "required": [
                    "value"
                ]
            },
            "setter": "setPartStatus",
            "enumCommands": []
        }
    },
    "commands": {
        "setPartStatus": {
            "name": "setPartStatus",
            "arguments": [
                {
                    "name": "value",
                    "optional": false,
                    "schema": {
                        "type": "string",
                        "maxLength": 16
                    }
                }
            ]
        }
    }
}

DASHBOARD SWITCH PRESENTATION

{
    "dashboard": {
        "states": [],
        "actions": [
            {
                "displayType": "switch",
                "switch": {
                    "command": {
                        "name": "setSwitch",
                        "on": "on",
                        "off": "off"
                    },
                    "state": {
                        "value": "switch.value",
                        "on": "on",
                        "off": "off"
                    }
                }
            }
        ],
        "basicPlus": []
    },
    "automation": {
        "conditions": [],
        "actions": []
    },
    "id": "partyvoice23922.dscdashswitch",
    "version": 1
}

DASHBOARD STATE LABEL PRESENTATION

{
    "dashboard": {
        "states": [
            {
                "label": "{{partStatus.value}}",
                "alternatives": [
                    {
                        "key": "Armed-away",
                        "value": "Armed-away",
                        "type": "active"
                    },
                    {
                        "key": "Arming away",
                        "value": "Arming away",
                        "type": "active"
                    },
                    {
                        "key": "Armed-stay",
                        "value": "Armed-stay",
                        "type": "active"
                    },
                    {
                        "key": "Arming stay",
                        "value": "Arming stay",
                        "type": "active"
                    },
                    {
                        "key": "Exit delay",
                        "value": "Exit delay",
                        "type": "active"
                    },
                    {
                        "key": "Ready",
                        "value": "Ready",
                        "type": "active"
                    },
                    {
                        "key": "Disarmed",
                        "value": "Disarmed",
                        "type": "active"
                    },
                    {
                        "key": "Disarming",
                        "value": "Disarming",
                        "type": "active"
                    },
                    {
                        "key": "ALARM!!",
                        "value": "ALARM!!",
                        "type": "active"
                    }
                ]
            }
        ],
        "actions": [],
        "basicPlus": []
    },
    "detailView": [
        {
            "label": "Partition Status",
            "displayType": "state",
            "state": {
                "label": "{{partStatus.value}}"
            }
        }
    ],
    "automation": {
        "conditions": [
            {
                "label": "Partition Status",
                "displayType": "list",
                "list": {
                    "alternatives": [
                        {
                            "key": "partStatus",
                            "value": "Arm-Away",
                            "type": "active"
                        },
                        {
                            "key": "partStatus",
                            "value": "Arm-Stay",
                            "type": "active"
                        },
                        {
                            "key": "partStatus",
                            "value": "Ready",
                            "type": "active"
                        }
                    ]
                }
            }
        ],
        "actions": []
    },
    "id": "partyvoice23922.partitionStatus",
    "version": 1
}

DEVICE CONFIGURATION

{
    "dashboard": {
        "states": [
            {
                "component": "main",
                "capability": "partyvoice23922.partitionStatus",
                "version": 1,
                "values": [],
                "patch": []
            }
        ],
        "actions": [
            {
                "component": "main",
                "capability": "partyvoice23922.dscdashswitch",
                "version": 1,
                "values": [],
                "patch": []
            }
        ]
    },
    "detailView": [
        {
            "component": "main",
            "capability": "partyvoice23922.partitionStatus",
            "version": 1,
            "values": [],
            "patch": []
        },
        {
            "component": "main",
            "capability": "partyvoice23922.dscstayswitch",
            "version": 1,
            "values": [],
            "patch": []
        },
        {
            "component": "main",
            "capability": "partyvoice23922.dscawayswitch",
            "version": 1,
            "values": [],
            "patch": []
        },
        {
            "component": "main",
            "capability": "partyvoice23922.dscselectswitch",
            "version": 1,
            "values": [],
            "patch": []
        },
        {
            "component": "main",
            "capability": "partyvoice23922.partitioncommand",
            "version": 1,
            "values": [],
            "patch": []
        }
    ],
    "automation": {
        "conditions": [
            {
                "component": "main",
                "capability": "partyvoice23922.partitionStatus",
                "version": 1,
                "values": [],
                "patch": []
            },
            {
                "component": "main",
                "capability": "healthCheck",
                "version": 1,
                "values": [],
                "patch": []
            }
        ],
        "actions": [
            {
                "component": "main",
                "capability": "healthCheck",
                "version": 1,
                "values": [],
                "patch": []
            },
            {
                "component": "main",
                "capability": "partyvoice23922.dscstayswitch",
                "version": 1,
                "values": [],
                "patch": []
            },
            {
                "component": "main",
                "capability": "partyvoice23922.dscawayswitch",
                "version": 1,
                "values": [],
                "patch": []
            },
            {
                "component": "main",
                "capability": "partyvoice23922.partitioncommand",
                "version": 1,
                "values": [],
                "patch": []
            }
        ]
    },
    "type": "profile",
    "presentationId": "ST_aced3851-8f1b-4e03-b1bf-fd41cd3c7276",
    "manufacturerName": "fE4b",
    "vid": "ST_aced3851-8f1b-4e03-b1bf-fd41cd3c7276",
    "mnmn": "fE4b",
    "version": "0.0.1"
}

DEVICE PRESENTATION

{
    "manufacturerName": "fE4b",
    "presentationId": "ST_aced3851-8f1b-4e03-b1bf-fd41cd3c7276",
    "mnmn": "fE4b",
    "vid": "ST_aced3851-8f1b-4e03-b1bf-fd41cd3c7276",
    "version": "0.0.1",
    "dashboard": {
        "states": [
            {
                "label": "{{partStatus.value}}",
                "capability": "partyvoice23922.partitionStatus",
                "version": 1,
                "component": "main"
            }
        ],
        "actions": [
            {
                "displayType": "switch",
                "switch": {
                    "command": {
                        "name": "setSwitch",
                        "on": "on",
                        "off": "off"
                    },
                    "state": {
                        "value": "switch.value",
                        "on": "on",
                        "off": "off"
                    }
                },
                "capability": "partyvoice23922.dscdashswitch",
                "version": 1,
                "component": "main"
            }
        ],
        "basicPlus": []
    },
    "detailView": [
        {
            "capability": "partyvoice23922.partitionStatus",
            "version": 1,
            "label": "Partition Status",
            "displayType": "state",
            "state": {
                "label": "{{partStatus.value}}"
            },
            "component": "main"
        },
        {
            "capability": "partyvoice23922.dscstayswitch",
            "version": 1,
            "label": "Arm Stay",
            "displayType": "toggleSwitch",
            "toggleSwitch": {
                "command": {
                    "name": "setSwitch",
                    "on": "on",
                    "off": "off"
                },
                "state": {
                    "value": "switch.value",
                    "on": "on",
                    "off": "off"
                }
            },
            "state": null,
            "component": "main"
        },
        {
            "capability": "partyvoice23922.dscawayswitch",
            "version": 1,
            "label": "Arm Away",
            "displayType": "toggleSwitch",
            "toggleSwitch": {
                "command": {
                    "name": "setSwitch",
                    "on": "on",
                    "off": "off"
                },
                "state": {
                    "value": "switch.value",
                    "on": "on",
                    "off": "off"
                }
            },
            "state": null,
            "component": "main"
        },
        {
            "capability": "partyvoice23922.dscselectswitch",
            "version": 1,
            "label": "Dashboard Arm Type",
            "displayType": "switch",
            "switch": {
                "command": {
                    "name": "setSwitch",
                    "on": "Type: Arm Away",
                    "off": "Type: Arm Stay"
                },
                "state": {
                    "value": "switch.value",
                    "on": "on",
                    "off": "off"
                }
            },
            "state": null,
            "component": "main"
        },
        {
            "capability": "partyvoice23922.partitioncommand",
            "version": 1,
            "label": "Tap for additional partition commands:",
            "displayType": "list",
            "list": {
                "command": {
                    "name": "sendPartitionCommand",
                    "alternatives": [
                        {
                            "key": "away",
                            "value": "Arm-Away",
                            "type": "active"
                        },
                        {
                            "key": "disarm",
                            "value": "Disarm",
                            "type": "active"
                        },
                        {
                            "key": "stayarm",
                            "value": "Arm-Stay",
                            "type": "active"
                        },
                        {
                            "key": "instantarm",
                            "value": "Arm-Instant",
                            "type": "active"
                        },
                        {
                            "key": "toggleinstant",
                            "value": "Instant Mode",
                            "type": "active"
                        },
                        {
                            "key": "togglenight",
                            "value": "Arm-Night",
                            "type": "active"
                        },
                        {
                            "key": "togglechime",
                            "value": "Toggle Chime",
                            "type": "active"
                        },
                        {
                            "key": "reset",
                            "value": "Reset",
                            "type": "active"
                        },
                        {
                            "key": "refresh",
                            "value": "Refresh",
                            "type": "active"
                        },
                        {
                            "key": "panic",
                            "value": "Panic Key",
                            "type": "active"
                        }
                    ]
                },
                "state": {
                    "value": "partitionCommand.value",
                    "alternatives": [
                        {
                            "key": "away",
                            "value": "Arm-Away",
                            "type": "active"
                        },
                        {
                            "key": "disarm",
                            "value": "Disarm",
                            "type": "active"
                        },
                        {
                            "key": "stayarm",
                            "value": "Arm-Stay",
                            "type": "active"
                        },
                        {
                            "key": "instantarm",
                            "value": "Arm-Instant",
                            "type": "active"
                        },
                        {
                            "key": "toggleinstant",
                            "value": "Instant Mode",
                            "type": "active"
                        },
                        {
                            "key": "togglenight",
                            "value": "Arm-Night",
                            "type": "active"
                        },
                        {
                            "key": "togglechime",
                            "value": "Toggle Chime",
                            "type": "active"
                        },
                        {
                            "key": "reset",
                            "value": "Reset",
                            "type": "active"
                        },
                        {
                            "key": "refresh",
                            "value": "Refresh",
                            "type": "active"
                        },
                        {
                            "key": "panic",
                            "value": "Panic Key",
                            "type": "active"
                        }
                    ]
                }
            },
            "state": null,
            "component": "main"
        }
    ],
    "automation": {
        "conditions": [
            {
                "capability": "partyvoice23922.partitionStatus",
                "version": 1,
                "label": "Partition Status",
                "displayType": "list",
                "list": {
                    "alternatives": [
                        {
                            "key": "partStatus",
                            "value": "Arm-Away",
                            "type": "active"
                        },
                        {
                            "key": "partStatus",
                            "value": "Arm-Stay",
                            "type": "active"
                        },
                        {
                            "key": "partStatus",
                            "value": "Ready",
                            "type": "active"
                        }
                    ],
                    "value": null
                },
                "exclusion": [],
                "component": "main"
            }
        ],
        "actions": [
            {
                "capability": "partyvoice23922.dscstayswitch",
                "version": 1,
                "label": "Arm Stay",
                "displayType": "list",
                "list": {
                    "alternatives": [
                        {
                            "key": "on",
                            "value": "on",
                            "type": "active"
                        },
                        {
                            "key": "off",
                            "value": "off",
                            "type": "inactive"
                        }
                    ],
                    "command": "setSwitch"
                },
                "component": "main",
                "exclusion": []
            },
            {
                "capability": "partyvoice23922.dscawayswitch",
                "version": 1,
                "label": "Arm Away",
                "displayType": "list",
                "list": {
                    "alternatives": [
                        {
                            "key": "on",
                            "value": "on",
                            "type": "active"
                        },
                        {
                            "key": "off",
                            "value": "off",
                            "type": "inactive"
                        }
                    ],
                    "command": "setSwitch"
                },
                "component": "main",
                "exclusion": []
            },
            {
                "capability": "partyvoice23922.partitioncommand",
                "version": 1,
                "label": "Partition Commands:",
                "displayType": "list",
                "list": {
                    "alternatives": [
                        {
                            "key": "away",
                            "value": "Arm-away",
                            "type": "active"
                        },
                        {
                            "key": "stay",
                            "value": "Arm-stay",
                            "type": "active"
                        },
                        {
                            "key": "disarm",
                            "value": "Disarm",
                            "type": "active"
                        },
                        {
                            "key": "instant",
                            "value": "Arm Instant",
                            "type": "active"
                        },
                        {
                            "key": "night",
                            "value": "Arm Night",
                            "type": "active"
                        },
                        {
                            "key": "bypassoff",
                            "value": "Bypass Off",
                            "type": "active"
                        },
                        {
                            "key": "autobypass",
                            "value": "Bypass",
                            "type": "active"
                        },
                        {
                            "key": "keyfire",
                            "value": "Key Fire",
                            "type": "active"
                        },
                        {
                            "key": "Keyaux",
                            "value": "Key Aux",
                            "type": "active"
                        },
                        {
                            "key": "keypanic",
                            "value": "Key Panic",
                            "type": "active"
                        },
                        {
                            "key": "reset",
                            "value": "Reset",
                            "type": "active"
                        },
                        {
                            "key": "chime",
                            "value": "Chime",
                            "type": "active"
                        }
                    ],
                    "command": "sendPartitionCommand"
                },
                "component": "main",
                "exclusion": []
            }
        ]
    },
    "dpInfo": [
        {
            "os": "ios",
            "dpUri": "plugin://com.samsung.ios.plugin.stplugin/assets/files/index.html"
        },
        {
            "os": "android",
            "dpUri": "plugin://com.samsung.android.plugin.stplugin"
        },
        {
            "os": "web",
            "dpUri": "wwst://com.samsung.one.plugin.stplugin"
        }
    ]
}

If I don’t include an alternatives section in my presentation json for the dashboard state label, then whatever my app sets for the attribute gets blanked out (it flashes for a brief moment but then disappears. The API documentation does not show that alternatives is a mandatory json section for dashboard, but it appears that it is.

It is not mandatory, the “state” should display correctly. In the device I created based on your capabilities, I changed the partitionStatus capability to:

"dashboard": {
        "states": [
            {
                "label": "{{partStatus.value}}"
            }
        ],
        "actions": [],
        "basicPlus": []
    }

When I receive the command from dscdashswitch, I change the partStatus value and it’s shown correctly. Was this similar to the configuration you had before the alternatives?

If the device application doesn’t update the dashboard state label value as well as the switch attribute when the switch is pressed, then the switch will spin for about 12 seconds. I found that updating the state label stops this spinning. So there does seem to be some kind of dependency between the action and state on the dashboard even if they are two separate capabilities.

When you issue this command, do you send the new value to dscdashswitch (on/off)? because as this uses a setter, a status update is expected, that’s why the icon could keep loading and when you send one, it doesn’t happen.

In the capability presentation json, trying to set any of the dashboard state label alternative types to “inactive” results in no state being shown for any value; and no change to the icon coloration. I want certain values to cause the icon to be greyed out vs. colored, and I thought this was the purpose of type=active/inactive in the alternatives section. However this doesn’t seem to be working with custom capabilities.

I see that now, the partitionStatus capability doesn’t have any inactive alternatives. I changed one alternative to inactive in my capability and the icon changed correctly. (Only the capability located in dashboard > states can control the icon)

Wow, I don’t know what to say. It works ok for you but not for me! I’ll have to keep experimenting, but to answer your questions:

When I receive the command from dscdashswitch, I change the partStatus value and it’s shown correctly. Was this similar to the configuration you had before the alternatives?

Yes, originally I did not include an alternatives section and my json matched yours, but the partStatus values weren’t being displayed, or flashed very briefly if I pressed the switch, and then immediately blanked back out. I’m closely monitoring all API activity to and from my device app, so I know it’s sending and receiving what is expected.

When you issue this command, do you send the new value to dscdashswitch (on/off)? because as this uses a setter, a status update is expected, that’s why the icon could keep loading and when you send one, it doesn’t happen.

Yes, when I receive an on command event I send an attribute update (“on”) back to the capability attribute (“switch”) ; same for off. The spinning continues though, until I also update the partStatus attribute.

I see that now, the partitionStatus capability doesn’t have any inactive alternatives. I changed one alternative to inactive in my capability and the icon changed correctly. (Only the capability located in dashboard > states can control the icon)

I had set all the types back to active because it wasn’t working if I set any of them to inactive. If I make the one minor json change to set type to ‘inactive’ for partStatus=Ready, then none of my partStatus value will be displayed. The json I posted above is working and displaying correctly, but only because I have all type=active in the alternatives section. I will try setting an inactive type again tonight to confirm I get the same problem tomorrow.

Could my problems be because I’m not using DTHs? This is a direct-connected device using the core SDK for direct connect devices. Maybe there are still bugs on that part of the platform?

Thanks for the information. I remembered you’re using an iOS device, right? I’ll verify the behavior you mention in that OS.
As for the icon change, there’s an open issue about this, only for iOS devices that our engineering team is working on.

Yes, IOS version 12.4.8 with SmartThings mobile app version 1.6.59-477.

Last night I made that one change to set the ‘Ready’ label value to ‘inactive’, and as of now it has had no effect. Which at least is an improvement over before when the label wouldn’t even display when I did that. Now, the label continues to be displayed ok, but the icon never turns grey. Perhaps this is the same problem as the open issue you referenced.

Hey, just to let you know that I was able to replicate the issue below only in another iOS device and I’ve reported this to our engineering department.

If the device application doesn’t update the dashboard state label value as well as the switch attribute when the switch is pressed, then the switch will spin for about 12 seconds. I found that updating the state label stops this spinning. So there does seem to be some kind of dependency between the action and state on the dashboard even if they are two separate capabilities.

Not this one, though, but I’ll keep trying and let you know my results.

If I don’t include an alternatives section in my presentation json for the dashboard state label, then whatever my app sets for the attribute gets blanked out (it flashes for a brief moment but then disappears. The API documentation does not show that alternatives is a mandatory json section for dashboard, but it appears that it is.

Thanks for bringing them up! :ok_hand:

Thank you. I appreciate your effort to investigate these. Once I have everything working satisfactorily given the known issues, I’ll go back to omitting my alternatives and see if I can re-create the same behavior I was seeing before. It is possible because of the platform updating delays and mobile app caching that the other confirmed issues (icon greyout and button spinning) could have been complicating cause and effect.

Let me ask you a question regarding the intended design of the alternatives section: Is it expected that this must list ALL possible values, or could it list only some? For example, since the default type is ‘active’ could you list in the alternatives section just the one or two values that you wanted to have a greyed-out icon with type=inactive? Any other values not listed would be treated as type=active . Or is the alternatives section treated as an ‘all or nothing’, and I would get an error if my device app tried to set any value not in the alternatives list?
Hope that makes sense. I’d prefer not to have the presentation json so dependent on the device application code and status values it may want to display.

Right now, if the capability’s alternatives don’t include all the possible values :

  • In the Dashboard View, if the value is different, it is not shown and the message “checking” appears instead.
  • In Detail View, the value is shown as is.

About the active/inactive values:

  • If you only include the inactive alternatives, the icon doesn’t change back to active with any other value.
  • This makes sense because the presentation doesn’t know how exactly to handle the unspecified values
  • The default active value is for the included options in the alternatives list that didn’t receive a type explicitly

I’ll ask our engineering dept about the alternatives’ limitations to give you an accurate answer.

If you want to limit the possible values, you need to define the ENUM property in the capability’s definition, and when you send a different value, it won’t be accepted, for example:

{
    "name": "alternativestest",
    "attributes": {
        "attr": {
            "schema": {
                "type": "object",
                "properties": {
                    "value": {
                        "type": "string",
                        "enum": [
                            "value",
                            "value1",
                            "value2"
                        ]
                    }
                },
                "additionalProperties": false,
                "required": [
                    "value"
                ]
            },
            "setter": "setAttr",
            "enumCommands": []
        }
    },
    "commands": {
        "setAttr": {
            "name": "setAttr",
            "arguments": [
                {
                    "name": "value",
                    "optional": false,
                    "schema": {
                        "type": "string",
                        "enum": [
                            "value",
                            "value1",
                            "value2"
                        ]
                    }
                }
            ]
        }
    }
}

Ok thank you. So alternatives, if provided in the json, do have to cover all cases. If I wanted to have complete flexibility in the values, then I would have to omit both alternatives and enum list. However, that means I can’t control the icon. So that’s the trade-off.

Why would that capability have a large range of values? Can you give an example, please?

In this case, the device app is a layer on top of the actual (LAN-based) device. I don’t have complete control of all the possible “organic” status values that might be coming from it. I can code for most, but there might be others that either my app didn’t anticipate, or the actual device could have been upgrade or changed where it may give new status strings. So I wanted to just pass along any I didn’t recognize and have them show up on the dashboard. That way the user doesn’t miss seeing anything important and my device app doesn’t have to be updated for every possible value. And for a few known key values, I wanted to control the icon colorization to get the user’s attention.

So it’s not necessarily about a LARGE range of values, but some may not be anticipated. I can imagine this would also be needed by any device app that is building the state string on the fly, based on whatever device-specific or other environmental factors. The possible values aren’t always fixed.

Another way to think about it is that I’d want the same flexibility as showing a temperature on the dashboard: you wouldn’t list those in alternatives because you can’t anticipate all the possible temp values. And you’d like to be able to change the icon highlighting based on the temperature range (e.g. normally grey but colored if below zero).

1 Like