dresden-elektronik / deconz-rest-plugin-v2

deCONZ REST-API version 2 development repository.
BSD 3-Clause "New" or "Revised" License
17 stars 0 forks source link

REST API Device Capabilities / Introspection #8

Open manup opened 3 years ago

manup commented 3 years ago

On the experimental /devices endpoint we can add some mechanism to query capabilities on items. Currently the output looks like the following snipped in the device_descriptions branch (no capabilities / introspection)

GET http://127.0.0.1/api/<apikey>/devices/00:17:88:01:08:f7:76:8c

{
    "lastseen": "2021-05-04T19:24:34Z",
    "manufacturername": "Philips",
    "modelid": "RWL021",
    "name": "Switch 5",
    "productid": "Dimmer Switch 1. Gen",
    "subdevices": [
        {
            "config": {
                "battery": {
                    "lastupdated": "2021-05-04T19:24:34Z",
                    "value": 58
                },
                "on": {
                    "lastupdated": "2021-05-03T20:58:47Z",
                    "value": true
                },
                "reachable": {
                    "lastupdated": "2021-05-03T20:58:47Z",
                    "value": true
                }
            },
            "state": {
                "buttonevent": {
                    "lastupdated": "2021-05-03T20:58:47Z",
                    "value": 1002
                },
                "eventduration": {
                    "lastupdated": "2021-05-03T20:59:21Z",
                    "value": 1
                }
            },
            "type": "ZHASwitch",
            "uniqueid": "00:17:88:01:08:f7:76:8c-02-fc00"
        }
    ],
    "swversion": "5.45.1.17846",
    "uniqueid": "00:17:88:01:08:f7:76:8c"
}

Introspection on attributes could be useful in general, we can add this to the above output — but I think it might be better to make it query-able with a specific request, something like:

GET http://127.0.0.1/api/<apikey>/devices/introspect/00:17:88:01:08:f7:76:8c GET http://127.0.0.1/api/<apikey>/devices/introspect/00:17:88:01:08:f7:76:8c-02-fc00

GET http://127.0.0.1/api/<apikey>/devices/introspect/00:17:88:01:08:f7:76:8c-02-fc00/state/buttonevent

{
  "type": "uint32",
  "buttons": [ { "button": 1000, "name": "Top Button" }, ... ],
  "values": [
    {"value": 1002, "description": "Short press"},
    {"value": 1001, "description": "Hold"},
   ...
  ]
}

Note: The uniqueid in the URL can be one from the /lights and /sensorendpoints.

Since we already have the button maps in the C++ code such a thing could be generated rather easily. For other attributes often a generic approach can be applied.

What do you think, any ideas on alternative formats and how the calls should look like?

Kane610 commented 3 years ago

Could we have battery as its on item in subdevices? Normalize battery to always be its own separate item, some devices report zhabattery others as part of config. Should always be the same way.

I would very much like to be able to do one major call that can explain all data both real data as well as device descriptions. Doing per device description requests would be up to 400 requests per start up.

A websocket for adding a device should send a complete device description so we don't need to additionally need to poll any data to create it.

Zehir commented 3 years ago

An unit items inside the state could be usefull like °C, second, kwh, lx, %, ... And like for temperature sometime you need to divide the value by 100 to get a good value like 2300 mean 23.00 °C

Zehir commented 3 years ago

About the buttonevent values, what about split the key id and the action id like having 1 and 2 instead of 1002 ?

Kane610 commented 3 years ago

About the buttonevent values, what about split the key id and the action id like having 1 and 2 instead of 1002 ?

Thats gonna be more complex because you will go into non standardized territory.

TURN_ON, TURN_OFF, DIM_UP, DIM_DOWN, LEFT, RIGHT, OPEN, CLOSE, BOTH_BUTTONS, TOP_BUTTONS, BOTTOM_BUTTONS, BUTTON_1, BUTTON_2, BUTTON_3, BUTTON_4, BUTTON_5, BUTTON_6, BUTTON_7, BUTTON_8, SIDE_1, SIDE_2, SIDE_3, SIDE_4, SIDE_5, SIDE_6

SHORT_PRESS,SHORT_RELEASE,LONG_PRESS,LONG_RELEASE,DOUBLE_PRESS,TRIPLE_PRESS,QUADRUPLE_PRESS,QUINTUPLE_PRESS,ROTATED,ROTATED_FAST,ROTATION_STOPPED,AWAKE,MOVE,DOUBLE_TAP,SHAKE,FREE_FALL,FLIP_90,FLIP_180,MOVE_ANY,DOUBLE_TAP_ANY,TURN_CW,TURN_CCW,ROTATE_FROM_SIDE_1,ROTATE_FROM_SIDE_2,ROTATE_FROM_SIDE_3,ROTATE_FROM_SIDE_4,ROTATE_FROM_SIDE_5,ROTATE_FROM_SIDE_6,

Zehir commented 3 years ago

About the buttonevent values, what about split the key id and the action id like having 1 and 2 instead of 1002 ?

Thats gonna be more complex because you will go into non standardized territory.

TURN_ON, TURN_OFF, DIM_UP, DIM_DOWN, LEFT, RIGHT, OPEN, CLOSE, BOTH_BUTTONS, TOP_BUTTONS, BOTTOM_BUTTONS, BUTTON_1, BUTTON_2, BUTTON_3, BUTTON_4, BUTTON_5, BUTTON_6, BUTTON_7, BUTTON_8, SIDE_1, SIDE_2, SIDE_3, SIDE_4, SIDE_5, SIDE_6

For Ikea remote (the round one) did you have TOOGLE, DIM_UP, DIM_DOWN, LEFT_BUTTON, RIGHT_BUTTON or TOOGLE, TOP_BUTTON, BOTTOM_BUTTON, LEFT_BUTTON, RIGHT_BUTTON ?

There is also the Mi Wireless Switch One Button that send an 1000 event. Like for long press you get 1000, 1001 and 1003

Kane610 commented 3 years ago
TRADFRI_REMOTE = {
    (CONF_SHORT_PRESS, CONF_TURN_ON): {CONF_EVENT: 1002},
    (CONF_LONG_PRESS, CONF_TURN_ON): {CONF_EVENT: 1001},
    (CONF_SHORT_PRESS, CONF_DIM_UP): {CONF_EVENT: 2002},
    (CONF_LONG_PRESS, CONF_DIM_UP): {CONF_EVENT: 2001},
    (CONF_LONG_RELEASE, CONF_DIM_UP): {CONF_EVENT: 2003},
    (CONF_SHORT_PRESS, CONF_DIM_DOWN): {CONF_EVENT: 3002},
    (CONF_LONG_PRESS, CONF_DIM_DOWN): {CONF_EVENT: 3001},
    (CONF_LONG_RELEASE, CONF_DIM_DOWN): {CONF_EVENT: 3003},
    (CONF_SHORT_PRESS, CONF_LEFT): {CONF_EVENT: 4002},
    (CONF_LONG_PRESS, CONF_LEFT): {CONF_EVENT: 4001},
    (CONF_LONG_RELEASE, CONF_LEFT): {CONF_EVENT: 4003},
    (CONF_SHORT_PRESS, CONF_RIGHT): {CONF_EVENT: 5002},
    (CONF_LONG_PRESS, CONF_RIGHT): {CONF_EVENT: 5001},
    (CONF_LONG_RELEASE, CONF_RIGHT): {CONF_EVENT: 5003},
}

I'd like to get rid of all these translation dictionaries :)

Kane610 commented 3 years ago

Make the static information as easy to get to as possible

Like this

{
  "type": "uint32",
  "buttons": [ { "button": 1000, "name": "Top Button" }, ... ],
  "values": {
    1002: {"description": "Short press"},
    1001: {"description": "Hold"},
   ...
  }
}

Not like this

{
  "type": "uint32",
  "buttons": [ { "button": 1000, "name": "Top Button" }, ... ],
  "values": [
    {"value": 1002, "description": "Short press"},
    {"value": 1001, "description": "Hold"},
   ...
  ]
}
Zehir commented 3 years ago

And what about a button definition in devices/introspect/ like this :

{
    "id": 1,
    "group": 1, // Like for remotes with more than one group of keys like big remotes with multiple on/off keys
    "type": "TOOGLE" // DIM, ON, OFF
    "side": "UP", //DOWN, LEFT, RIGHT like for ikea remote
    "icon" : "arrow-right" // The icon displayed on the device
}

I know you will need a big dictionary for that.

And for event just :

{
    "id": 1 // Button ID
    "type" : "LONG_PRESS"
}
Kane610 commented 3 years ago

I think it would make sense to provide a list per unique device model to keep it short.

Zehir commented 3 years ago

About the colorcapability could be have a array with key : boolean instead of en binary encoded inside an integer ?

manup commented 3 years ago

About the colorcapability could be have a array with key : boolean instead of en binary encoded inside an integer ?

When you specify the HTTP header: Accept: application/vnd.ddel.v1.1 the output for colorcapabilities will be an array of strings instead the bitmap.

{
  "colorcapabilities": [ "xy", "ct" ]
}
Mimiix commented 3 years ago

I am not developing with deCONZ but i work with REST api's a lot.

For the button maps, it could be useful to add a "metadata" value with a /get request. Perhaps this can also provide some "static" info about devices like its manufacturer name or capabilities.

The URL then points to the corresponding button map related to the device.

Zehir commented 3 years ago

Why set the light state is PUT /api/<apikey>/lights/<id>/state And set the group state is PUT /api/<apikey>/groups/<id>/action And for scenes PUT /api/<apikey>/groups/<group_id>/scenes/<scene_id>/lights/<light_id>/state That don't make sense for me ;)

Kane610 commented 3 years ago

Why set the light state is PUT /api/<apikey>/lights/<id>/state And set the group state is PUT /api/<apikey>/groups/<id>/action That don't make sense for me ;)

Indeed it would be nice to normalize the API as much as possible

Zehir commented 3 years ago

Why set the light state is PUT /api/<apikey>/lights/<id>/state And set the group state is PUT /api/<apikey>/groups/<id>/action That don't make sense for me ;)

Indeed it would be nice to normalize the API as much as possible

You "can't" read the colorloop state so for me action is better. The old one was the same pattern that the Hue API so that make sense for current api version but if you wan't to make your own api format it's should be action both.

Zehir commented 3 years ago

GET http://127.0.0.1/api/<apikey>/devices/introspect/00:17:88:01:08:f7:76:8c GET http://127.0.0.1/api/<apikey>/devices/introspect/00:17:88:01:08:f7:76:8c-02-fc00

Why it's not GET http://127.0.0.1/api/<apikey>/devices/00:17:88:01:08:f7:76:8c/introspect like all the rest of the api ?

manup commented 3 years ago

Good catch, yes it should follow the usual format.

Zehir commented 3 years ago

By splitting action and state you could get the current action or the current state instead of mixing both. Like action is read write and state readonly

Zehir commented 3 years ago

By splitting action and state you could get the current action or the current state instead of mixing both. Like action is read write and state readonly

The action could have the transitionend timestamp of the current action too.

manup commented 3 years ago
GET /api/3B72D8BA24/devices/90:fd:9f:ff:fe:f8:1c:d6-01-1000/state/buttonevent/introspect
{
  "type":"int32",
  "buttons": {
    "1": {"name": "Large middle button"},
    "2": {"name": "Top dimm up button"},
    "3": {"name": "Bottom dimm down button"},
    "4": {"name": "Left arrow button"},
    "5": {"name": "Right arrow button"}
  },
  "values": {
    "1001": {"action": "HOLD", "button":1},
    "1002": {"action": "SHORT_RELEASE", "button": 1},
    "2001": {"action": "HOLD", "button": 2},
    "2002": {"action": "SHORT_RELEASE", "button": 2},
    "2003": {"action": "LONG_RELEASE", "button": 2},
    "3001": {"action": "HOLD", "button": 3},
    "3002": {"action": "SHORT_RELEASE", "button": 3},
    "3003": {"action": "LONG_RELEASE", "button": 3},
    "4001": {"action": "HOLD", "button": 4},
    "4002": {"action": "SHORT_RELEASE", "button": 4},
    "4003": {"action": "LONG_RELEASE", "button": 4},
    "5001": {"action": "HOLD", "button": 5},
    "5002": {"action": "SHORT_RELEASE", "button": 5},
    "5003": {"action": "LONG_RELEASE", "button": 5}
  }
}

I'm currently working on the C++ code to get this running. The output above is for the IKEA 5 button remote based on the current button_maps.json file in v2.12.0-beta.

In values object the action is a constant string value which is mapped from the buttonevent MODULO 1000 value. The button is the buttonevent / 1000 value, here it is an integer and also serves as key into the buttons object for easier access — maybe it should be a string, but on the other hand buttonevent is naturally an integer too?

Another idea would be to replace the button integer by a string constant as well like BUTTON_1?

Zehir commented 3 years ago

For me if it's a numerical value it's should be an integer or a float except if they have leading zeros. And what about the devices not inside button_maps.json can you get the button count on with a zigbee query ?

manup commented 3 years ago

Rather difficult, for these I think it's simpler to just add entries in the button_maps.json just for introspection and documentation generator.