Skip to content

Commit

Permalink
Add alternative HID backend, transport autoprobing.
Browse files Browse the repository at this point in the history
  • Loading branch information
abcminiuser committed Oct 20, 2019
2 parents c829e11 + 1992f9b commit aff0c2b
Show file tree
Hide file tree
Showing 9 changed files with 296 additions and 30 deletions.
66 changes: 48 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,6 @@ front-ends, such as a custom control front-end for home automation software.

[Online Documentation](https://python-elgato-streamdeck.readthedocs.io)

## Credits:

I've used the reverse engineering notes from
[this GitHub](https://github.com/Lange/node-elgato-stream-deck/blob/master/NOTES.md)
repository to implement this library. Thanks Alex Van Camp!

Initial StreamDeck Mini support added by [Aetherdyne](https://github.com/Aetherdyne).

StreamDeck XL support assisted by [Pointshader](https://github.com/pointshader).

## Status:

Working - you can enumerate devices, set the brightness of the panel(s), set
Expand All @@ -31,24 +21,43 @@ Currently the following StreamDeck product variants are supported:
* StreamDeck Mini
* StreamDeck XL

## Dependencies:
## Package Dependencies:

The library core has no special dependencies, but does use one of (potentially)
several HID backend libraries. You will need to install the dependencies
appropriate to your chosen backend.

The included example requires the PIL fork "Pillow", although it can be swapped
The included examples require the PIL fork "Pillow", although it can be swapped
out if desired by the user application for any other image manipulation library.

To install all library dependencies at once, run:
```
pip install -r requirements.txt
```
### HID Backend

The recommended HID backend is the aptly named
[HID Python library](https://pypi.org/project/hid/), which should work across
the three major (Windows, Mac, Linux) OSes and is the most up-to-date. This can
be installed via `pip3 install hid`.

Note that Windows systems requires additional manual installation of a DLL in
order to function. The latest source for this DLL is the
[libUSB GitHub project](https://github.com/libusb/hidapi/releases).

Despite the additional setup, this will give the best results in terms of
reliability and performance.

### HIDAPI Backend

The default backend is the HIDAPI Python library, which should work across
the three major (Windows, Mac, Linux) OSes.
Another option is the older
[HIDAPI Python library](https://pypi.org/project/hidapi/), which originates from
the same original project as the HID library listed above, but is now entirely
unmaintained. This can be installed via `pip3 install hidapi`.

Several users report issues with this backend due to various bugs
that have not been patched in the packaged library version, but support for it
is included in this library due to its simple one-line setup on all three major
platforms.

Note the library package name conflicts with the HID backend above; only one or
the other should be installed at the same time.

## Package Installation:

Expand Down Expand Up @@ -81,6 +90,7 @@ sudo apt install -y libudev-dev libusb-1.0-0-dev
# Install dependencies
pip3 install hidapi
pip3 install pillow
# Add udev rule to allow all users non-root access to Elgato StreamDeck devices:
sudo tee /etc/udev/rules.d/10-streamdeck.rules << EOF
Expand All @@ -99,6 +109,26 @@ Note that after adding the `udev` rule, a restart will be required in order for
it to take effect and allow access to the StreamDeck device without requiring
root privileges.

## Credits:

I've used the reverse engineering notes from
[this GitHub](https://github.com/Lange/node-elgato-stream-deck/blob/master/NOTES.md)
repository to implement this library. Thanks Alex Van Camp!

Thank you to the following contributors, large and small, for helping with the
development and maintenance of this library:

- [Aetherdyne](https://github.com/Aetherdyne)
- [dirkk0](https://github.com/dirkk0)
- [Kalle-Wirsch](https://github.com/Kalle-Wirsch)
- [pointshader](https://github.com/pointshader)
- [spidererrol](https://github.com/Spidererrol)
- [Subsentient](https://github.com/Subsentient)
- [shanna](https://github.com/shanna)

If you've contributed in some manner, but I've accidentally missed you in the
list above, please let me know.

## License:

Released under the MIT license:
Expand Down
2 changes: 1 addition & 1 deletion doc/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
Sphinx==1.8.4
Sphinx==2.2.0
sphinx-rtd-theme==0.4.3
m2r==0.2.1
4 changes: 4 additions & 0 deletions doc/source/modules/transports.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ Modules: Communication Transports
.. automodule:: StreamDeck.Transport.Transport
:members:

.. automodule:: StreamDeck.Transport.HID
:members:
:show-inheritance:

.. automodule:: StreamDeck.Transport.HIDAPI
:members:
:show-inheritance:
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
hidapi==0.7.99.post21
Pillow>=5.0.0
43 changes: 35 additions & 8 deletions src/StreamDeck/DeviceManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@
from .Devices.StreamDeckMini import StreamDeckMini
from .Devices.StreamDeckXL import StreamDeckXL
from .Transport.Dummy import Dummy
from .Transport.HID import HID
from .Transport.HIDAPI import HIDAPI


class ProbeError(Exception):
pass


class DeviceManager:
"""
Central device manager, to enumerate any attached StreamDeck devices. An
Expand All @@ -28,30 +33,52 @@ class DeviceManager:
def _get_transport(transport):
"""
Creates a new HID transport instance from the given transport back-end
name.
name. If no specific transport is supplied, an attempt to find an
installed backend will be made.
:param str transport: Name of a supported HID transport back-end to use.
:param str transport: Name of a supported HID transport back-end to use, None to autoprobe.
:rtype: Transport.* instance
:return: Instance of a HID Transport class
"""

transports = {
"dummy": Dummy,
"hid": HID,
"hidapi": HIDAPI,
}

transport_class = transports.get(transport)
if transport_class is None:
raise IOError("Invalid HID transport backend \"{}\".".format(transport))
if transport:
transport_class = transports.get(transport)

if transport_class is None:
raise ProbeError("Unknown HID transport backend \"{}\".".format(transport))

try:
transport_class.probe()
return transport_class()
except Exception as transport_error:
raise ProbeError("Probe failed on HID backend \"{}\".".format(transport), transport_error)
else:
probe_errors = {}

for transport_name, transport_class in transports.items():
if transport_name == "dummy":
continue

try:
transport_class.probe()
return transport_class()
except Exception as transport_error:
probe_errors[transport_name] = transport_error

return transport_class()
raise ProbeError("Probe failed to find any functional HID backend.", probe_errors)

def __init__(self, transport="hidapi"):
def __init__(self, transport=None):
"""
Creates a new StreamDeck DeviceManager, used to detect attached StreamDeck devices.
:param str transport: name of the the HID transport backend to use
:param str transport: name of the the specific HID transport back-end to use, None to auto-probe.
"""
self.transport = self._get_transport(transport)

Expand Down
6 changes: 5 additions & 1 deletion src/StreamDeck/Transport/Dummy.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def write_feature(self, payload):

def read_feature(self, report_id, length):
logging.info('Deck feature read (length %s)', length)
return IOError("Dummy device!")
raise IOError("Dummy device!")

def write(self, payload):
logging.info('Deck report write (length %s): %s', len(payload), binascii.hexlify(payload))
Expand All @@ -48,5 +48,9 @@ def read(self, length):
logging.info('Deck report read (length %s)', length)
raise IOError("Dummy device!")

@staticmethod
def probe():
pass

def enumerate(self, vid, pid):
return [Dummy.Device("{}:{}".format(vid, pid))]

0 comments on commit aff0c2b

Please sign in to comment.