Skip to content

Commit

Permalink
replaced ssdp with zeroconf
Browse files Browse the repository at this point in the history
  • Loading branch information
Patrick762 committed Jun 15, 2023
1 parent c5e38ed commit 16c15e9
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 87 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
- 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.

### Limitations
Discovery over SSDP might not work.
Discovery might not work.

### Installation on Linux / Raspberry Pi

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"streamdeck==0.9.3",
"pillow>=9.4.0,<10.0.0",
"cairosvg==2.7.0",
"ssdp!=1.2.0",
"zeroconf",
],
keywords=[],
entry_points={
Expand Down
1 change: 1 addition & 0 deletions streamdeckapi/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@

DB_FILE = "streamdeckapi.db"
SD_SSDP = "urn:home-assistant-device:stream-deck"
SD_ZEROCONF = "_stream-deck-api._tcp.local."
DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S.%f"
LONG_PRESS_SECONDS = 2
110 changes: 25 additions & 85 deletions streamdeckapi/server.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
"""Stream Deck API Server."""

from concurrent.futures import ProcessPoolExecutor
import re
import io
import asyncio
import platform
import sqlite3
import base64
import socket
from uuid import uuid4
from datetime import datetime
from typing import List, Dict
import aiohttp
Expand All @@ -19,7 +19,8 @@
from StreamDeck.ImageHelpers import PILHelper
import cairosvg
from PIL import Image
import ssdp
from zeroconf import IPVersion, ServiceInfo, Zeroconf
from zeroconf.asyncio import AsyncServiceInfo, AsyncZeroconf

from streamdeckapi.const import (
DATETIME_FORMAT,
Expand All @@ -28,7 +29,7 @@
PLUGIN_ICON,
PLUGIN_INFO,
PLUGIN_PORT,
SD_SSDP,
SD_ZEROCONF,
)
from streamdeckapi.types import SDApplication, SDButton, SDButtonPosition, SDDevice

Expand Down Expand Up @@ -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)

Timer(10, broadcast_status)
# TODO add check if websocket is used, otherwise display warning on streamdeck


def get_position(deck: StreamDeck, key: int) -> SDButtonPosition:
Expand Down Expand Up @@ -529,75 +531,6 @@ 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


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:
"""Timer class."""

Expand All @@ -619,24 +552,32 @@ def cancel(self):
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():
"""Entrypoint."""
init_all()

loop = asyncio.get_event_loop()
executor = ProcessPoolExecutor(2)

# SSDP server
if platform.system() == "Windows":
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
# Zeroconf server
loop.run_in_executor(executor, start_zeroconf)

# API server
loop = asyncio.get_event_loop()
Expand All @@ -647,5 +588,4 @@ def start():
except KeyboardInterrupt:
pass

transport.close()
loop.close()

0 comments on commit 16c15e9

Please sign in to comment.