30 Commits

Author SHA1 Message Date
Florian Kaiser
d76717c163 Fix missing import 2021-01-01 20:34:42 +01:00
Florian Kaiser
786657cc90 Merge branch 'master' of https://github.com/flok/pydualsense into master 2021-01-01 20:33:39 +01:00
Florian Kaiser
11e78fbece Fix Python > 3.8 dll import 2021-01-01 20:32:55 +01:00
Florian K
a3f697866b Changed place for hidapi.dll
Adding dlls to your System32 is not a good idea. Place the dll into your Workspace
2021-01-01 19:01:30 +01:00
Florian K
1530c79dd7 Update install instructions
Updated the install instructions with the hidapi download and placement.
2021-01-01 11:36:35 +01:00
Florian Kaiser
93b5e38e6e v0.4.1
- Fix mypy errors
2020-12-31 23:53:23 +01:00
Florian Kaiser
94cb09dbdd v0.4.0
- refactored code structure
- fixed playerID led display
- added Color function with tuple support
- added type checking in every function
- added more Exceptions for out of bound values
2020-12-31 23:48:34 +01:00
Florian Kaiser
8fb31f86ba added mypy static analyzer action on push 2020-12-31 23:09:03 +01:00
Florian Kaiser
e04766d48d Add requirements.txt for dependabot 2020-12-27 15:06:23 +01:00
Florian K
c39f3f2ea5 Merge pull request #6 from flok/examples
Examples
2020-12-22 15:12:05 +01:00
Florian K
ea319db5a3 Merge branch 'master' into examples 2020-12-22 15:09:36 +01:00
Florian Kaiser
d62e8d133e update version and readme 2020-12-22 15:05:21 +01:00
Florian Kaiser
fe435f6e36 Update 0.3.0
* Added low freq motor support
2020-12-22 15:00:46 +01:00
Florian Kaiser
cc767d5fcd add and update examples 2020-12-22 14:58:21 +01:00
Florian Kaiser
1ab69d6c96 Aligned the sticks x and y values so idle position is 0 on both axes 2020-12-22 14:32:39 +01:00
Florian Kaiser
b004d2bc7b Added init function for better usability, added check for HIDGuardian usage 2020-12-22 14:15:42 +01:00
Florian K
3f16538555 added credits to README.md 2020-12-22 14:15:42 +01:00
Florian Kaiser
f1be774e68 0.2.0
- added more light functions
- added docstrings for functions
2020-12-22 14:15:42 +01:00
Florian Kaiser
9560d8e637 delete demo 2020-12-22 14:15:42 +01:00
Florian Kaiser
ecb42d9c0a Added init function for better usability, added check for HIDGuardian usage 2020-12-22 14:11:33 +01:00
Florian K
604c5f2800 added credits to README.md 2020-11-30 21:25:13 +01:00
Florian Kaiser
a54fb55b91 0.2.0
- added more light functions
- added docstrings for functions
2020-11-29 22:41:09 +01:00
Florian Kaiser
0bf55f756b delete demo 2020-11-29 22:32:02 +01:00
Florian K
2a5afd7cb0 Merge pull request #2 from flok/examples
- examples
- verbose mode
2020-11-29 19:47:57 +01:00
Florian Kaiser
0a6fee2f85 Reference example in README 2020-11-29 19:44:53 +01:00
Florian Kaiser
2f5579cc49 Examples, closing controller HID device on close function 2020-11-29 19:40:37 +01:00
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
13 changed files with 620 additions and 230 deletions

21
.github/workflows/python-mypy.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: Mypy
on: [push]
jobs:
build:
runs-on: ubuntu-latest
name: Mypy
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install mypy
- name: mypy
run: |
mypy pydualsense/

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,10 +1,13 @@
# pydualsense
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.
control your dualsense through python. using the hid library this package implements the report features for controlling your new PS5 controller.
# dependecies
# install
- hid >= 1.0.4
Download [hidapi](https://github.com/libusb/hidapi/releases) and place the x64 .dll file into your Workspace. After that install the package from [pypi](https://pypi.org/project/pydualsense/).
```bash
pip install pydualsense
```
# usage
```python
@@ -12,11 +15,35 @@ control your dualsense through python. using the hid library this module impleme
from pydualsense import pydualsense
ds = pydualsense() # open controller
ds.setColor(255,0,0) # set touchpad color to red
ds.init() # initialize controller
ds.light.setColorI(255,0,0) # set touchpad color to red
ds.triggerL.setMode(TriggerModes.Rigid)
ds.triggerL.setForce(1, 255)
ds.close() # closing the controller
```
See [examples](https://github.com/flok/pydualsense/tree/master/examples) folder for some more ideas
# Help wanted
Help wanted from people that want to use this and have feature requests. Just open a issue with the correct label.
# dependecies
- hid >= 1.0.4
# Credits
Most stuff for this implementation were provided by and used from:
- [https://www.reddit.com/r/gamedev/comments/jumvi5/dualsense_haptics_leds_and_more_hid_output_report/](https://www.reddit.com/r/gamedev/comments/jumvi5/dualsense_haptics_leds_and_more_hid_output_report/)
- [https://github.com/Ryochan7/DS4Windows](https://github.com/Ryochan7/DS4Windows)
# Coming soon
- reading the states of the controller to enable a fully compatibility with python
- add documentation
- add bluetooth support
- add multiple controllers
- reading the states of the controller to enable a fully compatibility with python - partially done
- add documentation using sphinx

15
examples/README.md Normal file
View File

@@ -0,0 +1,15 @@
# Examples
This folder contains some examples on applications for the library and its usage
## leds.py
The leds.py shows you how you can interact and change the lights of the controller
## effects.py
The effects.py show some effects of the controller
## read_controller.py
The read_controller.py display how you can access the button state of the controller

22
examples/effects.py Normal file
View File

@@ -0,0 +1,22 @@
from pydualsense import *
# get dualsense instance
dualsense = pydualsense()
dualsense.init()
print('Trigger Effect demo started')
dualsense.setLeftMotor(255)
dualsense.setRightMotor(100)
dualsense.triggerL.setMode(TriggerModes.Rigid)
dualsense.triggerL.setForce(1, 255)
dualsense.triggerR.setMode(TriggerModes.Pulse_A)
dualsense.triggerR.setForce(0, 200)
dualsense.triggerR.setForce(1, 255)
dualsense.triggerR.setForce(2, 175)
import time; time.sleep(3)
# terminate the thread for message and close the device
dualsense.close()

16
examples/leds.py Normal file
View File

@@ -0,0 +1,16 @@
from pydualsense import *
# get dualsense instance
dualsense = pydualsense()
dualsense.init()
# set color around touchpad to red
dualsense.light.setColorI(255,0,0)
# enable microphone indicator
dualsense.audio.setMicrophoneLED(1)
# set all player 1 indicator on
dualsense.light.setPlayerID(PlayerID.player1)
# sleep a little to see the result on the controller
# this is not needed in normal usage
import time; time.sleep(2)
# terminate the thread for message and close the device
dualsense.close()

View File

@@ -0,0 +1,15 @@
from pydualsense import *
# create dualsense
dualsense = pydualsense()
# find device and initialize
dualsense.init()
# read controller state until R1 is pressed
while not dualsense.state.R1:
print(f"Circle : {dualsense.state.circle} Cross : {dualsense.state.cross} L Stick X : {dualsense.state.LX} L Stick Y : {dualsense.state.LY}")
# close device
dualsense.close()

View File

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

View File

@@ -1,6 +1,6 @@
from enum import IntFlag
class LedOptions(IntFlag):
class LedOptions(IntFlag):
Off=0x0,
PlayerLedBrightness=0x1,
UninterrumpableLed=0x2,
@@ -17,20 +17,20 @@ class Brightness(IntFlag):
low = 0x2
class PlayerID(IntFlag):
player1 = 1,
player2 = 2,
player3 = 4,
player4 = 8,
player5 = 16,
player1 = 4,
player2 = 10,
player3 = 21,
player4 = 27,
all = 31
class TriggerModes(IntFlag):
Off =0x0, # no resistance
Rigid =0x1, # continous resistance
Pulse =0x2, # section resistance
Rigid_A=0x1 | 0x20,
Rigid_B=0x1 | 0x04,
Rigid_AB=0x1 | 0x20 | 0x04,
Off = 0x0, # no resistance
Rigid = 0x1, # continous resistance
Pulse = 0x2, # section resistance
Rigid_A = 0x1 | 0x20,
Rigid_B = 0x1 | 0x04,
Rigid_AB = 0x1 | 0x20 | 0x04,
Pulse_A = 0x2 | 0x20,
Pulse_B = 0x2 | 0x04,
Pulse_AB = 0x2 | 0x20 | 0x04,

View File

@@ -1,34 +0,0 @@
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
from interface import Ui_MainWindow
from pydualsense import pydualsense
def colorR(value):
global colorR
colorR = value
def colorG(value):
global colorG
colorG = value
def colorB(value):
global colorB
colorB = value
def send():
ds.setColor(colorR, colorG, colorB)
ds.sendReport()
if __name__ == "__main__":
global ds
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
ds = pydualsense()
# connect interface to
ui.slider_r.valueChanged.connect(colorR)
ui.slider_g.valueChanged.connect(colorG)
ui.slider_b.valueChanged.connect(colorB)
ui.pushButton.clicked.connect(send)
MainWindow.show()
sys.exit(app.exec_())

View File

@@ -1,203 +1,502 @@
import hid
# needed for python > 3.8
import os, sys
if sys.version_info >= (3,8):
os.add_dll_directory(os.getcwd())
import hid # type: ignore
from .enums import (LedOptions, PlayerID,
PulseOptions, TriggerModes, Brightness)
import threading
import winreg
class pydualsense:
def __init__(self) -> None:
def __init__(self, verbose: bool = False) -> None:#
# TODO: maybe add a init function to not automatically allocate controller when class is declared
self.device = self.__find_device()
self.verbose = verbose
self.receive_buffer_size = 64
self.send_report_size = 48
self.leftMotor = 0
self.rightMotor = 0
def init(self):
"""initialize module and device states
"""
self.device: hid.Device = self.__find_device()
self.light = DSLight() # control led light of ds
self.audio = DSAudio()
self.triggerL = DSTrigger()
self.triggerR = DSTrigger()
self.audio = DSAudio() # ds audio setting
self.triggerL = DSTrigger() # left trigger
self.triggerR = DSTrigger() # right trigger
# set default for the controller
self.color = (0,0,255) # set dualsense color around the touchpad to blue
self.state = DSState() # controller states
self.send_thread = True
# thread for receiving and sending
self.ds_thread = True
self.report_thread = threading.Thread(target=self.sendReport)
self.report_thread.start()
# create thread for sending
self.init = True
def close(self):
self.send_thread = False
"""
Stops the report thread and closes the HID device
"""
self.ds_thread = False
self.report_thread.join()
self.device.close()
def __find_device(self):
devices = hid.enumerate(vid=0x054c)
found_devices = []
for device in devices:
if device['vendor_id'] == 0x054c and device['product_id'] == 0x0CE6:
found_devices.append(device)
def _check_hide(self) -> bool:
"""check if hidguardian is used and controller is hidden
"""
if sys.platform.startswith('win32'):
try:
access_reg = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
access_key = winreg.OpenKey(access_reg, 'SYSTEM\CurrentControlSet\Services\HidGuardian\Parameters', 0, winreg.KEY_READ)
affected_devices = winreg.QueryValueEx(access_key, 'AffectedDevices')[0]
if "054C" in affected_devices and "0CE6" in affected_devices:
return True
return False
except OSError as e:
print(e)
return False
def __find_device(self) -> hid.Device:
"""
find HID device and open it
Raises:
Exception: HIDGuardian detected
Exception: No device detected
Returns:
hid.Device: returns opened controller device
"""
# TODO: detect connection mode, bluetooth has a bigger write buffer
# TODO: implement multiple controllers working
if len(found_devices) != 1:
raise Exception('no dualsense controller detected')
if self._check_hide():
raise Exception('HIDGuardian detected. Delete the controller from HIDGuardian and restart PC to connect to controller')
detected_device: hid.Device = None
devices = hid.enumerate(vid=0x054c)
for device in devices:
if device['vendor_id'] == 0x054c and device['product_id'] == 0x0CE6:
detected_device = device
dual_sense = hid.Device(vid=found_devices[0]['vendor_id'], pid=found_devices[0]['product_id'])
if detected_device == None:
raise Exception('No device detected')
dual_sense = hid.Device(vid=detected_device['vendor_id'], pid=detected_device['product_id'])
return dual_sense
# color stuff
def setColor(self, r: int, g:int, b:int):
if r > 255 or g > 255 or b > 255:
raise Exception('colors have values from 0 to 255 only')
self.color = (r,g,b)
# right trigger
def setRightTriggerMode(self, mode: TriggerModes):
"""set the trigger mode for R2
:param mode: enum of Trigger mode
:type mode: TriggerModes
def setLeftMotor(self, intensity: int):
"""
self.triggerR.mode = mode
set left motor rumble
def setRightTriggerForce(self, forceID: int, force: int):
"""set the right trigger force. trigger consist of 7 parameter
Args:
intensity (int): rumble intensity
:param forceID: parameter id from 0 to 6
:type forceID: int
:param force: force from 0..ff (0..255) applied to the trigger
:type force: int
Raises:
TypeError: intensity false type
Exception: intensity out of bounds 0..255
"""
if forceID > 6:
raise Exception('only 7 parameters available')
if not isinstance(intensity, int):
raise TypeError('left motor intensity needs to be an int')
self.triggerR.setForce(id=forceID, force=force)
if intensity > 255 or intensity < 0:
raise Exception('maximum intensity is 255')
self.leftMotor = intensity
# left trigger
def setLeftTriggerMode(self, mode: TriggerModes):
"""set the trigger mode for L2
:param mode: enum of Trigger mode
:type mode: TriggerModes
def setRightMotor(self, intensity: int):
"""
self.triggerL.mode = mode
set right motor rumble
def setLeftTriggerForce(self, forceID: int, force: int):
"""set the left trigger force. trigger consist of 7 parameter
Args:
intensity (int): rumble intensity
:param forceID: parameter id from 0 to 6
:type forceID: int
:param force: force from 0..ff (0..255) applied to the trigger
:type force: int
Raises:
TypeError: intensity false type
Exception: intensity out of bounds 0..255
"""
if not isinstance(intensity, int):
raise TypeError('right motor intensity needs to be an int')
if forceID > 6:
raise Exception('only 7 parameters available')
self.triggerL.setForce(id=forceID, force=force)
# TODO: audio
# audio stuff
def setMicrophoneLED(self, value):
self.audio.microphoneLED = 0x1
if intensity > 255 or intensity < 0:
raise Exception('maximum intensity is 255')
self.rightMotor = intensity
def sendReport(self):
while self.send_thread:
outReport = [0] * 48 # create empty list with range of output report
# packet type
outReport[0] = 0x2
"""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)
# flags determing what changes this packet will perform
# 0x01 set the main motors (also requires flag 0x02); setting this by itself will allow rumble to gracefully terminate and then re-enable audio haptics, whereas not setting it will kill the rumble instantly and re-enable audio haptics.
# 0x02 set the main motors (also requires flag 0x01; without bit 0x01 motors are allowed to time out without re-enabling audio haptics)
# 0x04 set the right trigger motor
# 0x08 set the left trigger motor
# 0x10 modification of audio volume
# 0x20 toggling of internal speaker while headset is connected
# 0x40 modification of microphone volume
outReport[1] = 0xff # [1]
# prepare new report for device
outReport = self.prepareReport()
# further flags determining what changes this packet will perform
# 0x01 toggling microphone LED
# 0x02 toggling audio/mic mute
# 0x04 toggling LED strips on the sides of the touchpad
# 0x08 will actively turn all LEDs off? Convenience flag? (if so, third parties might not support it properly)
# 0x10 toggling white player indicator LEDs below touchpad
# 0x20 ???
# 0x40 adjustment of overall motor/effect power (index 37 - read note on triggers)
# 0x80 ???
outReport[2] = 0x1 | 0x2 | 0x4 | 0x10 | 0x40 # [2]
outReport[3]= 0 # left low freq motor 0-255 # [3]
outReport[4] = 0 # right low freq motor 0-255 # [4]
# write the report to the device
self.writeReport(outReport)
def readInput(self, inReport):
"""
read the input from the controller and assign the states
Args:
inReport (bytearray): read bytearray containing the state of the whole controller
"""
states = list(inReport) # convert bytes to list
# states 0 is always 1
self.state.LX = states[1] - 127
self.state.LY = states[2] - 127
self.state.RX = states[3] - 127
self.state.RY = states[4] - 127
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
# outReport[5] - outReport[8] audio related
# dpad
dpad_state = buttonState & 0x0F
self.state.setDPadState(dpad_state)
# set Micrphone LED, setting doesnt effect microphone settings
outReport[9] = self.audio.microphone_led # [9]
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
# set microphone muting
misc2 = states[10]
self.state.ps = (misc2 & (1 << 0)) != 0
self.state.touchBtn = (misc2 & 0x02) != 0
# add right trigger mode + parameters to packet
outReport[11] = self.triggerR.mode.value
outReport[12] = self.triggerR.forces[0]
outReport[13] = self.triggerR.forces[1]
outReport[14] = self.triggerR.forces[2]
outReport[15] = self.triggerR.forces[3]
outReport[16] = self.triggerR.forces[4]
outReport[17] = self.triggerR.forces[5]
outReport[20] = self.triggerR.forces[6]
# 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)
outReport[22] = self.triggerL.mode.value
outReport[23] = self.triggerL.forces[0]
outReport[24] = self.triggerL.forces[1]
outReport[25] = self.triggerL.forces[2]
outReport[26] = self.triggerL.forces[3]
outReport[27] = self.triggerL.forces[4]
outReport[28] = self.triggerL.forces[5]
outReport[31] = self.triggerL.forces[6]
# 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)
outReport[39] = self.light.ledOption.value
outReport[42] = self.light.pulseOptions.value
outReport[43] = self.light.brightness.value
outReport[44] = self.light.playerNumber.value
outReport[45] = self.color[0]
outReport[46] = self.color[1]
outReport[47] = self.color[2]
self.device.write(bytes(outReport)) # send to controller
# 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 report to the device
Args:
outReport (list): report to be written to device
"""
self.device.write(bytes(outReport))
def prepareReport(self):
"""
prepare the output to be send to the controller
Returns:
list: report to send to controller
"""
outReport = [0] * 48 # create empty list with range of output report
# packet type
outReport[0] = 0x2
# flags determing what changes this packet will perform
# 0x01 set the main motors (also requires flag 0x02); setting this by itself will allow rumble to gracefully terminate and then re-enable audio haptics, whereas not setting it will kill the rumble instantly and re-enable audio haptics.
# 0x02 set the main motors (also requires flag 0x01; without bit 0x01 motors are allowed to time out without re-enabling audio haptics)
# 0x04 set the right trigger motor
# 0x08 set the left trigger motor
# 0x10 modification of audio volume
# 0x20 toggling of internal speaker while headset is connected
# 0x40 modification of microphone volume
outReport[1] = 0xff # [1]
# further flags determining what changes this packet will perform
# 0x01 toggling microphone LED
# 0x02 toggling audio/mic mute
# 0x04 toggling LED strips on the sides of the touchpad
# 0x08 will actively turn all LEDs off? Convenience flag? (if so, third parties might not support it properly)
# 0x10 toggling white player indicator LEDs below touchpad
# 0x20 ???
# 0x40 adjustment of overall motor/effect power (index 37 - read note on triggers)
# 0x80 ???
outReport[2] = 0x1 | 0x2 | 0x4 | 0x10 | 0x40 # [2]
outReport[3] = self.leftMotor # left low freq motor 0-255 # [3]
outReport[4] = self.rightMotor # 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]
# add right trigger mode + parameters to packet
outReport[11] = self.triggerR.mode.value
outReport[12] = self.triggerR.forces[0]
outReport[13] = self.triggerR.forces[1]
outReport[14] = self.triggerR.forces[2]
outReport[15] = self.triggerR.forces[3]
outReport[16] = self.triggerR.forces[4]
outReport[17] = self.triggerR.forces[5]
outReport[20] = self.triggerR.forces[6]
outReport[22] = self.triggerL.mode.value
outReport[23] = self.triggerL.forces[0]
outReport[24] = self.triggerL.forces[1]
outReport[25] = self.triggerL.forces[2]
outReport[26] = self.triggerL.forces[3]
outReport[27] = self.triggerL.forces[4]
outReport[28] = self.triggerL.forces[5]
outReport[31] = self.triggerL.forces[6]
outReport[39] = self.light.ledOption.value
outReport[42] = self.light.pulseOptions.value
outReport[43] = self.light.brightness.value
outReport[44] = self.light.playerNumber.value
outReport[45] = self.light.TouchpadColor[0]
outReport[46] = self.light.TouchpadColor[1]
outReport[47] = self.light.TouchpadColor[2]
if self.verbose:
print(outReport)
return outReport
class DSTouchpad:
def __init__(self) -> None:
"""
Class represents the Touchpad of the controller
"""
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:
"""DualSense Light class
make it simple, no get or set functions. quick and dirty
"""
Represents all features of lights on the controller
"""
def __init__(self) -> None:
self.brightness: Brightness = Brightness.low # sets
self.brightness: Brightness = Brightness.low # sets
self.playerNumber: PlayerID = PlayerID.player1
self.ledOption : LedOptions = LedOptions.Both
self.pulseOptions : PulseOptions = PulseOptions.Off
self.TouchpadColor = (0,0,255)
def setLEDOption(self, option: LedOptions):
"""
Sets the LED Option
Args:
option (LedOptions): Led option
Raises:
TypeError: LedOption is false type
"""
if not isinstance(option, LedOptions):
raise TypeError('Need LEDOption type')
self.ledOption = option
def setPulseOption(self, option: PulseOptions):
"""
Sets the Pulse Option of the LEDs
Args:
option (PulseOptions): pulse option of the LEDs
Raises:
TypeError: Pulse option is false type
"""
if not isinstance(option, PulseOptions):
raise TypeError('Need PulseOption type')
self.pulseOptions = option
def setBrightness(self, brightness: Brightness):
self._brightness = brightness
"""
Defines the brightness of the Player LEDs
def setPlayerNumer(self, player):
if player > 5:
raise Exception('only 5 players supported. choose 1-5')
Args:
brightness (Brightness): brightness of LEDS
Raises:
TypeError: brightness false type
"""
if not isinstance(brightness, Brightness):
raise TypeError('Need Brightness type')
self.brightness = brightness
def setPlayerID(self, player : PlayerID):
"""
Sets the PlayerID of the controller with the choosen LEDs.
The controller has 4 Player states
Args:
player (PlayerID): chosen PlayerID for the Controller
Raises:
TypeError: [description]
"""
if not isinstance(player, PlayerID):
raise TypeError('Need PlayerID type')
self.playerNumber = player
def setColorI(self, r: int , g: int, b: int) -> None:
"""
Sets the Color around the Touchpad of the controller
Args:
r (int): red channel
g (int): green channel
b (int): blue channel
Raises:
TypeError: color channels have wrong type
Exception: color channels are out of bounds
"""
if not isinstance(r, int) or not isinstance(g, int) or not isinstance(b, int):
raise TypeError('Color parameter need to be int')
# check if color is out of bounds
if (r > 255 or g > 255 or b > 255) or (r < 0 or g < 0 or b < 0):
raise Exception('colors have values from 0 to 255 only')
self.TouchpadColor = (r,g,b)
def setColorT(self, color: tuple) -> None:
"""
Sets the Color around the Touchpad as a tuple
Args:
color (tuple): color as tuple
Raises:
TypeError: color has wrong type
Exception: color channels are out of bounds
"""
if not isinstance(color, tuple):
raise TypeError('Color type is tuple')
# unpack for out of bounds check
r,g,b = map(int, color)
# check if color is out of bounds
if (r > 255 or g > 255 or b > 255) or (r < 0 or g < 0 or b < 0):
raise Exception('colors have values from 0 to 255 only')
self.TouchpadColor = (r,g,b)
class DSAudio:
def __init__(self) -> None:
self.microphone_mute = 0
self.microphone_led = 0
def setMicrophoneLED(self, value):
"""
Activates or disables the microphone led.
This doesnt change the mute/unmutes the microphone itself.
Args:
value (int): On or off microphone LED
Raises:
Exception: false state for the led
"""
if value > 1 or value < 0:
raise Exception('Microphone LED can only be on or off (0 .. 1)')
self.microphone_led = value
class DSTrigger:
def __init__(self) -> None:
# trigger modes
@@ -206,50 +505,37 @@ class DSTrigger:
# force parameters for the triggers
self.forces = [0 for i in range(7)]
def setForce(self, id:int = 0, force:int = 0):
"""set the force of the trigger
:param id: id of the trigger parameters. 6 possible, defaults to 0
:type id: int, optional
:param force: force 0 to 255, defaults to 0
:type force: int, optional
:raises Exception: false trigger parameter accessed. only available trigger parameters from 0 to 6
def setForce(self, forceID: int = 0, force: int = 0):
"""
if id > 6 or id < 0:
raise Exception('only trigger parameters 0 to 6 available')
self.forces[id] = force
Sets the forces of the choosen force parameter
Args:
forceID (int, optional): force parameter. Defaults to 0.
force (int, optional): applied force to the parameter. Defaults to 0.
Raises:
TypeError: wrong type of forceID or force
Exception: choosen a false force parameter
"""
if not isinstance(forceID, int) or not isinstance(force, int):
raise TypeError('forceID and force needs to be type int')
if forceID > 6 or forceID < 0:
raise Exception('only 7 parameters available')
self.forces[forceID] = force
def setMode(self, mode: TriggerModes):
"""set mode on the trigger
:param mode: mode for trigger
:type mode: TriggerModes
"""
self.mode = mode
Set the Mode for the Trigger
def getTriggerPacket(self):
"""returns array of the trigger modes and its parameters
Args:
mode (TriggerModes): Trigger mode
:return: packet of the trigger settings
:rtype: list
Raises:
TypeError: false Trigger mode type
"""
# create packet
packet = [self.mode.value]
packet += [self.forces[i] for i in range(6)]
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)
if not isinstance(mode, TriggerModes):
raise TypeError('Trigger mode parameter needs to be of type `TriggerModes`')
self.mode = mode

1
requirements.txt Normal file
View File

@@ -0,0 +1 @@
hid==1.0.4

View File

@@ -6,12 +6,12 @@ with open("README.md", "r") as fh:
setup(
name='pydualsense',
version='0.0.4',
description='control your DualSense (PS5) controller with python',
version='0.4.2',
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='Florian K',
license='MIT License',
packages=setuptools.find_packages(),
install_requires=['hid>=1.0.4']