Skip to content

Commit

Permalink
Merge pull request #1 from Patrick762/server
Browse files Browse the repository at this point in the history
Add standalone server
  • Loading branch information
Patrick762 authored May 24, 2023
2 parents 40368ab + 0dd111b commit 91af39b
Show file tree
Hide file tree
Showing 8 changed files with 628 additions and 33 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
**/__pycache__/
build
*.egg-info
*.db
*.db-journal
*.png
35 changes: 34 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,40 @@
# streamdeckapi
Stream Deck API Library for Home Assistant Stream Deck Integration

Only compatible with separate [Stream Deck Plugin](https://github.com/Patrick762/streamdeckapi-plugin)
Only compatible with separate [Stream Deck Plugin](https://github.com/Patrick762/streamdeckapi-plugin) or the bundled server.

## Dependencies
- [websockets](https://pypi.org/project/websockets/) 11.0.2

## Server
This library also contains a server to use the streamdeck with Linux or without the official Stream Deck Software.

For this to work, the following software is required:

- LibUSB HIDAPI [Installation instructions](https://python-elgato-streamdeck.readthedocs.io/en/stable/pages/backend_libusb_hidapi.html) or [Installation instructions](https://github.com/jamesridgway/devdeck/wiki/Installation)
- cairo [Installation instructions for Windows](https://stackoverflow.com/a/73913080)

The event `doubleTap` is not working with this server software.

### Installation on Linux / Raspberry Pi

Install requirements:
`sudo apt install -y libudev-dev libusb-1.0-0-dev libhidapi-libusb0 libjpeg-dev zlib1g-dev libopenjp2-7 libtiff5`

Allow all users non-root access to Stream Deck Devices:
```bash
sudo tee /etc/udev/rules.d/10-streamdeck.rules << EOF
SUBSYSTEMS=="usb", ATTRS{idVendor}=="0fd9", GROUP="users", TAG+="uaccess"
EOF
```

Reload access rules:
`sudo udevadm control --reload-rules`

Install the package:
`pip install streamdeckapi`

Reboot your system

Start the server:
`streamdeckapi-server`
21 changes: 17 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
with codecs.open(os.path.join(here, "README.md"), encoding="utf-8") as fh:
long_description = "\n" + fh.read()

VERSION = '0.0.2'
DESCRIPTION = 'Stream Deck API Library'
VERSION = "0.0.3"
DESCRIPTION = "Stream Deck API Library"

# Setting up
setup(
Expand All @@ -21,14 +21,27 @@
long_description=long_description,
url="https://github.com/Patrick762/streamdeckapi",
packages=find_packages(),
install_requires=["websockets==11.0.2"],
install_requires=[
"requests==2.28.2",
"websockets==11.0.2",
"aiohttp==3.8.4",
"human-readable-ids==0.1.3",
"jsonpickle==3.0.1",
"streamdeck==0.9.3",
"pillow>=9.4.0,<10.0.0",
"cairosvg==2.7.0",
"ssdpy==0.4.1",
],
keywords=[],
entry_points={
"console_scripts": ["streamdeckapi-server = streamdeckapi.server:start"]
},
classifiers=[
"Development Status :: 1 - Planning",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3",
"Operating System :: Unix",
"Operating System :: MacOS :: MacOS X",
"Operating System :: Microsoft :: Windows",
]
],
)
4 changes: 0 additions & 4 deletions streamdeckapi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
"""Stream Deck API."""

from streamdeckapi.api import StreamDeckApi
from streamdeckapi.types import SDInfo, SDWebsocketMessage, SDSize, SDApplication, SDButton, SDButtonPosition, SDDevice
from streamdeckapi.tools import get_model
47 changes: 24 additions & 23 deletions streamdeckapi/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@
from websockets.client import connect
from websockets.exceptions import WebSocketException

from .types import SDInfo, SDWebsocketMessage
from streamdeckapi.const import PLUGIN_ICON, PLUGIN_INFO, PLUGIN_PORT

_PLUGIN_PORT = 6153
_PLUGIN_INFO = "/sd/info"
_PLUGIN_ICON = "/sd/icon"
from .types import SDInfo, SDWebsocketMessage

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -53,17 +51,17 @@ def host(self) -> str:
@property
def _info_url(self) -> str:
"""URL to info endpoint."""
return f"http://{self._host}:{_PLUGIN_PORT}{_PLUGIN_INFO}"
return f"http://{self._host}:{PLUGIN_PORT}{PLUGIN_INFO}"

@property
def _icon_url(self) -> str:
"""URL to icon endpoint."""
return f"http://{self._host}:{_PLUGIN_PORT}{_PLUGIN_ICON}/"
return f"http://{self._host}:{PLUGIN_PORT}{PLUGIN_ICON}/"

@property
def _websocket_url(self) -> str:
"""URL to websocket."""
return f"ws://{self._host}:{_PLUGIN_PORT}"
return f"ws://{self._host}:{PLUGIN_PORT}"

#
# API Methods
Expand Down Expand Up @@ -92,7 +90,8 @@ def _post_request(url: str, data: str, headers) -> None | requests.Response:
try:
res = requests.post(url, data, headers=headers, timeout=5)
except requests.RequestException:
_LOGGER.debug("Error sending data to Stream Deck Plugin (exception)")
_LOGGER.debug(
"Error sending data to Stream Deck Plugin (exception)")
return None
if res.status_code != 200:
_LOGGER.debug(
Expand Down Expand Up @@ -121,7 +120,8 @@ async def get_info(self, in_executor: bool = True) -> None | SDInfo:
try:
info = SDInfo(rjson)
except KeyError:
_LOGGER.debug("Error parsing response from %s to SDInfo", self._info_url)
_LOGGER.debug(
"Error parsing response from %s to SDInfo", self._info_url)
return None
return info

Expand Down Expand Up @@ -180,7 +180,8 @@ def _on_message(self, msg: str):
try:
datajson = json.loads(msg)
except json.JSONDecodeError:
_LOGGER.debug("Method _on_message: Websocket message couldn't get parsed")
_LOGGER.debug(
"Method _on_message: Websocket message couldn't get parsed")
return
try:
data = SDWebsocketMessage(datajson)
Expand All @@ -195,18 +196,17 @@ def _on_message(self, msg: str):
if self._on_ws_message is not None:
self._on_ws_message(data)

match data.event:
case "keyDown":
self._on_button_change(data.args, True)
case "keyUp":
self._on_button_change(data.args, False)
case "status":
self._on_ws_status_update(data.args)
case _:
_LOGGER.debug(
"Method _on_message: Unknown event from Stream Deck Plugin received (Event: %s)",
data.event,
)
if data.event == "keyDown":
self._on_button_change(data.args, True)
elif data.event == "keyUp":
self._on_button_change(data.args, False)
elif data.event == "status":
self._on_ws_status_update(data.args)
else:
_LOGGER.debug(
"Method _on_message: Unknown event from Stream Deck Plugin received (Event: %s)",
data.event,
)

async def _websocket_loop(self):
"""Start the websocket client loop."""
Expand All @@ -226,7 +226,8 @@ async def _websocket_loop(self):
)
self._on_message(data)
await websocket.close()
_LOGGER.debug("Method _websocket_loop: Websocket closed")
_LOGGER.debug(
"Method _websocket_loop: Websocket closed")
except WebSocketException:
_LOGGER.debug(
"Method _websocket_loop: Websocket client crashed. Restarting it"
Expand Down
10 changes: 10 additions & 0 deletions streamdeckapi/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""Stream Deck API const."""

PLUGIN_PORT = 6153
PLUGIN_INFO = "/sd/info"
PLUGIN_ICON = "/sd/icon"

DB_FILE = "streamdeckapi.db"
SD_SSDP = "urn:home-assistant-device:stream-deck"
DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S.%f"
LONG_PRESS_SECONDS = 2
Loading

0 comments on commit 91af39b

Please sign in to comment.