-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
build: cleanup project and use pyproject.toml instead of setup.py (#6)
- Remove legacy setup.py and only use pyproject.toml - Allow patch updates in dependencies - Set version information dynamically from Git tags with setuptools_scm and embedd in package - Add a setup-flow example
- Loading branch information
Showing
17 changed files
with
343 additions
and
129 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -100,3 +100,4 @@ ENV/ | |
.vscode/ | ||
|
||
.DS_Store | ||
/ucapi/_version.py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# API wrapper examples | ||
|
||
This directory contains a few examples on how to use the Remote Two Integration-API wrapper. | ||
|
||
Each example uses a driver metadata definition file. It's a json file named after the example. | ||
The most important fields are: | ||
|
||
- `driver_id`: unique identifier of the driver. Make sure you create a new ID for every driver. | ||
- `port` defines the listening port of the WebSocket server for the Remote Two to connect to. | ||
- This port is published in the mDNS service information. | ||
- `name`: Friendly name of the driver to show. | ||
|
||
See the [WebSocket Integration API documentation](https://github.com/unfoldedcircle/core-api/tree/main/doc/integration-driver) | ||
|
||
## hello_integration | ||
|
||
The [hello_integration.py](hello_integration.py) example is a "hello world" example showing the bare minimum required | ||
to start with an integration driver for the Remote Two. | ||
|
||
It defines a single push button with a callback handler. When pushed, it just prints a message in the console. | ||
|
||
## setup_flow | ||
|
||
The [setup_flow](setup_flow.py) example shows how to define a dynamic setup flow for the driver setup. | ||
|
||
If the user selects the _expert_ option in the main setup screen: | ||
1. An input screen is shown asking to select an item from a dropdown list. | ||
2. The chosen option will be shown in the next input screen with another setting, on how many button entities to create. | ||
3. The number of push buttons are created. | ||
|
||
The available input settings are defined in the [Integration-API asyncapi.yaml definition](https://github.com/unfoldedcircle/core-api/tree/main/integration-api) | ||
and are not yet available as typed Python objects. | ||
|
||
See `Setting` object definition and the referenced SettingTypeNumber, SettingTypeText, SettingTypeTextArea, | ||
SettingTypePassword, SettingTypeCheckbox, SettingTypeDropdown, SettingTypeLabel. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
{ | ||
"driver_id": "setupflow_example", | ||
"version": "0.0.1", | ||
"min_core_api": "0.20.0", | ||
"name": { "en": "Setup Flow Demo" }, | ||
"icon": "uc:integration", | ||
"description": { | ||
"en": "Setup Flow Python integration driver example." | ||
}, | ||
"port": 9081, | ||
"developer": { | ||
"name": "Unfolded Circle ApS", | ||
"email": "[email protected]", | ||
"url": "https://www.unfoldedcircle.com" | ||
}, | ||
"home_page": "https://www.unfoldedcircle.com", | ||
"setup_data_schema": { | ||
"title": { | ||
"en": "Example settings", | ||
"de": "Beispiel Konfiguration", | ||
"fr": "Exemple de configuration" | ||
}, | ||
"settings": [ | ||
{ | ||
"id": "expert", | ||
"label": { | ||
"en": "Configure enhanced options", | ||
"de": "Erweiterte Optionen konfigurieren", | ||
"fr": "Configurer les options avancées" | ||
}, | ||
"field": { | ||
"checkbox": { | ||
"value": false | ||
} | ||
} | ||
} | ||
] | ||
}, | ||
"release_date": "2023-11-03" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
#!/usr/bin/env python3 | ||
"""Integration setup flow example.""" | ||
import asyncio | ||
import logging | ||
from typing import Any | ||
|
||
import ucapi | ||
|
||
loop = asyncio.get_event_loop() | ||
api = ucapi.IntegrationAPI(loop) | ||
|
||
|
||
async def driver_setup_handler(msg: ucapi.SetupDriver) -> ucapi.SetupAction: | ||
""" | ||
Dispatch driver setup requests to corresponding handlers. | ||
Either start the setup process or handle the provided user input data. | ||
:param msg: the setup driver request object, either DriverSetupRequest or UserDataResponse | ||
:return: the setup action on how to continue | ||
""" | ||
if isinstance(msg, ucapi.DriverSetupRequest): | ||
return await handle_driver_setup(msg) | ||
if isinstance(msg, ucapi.UserDataResponse): | ||
return await handle_user_data_response(msg) | ||
|
||
# user confirmation not used in our demo setup process | ||
# if isinstance(msg, UserConfirmationResponse): | ||
# return handle_user_confirmation(msg) | ||
|
||
return ucapi.SetupError() | ||
|
||
|
||
async def handle_driver_setup(msg: ucapi.DriverSetupRequest) -> ucapi.RequestUserInput | ucapi.SetupError: | ||
""" | ||
Start driver setup. | ||
Initiated by Remote Two to set up the driver. | ||
:param msg: not used, value(s) of input fields in the first setup screen. See setup_data_schema in driver metadata. | ||
:return: the setup action on how to continue | ||
""" | ||
# for our demo we clear everything, a real driver might have to handle this differently | ||
api.available_entities.clear() | ||
api.configured_entities.clear() | ||
|
||
# check if user selected the expert option in the initial setup screen | ||
# please note that all values are returned as strings! | ||
if "expert" not in msg.setup_data or msg.setup_data["expert"] != "true": | ||
return ucapi.SetupComplete() | ||
|
||
# Dropdown selections are usually set dynamically, e.g. with found devices etc. | ||
dropdown_items = [ | ||
{"id": "red", "label": {"en": "Red", "de": "Rot"}}, | ||
{"id": "green", "label": {"en": "Green", "de": "Grün"}}, | ||
{"id": "blue", "label": {"en": "Blue", "de": "Blau"}}, | ||
] | ||
|
||
return ucapi.RequestUserInput( | ||
{"en": "Please choose", "de": "Bitte auswählen"}, | ||
[ | ||
{ | ||
"id": "info", | ||
"label": {"en": "Setup flow example", "de": "Setup Flow Beispiel"}, | ||
"field": { | ||
"label": { | ||
"value": { | ||
"en": "This is just some informational text.\n" | ||
+ "Simple **Markdown** is supported!\n" | ||
+ "For example _some italic text_.\n" | ||
+ "## Or a header text\n~~strikethrough txt~~", | ||
} | ||
} | ||
}, | ||
}, | ||
{ | ||
"field": {"dropdown": {"value": "", "items": dropdown_items}}, | ||
"id": "step1.choice", | ||
"label": { | ||
"en": "Choose color", | ||
"de": "Wähle Farbe", | ||
}, | ||
}, | ||
], | ||
) | ||
|
||
|
||
async def handle_user_data_response(msg: ucapi.UserDataResponse) -> ucapi.SetupAction: | ||
""" | ||
Process user data response in a setup process. | ||
Driver setup callback to provide requested user data during the setup process. | ||
:param msg: response data from the requested user data | ||
:return: the setup action on how to continue: SetupComplete if finished. | ||
""" | ||
# values from all screens are returned: check in reverse order | ||
if "step2.count" in msg.input_values: | ||
for x in range(int(msg.input_values["step2.count"])): | ||
button = ucapi.Button( | ||
f"button{x}", | ||
f"Button {x + 1}", | ||
cmd_handler=cmd_handler, | ||
) | ||
api.available_entities.add(button) | ||
|
||
return ucapi.SetupComplete() | ||
|
||
if "step1.choice" in msg.input_values: | ||
choice = msg.input_values["step1.choice"] | ||
print(f"Chosen color: {choice}") | ||
return ucapi.RequestUserInput( | ||
{"en": "Step 2"}, | ||
[ | ||
{ | ||
"id": "info", | ||
"label": { | ||
"en": "Selected value from previous step:", | ||
"de": "Selektierter Wert vom vorherigen Schritt:", | ||
}, | ||
"field": { | ||
"label": { | ||
"value": { | ||
"en": choice, | ||
} | ||
} | ||
}, | ||
}, | ||
{ | ||
"field": {"number": {"value": 1, "min": 1, "max": 100, "steps": 2}}, | ||
"id": "step2.count", | ||
"label": { | ||
"en": "Button instance count", | ||
"de": "Anzahl Button Instanzen", | ||
}, | ||
}, | ||
], | ||
) | ||
|
||
print("No choice was received") | ||
return ucapi.SetupError() | ||
|
||
|
||
async def cmd_handler(entity: ucapi.Button, cmd_id: str, _params: dict[str, Any] | None) -> ucapi.StatusCodes: | ||
""" | ||
Push button command handler. | ||
Called by the integration-API if a command is sent to a configured button-entity. | ||
:param entity: button entity | ||
:param cmd_id: command | ||
:param _params: optional command parameters | ||
:return: status of the command | ||
""" | ||
print(f"Got {entity.id} command request: {cmd_id}") | ||
|
||
return ucapi.StatusCodes.OK | ||
|
||
|
||
@api.listens_to(ucapi.Events.CONNECT) | ||
async def on_connect() -> None: | ||
"""When the remote connects, we just set the device state. We are ready all the time!""" | ||
await api.set_device_state(ucapi.DeviceStates.CONNECTED) | ||
|
||
|
||
if __name__ == "__main__": | ||
logging.basicConfig() | ||
|
||
loop.run_until_complete(api.init("setup_flow.json", driver_setup_handler)) | ||
loop.run_forever() |
Oops, something went wrong.