From d345c2ba3b356f1b0759130be40e6f1a556e7c6b Mon Sep 17 00:00:00 2001 From: Patrick762 Date: Thu, 25 May 2023 17:18:00 +0200 Subject: [PATCH 1/4] fixed ssdp location field --- README.md | 6 +++--- streamdeckapi/server.py | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 31f96f9..bfcf8bc 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,6 @@ Stream Deck API Library for Home Assistant Stream Deck Integration 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. @@ -18,6 +15,9 @@ For this to work, the following software is required: The event `doubleTap` is not working with this server software. +### Limitations +Discovery over SSDP might not work. + ### Installation on Linux / Raspberry Pi Install requirements: diff --git a/streamdeckapi/server.py b/streamdeckapi/server.py index b39d71d..42105fc 100644 --- a/streamdeckapi/server.py +++ b/streamdeckapi/server.py @@ -6,6 +6,8 @@ import asyncio import platform import sqlite3 import base64 +import socket +from uuid import uuid4 from datetime import datetime from multiprocessing import Process import aiohttp @@ -496,15 +498,47 @@ def init_all(): deck.set_key_callback_async(on_key_change) +def get_local_ip(): + """Get local ip address.""" + connection = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + try: + connection.connect(('192.255.255.255', 1)) + address = connection.getsockname()[0] + except socket.error: + address = '127.0.0.1' + finally: + connection.close() + return address + + def start_ssdp_server(): """Start SSDP server.""" print("Starting SSDP server ...") - server = SSDPServer(SD_SSDP) + + address = get_local_ip() + broadcast = "239.255.255.250" + location = f"http://{address}:{PLUGIN_PORT}/device.xml" + usn = f"uuid:{str(uuid4())}::{SD_SSDP}" + server = "python/3 UPnP/1.1 ssdpy/0.4.1" + + print(f"IP Address for SSDP: {address}") + print(f"SSDP broadcast ip: {broadcast}") + print(f"SSDP location: {location}") + + server = SSDPServer(usn, + address=broadcast, + location=location, + max_age=1800, + extra_fields={ + "st": SD_SSDP, + "server": server + }) server.serve_forever() class Timer: """Timer class.""" + def __init__(self, interval, callback): """Init timer.""" self._interval = interval From 5c240a4d7dbc0a6c753b480e326c2b41da99650a Mon Sep 17 00:00:00 2001 From: Patrick762 Date: Wed, 31 May 2023 20:04:39 +0200 Subject: [PATCH 2/4] swapped x and y position --- streamdeckapi/server.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/streamdeckapi/server.py b/streamdeckapi/server.py index 42105fc..d18a85d 100644 --- a/streamdeckapi/server.py +++ b/streamdeckapi/server.py @@ -50,8 +50,8 @@ DEFAULT_ICON = re.sub( application: SDApplication = SDApplication( { - "font": "", - "language": "", + "font": "Segoe UI", + "language": "en", "platform": platform.system(), "platformVersion": platform.version(), "version": "0.0.1", @@ -484,7 +484,7 @@ def init_all(): { "uuid": hri.get_new_id().lower().replace(" ", "-"), "device": serial, - "position": {"x": position.x_pos, "y": position.y_pos}, + "position": {"x": position.y_pos, "y": position.x_pos}, # bug in python library "svg": DEFAULT_ICON, } ) @@ -525,7 +525,7 @@ def start_ssdp_server(): print(f"SSDP broadcast ip: {broadcast}") print(f"SSDP location: {location}") - server = SSDPServer(usn, + server = SSDPServer(usn, # FIXME socket.setsockopt(): no such device No such device address=broadcast, location=location, max_age=1800, From c50d5f106c1db6199e4951871865fa446d2df5fc Mon Sep 17 00:00:00 2001 From: Patrick762 Date: Mon, 5 Jun 2023 14:17:31 +0200 Subject: [PATCH 3/4] added deviceType to ssdp --- streamdeckapi/server.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/streamdeckapi/server.py b/streamdeckapi/server.py index d18a85d..b85e248 100644 --- a/streamdeckapi/server.py +++ b/streamdeckapi/server.py @@ -484,7 +484,7 @@ def init_all(): { "uuid": hri.get_new_id().lower().replace(" ", "-"), "device": serial, - "position": {"x": position.y_pos, "y": position.x_pos}, # bug in python library + "position": {"x": position.y_pos, "y": position.x_pos}, "svg": DEFAULT_ICON, } ) @@ -531,7 +531,8 @@ def start_ssdp_server(): max_age=1800, extra_fields={ "st": SD_SSDP, - "server": server + "server": server, + "deviceType": SD_SSDP, }) server.serve_forever() From 532f77bf404f70c378e5b92bfb0b8d7830c2b714 Mon Sep 17 00:00:00 2001 From: Patrick762 Date: Mon, 5 Jun 2023 16:45:30 +0200 Subject: [PATCH 4/4] handling long press with timer --- streamdeckapi/server.py | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/streamdeckapi/server.py b/streamdeckapi/server.py index b85e248..c5feb3b 100644 --- a/streamdeckapi/server.py +++ b/streamdeckapi/server.py @@ -382,6 +382,28 @@ def get_position(deck: StreamDeck, key: int) -> SDButtonPosition: return SDButtonPosition({"x": int(key / deck.KEY_COLS), "y": key % deck.KEY_COLS}) +async def long_press_callback(key: int): + """Handle callback after long press seconds.""" + print("Long press detected") + + # Check state of button + for deck in streamdecks: + if not deck.is_visual(): + continue + + if not deck.is_open(): + deck.open() + + states = deck.key_states() + + button = get_button(key) + if not isinstance(button, SDButton): + return + + if states[key] is True: + await websocket_broadcast(encode({"event": "longPress", "args": button.uuid})) + + async def on_key_change(_: StreamDeck, key: int, state: bool): """Handle key change callbacks.""" button = get_button(key) @@ -391,6 +413,9 @@ async def on_key_change(_: StreamDeck, key: int, state: bool): if state is True: await websocket_broadcast(encode( {"event": "keyDown", "args": button.uuid})) + print("Waiting for button release") + # Start timer + Timer(LONG_PRESS_SECONDS, lambda: long_press_callback(key), False) else: await websocket_broadcast(encode( {"event": "keyUp", "args": button.uuid})) @@ -414,13 +439,6 @@ async def on_key_change(_: StreamDeck, key: int, state: bool): write_button_state(key, state, now.strftime(DATETIME_FORMAT)) return - # TODO: Work with timer instead - if last_state is True and state is False and diff.seconds >= LONG_PRESS_SECONDS: - await websocket_broadcast( - encode({"event": "longPress", "args": button.uuid})) - write_button_state(key, state, now.strftime(DATETIME_FORMAT)) - return - write_button_state(key, state, now.strftime(DATETIME_FORMAT)) @@ -540,16 +558,18 @@ def start_ssdp_server(): class Timer: """Timer class.""" - def __init__(self, interval, callback): + def __init__(self, interval, callback, repeating=True): """Init timer.""" self._interval = interval self._callback = callback + self._repeating = repeating self._task = asyncio.ensure_future(self._job()) async def _job(self): await asyncio.sleep(self._interval) await self._callback() - self._task = asyncio.ensure_future(self._job()) + if self._repeating: + self._task = asyncio.ensure_future(self._job()) def cancel(self): """Cancel timer."""