Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions ds4drv/backends/bluetooth.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from ..backend import Backend
from ..exceptions import BackendError, DeviceError
from ..device import DS4Device
from ..device import DS4Device, DSenseDevice
from ..utils import zero_copy_slice


Expand All @@ -16,6 +16,9 @@
REPORT_ID = 0x11
REPORT_SIZE = 79

DSENSE_REPORT_ID = 0x31
DSENSE_REPORT_SIZE = 79


class BluetoothDS4Device(DS4Device):
@classmethod
Expand Down Expand Up @@ -79,7 +82,6 @@ def close(self):
self.int_sock.close()
self.ctl_sock.close()


class BluetoothBackend(Backend):
__name__ = "bluetooth"

Expand Down
88 changes: 87 additions & 1 deletion ds4drv/backends/hidraw.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from ..backend import Backend
from ..exceptions import DeviceError
from ..device import DS4Device
from ..device import DS4Device, DSenseDevice
from ..utils import zero_copy_slice


Expand Down Expand Up @@ -102,11 +102,97 @@ def set_operational(self):
self.device_name = "{0} {1}".format(addr, self.device_name)
self.device_addr = addr

class HidrawDSenseDevice(DSenseDevice):
def __init__(self, name, addr, type, hidraw_device, event_device):
try:
self.report_fd = os.open(hidraw_device, os.O_RDWR | os.O_NONBLOCK)
self.fd = FileIO(self.report_fd, "rb+", closefd=False)
self.input_device = InputDevice(event_device)
self.input_device.grab()
except (OSError, IOError) as err:
raise DeviceError(err)

self.buf = bytearray(self.report_size)

super(HidrawDSenseDevice, self).__init__(name, addr, type)

def read_report(self):
try:
ret = self.fd.readinto(self.buf)
except IOError:
return

# Disconnection
if ret == 0:
return

# Invalid report size or id, just ignore it
if ret < self.report_size or self.buf[0] != self.valid_report_id:
return False

if self.type == "bluetooth":
# Cut off bluetooth data
buf = zero_copy_slice(self.buf, 1)
else:
buf = self.buf

return self.parse_report(buf)

def read_feature_report(self, report_id, size):
op = HIDIOCGFEATURE(size + 1)
buf = bytearray(size + 1)
buf[0] = report_id

return fcntl.ioctl(self.fd, op, bytes(buf))

def write_report(self, report_id, data):
hid = bytearray((report_id,))
self.fd.write(hid + data)

def close(self):
try:
# Reset LED to original hidraw pairing colour.
self.set_led(0, 0, 1)

self.fd.close()
self.input_device.ungrab()
except IOError:
pass


class HidrawBluetoothDSenseDevice(HidrawDSenseDevice):
__type__ = "bluetooth"

report_size = 78
valid_report_id = 0x31

def set_operational(self):
self.read_feature_report(0x05, 41)


class HidrawUSBDSenseDevice(HidrawDSenseDevice):
__type__ = "usb"

report_size = 64
valid_report_id = 0x01

def set_operational(self):
# Get the bluetooth MAC
addr = self.read_feature_report(0x81, 6)[1:]
addr = ["{0:02x}".format(c) for c in bytearray(addr)]
addr = ":".join(reversed(addr)).upper()

self.device_name = "{0} {1}".format(addr, self.device_name)
self.device_addr = addr


HID_DEVICES = {
"Sony Interactive Entertainment Wireless Controller": HidrawUSBDS4Device,
"Sony Computer Entertainment Wireless Controller": HidrawUSBDS4Device,
"Wireless Controller": HidrawBluetoothDS4Device,
"Sony Interactive Entertainment DualSense Wireless Controller": HidrawUSBDSenseDevice,
"Sony Computer Entertainment DualSense Wireless Controller": HidrawUSBDSenseDevice,
"DualSense Wireless Controller": HidrawBluetoothDSenseDevice,
}


Expand Down
156 changes: 148 additions & 8 deletions ds4drv/device.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from struct import Struct
from sys import version_info as sys_version


class StructHack(Struct):
"""Python <2.7.4 doesn't support struct unpack from bytearray."""
def unpack_from(self, buf, offset=0):
Expand Down Expand Up @@ -41,12 +40,12 @@ class DS4Report(object):
"button_options",
"button_trackpad",
"button_ps",
"motion_y",
"motion_x",
"motion_y",
"motion_z",
"orientation_roll",
"orientation_yaw",
"orientation_pitch",
"orientation_yaw",
"trackpad_touch0_id",
"trackpad_touch0_active",
"trackpad_touch0_x",
Expand Down Expand Up @@ -182,15 +181,15 @@ def parse_report(self, buf):
(buf[7] & 2) != 0, (buf[7] & 1) != 0,

# Acceleration
S16LE.unpack_from(buf, 19)[0],
S16LE.unpack_from(buf, 21)[0],
S16LE.unpack_from(buf, 23)[0],

# Orientation
S16LE.unpack_from(buf, 13)[0],
S16LE.unpack_from(buf, 15)[0],
S16LE.unpack_from(buf, 17)[0],

# Orientation
-(S16LE.unpack_from(buf, 19)[0]),
S16LE.unpack_from(buf, 21)[0],
S16LE.unpack_from(buf, 23)[0],

# Trackpad touch 1: id, active, x, y
buf[35] & 0x7f, (buf[35] >> 7) == 0,
((buf[37] & 0x0f) << 8) | buf[36],
Expand Down Expand Up @@ -234,3 +233,144 @@ def name(self):
type_name = "USB"

return "{0} Controller ({1})".format(type_name, self.device_name)

class DSenseDevice(object):
"""A DSense controller object.

Used to control the device functions and reading HID reports.
"""

def __init__(self, device_name, device_addr, type):
self.device_name = device_name
self.device_addr = device_addr
self.type = type

self._led = (0, 0, 0)
self._led_flash = (0, 0)
self._led_flashing = False

self.set_operational()

def _control(self, **kwargs):
self.control(led_red=self._led[0], led_green=self._led[1],
led_blue=self._led[2], flash_led1=self._led_flash[0],
flash_led2=self._led_flash[1], **kwargs)

def rumble(self, small=0, big=0):
"""Sets the intensity of the rumble motors. Valid range is 0-255."""
self._control(small_rumble=small, big_rumble=big)

def set_led(self, red=0, green=0, blue=0):
"""Sets the LED color. Values are RGB between 0-255."""
self._led = (red, green, blue)
self._control()

def start_led_flash(self, on, off):
"""Starts flashing the LED."""
if not self._led_flashing:
self._led_flash = (on, off)
self._led_flashing = True
self._control()

def stop_led_flash(self):
"""Stops flashing the LED."""
if self._led_flashing:
self._led_flash = (0, 0)
self._led_flashing = False
# Call twice, once to stop flashing...
self._control()
# ...and once more to make sure the LED is on.
self._control()

def control(self, big_rumble=0, small_rumble=0,
led_red=0, led_green=0, led_blue=0,
flash_led1=0, flash_led2=0):
pass # not implemented

def parse_report(self, buf):
"""Parse a buffer containing a HID report."""
dpad = buf[8] % 16

return DS4Report(
# Left analog stick
buf[1], buf[2],

# Right analog stick
buf[3], buf[4],

# L2 and R2 analog
buf[5], buf[6],

# DPad up, down, left, right
(dpad in (0, 1, 7)), (dpad in (3, 4, 5)),
(dpad in (5, 6, 7)), (dpad in (1, 2, 3)),

# Buttons cross, circle, square, triangle
(buf[8] & 32) != 0, (buf[8] & 64) != 0,
(buf[8] & 16) != 0, (buf[8] & 128) != 0,

# L1, L2 and L3 buttons
(buf[9] & 1) != 0, (buf[9] & 4) != 0, (buf[9] & 64) != 0,

# R1, R2,and R3 buttons
(buf[9] & 2) != 0, (buf[9] & 8) != 0, (buf[9] & 128) != 0,

# Share and option buttons
(buf[9] & 16) != 0, (buf[9] & 32) != 0,

# Trackpad and PS buttons
(buf[10] & 2) != 0, (buf[10] & 1) != 0,

# Linear acceleration
S16LE.unpack_from(buf, 22)[0],
S16LE.unpack_from(buf, 24)[0],
S16LE.unpack_from(buf, 26)[0],

# Angular velocity
S16LE.unpack_from(buf, 16)[0],
S16LE.unpack_from(buf, 18)[0],
S16LE.unpack_from(buf, 20)[0],

# Trackpad touch 1: id, active, x, y
buf[33] & 0x7f, (buf[33] >> 7) == 0,
((buf[35] & 0x0f) << 8) | buf[34],
buf[36] << 4 | ((buf[35] & 0xf0) >> 4),

# Trackpad touch 2: id, active, x, y
buf[37] & 0x7f, (buf[37] >> 7) == 0,
((buf[39] & 0x0f) << 8) | buf[38],
buf[40] << 4 | ((buf[39] & 0xf0) >> 4),

# Timestamp and battery
buf[7],
buf[53] % 16,

# External inputs (usb, audio, mic)
0, 0, #(buf[30] & 16) != 0, (buf[30] & 32) != 0,
0 #(buf[30] & 64) != 0
)

def read_report(self):
"""Read and parse a HID report."""
pass

def write_report(self, report_id, data):
"""Writes a HID report to the control channel."""
pass

def set_operational(self):
"""Tells the DS4 controller we want full HID reports."""
pass

def close(self):
"""Disconnects from the device."""
pass

@property
def name(self):
if self.type == "bluetooth":
type_name = "Bluetooth"
elif self.type == "usb":
type_name = "USB"

return "{0} Controller ({1})".format(type_name, self.device_name)
2 changes: 2 additions & 0 deletions udev/50-ds4drv.rules
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="054c", ATTRS{idProduct
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", KERNELS=="0005:054C:05C4.*", MODE="0666"
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="054c", ATTRS{idProduct}=="09cc", MODE="0666"
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", KERNELS=="0005:054C:09CC.*", MODE="0666"
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="054c", ATTRS{idProduct}=="0ce6", MODE="0666"
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", KERNELS=="0005:054C:0CE6.*", MODE="0666"