5 Commits
0.0.1 ... 0.1.0

Author SHA1 Message Date
Florian K
a353bb006a Merge pull request #1 from flok/read_input
basic input reading done, restructured code
2020-11-29 17:22:45 +01:00
Florian Kaiser
b9d0edf08e basic input reading done, restructured code
- only using 1 thread for reading inputs and updating the controller
- reading the inputs mostly done, gyro and accel missing
2020-11-29 17:19:56 +01:00
Florian Kaiser
0521f0180b Add MIT license 2020-11-29 13:25:31 +01:00
Florian Kaiser
567d712f67 Add install instructions 2020-11-29 13:18:40 +01:00
Florian Kaiser
a55fe59d2b 0.0.4
fixed structure of the package
added background send thread
added close function for terminating the background thread
2020-11-29 12:49:16 +01:00
7 changed files with 242 additions and 50 deletions

2
.gitignore vendored
View File

@@ -151,3 +151,5 @@ dmypy.json
# End of https://www.toptal.com/developers/gitignore/api/python,vscode
pydualsense/interface.py
pydualsense/interface.ui

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Florian K.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,2 +1,29 @@
# pydualsense
controll your dualsense through python
control your dualsense through python. using the hid library this module implements the sending report for controlling you new PS5 controller. It creates a background thread to constantly update the controller.
# install
Just install the package from pypi
```bash
pip install pydualsense
```
# usage
```python
from pydualsense import pydualsense
ds = pydualsense() # open controller
ds.setColor(255,0,0) # set touchpad color to red
ds.setLeftTriggerMode(TriggerModes.Rigid)
ds.setLeftTriggerForce(1, 255)
ds.close() # closing the controller
```
# dependecies
- hid >= 1.0.4
# Coming soon
- reading the states of the controller to enable a fully compatibility with python - partially done
- add documentation using sphinx

View File

@@ -0,0 +1,2 @@
from .enums import LedOptions,Brightness,PlayerID,PulseOptions,TriggerModes
from .pydualsense import pydualsense, DSAudio, DSLight, DSTrigger

View File

@@ -1,5 +1,4 @@
from enum import Enum, IntFlag
from flags import Flags # bitflag
from enum import IntFlag
class LedOptions(IntFlag):
Off=0x0,

View File

@@ -1,5 +1,5 @@
import hid
from enums import (LedOptions, PlayerID,
from .enums import (LedOptions, PlayerID,
PulseOptions, TriggerModes, Brightness)
import threading
@@ -7,25 +7,33 @@ class pydualsense:
def __init__(self) -> None:
# TODO: maybe add a init function to not automatically allocate controller when class is declared
self.device = self.__find_device()
self.device: hid.Device = self.__find_device()
self.light = DSLight() # control led light of ds
self.audio = DSAudio()
self.triggerL = DSTrigger()
self.triggerR = DSTrigger()
# set default for the controller
self.color = (0,0,255) # set dualsense color around the touchpad to blue
self.color = (0,0,255) # set color around touchpad to blue
self.send_thread = True
send_report = threading.Thread(target=self.sendReport)
#send_report.start()
# create thread for sending
self.receive_buffer_size = 64
self.send_report_size = 48
# controller states
self.state = DSState()
# thread for receiving and sending
self.ds_thread = True
self.report_thread = threading.Thread(target=self.sendReport)
self.report_thread.start()
def close(self):
self.ds_thread = False
self.report_thread.join()
def __find_device(self):
devices = hid.enumerate(vid=0x054c)
found_devices = []
for device in devices:
if device['vendor_id'] == 0x54c and device['product_id'] == 0xCE6:
if device['vendor_id'] == 0x054c and device['product_id'] == 0x0CE6:
found_devices.append(device)
# TODO: detect connection mode, bluetooth has a bigger write buffer
@@ -94,11 +102,109 @@ class pydualsense:
# TODO: audio
# audio stuff
def setMicrophoneLED(self, value):
self.audio.microphoneLED = 0x1
self.audio.microphoneLED = value
def sendReport(self):
# while self.send_thread:
"""background thread handling the reading of the device and updating its states
"""
while self.ds_thread:
# read data from the input report of the controller
inReport = self.device.read(self.receive_buffer_size)
# decrypt the packet and bind the inputs
self.readInput(inReport)
# prepare new report for device
outReport = self.prepareReport()
# write the report to the device
self.writeReport(outReport)
def readInput(self, inReport):
"""read the reported data from the controller
:param inReport: report of the controller
:type inReport: bytes
"""
states = list(inReport) # convert bytes to list
# states 0 is always 1
self.state.LX = states[1]
self.state.LY = states[2]
self.state.RX = states[3]
self.state.RY = states[4]
self.state.L2 = states[5]
self.state.R2 = states[6]
# state 7 always increments -> not used anywhere
buttonState = states[8]
self.state.triangle = (buttonState & (1 << 7)) != 0
self.state.circle = (buttonState & (1 << 6)) != 0
self.state.cross = (buttonState & (1 << 5)) != 0
self.state.square = (buttonState & (1 << 4)) != 0
# dpad
dpad_state = buttonState & 0x0F
self.state.setDPadState(dpad_state)
misc = states[9]
self.state.R3 = (misc & (1 << 7)) != 0
self.state.L3 = (misc & (1 << 6)) != 0
self.state.options = (misc & (1 << 5)) != 0
self.state.share = (misc & (1 << 4)) != 0
self.state.R2Btn = (misc & (1 << 3)) != 0
self.state.L2Btn = (misc & (1 << 2)) != 0
self.state.R1 = (misc & (1 << 1)) != 0
self.state.L1 = (misc & (1 << 0)) != 0
misc2 = states[10]
self.state.ps = (misc2 & (1 << 0)) != 0
self.state.touchBtn = (misc2 & 0x02) != 0
# trackpad touch
self.state.trackPadTouch0.ID = inReport[33] & 0x7F
self.state.trackPadTouch0.isActive = (inReport[33] & 0x80) == 0
self.state.trackPadTouch0.X = ((inReport[35] & 0x0f) << 8) | (inReport[34])
self.state.trackPadTouch0.Y = ((inReport[36]) << 4) | ((inReport[35] & 0xf0) >> 4)
# trackpad touch
self.state.trackPadTouch1.ID = inReport[37] & 0x7F
self.state.trackPadTouch1.isActive = (inReport[37] & 0x80) == 0
self.state.trackPadTouch1.X = ((inReport[39] & 0x0f) << 8) | (inReport[38])
self.state.trackPadTouch1.Y = ((inReport[40]) << 4) | ((inReport[39] & 0xf0) >> 4)
# print(f'1Active = {self.state.trackPadTouch0.isActive}')
# print(f'X1: {self.state.trackPadTouch0.X} Y2: {self.state.trackPadTouch0.Y}')
# print(f'2Active = {self.state.trackPadTouch1.isActive}')
# print(f'X2: {self.state.trackPadTouch1.X} Y2: {self.state.trackPadTouch1.Y}')
# print(f'DPAD {self.state.DpadLeft} {self.state.DpadUp} {self.state.DpadRight} {self.state.DpadDown}')
# TODO: implement gyrometer and accelerometer
# TODO: control mouse with touchpad for fun as DS4Windows
def writeReport(self, outReport):
"""Write the given report to the device
:param outReport: report with data for the controller
:type outReport: list
"""
self.device.write(bytes(outReport))
def prepareReport(self):
"""prepare the report for the controller with all the settings set since the previous update
:return: report for the controller with all infos
:rtype: list
"""
outReport = [0] * 48 # create empty list with range of output report
# packet type
outReport[0] = 0x2
@@ -128,17 +234,11 @@ class pydualsense:
outReport[3]= 0 # left low freq motor 0-255 # [3]
outReport[4] = 0 # right low freq motor 0-255 # [4]
# outReport[5] - outReport[8] audio related
# set Micrphone LED, setting doesnt effect microphone settings
outReport[9] = self.audio.microphone_led # [9]
# set microphone muting
# add right trigger mode + parameters to packet
outReport[11] = self.triggerR.mode.value
outReport[12] = self.triggerR.forces[0]
@@ -158,16 +258,6 @@ class pydualsense:
outReport[28] = self.triggerL.forces[5]
outReport[31] = self.triggerL.forces[6]
"""
outReport.append(self.light.ledOption.value[0]) #
outReport.append(self.light.pulseOptions.value[0])
outReport.append(self.light.brightness.value[0])
outReport.append(self.light.playerNumber.value[0])
outReport.append(self.color[0]) # r
outReport.append(self.color[1]) # g
outReport.append(self.color[2]) # b
"""
outReport[39] = self.light.ledOption.value
outReport[42] = self.light.pulseOptions.value
outReport[43] = self.light.brightness.value
@@ -175,8 +265,74 @@ class pydualsense:
outReport[45] = self.color[0]
outReport[46] = self.color[1]
outReport[47] = self.color[2]
self.device.write(bytes(outReport)) # send to controller
return outReport
class DSTouchpad:
def __init__(self) -> None:
self.isActive = False
self.ID = 0
self.X = 0
self.Y = 0
class DSState:
def __init__(self) -> None:
self.packerC = 0
self.square, self.triangle, self.circle, self.cross = False, False, False, False
self.DpadUp, self.DpadDown, self.DpadLeft, self.DpadRight = False, False, False, False
self.L1, self.L2, self.L3, self.R1, self.R2, self.R3, self.R2Btn, self.L2Btn = False, False, False, False, False, False, False, False
self.share, self.options, self.ps, self.touch1, self.touch2, self.touchBtn, self.touchRight, self.touchLeft = False, False, False, False, False, False, False, False
self.touchFinger1, self.touchFinger2 = False, False
self.RX, self.RY, self.LX, self.LY = 128,128,128,128
self.trackPadTouch0, self.trackPadTouch1 = DSTouchpad(), DSTouchpad()
def setDPadState(self, dpad_state):
if dpad_state == 0:
self.DpadUp = True
self.DpadDown = False
self.DpadLeft = False
self.DpadRight = False
elif dpad_state == 1:
self.DpadUp = True
self.DpadDown = False
self.DpadLeft = False
self.DpadRight = True
elif dpad_state == 2:
self.DpadUp = False
self.DpadDown = False
self.DpadLeft = False
self.DpadRight = True
elif dpad_state == 3:
self.DpadUp = False
self.DpadDown = True
self.DpadLeft = False
self.DpadRight = True
elif dpad_state == 4:
self.DpadUp = False
self.DpadDown = True
self.DpadLeft = False
self.DpadRight = False
elif dpad_state == 5:
self.DpadUp = False
self.DpadDown = True
self.DpadLeft = False
self.DpadRight = False
elif dpad_state == 6:
self.DpadUp = False
self.DpadDown = False
self.DpadLeft = True
self.DpadRight = False
elif dpad_state == 7:
self.DpadUp = True
self.DpadDown = False
self.DpadLeft = True
self.DpadRight = False
else:
self.DpadUp = False
self.DpadDown = False
self.DpadLeft = False
self.DpadRight = False
class DSLight:
@@ -245,17 +401,3 @@ class DSTrigger:
packet += [0,0] # unknown what these do ?
packet.append(self.forces[-1]) # last force has a offset of 2 from the other forces. this is the frequency of the actuation
return packet
if __name__ == "__main__":
ds = pydualsense()
import time
# ds.triggerR.setMode(TriggerModes.Rigid)
# ds.triggerR.setForce(0, 255)
ds.setLeftTriggerMode(TriggerModes.Pulse)
ds.setLeftTriggerForce(1, 255)
ds.setRightTriggerMode(TriggerModes.Rigid)
ds.setRightTriggerForce(1, 255)
# ds.triggerL.setForce(6,255)
ds.sendReport()
time.sleep(2)
time.sleep(3)

View File

@@ -6,14 +6,13 @@ with open("README.md", "r") as fh:
setup(
name='pydualsense',
version='0.0.1',
description='control your dualsense controller with python',
version='0.1.0',
description='use your DualSense (PS5) controller with python',
long_description=long_description,
long_description_content_type="text/markdown",
url='https://github.com/flok/pydualsense',
author='Florian Kaiser',
author_email='shudson@anl.gov',
license='BSD 2-clause',
author='Florian K',
license='MIT License',
packages=setuptools.find_packages(),
install_requires=['hid>=1.0.4']
)