Unable to see custom capabilities in UI for "Direct-Connected" device

I’m testing a direct-connected device with a custom capability, which is working from the device standpoint (I’m able to return a handle for the device components and update the custom attributes, etc.) But despite my best efforts, the device components associated with the custom capability won’t show up in the UI.

Most of the references I can find on similar issues are specific to DTH based devices, I’m not sure I’ve found any references to this scenario specifically, all the examples use platform provided capabilities. As best I can tell everything is there, but the device only shows attributes associated with the built-in types.

I’ll include some of the configs here, and just out of sheer bewilderment, I’ve tried both ios and android clients. Any suggestions would be much appreciated…

CAPABILITY
{
    "id": "nightpeace33800.force",
    "version": 1,
    "status": "proposed",
    "name": "Force",
    "attributes": {
        "fullForce": {
            "schema": {
                "type": "object",
                "properties": {
                    "value": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 5000
                    },
                    "unit": {
                        "type": "string",
                        "enum": [
                            "grams"
                        ],
                        "default": "grams"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "value"
                ]
            },
            "setter": "setFullForce",
            "enumCommands": []
        },
        "force": {
            "schema": {
                "type": "object",
                "properties": {
                    "value": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 5000
                    },
                    "unit": {
                        "type": "string",
                        "enum": [
                            "grams"
                        ],
                        "default": "grams"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "value"
                ]
            },
            "enumCommands": []
        }
    },
    "commands": {
        "setFullForce": {
            "name": "setFullForce",
            "arguments": [
                {
                    "name": "grams",
                    "optional": false,
                    "schema": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 5000
                    }
                }
            ]
        }
    }
}
CAPABILITY_PRESENTATION
{
    "dashboard": {
        "states": [
            {
                "label": "{{force.value}} {{force.unit}}",
                "alternatives": null
            }
        ],
        "actions": [],
        "basicPlus": []
    },
    "detailView": [
        {
            "label": "Force",
            "displayType": "slider",
            "slider": {
                "range": [
                    0,
                    5000
                ],
                "step": null,
                "unit": "force.unit",
                "value": "force.value",
                "valueType": "integer"
            },
            "state": null
        },
        {
            "label": "Set As Full",
            "displayType": "pushButton",
            "pushButton": {
                "command": "setFullForce",
                "argument": "{{force.value}}",
                "argumentType": "integer"
            },
            "state": null
        }
    ],
    "automation": {
        "conditions": [
            {
                "label": "Force",
                "displayType": "slider",
                "slider": {
                    "range": [
                        0,
                        5000
                    ],
                    "step": 1,
                    "unit": "force.unit",
                    "value": "force.value",
                    "valueType": "integer"
                }
            }
        ],
        "actions": []
    },
    "id": "nightpeace33800.force",
    "version": 1
}
DEVICE_PROFILE
{
    "id": "20d5e3ff-9985-46a4-b78c-bb21b2c535c3",
    "name": "Containers",
    "components": [
        {
            "label": "main",
            "id": "main",
            "capabilities": [
                {
                    "id": "healthCheck",
                    "version": 1
                }
            ],
            "categories": []
        },
        {
            "label": "Container1",
            "id": "Container1",
            "capabilities": [
                {
                    "id": "presenceSensor",
                    "version": 1
                },
                {
                    "id": "battery",
                    "version": 1
                },
                {
                    "id": "nightpeace33800.force",
                    "version": 1
                }
            ],
            "categories": []
        },
        {
            "label": "Container2",
            "id": "Container2",
            "capabilities": [
                {
                    "id": "presenceSensor",
                    "version": 1
                },
                {
                    "id": "battery",
                    "version": 1
                },
                {
                    "id": "nightpeace33800.force",
                    "version": 1
                }
            ],
            "categories": []
        },
        {
            "label": "Container3",
            "id": "Container3",
            "capabilities": [
                {
                    "id": "presenceSensor",
                    "version": 1
                },
                {
                    "id": "battery",
                    "version": 1
                },
                {
                    "id": "nightpeace33800.force",
                    "version": 1
                }
            ],
            "categories": []
        },
        {
            "label": "Container4",
            "id": "Container4",
            "capabilities": [
                {
                    "id": "presenceSensor",
                    "version": 1
                },
                {
                    "id": "battery",
                    "version": 1
                },
                {
                    "id": "nightpeace33800.force",
                    "version": 1
                }
            ],
            "categories": []
        }
    ],
    "metadata": {
        "vid": "98a50b8a-9907-323f-bc4b-9a199b444f76",
        "deviceType": "Others",
        "mnmn": "0ATj",
        "ocfDeviceType": "oic.d.others",
        "deviceTypeId": "Others",
        "ocfSpecVer": "core 1.1.0",
        "mnid": "0ATj",
        "mnId": "0ATj"
    },
    "status": "DEVELOPMENT",
    "preferences": []
}
DEVICE_CONFIG
{
    "mnmn": "0ATj",
    "vid": "98a50b8a-9907-323f-bc4b-9a199b444f76",
    "version": "0.0.1",
    "type": "profile",
    "dashboard": {
        "states": [
            {
                "component": "main",
                "capability": "healthCheck",
                "version": 1,
                "idx": 0,
                "group": "main",
                "values": [],
                "composite": false
            }
        ],
        "actions": [
            {
                "component": "main",
                "capability": "healthCheck",
                "version": 1,
                "idx": 0,
                "group": "main"
            }
        ]
    },
    "detailView": [
        {
            "component": "main",
            "capability": "healthCheck",
            "version": 1,
            "values": [],
            "patch": []
        },
        {
            "component": "Container1",
            "capability": "presenceSensor",
            "version": 1,
            "values": [],
            "patch": []
        },
        {
            "component": "Container1",
            "capability": "battery",
            "version": 1,
            "values": [],
            "patch": []
        },
        {
            "component": "Container1",
            "capability": "nightpeace33800.force",
            "version": 1,
            "values": [],
            "patch": []
        },
        {
            "component": "Container2",
            "capability": "presenceSensor",
            "version": 1,
            "values": [],
            "patch": []
        },
        {
            "component": "Container2",
            "capability": "battery",
            "version": 1,
            "values": [],
            "patch": []
        },
        {
            "component": "Container2",
            "capability": "nightpeace33800.force",
            "version": 1,
            "values": [],
            "patch": []
        },
        {
            "component": "Container3",
            "capability": "presenceSensor",
            "version": 1,
            "values": [],
            "patch": []
        },
        {
            "component": "Container3",
            "capability": "battery",
            "version": 1,
            "values": [],
            "patch": []
        },
        {
            "component": "Container3",
            "capability": "nightpeace33800.force",
            "version": 1,
            "values": [],
            "patch": []
        },
        {
            "component": "Container4",
            "capability": "presenceSensor",
            "version": 1,
            "values": [],
            "patch": []
        },
        {
            "component": "Container4",
            "capability": "battery",
            "version": 1,
            "values": [],
            "patch": []
        },
        {
            "component": "Container4",
            "capability": "nightpeace33800.force",
            "version": 1,
            "values": [],
            "patch": []
        }
    ],
    "automation": {
        "conditions": [
            {
                "component": "main",
                "capability": "healthCheck",
                "version": 1,
                "values": [],
                "patch": [],
                "exclusion": []
            },
            {
                "component": "Container1",
                "capability": "presenceSensor",
                "version": 1,
                "values": [],
                "patch": [],
                "exclusion": []
            },
            {
                "component": "Container1",
                "capability": "battery",
                "version": 1,
                "values": [],
                "patch": [],
                "exclusion": []
            },
            {
                "component": "Container1",
                "capability": "nightpeace33800.force",
                "version": 1,
                "values": [],
                "patch": [],
                "exclusion": []
            },
            {
                "component": "Container2",
                "capability": "presenceSensor",
                "version": 1,
                "values": [],
                "patch": [],
                "exclusion": []
            },
            {
                "component": "Container2",
                "capability": "battery",
                "version": 1,
                "values": [],
                "patch": [],
                "exclusion": []
            },
            {
                "component": "Container2",
                "capability": "nightpeace33800.force",
                "version": 1,
                "values": [],
                "patch": [],
                "exclusion": []
            },
            {
                "component": "Container3",
                "capability": "presenceSensor",
                "version": 1,
                "values": [],
                "patch": [],
                "exclusion": []
            },
            {
                "component": "Container3",
                "capability": "battery",
                "version": 1,
                "values": [],
                "patch": [],
                "exclusion": []
            },
            {
                "component": "Container3",
                "capability": "nightpeace33800.force",
                "version": 1,
                "values": [],
                "patch": [],
                "exclusion": []
            },
            {
                "component": "Container4",
                "capability": "presenceSensor",
                "version": 1,
                "values": [],
                "patch": [],
                "exclusion": []
            },
            {
                "component": "Container4",
                "capability": "battery",
                "version": 1,
                "values": [],
                "patch": [],
                "exclusion": []
            },
            {
                "component": "Container4",
                "capability": "nightpeace33800.force",
                "version": 1,
                "values": [],
                "patch": [],
                "exclusion": []
            }
        ],
        "actions": [
            {
                "component": "main",
                "capability": "healthCheck",
                "version": 1,
                "values": [],
                "patch": [],
                "exclusion": []
            },
            {
                "component": "Container1",
                "capability": "nightpeace33800.force",
                "version": 1,
                "values": [],
                "patch": [],
                "exclusion": []
            },
            {
                "component": "Container2",
                "capability": "nightpeace33800.force",
                "version": 1,
                "values": [],
                "patch": [],
                "exclusion": []
            },
            {
                "component": "Container3",
                "capability": "nightpeace33800.force",
                "version": 1,
                "values": [],
                "patch": [],
                "exclusion": []
            },
            {
                "component": "Container4",
                "capability": "nightpeace33800.force",
                "version": 1,
                "values": [],
                "patch": [],
                "exclusion": []
            }
        ]
    },
    "presentationId": "98a50b8a-9907-323f-bc4b-9a199b444f76",
    "manufacturerName": "0ATj"
}

Hey, @ChristopherH

Is the onboarding_config.json pointing to the same custom vid / presentationId? e.g:

{
  "onboardingConfig": {
    "deviceOnboardingId": "...",
    "mnId": "...",
    "setupId": "...",
    "vid": "...",     <<<< same from deviceprofile.metadata
    "deviceTypeId": "Switch",
    "ownershipValidationTypes": [
      "JUSTWORKS"
    ],
    "identityType": "...",
    "deviceIntegrationProfileKey": {
      "id": "xxxxx",    <<<< same key generated by DevWS
      "majorVersion": 0,
      "minorVersion": 1
    },
    "productId": "xxxxx"
  }
}

Hi @erickv – yeah, the config loaded on the device appears to be correct. Here’s what it looks like for a second set of eyes, but I believe it’s correct. The vid definitely matches the above definitions, and the deviceIntegrationProfileKey id is what’s listed in the workspace view.

{
  "onboardingConfig": {
    "deviceOnboardingId": "Inventory",
    "mnId": "0ATj",
    "setupId": "002",
    "vid": "98a50b8a-9907-323f-bc4b-9a199b444f76",
    "deviceTypeId": "Others",
    "ownershipValidationTypes": [
      "JUSTWORKS"
    ],
    "identityType": "ED25519",
    "deviceIntegrationProfileKey": {
      "id": "67cd302f-5c90-42dd-b6d3-217700272049",
      "majorVersion": 0,
      "minorVersion": 1
    },
    "productId": "455b6c3c-c9d9-4eec-b6cb-933358aec119"
  }
}

Fair enough! The issue might be related to the capability then, but let me check it in detail.

In the meantime, consider the following:

  1. At the moment, the custom capabilities can only handle a single attribute.
  2. From the presentation:
    • dashboard: instead of setting alternatives as null, set it as an empty array.
    • detailView: same as attributes, presentations can handle only one display type.

With this in mind, I recommend you split attributes into individual capabilities.


Update

Initially, I can confirm that if the dashboard.alternatives changes to an empty array, the value will display properly (ignore the lock icon… that was just for fun):
custom-force

Then, by tinkering up the slider display type as follows:

    ...
    "detailView": [
        {
            "label": "Force",
            "displayType": "slider",
            "slider": {
                "range": [
                    0,
                    5000
                ],
                "unit": "force.unit",
                "value": "force.value",
                "command": "setFullForce"
            },
            "state": {
                "label": "{{force.value}} {{force.unit}}",
                "unit": "force.unit",
                "alternatives": []
            }
        },
        <<< This second item will be ignored
        <<< but I left it there to minimize updates
        {       
            "label": "Set As Full",
            "displayType": "pushButton",
            "pushButton": {
                "command": "setFullForce",
                "argument": "{{force.value}}",
                "argumentType": "integer"
            }
        }
    ],

I was able to display it at the detailView… but the command wasn’t working because there wasn’t a setter association, so, with just a small update on your capability schema you get the command to work as well, i.e.:

           ...
            "enumCommands": [],
           "setter": "setFullForce" <<< setter association
        }
    },
    "commands": {
        "setFullForce": {
            "name": "setFullForce",
             ...

1 Like

ok thank you, that’s great… however I’m having a bit of trouble getting there, when I POST the updated JSON, the resulting object persists null. In fact, I’m fairly certain my original JSON used an empty list rather than null, but I experienced this same behavior then. Do you happen to have any ideas about that?

I’ve tried starting from scratch several times to no avail, but since I have a few hints on what to watch for now, I may try that again and see what I observe. I’m just not clear why that keeps reverting to null…?

thanks again for your help!

Wow, it’s true!

I hadn’t noticed that empty arrays/objects were parsed to null. Well then, we can ignore that point. I assume that I got the value at the dashboard because my main component was supporting the capability.

I posted a new update regarding the detailView that I recommend you to follow and once you finish the capability updates, clear your app’s cache which will force the plugin to download the metadata again.

thanks @erickv – I’ve confirmed that every place that I’m aware of is now up-to-date with the suggested modifications, and I cleared the app cache and recreated the device. I get the same view, only the battery and presence capabilities in the detail view. Note: if I go to the automations for the device, I do get a slider for the custom capability attribute – so it seems it’s definitely just an issue with the view rendering.

Thanks for the heads-up, @ChristopherH!

I’ll check it out closely and will share with you the outcome.

@ChristopherH Very well, it seems that the pushButton display type reference must be removed to render the capability at the detailView properly. For a moment, when I specified more components to my test case device, it was rendering “Connected” instead of the sliders that I showed before.

What I just did was to remove the additional display type and clear the app’s cache… that made the trick:

And, for a more complex reference, here’s a test case device that supports battery and another component.


If you’re still having problems setting up the presentation, let me know = )

I’ve made sure that all of the pushButton references are gone, so there are no actions, the only display object configured is the slider now. But still, it doesn’t seem to be updating for me… here’s what it looks like, so you don’t think I’m lying :slight_smile:

On every update, is your device forced to download the metadata? i.e.:

Nope, I’ve never seen that message before… I’m clearing the cache via the android interface. Is there something I’m missing?

1 Like

Try clearing the cache at: Menu > Settings > Developer Mode (at the bottom) > RESTART APP.

I’m guessing that on ios? I looked around earlier for an in-app cache control but couldn’t find one. the only option under Developer Options is Hide Developer Options so I’m using the option via the android app management.

Nope, unfortunately, iOS doesn’t provide this feature, however, did you tap the “Developer mode” tab… sentence?.. yeah, it’s not a very obvious window, though.

Oh I see, yeah that’s not obvious – but I see now. Unfortunately the device behavior is the same after the restart :frowning:

OH! I’m pretty confused, but it appears now that re-creating the device, after going through that restart process, appears to get me the widgets! Ok, this is a good start! Let me see if I can get from here to where I want to be, but thank you so much for your help! There’s definitely still some black magic in the system, so thank you for helping me untangle it!

1 Like

@ChristopherH No problem!

Ping me back if face any other issue = )

Is this

    1. At the moment, the custom capabilities can only handle a single attribute .*

still true. I made a custom cap with multiple attributes (multiple setting values for a device).
I then made the presentation json and uploaded them both etc.

But the capability is not visible!

Cheers

Hi!
Yes, it is still true, you can add multiple attributes in the capability definition and use all in the background but you can display only one in the app.
To verify why the capability is missing, you should:

  1. Update your capability presentation to show only one attribute (changing the definition is optional)
  2. Depending on your integration type, you should update the device presentation or make sure it was refreshed:
    a. This can be done by getting the current presentationId used by the device (from the list) and query its configuration