Skip to content

Commit 25a9b62

Browse files
authored
Merge pull request #11 from strohganoff/feature/debug-mode
Debug Mode
2 parents 53276b4 + e8b06dc commit 25a9b62

File tree

4 files changed

+80
-6
lines changed

4 files changed

+80
-6
lines changed

README.md

+53-1
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ The following commands are required, which are the same as the Stream Deck softw
157157

158158
- `-info`: Additional information (formatted as json) about the plugin environment, as provided by the Stream Deck software.
159159

160-
There are also two additional options for specifying action scripts to load. Note that you can't use both of these options together, and the Stream Deck software doesn't pass in these options.
160+
There are also two additional options for specifying action scripts to load. Note that you can't use both of these options together, and the Stream Deck software doesn't pass in these options.
161161

162162
- Plugin Directory: Pass the directory containing the plugin files as a positional argument:
163163

@@ -172,6 +172,58 @@ There are also two additional options for specifying action scripts to load. No
172172
streamdeck --action-scripts actions1.py actions2.py
173173
```
174174

175+
In addition to these, there is an additional option to use **debug mode**, which is discussed below.
176+
177+
#### Debugging
178+
179+
The SDK supports remote debugging capabilities, allowing you to attach a debugger after the plugin has started. This is particularly useful since Stream Deck plugins run as separate processes.
180+
181+
To enable debug mode, pass in the option `--debug {debug port number}`, which tells the plugin to wait for a debugger to attach on that port number.
182+
183+
```bash
184+
streamdeck --debug 5675
185+
```
186+
187+
When running in debug mode, the plugin will pause for 10 seconds after initialization, giving you time to attach your debugger. You'll see a message in the logs indicating that the plugin is waiting for a debugger to attach.
188+
189+
NOTE: If things get messy, and you have a prior instance already listening to that port, you should kill the process with something like the following command:
190+
191+
```bash
192+
kill $(lsof -t -i:$DEBUG_PORT)
193+
```
194+
195+
#### Debugging with VS Code
196+
197+
1. Create a launch configuration in `.vscode/launch.json`:
198+
199+
```json
200+
{
201+
"version": "0.2.0",
202+
"configurations": [
203+
{
204+
"name": "Python: Attach to Stream Deck Plugin",
205+
"type": "debugpy",
206+
"request": "attach",
207+
"connect": {
208+
"host": "localhost",
209+
"port": 5678
210+
}
211+
"pathMappings": [
212+
{
213+
"localRoot": "${workspaceFolder}",
214+
"remoteRoot": "."
215+
}
216+
],
217+
"justMyCode": false,
218+
}
219+
]
220+
}
221+
```
222+
223+
2. Start your plugin with debugging enabled
224+
3. When you see the "waiting for debugger" message, use VS Code's Run and Debug view to attach using the configuration above
225+
4. Set breakpoints and debug as normal
226+
175227
176228
#### Configuration
177229

pyproject.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
"pytest-mock >= 3.14.0",
7575
"pytest-sugar >= 1.0.0",
7676
"tox >= 4.23.2",
77+
"debugpy",
7778
]
7879

7980
[project.urls]
@@ -83,7 +84,7 @@
8384
"Issues" = "https://github.com/strohganoff/python-streamdeck-plugin-sdk/issues"
8485

8586
[project.scripts]
86-
streamdeck = "streamdeck.__main__:main"
87+
streamdeck = "streamdeck.__main__:plugin"
8788

8889
[tool.setuptools.packages.find]
8990
include = ["streamdeck*"]

streamdeck/__main__.py

+18-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import logging
33
import sys
44
from pathlib import Path
5-
from typing import Annotated, Union
5+
from typing import Annotated, Optional
66

77
import typer
88

@@ -18,14 +18,25 @@
1818
plugin = typer.Typer()
1919

2020

21+
def setup_debug_mode(debug_port: int) -> None:
22+
"""Setup the debug mode for the plugin and wait for the debugger to attach."""
23+
import debugpy
24+
25+
debugpy.listen(debug_port)
26+
logger.info("Starting in debug mode. Waiting for debugger to attach on port %d...", debug_port)
27+
debugpy.wait_for_client()
28+
logger.info("Debugger attached.")
29+
30+
2131
@plugin.command()
2232
def main(
2333
port: Annotated[int, typer.Option("-p", "-port")],
2434
plugin_registration_uuid: Annotated[str, typer.Option("-pluginUUID")],
2535
register_event: Annotated[str, typer.Option("-registerEvent")],
2636
info: Annotated[str, typer.Option("-info")],
2737
plugin_dir: Annotated[Path, typer.Option(file_okay=False, exists=True, readable=True)] = Path.cwd(), # noqa: B008
28-
action_scripts: Union[list[str], None] = None, # noqa: UP007
38+
action_scripts: Optional[list[str]] = None, # noqa: UP007
39+
debug_port: Annotated[Optional[int], typer.Option("--debug", "-d")] = None, # noqa: UP007
2940
) -> None:
3041
"""Start the Stream Deck plugin with the given configuration.
3142
@@ -43,6 +54,11 @@ def main(
4354
# a child logger with `logging.getLogger("streamdeck.mycomponent")`, all with the same handler/formatter configuration.
4455
configure_streamdeck_logger(name="streamdeck", plugin_uuid=plugin_uuid)
4556

57+
logger.info("Stream Deck listening to plugin UUID '%s' on port %d", plugin_uuid, port)
58+
59+
if debug_port:
60+
setup_debug_mode(debug_port)
61+
4662
pyproject = PyProjectConfigs.validate_from_toml_file(plugin_dir / "pyproject.toml", action_scripts=action_scripts)
4763
actions = pyproject.streamdeck_plugin_actions
4864

streamdeck/websocket.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,9 @@ def listen(self) -> Generator[str | bytes, Any, None]:
5959
message: str | bytes = self._client.recv()
6060
yield message
6161

62-
except ConnectionClosedOK:
62+
except ConnectionClosedOK as exc:
6363
logger.debug("Connection was closed normally, stopping the client.")
64+
logger.exception(dir(exc))
6465

6566
except ConnectionClosed:
6667
logger.exception("Connection was closed with an error.")
@@ -70,7 +71,11 @@ def listen(self) -> Generator[str | bytes, Any, None]:
7071

7172
def start(self) -> None:
7273
"""Start the connection to the websocket server."""
73-
self._client = connect(uri=f"ws://localhost:{self._port}")
74+
try:
75+
self._client = connect(uri=f"ws://localhost:{self._port}")
76+
except ConnectionRefusedError:
77+
logger.exception("Failed to connect to the WebSocket server. Make sure the Stream Deck software is running.")
78+
raise
7479

7580
def stop(self) -> None:
7681
"""Close the WebSocket connection, if open."""

0 commit comments

Comments
 (0)