replaced ssdp with zeroconf
This commit is contained in:
@@ -13,10 +13,17 @@ 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)
|
- 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)
|
- cairo [Installation instructions for Windows](https://stackoverflow.com/a/73913080)
|
||||||
|
|
||||||
|
Cairo Installation for Windows:
|
||||||
|
```bash
|
||||||
|
pip install pipwin
|
||||||
|
|
||||||
|
pipwin install cairocffi
|
||||||
|
```
|
||||||
|
|
||||||
The event `doubleTap` is not working with this server software.
|
The event `doubleTap` is not working with this server software.
|
||||||
|
|
||||||
### Limitations
|
### Limitations
|
||||||
Discovery over SSDP might not work.
|
Discovery might not work.
|
||||||
|
|
||||||
### Installation on Linux / Raspberry Pi
|
### Installation on Linux / Raspberry Pi
|
||||||
|
|
||||||
|
|||||||
2
setup.py
2
setup.py
@@ -32,7 +32,7 @@ setup(
|
|||||||
"streamdeck==0.9.3",
|
"streamdeck==0.9.3",
|
||||||
"pillow>=9.4.0,<10.0.0",
|
"pillow>=9.4.0,<10.0.0",
|
||||||
"cairosvg==2.7.0",
|
"cairosvg==2.7.0",
|
||||||
"ssdp!=1.2.0",
|
"zeroconf",
|
||||||
],
|
],
|
||||||
keywords=[],
|
keywords=[],
|
||||||
entry_points={
|
entry_points={
|
||||||
|
|||||||
@@ -6,5 +6,6 @@ PLUGIN_ICON = "/sd/icon"
|
|||||||
|
|
||||||
DB_FILE = "streamdeckapi.db"
|
DB_FILE = "streamdeckapi.db"
|
||||||
SD_SSDP = "urn:home-assistant-device:stream-deck"
|
SD_SSDP = "urn:home-assistant-device:stream-deck"
|
||||||
|
SD_ZEROCONF = "_stream-deck-api._tcp.local."
|
||||||
DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S.%f"
|
DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S.%f"
|
||||||
LONG_PRESS_SECONDS = 2
|
LONG_PRESS_SECONDS = 2
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
"""Stream Deck API Server."""
|
"""Stream Deck API Server."""
|
||||||
|
|
||||||
|
from concurrent.futures import ProcessPoolExecutor
|
||||||
import re
|
import re
|
||||||
import io
|
import io
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -7,7 +8,6 @@ import platform
|
|||||||
import sqlite3
|
import sqlite3
|
||||||
import base64
|
import base64
|
||||||
import socket
|
import socket
|
||||||
from uuid import uuid4
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
import aiohttp
|
import aiohttp
|
||||||
@@ -19,7 +19,8 @@ from StreamDeck.Devices.StreamDeck import StreamDeck
|
|||||||
from StreamDeck.ImageHelpers import PILHelper
|
from StreamDeck.ImageHelpers import PILHelper
|
||||||
import cairosvg
|
import cairosvg
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import ssdp
|
from zeroconf import IPVersion, ServiceInfo, Zeroconf
|
||||||
|
from zeroconf.asyncio import AsyncServiceInfo, AsyncZeroconf
|
||||||
|
|
||||||
from streamdeckapi.const import (
|
from streamdeckapi.const import (
|
||||||
DATETIME_FORMAT,
|
DATETIME_FORMAT,
|
||||||
@@ -28,7 +29,7 @@ from streamdeckapi.const import (
|
|||||||
PLUGIN_ICON,
|
PLUGIN_ICON,
|
||||||
PLUGIN_INFO,
|
PLUGIN_INFO,
|
||||||
PLUGIN_PORT,
|
PLUGIN_PORT,
|
||||||
SD_SSDP,
|
SD_ZEROCONF,
|
||||||
)
|
)
|
||||||
from streamdeckapi.types import SDApplication, SDButton, SDButtonPosition, SDDevice
|
from streamdeckapi.types import SDApplication, SDButton, SDButtonPosition, SDDevice
|
||||||
|
|
||||||
@@ -391,6 +392,7 @@ async def start_server_async(host: str = "0.0.0.0", port: int = PLUGIN_PORT):
|
|||||||
print("Started Stream Deck API server on port", PLUGIN_PORT)
|
print("Started Stream Deck API server on port", PLUGIN_PORT)
|
||||||
|
|
||||||
Timer(10, broadcast_status)
|
Timer(10, broadcast_status)
|
||||||
|
# TODO add check if websocket is used, otherwise display warning on streamdeck
|
||||||
|
|
||||||
|
|
||||||
def get_position(deck: StreamDeck, key: int) -> SDButtonPosition:
|
def get_position(deck: StreamDeck, key: int) -> SDButtonPosition:
|
||||||
@@ -529,75 +531,6 @@ def init_all():
|
|||||||
deck.set_key_callback_async(on_key_change)
|
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
|
|
||||||
|
|
||||||
|
|
||||||
class StreamDeckApiSsdpProtocol(ssdp.SimpleServiceDiscoveryProtocol):
|
|
||||||
"""Protocol to handle responses and requests."""
|
|
||||||
|
|
||||||
def response_received(self, response: ssdp.SSDPResponse, addr: tuple):
|
|
||||||
"""Handle an incoming response."""
|
|
||||||
print(
|
|
||||||
"received response: %s %s %s",
|
|
||||||
response.status_code,
|
|
||||||
response.reason,
|
|
||||||
response.version,
|
|
||||||
)
|
|
||||||
|
|
||||||
for header in response.headers:
|
|
||||||
print("header: %s", header)
|
|
||||||
|
|
||||||
print()
|
|
||||||
|
|
||||||
def request_received(self, request: ssdp.SSDPRequest, addr: tuple):
|
|
||||||
"""Handle an incoming request and respond to it."""
|
|
||||||
print(
|
|
||||||
"received request: %s %s %s", request.method, request.uri, request.version
|
|
||||||
)
|
|
||||||
|
|
||||||
for header in request.headers:
|
|
||||||
print("header: %s", header)
|
|
||||||
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Build response and send it.
|
|
||||||
print("Sending a response back to %s:%s", *addr)
|
|
||||||
|
|
||||||
address = get_local_ip()
|
|
||||||
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 location: {location}")
|
|
||||||
|
|
||||||
ssdp_response = ssdp.SSDPResponse(
|
|
||||||
200,
|
|
||||||
"OK",
|
|
||||||
headers={
|
|
||||||
"Cache-Control": "max-age=30",
|
|
||||||
"Location": location,
|
|
||||||
"Server": server,
|
|
||||||
"ST": SD_SSDP,
|
|
||||||
"USN": usn,
|
|
||||||
"EXT": "",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
msg = bytes(ssdp_response) + b"\r\n" + b"\r\n"
|
|
||||||
self.transport.sendto(msg, addr)
|
|
||||||
|
|
||||||
|
|
||||||
class Timer:
|
class Timer:
|
||||||
"""Timer class."""
|
"""Timer class."""
|
||||||
|
|
||||||
@@ -619,24 +552,32 @@ class Timer:
|
|||||||
self._task.cancel()
|
self._task.cancel()
|
||||||
|
|
||||||
|
|
||||||
|
def start_zeroconf():
|
||||||
|
"""Start Zeroconf server."""
|
||||||
|
|
||||||
|
info = ServiceInfo(
|
||||||
|
SD_ZEROCONF,
|
||||||
|
f"Stream Deck API Server.{SD_ZEROCONF}",
|
||||||
|
addresses=[socket.inet_aton("127.0.0.1")],
|
||||||
|
port=80,
|
||||||
|
)
|
||||||
|
|
||||||
|
zeroconf = Zeroconf()
|
||||||
|
|
||||||
|
print("Zeroconf starting")
|
||||||
|
|
||||||
|
zeroconf.register_service(info)
|
||||||
|
|
||||||
|
|
||||||
def start():
|
def start():
|
||||||
"""Entrypoint."""
|
"""Entrypoint."""
|
||||||
init_all()
|
init_all()
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
|
executor = ProcessPoolExecutor(2)
|
||||||
|
|
||||||
# SSDP server
|
# Zeroconf server
|
||||||
if platform.system() == "Windows":
|
loop.run_in_executor(executor, start_zeroconf)
|
||||||
print("SSDP not working on windows. Skipping ...")
|
|
||||||
else:
|
|
||||||
connect = loop.create_datagram_endpoint(
|
|
||||||
StreamDeckApiSsdpProtocol,
|
|
||||||
family=socket.AF_INET,
|
|
||||||
local_addr=(StreamDeckApiSsdpProtocol.MULTICAST_ADDRESS, 1900),
|
|
||||||
)
|
|
||||||
transport, _ = loop.run_until_complete(connect)
|
|
||||||
|
|
||||||
StreamDeckApiSsdpProtocol.transport = transport
|
|
||||||
|
|
||||||
# API server
|
# API server
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
@@ -647,5 +588,4 @@ def start():
|
|||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
transport.close()
|
|
||||||
loop.close()
|
loop.close()
|
||||||
|
|||||||
Reference in New Issue
Block a user