diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ca598c..606ada4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ _Changes in the next release_ ### Added - New media-player entity features: context_menu, settings. Support has been included in firmware v1.7.4. +- New remote-entity for sending commands. This allows to write custom integrations for devices which don't fit a media-player entity. + - The first integration supporting remote-entity will be Home Assistant. --- diff --git a/doc/entities/README.md b/doc/entities/README.md index 9bc0356..195e19b 100644 --- a/doc/entities/README.md +++ b/doc/entities/README.md @@ -1,13 +1,5 @@ # Entities ---- -**TODO** -- Describe setup process. -- Add `virtual remote` concept & custom layouts. -- Add `activities` concept. -- Link to demo integration once published. ---- - Entities represent devices by describing features and exposing controls. An integration can offer available entities for control. The user might select and configure entities, that will be available for the user interface. @@ -29,6 +21,7 @@ Supported entities: - [Cover](entity_cover.md) - [Light](entity_light.md) - [Media Player](entity_media_player.md) +- [Remote](entity_remote.md) - [Sensor](entity_sensor.md) The 🚧 icon within the entity descriptions indicates a planned feature and will most likely not be (fully) implemented diff --git a/doc/entities/entity_remote.md b/doc/entities/entity_remote.md new file mode 100644 index 0000000..0a79903 --- /dev/null +++ b/doc/entities/entity_remote.md @@ -0,0 +1,556 @@ +# Remote Entity + +A remote entity can send commands to a controllable device. + +â„šī¸ Supported in remote-core / Core Simulator from version 0.43.0. + +## Features + +| Name | R | W | Description | +|----------|---|----|------------------------------------------------------------------------------------------------------------------------------------------------------| +| send_cmd | ❌ | ✅ | Default feature of a remote entity. Always present, even if not specified. | +| on_off | ✅ | ✅ | Remote has on and off commands. | +| toggle | ❌ | ✅ | Power toggle support. If there's no native support, the remote will use the current state of the remote to send the corresponding on or off command. | + +- R: readable + - ✅ Feature has a readable attribute to retrieve the current or available values. + - ❌ Feature value(s) cannot be read. +- W: writeable + - ✅ Feature has one or multiple commands to trigger an action or set a value. + - ❌ No corresponding command(s), only the current value(s) of the feature can be read. + +### Attributes + +Entity attributes are controlled by features. Multiple features can act on the same attribute. See Events on how to +notify the remote about an updated attribute. The attributes have to be listed as properties under `attributes` with +their current value. + +| Attribute | Features | Type | Values | Description | +|-----------|----------|------|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------| +| state | on_off | enum | [States](#states) | State of the controlled device, it's either on or off. | +| | toggle | | | Toggle inverts the current state. If the driver doesn't provide the toggle feature, the remote uses the current value and calls on or off. | + +### States + +The remote entity provides the following entity `state` values: + +| Value | Description | +|-------|-------------------------------| +| ON | The controlled device is on. | +| OFF | The controlled device is off. | + +See [common entity states](README.md#states). + +### Device classes + +None. + +### Options + +Optional features of the remote entity. + +| Name | Type | Values | Default | Description | +|-----------------|--------|---------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------| +| simple_commands | array | string(20) | [] | Optional list of supported commands. If not provided, the integration driver has to document the available commands for the user. | +| button_mapping | array | DeviceButtonMapping | [] | Optional command mapping for the physical buttons. | +| user_interface | object | UserInterface | {} | Optional user interface definition for the supported commands. | + +If the available commands are known by the integration driver, they should be made available in the `simple_commands` +option. This allows the Remote Two configuration in the web-configurator to show all supported commands to the user. +Otherwise, the user has to know the command names and has to enter them manually for each command. + +If `button_mapping` or `user_interface` options are not provided, an automatic mapping is attempted based on the command +names in `simple_commands`. The mapping logic is similar to the infrared codesets in the internal IR-remotes. +See [Command name patterns](#command-name-patterns) below on how commands should be named. + +The automatic button and user interface creation is only done once at the initial entity configuration. From that point +on the user can independently modify the UI and button mapping. Only changes in the `simple_commands` list will be +applied to the configured entities in Remote Two, if the available entities are reloaded from the integration driver. + +#### Simple commands + +A simple command is fully defined by its name and doesn't have any further arguments or related attributes. It's +comparable to the simple commands in the media-player entity, with fewer naming restrictions. + +Restrictions: +- A command name may not include whitespace characters. +- The maximum length of a command name is 20 characters. +- A command name may not be any of the defined `cmd_id` commands: on, off, toggle, send_cmd, send_cmd_sequence. +- Commands cannot have any further parameters. For example, it's not possible to have a `switch_channel` command with + the channel number as parameter. + +Command naming recommendations: + +- Command names should be short and follow naming patterns. +- Uppercase naming is preferred. +- Dedicated power on/off or toggle commands should not be added. Use the `on_off` and `toggle` features with the defined + `on`, `off` and `toggle` commands. + +#### Command Name Patterns + +Even though command names can be freely defined, it's highly recommended to follow a naming pattern and use the +recommended names for common commands. This allows better integration into Remote Two, like default UI mappings and +grouping of similar commands. + +- Navigation: `CURSOR_UP`, `CURSOR_DOWN`, `CURSOR_LEFT`, `CURSOR_RIGHT`, `CURSOR_ENTER`, `BACK`, `EXIT` +- Menus: `HOME`, `MENU`, `CONTEXT_MENU`, `GUIDE`, `INFO`, `SETTINGS` +- Volume control: `VOLUME_UP`, `VOLUME_DOWN`, `MUTE_TOGGLE`, `MUTE`, `UNMUTE` +- Media playback: `PLAY_PAUSE`, `STOP`, `PREVIOUS`, `NEXT`, `FAST_FORWARD`, `REWIND` + +See [media-player entity commands](entity_media_player.md#commands) for further commands. + +Prefixes for other common functions: + +- `INPUT_`: source inputs, e.g. `INPUT_AUX1`, `INPUT_TV`, `INPUT_RADIO` +- `APP_`: applications, e.g. `APP_MY_TV_STREAMING`, `APP_INTERNET` +- `MODE_`: mode changing functions, e.g. `MODE_16/9`, `MODE_SURROUND`, `MODE_FOOTBALL` +- `MENU_`: additional menus, e.g. `MENU_SMART_HOME`, `MENU_QUICK` +- `DIGIT_`: additional input digits besides the pre-defined numpad digits, e.g. `DIGIT_10`, `DIGIT_10+` +- `ZONE_`: multi-room functions, e.g. `ZONE_A`, `ZONE_MULTIROOM` + +#### Button mapping + +A default button mapping can be provided in `options.button_mapping`. + +The `DeviceButtonMapping` object is defined in the [Integration-API](../../integration-api) and is based on the same +definition in the Core-API. + +Example of `entity.options` object: +```json +{ + "simple_commands": [ + "VOLUME_UP", + "VOLUME_DOWN", + "HOME", + "CURSOR_UP", + "CURSOR_DOWN", + "CURSOR_LEFT", + "CURSOR_RIGHT", + "CURSOR_ENTER" + ], + "button_mapping": [ + { + "button": "POWER", + "short_press": { + "cmd_id": "remote.toggle" + } + }, + { + "button": "RED", + "short_press": { + "cmd_id": "remote.send_cmd", + "params": { + "command": "VOLUME_DOWN", + "repeat": 10 + } + } + }, + { + "button": "DPAD_UP", + "short_press": { + "cmd_id": "CURSOR_UP" + } + }, + { + "button": "DPAD_MIDDLE", + "short_press": { + "cmd_id": "CURSOR_ENTER" + }, + "long_press": { + "cmd_id": "MENU" + } + }, + { + "button": "DPAD_DOWN", + "short_press": { + "cmd_id": "CURSOR_DOWN" + } + }, + { + "button": "BLUE", + "short_press": { + "cmd_id": "remote.send_cmd_sequence", + "params": { + "sequence": "HOME,CURSOR_DOWN,CURSOR_RIGHT,CURSOR_ENTER", + "delay": 200 + } + } + } + ] +} +``` + +See [Commands](#commands) about entity commands and their short vs fully qualified syntax. For example `on` and `off` vs +`remote.on` and `remote.off`. + +Button identifiers for Remote Two: +- `BACK` +- `HOME` +- `VOICE` +- `VOLUME_UP`, `VOLUME_DOWN`, `MUTE` +- `DPAD_UP`, `DPAD_DOWN`, `DPAD_LEFT`, `DPAD_RIGHT`, `DPAD_MIDDLE` +- `GREEN`, `YELLOW`, `RED`, `BLUE` +- `CHANNEL_UP`, `CHANNEL_DOWN` +- `PREV`, `PLAY`, `NEXT` +- `POWER` + +A detailed button mapping can be retrieved with the Core-API: `GET /api/cfg/device/button_layout`. + +#### User interface + +A default user interface can be provided in `options.user_interface`. + +The `UserInterface` object is defined in the [Integration-API](../../integration-api) and is based on the Core-API +`ActivityUserInterface` definition. + +##### UI page example + +![ui-page-media.png](../img/ui-page-media.png) + +`entity.options` object: +```json +{ + "simple_commands": [ + "MY_RECORDINGS", + "MY_APPS", + "REVERSE", + "PLAY", + "PAUSE", + "FORWARD", + "RECORD" + ], + "user_interface": { + "pages": [ + { + "page_id": "media", + "name": "Media", + "grid": { "width": 4, "height": 6 }, + "items": [ + { + "type": "text", + "text": "Recordings", + "command": { + "cmd_id": "MY_RECORDINGS" + }, + "location": { "x": 0, "y": 2 }, + "size": { "width": 2, "height": 1 } + }, + { + "type": "text", + "text": "Apps", + "command": { + "cmd_id": "MY_APPS" + }, + "location": { "x": 2, "y": 2 }, + "size": { "width": 2, "height": 1 } + }, + { + "type": "icon", + "icon": "uc:bw", + "command": { + "cmd_id": "REVERSE" + }, + "location": { "x": 0, "y": 5 } + }, + { + "type": "icon", + "icon": "uc:play", + "command": { + "cmd_id": "PLAY" + }, + "location": { "x": 1, "y": 5 } + }, + { + "type": "icon", + "icon": "uc:pause", + "command": { + "cmd_id": "PAUSE" + }, + "location": { "x": 2, "y": 5 } + }, + { + "type": "icon", + "icon": "uc:ff", + "command": { + "cmd_id": "FORWARD" + }, + "location": { "x": 3, "y": 5 } + }, + { + "type": "icon", + "icon": "uc:rec", + "command": { + "cmd_id": "RECORD" + }, + "location": { "x": 2, "y": 4 } + } + ] + } + ] + } +} +``` + +Screen grid sizes for Remote Two: +- Minimal size: 1 x 1 +- Maximum size: 8 x 12 +- Default: 4 x 6 + +The screen layout grid definition can be retrieved with the Core-API: `GET /api/cfg/device/screen_layout`. + +## Integration API + +### Commands + +The integration driver has to implement a handler for the `entity_command` WebSocket message to process the following +command requests in `msg_data.cmd_id`. See [Command examples](#command-examples) and [Integration-API](../../integration-api) +for the full message structure. + +| cmd_id | Parameters | Type | Description | +|-------------------|------------|--------------|----------------------------------------------------------------------------------------------| +| on | - | | Send the on-command to the controlled device. | +| off | - | | Send the off-command. | +| toggle | - | | Toggle the power state of the controlled device, either from on -> off or from off -> on. | +| send_cmd | command | String(20) | A single command. | +| | repeat | Number | Optional: how many times the command shall be repeated. Defaults to 1 if not specified. | +| | delay | Number | Optional: delay in milliseconds between repeated commands. | +| | hold | Number | Optional: time in milliseconds before a command is released. Defaults to 0 if not specified. | +| send_cmd_sequence | sequence | String array | Command list. Same defaults are used as for the `send_cmd` command. | +| | repeat | Number | Optional: how many times each command shall be repeated. | +| | delay | Number | Optional: delay in milliseconds between commands. | +| | hold | Number | Optional: time in milliseconds before each command is released. | + +- The `command` and `sequence` parameters will either contain a simple command (if specified in entity options) or a + freetext command. It is up to the integration driver to verify and validate commands. +- A command name may not include whitespace characters. +- The maximum length of a command name is 20 characters. +- A command name may not be any of the defined `cmd_id` commands: on, off, toggle, send_cmd, send_cmd_sequence +- If no `delay` parameter is included, the integration driver has to choose an appropriate delay based on the command + and controlled device. + +#### Using commands for button mappings and UI elements + +â€ŧī¸ Attention: the `cmd_id` for button mapping and UI item commands is slightly different than the remote-entity +[Commands](#commands)! +- Core-API button mapping and UI item commands: + - The `cmd_id` value is either a simple command or an entity command with the entity type prefix. +- Integration-API entity commands: + - The `cmd_id` value refers to the defined [Commands](#commands) without the entity type prefix. + - A simple command will always be wrapped into the remote-entity command `send_cmd`. + +When defining commands in button mappings or UI elements, either entity commands or simple commands can be used. +- Entity commands are all the commands an entity defines (see `cmd_id` column in [Commands](#commands) above). Example: +```json +{ + "cmd_id": "toggle" +} +``` + +- If the integration driver provides the `simple_commands` option, the specified commands can be directly used in + `cmd_id`. Example: +```json +{ + "cmd_id": "PLAY" +} +``` + +- Specified simple commands can also be used in the `send_cmd` entity command. +- The `command` parameter of `send_cmd` allows any command name ("free-text") and it's up to the integration driver to + validate the received commands. + Example of a simple command used in `send_cmd`: +```json +{ + "cmd_id": "send_cmd", + "params": { + "command": "PLAY" + } +} +``` + +- If an entity command is used it's recommended to prefix it with the entity type to use the same naming convention as + in the Core-API. + - Using the fully qualified command name might make it easier in the integration driver to distinguish the commands. + - A missing entity type prefix is automatically added when an available remote-entity is configured in Remote Two. + - When working with the Core-API, for example reconfiguring a button mapping, the fully qualified command name is + required. + +Recommended fully-qualified entity command name: +```json +{ + "cmd_id": "remote.send_cmd", + "params": { + "command": "PLAY" + } +} +``` + +### Events + +The `entity_change` event must be emitted by the integration driver if the state or an attribute of the remote device +changes. + +The following attributes must be included: + +| Attribute | Description | +|---------------|-------------------------------| +| state | New entity [state](#states). | + +### Command examples + +Remote-entity examples of received `entity_command` WebSocket messages in an integration driver. + +#### on + +```json +{ + "kind": "req", + "id": 124, + "msg": "entity_command", + "msg_data": { + "entity_type": "remote", + "entity_id": "remote-1", + "cmd_id": "on" + } +} +``` + +#### off + +```json +{ + "kind": "req", + "id": 124, + "msg": "entity_command", + "msg_data": { + "entity_type": "remote", + "entity_id": "remote-1", + "cmd_id": "off" + } +} +``` + +#### toggle + +```json +{ + "kind": "req", + "id": 124, + "msg": "entity_command", + "msg_data": { + "entity_type": "remote", + "entity_id": "remote-1", + "cmd_id": "toggle" + } +} +``` + +#### send command + +```json +{ + "kind": "req", + "id": 124, + "msg": "entity_command", + "msg_data": { + "entity_type": "remote", + "entity_id": "remote-1", + "cmd_id": "send_cmd", + "params": { + "command": "DPAD_UP" + } + } +} +``` + +#### send long-press command + +```json +{ + "kind": "req", + "id": 124, + "msg": "entity_command", + "msg_data": { + "entity_type": "remote", + "entity_id": "remote-1", + "cmd_id": "send_cmd", + "params": { + "command": "CURSOR_ENTER", + "hold": 800 + } + } +} +``` + +#### send repeated command + +```json +{ + "kind": "req", + "id": 124, + "msg": "entity_command", + "msg_data": { + "entity_type": "remote", + "entity_id": "remote-1", + "cmd_id": "send_cmd", + "params": { + "command": "VOLUME_DOWN", + "repeat": 5 + } + } +} +``` + +#### send sequence + +```json +{ + "kind": "req", + "id": 124, + "msg": "entity_command", + "msg_data": { + "entity_type": "remote", + "entity_id": "remote-1", + "cmd_id": "send_cmd_sequence", + "params": { + "sequence": ["1", "2", "3", "ENTER"], + "delay": 100 + } + } +} +``` + +### Event examples + +#### device was turned on + +```json +{ + "kind": "event", + "msg": "entity_change", + "cat": "ENTITY", + "msg_data": { + "entity_type": "remote", + "entity_id": "remote-1", + "attributes": { + "state": "on" + } + } +} +``` + +#### device was turned off + +```json +{ + "kind": "event", + "msg": "entity_change", + "cat": "ENTITY", + "msg_data": { + "entity_type": "remote", + "entity_id": "remote-1", + "attributes": { + "state": "off" + } + } +} +``` diff --git a/doc/img/ui-page-media.png b/doc/img/ui-page-media.png new file mode 100644 index 0000000..612a70c Binary files /dev/null and b/doc/img/ui-page-media.png differ diff --git a/integration-api/asyncapi.yaml b/integration-api/asyncapi.yaml index cc8174d..5be28f1 100644 --- a/integration-api/asyncapi.yaml +++ b/integration-api/asyncapi.yaml @@ -8,10 +8,11 @@ asyncapi: 2.2.0 id: 'urn:com:unfoldedcircle:integration' info: title: Remote Two WebSocket Integration API - version: '0.9.1-beta' + version: '0.10.0-beta' contact: name: API Support url: https://github.com/unfoldedcircle/core-api/issues + email: support@unfoldedcircle.com license: name: Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) url: https://creativecommons.org/licenses/by-sa/4.0/ @@ -1298,8 +1299,10 @@ components: cmd_id: type: string params: + # TODO Define parameter objects with oneOf or just document possible objects? description: | - TODO Define parameter objects with oneOf or just document possible objects? + See for command parameter + definitions. type: object required: - entity_type @@ -1611,7 +1614,10 @@ components: entity: description: | Common definition of an entity. Concrete entities are: `cover`, `button`, `climate`, `light`, `media_player`, - `sensor`, `switch`. + `remote`,`sensor`, `switch`. + + See [entity documentation](https://github.com/unfoldedcircle/core-api/blob/main/doc/entities/) + for more information. The `entity_type` value acts as discriminator for the entity type, which defines the supported features and options of an entity. @@ -1660,6 +1666,9 @@ components: A button is stateless. To represent something that can be turned on and off, then the `switch` entity should be used. + + See [button entity documentation](https://github.com/unfoldedcircle/core-api/blob/main/doc/entities/entity_button.md) + for more information. allOf: - $ref: '#/components/schemas/entity' - type: object @@ -1680,6 +1689,9 @@ components: user for the current state. If the switch controls a light source, then the 'light' entity is usually a better choice. + + See [switch entity documentation](https://github.com/unfoldedcircle/core-api/blob/main/doc/entities/entity_switch.md) + for more information. allOf: - $ref: '#/components/schemas/entity' - type: object @@ -1713,6 +1725,9 @@ components: description: | A climate entity controls heating, ventilation and air conditioning (HVAC) devices. This can range from simple fans to personal air conditioning units to integrated building devices. + + See [climate entity documentation](https://github.com/unfoldedcircle/core-api/blob/main/doc/entities/entity_climate.md) + for more information. allOf: - $ref: '#/components/schemas/entity' - type: object @@ -1775,6 +1790,9 @@ components: Entity for covering or opening things like blinds, window covers, curtains, etc. The entity `features` specify the abilities of the cover and the controllable properties, whereas the `device_class` specifies the UI representation. + + See [cover entity documentation](https://github.com/unfoldedcircle/core-api/blob/main/doc/entities/entity_cover.md) + for more information. allOf: - $ref: '#/components/schemas/entity' - type: object @@ -1820,6 +1838,9 @@ components: controlled like setting brightness, hue, color saturation and color temperature. The [HSV color model](https://en.wikipedia.org/wiki/HSL_and_HSV) is used for adjusting color and brightness. + + See [light entity documentation](https://github.com/unfoldedcircle/core-api/blob/main/doc/entities/entity_light.md) + for more information. allOf: - $ref: '#/components/schemas/entity' - type: object @@ -1850,6 +1871,9 @@ components: description: | A media player entity controls playback of media on a device. This could be an online music streaming service, a TV, a stereo or a video player. + + See [media player entity documentation](https://github.com/unfoldedcircle/core-api/blob/main/doc/entities/entity_media_player.md) + for more information. allOf: - $ref: '#/components/schemas/entity' - type: object @@ -1927,6 +1951,42 @@ components: minimum: 2 maximum: 100 + remote: + description: | + A remote entity can send commands to a controllable device. + + See [remote entity documentation](https://github.com/unfoldedcircle/core-api/blob/main/doc/entities/entity_remote.md) + for more information. + allOf: + - $ref: '#/components/schemas/entity' + - type: object + properties: + features: + type: array + items: + type: string + enum: + - send_cmd + - on_off + - toggle + options: + type: object + properties: + simple_commands: + description: | + Optional list of supported commands. If not provided, the integration driver has to document the + available commands for the user. + Example: `["EXIT", "CHANNEL_UP", "CHANNEL_DOWN"]` + type: array + items: + $ref: '#/components/schemas/SimpleRemoteCommand' + button_mapping: + type: array + items: + $ref: '#/components/schemas/DeviceButtonMapping' + user_interface: + $ref: '#/components/schemas/UserInterface' + sensor: description: | A sensor entity provides measured values from devices or dedicated hardware sensors. @@ -1935,6 +1995,9 @@ components: - The `custom` device class allows arbitrary UI labels and units. - The `temperature` device class performs automatic conversion between °C and °F. + + See [sensor entity documentation](https://github.com/unfoldedcircle/core-api/blob/main/doc/entities/entity_sensor.md) + for more information. allOf: - $ref: '#/components/schemas/entity' - type: object @@ -2007,7 +2070,11 @@ components: SimpleCommand: type: string - format: "^[A-Z0-9\/_.:+#*°@%()?-]{1,20}$" + format: "^[A-Z0-9/_.:+#*°@%()?-]{1,20}$" + + SimpleRemoteCommand: + type: string + format: "^\\S{1,20}$" # derived from Core-API integrationSetupChange: without driver_id driverSetupChange: @@ -2387,3 +2454,136 @@ components: description: | Optional icon identifier. If specified the icon will be set. An empty identifier while updating the object removes the existing icon. + + DeviceButtonMapping: + type: object + properties: + button: + description: Physical button identifier + type: string + short_press: + $ref: '#/components/schemas/EntityCommand' + long_press: + $ref: '#/components/schemas/EntityCommand' + required: + - button + + EntityCommand: + type: object + properties: + cmd_id: + description: | + Simple command name or entity command identifier, as returned in the entity command metadata. + type: string + params: + description: | + For entity commands: optional command parameters as key / value pairs. + See entity documentation for available parameters. + type: object + required: + - cmd_id + example: + cmd_id: remote.on + + UserInterface: + type: object + properties: + pages: + type: array + items: + $ref: '#/components/schemas/UserInterfacePage' + + UserInterfacePage: + type: object + properties: + page_id: + type: string + name: + description: Optional page name + type: string + grid: + $ref: '#/components/schemas/Grid' + items: + type: array + items: + $ref: '#/components/schemas/UserInterfaceItem' + required: + - page_id + - grid + - items + + UserInterfaceItem: + description: | + A user interface item is either an icon, text or media information from a media-player entity. + - Icon and text items can be static or linked to a command specified in the `command` field. + - Default size is 1x1 if not specified. + type: object + properties: + type: + description: | + Type of the user interface item: + - `icon`: show an icon, either a UC icon or a custom icon. Field `icon` must contain the icon identifier. + - `text`: show text only from field `text`. + type: string + enum: + - icon + - text + - numpad + icon: + description: | + Optional icon identifier. The identifier consists of a prefix and a resource identifier, separated by `:`. + Available prefixes: + - `uc:` - integrated icon font + - `custom:` - custom resource + type: string + text: + type: string + command: + $ref: '#/components/schemas/EntityCommand' + location: + $ref: '#/components/schemas/GridLocation' + size: + $ref: '#/components/schemas/GridItemSize' + required: + - type + - location + + Grid: + description: Grid layout size. + type: object + properties: + width: + type: integer + minimum: 1 + height: + type: integer + minimum: 1 + required: + - width + - height + + GridItemSize: + description: 'Item size in the button grid. Default size if not specified: 1 x 1' + type: object + properties: + width: + type: integer + minimum: 1 + default: 1 + height: + type: integer + minimum: 1 + default: 1 + GridLocation: + description: Button placement in the grid with 0-based coordinates. + type: object + properties: + x: + type: integer + minimum: 0 + 'y': + type: integer + minimum: 0 + required: + - x + - 'y'