Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
33d23f2
Added configurable volume step
May 8, 2025
edf094d
Linting
May 8, 2025
d489b32
Updated version
May 8, 2025
5ff83c6
Removed duplicate volume/mute features
May 9, 2025
f2fa96a
Fixed wrong volume step management
May 9, 2025
7f5b044
Fixed wrong volume step management
May 9, 2025
af816ca
Fixed volume control replaced by chromecast volume commands even if n…
May 9, 2025
a454aa0
Added 3 simple commands for volume control because Chromecast when en…
May 10, 2025
b54674f
Fixed bug in volume set
May 10, 2025
1e333bd
Added new simple commands to other profiles where volume up/down is s…
May 10, 2025
f8dc641
Fixed volume step not updated in reconfiguration and added traces
May 10, 2025
bb06ebc
Fixed volume step not updated in reconfiguration and added traces
May 10, 2025
800e7be
Fix
May 10, 2025
932793b
Linting
May 10, 2025
857f0eb
Potential fix for failed reconnection
albaintor Jul 18, 2025
18f9ec3
Merge branch 'main' into stopworking
albaintor Jul 18, 2025
cf6a983
Added remote entity in addition of media player entity
albaintor Sep 6, 2025
be86752
Merge branch 'main' into remote_entity
albaintor Sep 6, 2025
8a9b303
Linting
albaintor Sep 6, 2025
a095050
Updated readme and fix on remote entity
albaintor Sep 6, 2025
87c8191
Typo
albaintor Sep 6, 2025
62927a8
Added buttons and UI mapping for remote entity
albaintor Sep 6, 2025
1b6263e
Linting
albaintor Sep 6, 2025
bbc8a29
Requested changes
albaintor Sep 17, 2025
4d30bd5
Better async handling of commands/command sequenc
albaintor Sep 17, 2025
0786a3f
Linting
albaintor Sep 17, 2025
fa8bf44
Better way to handle long commands
albaintor Sep 18, 2025
fbb9678
Linting
albaintor Sep 18, 2025
db2a1ed
Fixed wrong timeout unit
albaintor Sep 18, 2025
c54fcfe
Removed simple commands block because simple commands should be handl…
albaintor Sep 18, 2025
c525d13
Cleaning
albaintor Sep 18, 2025
cec539c
Linting
albaintor Sep 18, 2025
3021b28
Fixes after testing
albaintor Sep 19, 2025
312e663
Merge branch 'main' into remote_entity
zehnm Oct 1, 2025
e02a933
remote.py linting
zehnm Oct 1, 2025
ba37423
Merge branch 'main' into remote_entity
zehnm Nov 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 74 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ development.

- [Requirements and setting](docs/settings.md).
- Multiple Android TV devices are supported with version 0.5.0 and newer.
- A [media player entity](https://github.com/unfoldedcircle/core-api/blob/main/doc/entities/entity_media_player.md)
is exposed per Android TV device to the Remote.
- A [media player entity](https://github.com/unfoldedcircle/core-api/blob/main/doc/entities/entity_media_player.md) and a [remote entity](https://github.com/unfoldedcircle/core-api/blob/main/doc/entities/entity_remote.md)
are exposed per Android TV device to the Remote.
- Device profiles allow device-specific support and custom key bindings, for example, double-click or long-press actions.
See [command mappings](docs/command_mapping.md) for more information.

Expand All @@ -30,6 +30,78 @@ Preview features:

The preview features are not enabled by default. They can be enabled in the device configuration of the setup flow.

## Configuration

After running the setup flow and configuring your device, 2 new entities will be available :
- Media Player entity : should cover most needs with predefined commands
- Remote entity : should be used to run custom commands or command sequences

The available commands depend on the device capabilities :

| Command | Description |
|-------------------|---------------------|
| on | Turn on |
| off | Turn off |
| toggle | Power toggle |
| play_pause | Play/pause |
| stop | Stop |
| previous | Previous chapter |
| next | Next chapter |
| fast_forward | Fast forward |
| rewind | Rewind |
| volume_up | Volume up |
| volume_down | Volume down |
| mute_toggle | Mute toggle |
| mute | Mute |
| unmute | Unmute |
| repeat | Repeat |
| shuffle | Shuffle |
| channel_up | Channel up |
| channel_down | Channel down |
| cursor_up | Cursor up |
| cursor_down | Cursor down |
| cursor_left | Cursor left |
| cursor_right | Cursor right |
| cursor_enter | Cursor enter |
| digit_0 | 0 |
| digit_1 | 1 |
| digit_2 | 2 |
| digit_3 | 3 |
| digit_4 | 4 |
| digit_5 | 5 |
| digit_6 | 6 |
| digit_7 | 7 |
| digit_8 | 8 |
| digit_9 | 9 |
| function_red | Red |
| function_green | Green |
| function_yellow | Yellow |
| function_blue | Blue |
| home | Home |
| menu | Menu |
| context_menu | Context menu |
| guide | Guide |
| info | Info |
| back | Back |
| record | Record |
| my_recordings | My recordings |
| live | Live |
| audio_track | Next audio track |
| subtitle | Next subtitle track |
| settings | Settings |

In addition these specific commands are also available depending on the device capabilities :

**Any device :**
`CURSOR_ENTER_LONG, HOME_LONG, MENU_LONG, KEYCODE_STAR, KEYCODE_POUND, KEYCODE_A to KEYCODE_Z, KEYCODE_COMMA, KEYCODE_PERIOD, KEYCODE_SPACE, KEYCODE_DEL, KEYCODE_MINUS, KEYCODE_EQUALS, KEYCODE_LEFT_BRACKET, KEYCODE_RIGHT_BRACKET, KEYCODE_BACKSLASH, KEYCODE_SEMICOLON, KEYCODE_APOSTROPHE, KEYCODE_SLASH, KEYCODE_AT, KEYCODE_PLUS, KEYCODE_PAGE_UP, KEYCODE_PAGE_DOWN, KEYCODE_F1 to KEYCODE_F12`

**Dune HD :** `YOUTUBE, NETFLIX, PRIMEVIDEO, FACTORYTEST, DISNEY`

**Shield TV :** `SCREENSAVER`

**Philips Android TV:** `TELETEXT`


## Standalone Usage
### Setup

Expand Down
2 changes: 1 addition & 1 deletion driver.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,5 @@
}
]
},
"release_date": "2025-09-18"
"release_date": "2025-09-06"
}
19 changes: 19 additions & 0 deletions src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,30 @@
from dataclasses import dataclass
from typing import Iterator

from ucapi import EntityTypes

_LOG = logging.getLogger(__name__)

_CFG_FILENAME = "config.json"


def create_entity_id(device_id: str, entity_type: EntityTypes) -> str:
"""Create a unique entity identifier for the given receiver and entity type."""
return f"{entity_type.value}.{device_id}"


def device_from_entity_id(entity_id: str) -> str | None:
"""
Return the device id prefix of an entity_id.

The prefix is the part before the first dot in the name and refers to the device identifier.

:param entity_id: the entity identifier
:return: the device prefix, or None if entity_id doesn't contain a dot
"""
return entity_id.split(".", 1)[1]


@dataclass
class AtvDevice:
"""Android TV device configuration."""
Expand Down
104 changes: 104 additions & 0 deletions src/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"""
Android TV constants.

:copyright: (c) 2023-2025 by Unfolded Circle ApS.
:license: MPL-2.0, see LICENSE for more details.
"""

from ucapi.media_player import Commands

# pylint: disable=C0301
from ucapi.ui import Buttons, DeviceButtonMapping, EntityCommand, UiPage

REMOTE_BUTTONS_MAPPING: list[DeviceButtonMapping] = [
DeviceButtonMapping(button=Buttons.BACK, short_press=EntityCommand(**{"cmd_id": Commands.BACK})),
DeviceButtonMapping(button=Buttons.HOME, short_press=EntityCommand(**{"cmd_id": Commands.HOME})),
DeviceButtonMapping(button=Buttons.CHANNEL_DOWN, short_press=EntityCommand(**{"cmd_id": Commands.CHANNEL_DOWN})),
DeviceButtonMapping(button=Buttons.CHANNEL_UP, short_press=EntityCommand(**{"cmd_id": Commands.CHANNEL_UP})),
DeviceButtonMapping(button=Buttons.DPAD_UP, short_press=EntityCommand(**{"cmd_id": Commands.CURSOR_UP})),
DeviceButtonMapping(button=Buttons.DPAD_DOWN, short_press=EntityCommand(**{"cmd_id": Commands.CURSOR_DOWN})),
DeviceButtonMapping(button=Buttons.DPAD_LEFT, short_press=EntityCommand(**{"cmd_id": Commands.CURSOR_LEFT})),
DeviceButtonMapping(button=Buttons.DPAD_RIGHT, short_press=EntityCommand(**{"cmd_id": Commands.CURSOR_RIGHT})),
DeviceButtonMapping(button=Buttons.DPAD_MIDDLE, short_press=EntityCommand(**{"cmd_id": Commands.CURSOR_ENTER})),
DeviceButtonMapping(button=Buttons.PLAY, short_press=EntityCommand(**{"cmd_id": Commands.PLAY_PAUSE})),
DeviceButtonMapping(button=Buttons.PREV, short_press=EntityCommand(**{"cmd_id": Commands.PREVIOUS})),
DeviceButtonMapping(button=Buttons.NEXT, short_press=EntityCommand(**{"cmd_id": Commands.NEXT})),
DeviceButtonMapping(button=Buttons.VOLUME_UP, short_press=EntityCommand(**{"cmd_id": Commands.VOLUME_UP})),
DeviceButtonMapping(button=Buttons.VOLUME_DOWN, short_press=EntityCommand(**{"cmd_id": Commands.VOLUME_DOWN})),
DeviceButtonMapping(button=Buttons.MUTE, short_press=EntityCommand(**{"cmd_id": Commands.MUTE_TOGGLE})),
DeviceButtonMapping(button=Buttons.STOP, short_press=EntityCommand(**{"cmd_id": Commands.STOP})),
DeviceButtonMapping(button=Buttons.MENU, short_press=EntityCommand(**{"cmd_id": Commands.CONTEXT_MENU})),
]


REMOTE_UI_PAGES: list[UiPage] = [
UiPage(
**{
"page_id": "Android TV numbers",
"name": "Android TV numbers",
"grid": {"height": 4, "width": 3},
"items": [
{
"command": {"cmd_id": "remote.send", "params": {"command": Commands.DIGIT_1}},
"location": {"x": 0, "y": 0},
"text": "1",
"type": "text",
},
{
"command": {"cmd_id": "remote.send", "params": {"command": Commands.DIGIT_2}},
"location": {"x": 1, "y": 0},
"text": "2",
"type": "text",
},
{
"command": {"cmd_id": "remote.send", "params": {"command": Commands.DIGIT_3}},
"location": {"x": 2, "y": 0},
"text": "3",
"type": "text",
},
{
"command": {"cmd_id": "remote.send", "params": {"command": Commands.DIGIT_4}},
"location": {"x": 0, "y": 1},
"text": "4",
"type": "text",
},
{
"command": {"cmd_id": "remote.send", "params": {"command": Commands.DIGIT_5}},
"location": {"x": 1, "y": 1},
"text": "5",
"type": "text",
},
{
"command": {"cmd_id": "remote.send", "params": {"command": Commands.DIGIT_6}},
"location": {"x": 2, "y": 1},
"text": "6",
"type": "text",
},
{
"command": {"cmd_id": "remote.send", "params": {"command": Commands.DIGIT_7}},
"location": {"x": 0, "y": 2},
"text": "7",
"type": "text",
},
{
"command": {"cmd_id": "remote.send", "params": {"command": Commands.DIGIT_8}},
"location": {"x": 1, "y": 2},
"text": "8",
"type": "text",
},
{
"command": {"cmd_id": "remote.send", "params": {"command": Commands.DIGIT_9}},
"location": {"x": 2, "y": 2},
"text": "9",
"type": "text",
},
{
"command": {"cmd_id": "remote.send", "params": {"command": Commands.DIGIT_0}},
"location": {"x": 1, "y": 3},
"text": "0",
"type": "text",
},
],
}
),
]
Loading