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
7 changes: 6 additions & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,9 @@ The effects.py show some effects of the controller
The read_controller.py display how you can access the button state of the controller

## test_trigger_value.py
The `test_trigger_value.py` show the left / right trigger analog value changing when press button (range from 0 to 255)

The `test_trigger_value.py` show the left / right trigger analog value changing when press button (range from 0 to 255)

## trigger_effects.py

The `trigger_effects.py` set the left / right trigger with a few effects: rigid, weapon and vibration
21 changes: 21 additions & 0 deletions examples/multi-controllers-selector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from pydualsense import *

import time

# get dualsense instance
dualsense = pydualsense()
dualsense.init()

dualsense.setController.choose(0) # choose controller 1
time.sleep(0.01) # sleep a little to see the result on the controller
dualsense.light.setColorI(0, 255, 0) # set color around touchpad to red
dualsense.light.setPlayerID(PlayerID.PLAYER_1) # set all player 1 indicator on
time.sleep(2) # sleep a little to see the result on the controller, this is not needed in normal usage

dualsense.setController.choose(1) # choose controller 2
time.sleep(0.01)
dualsense.light.setColorI(255, 0, 0)# set color around touchpad to green
dualsense.light.setPlayerID(PlayerID.PLAYER_2) # set all player 2 indicator on
time.sleep(2)

dualsense.close() # terminate the thread for message and close the device
23 changes: 23 additions & 0 deletions examples/trigger_effects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from pydualsense import *
import time

dualsense = pydualsense()
dualsense.init()


dualsense.triggerR.setEffect(TriggersEffects.RIGID)
dualsense.triggerL.setEffect(TriggersEffects.RIGID)
time.sleep(5)

dualsense.triggerR.setEffect(TriggersEffects.WEAPON)
dualsense.triggerL.setEffect(TriggersEffects.WEAPON)
time.sleep(5)

dualsense.triggerR.setEffect(TriggersEffects.VIBRATION)
dualsense.triggerL.setEffect(TriggersEffects.VIBRATION)
time.sleep(5)

dualsense.triggerR.setEffect(TriggersEffects.OFF)
dualsense.triggerL.setEffect(TriggersEffects.OFF)
time.sleep(0.5)
dualsense.close()
135 changes: 66 additions & 69 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pydualsense/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import sys
sys.path.append(os.path.dirname(__file__))

from .enums import LedOptions, Brightness, PlayerID, PulseOptions, TriggerModes # noqa : F401
from .enums import LedOptions, Brightness, PlayerID, PulseOptions, TriggerModes, TriggersEffects # noqa : F401
from .event_system import Event # noqa : F401
from .pydualsense import pydualsense, DSLight, DSState, DSTouchpad, DSTrigger, DSAudio # noqa : F401

Expand Down
8 changes: 7 additions & 1 deletion pydualsense/enums.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from enum import IntFlag
from enum import IntFlag, Enum


class ConnectionType(IntFlag):
Expand Down Expand Up @@ -55,3 +55,9 @@ class BatteryState(IntFlag):
POWER_SUPPLY_STATUS_ERROR = 0xF
POWER_SUPPLY_TEMP_OR_VOLTAGE_OUT_OF_RANGE = 0xA
POWER_SUPPLY_STATUS_UNKNOWN = 0x0

class TriggersEffects(Enum):
VIBRATION = (TriggerModes.Pulse_AB, [255, 3, 255, 255, 255, 63, 15])
WEAPON = (TriggerModes.Rigid_AB, [36, 0, 7, 0, 0, 0, 0])
RIGID = (TriggerModes.Rigid, [0, 255, 0, 0, 0, 0, 0])
OFF = (TriggerModes.Off, [0] * 7)
63 changes: 50 additions & 13 deletions pydualsense/pydualsense.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
PlayerID,
PulseOptions,
TriggerModes,
TriggersEffects,
)
from .event_system import Event

Expand Down Expand Up @@ -119,7 +120,18 @@ def init(self) -> None:
"""
initialize module and device states. Starts the sendReport background thread at the end
"""
self.device, self.is_edge = self.__find_device() # type: Tuple[hidapi.Device, bool]

self.setController = selectController() # controller selector
self.devices = self.__find_device() # type: Tuple[hidapi.Device, bool]

self.device = self.devices['controller0']["deviceConection"]

for dev in self.devices.keys(): # In case that an edge controller is connected, the edge events and buttons is enabled
if self.devices[dev]["isEdge"] == True:
self.is_edge = True
else:
self.is_edge = False

self.light = DSLight() # control led light of ds
self.audio = DSAudio() # ds audio setting
self.triggerL = DSTrigger() # left trigger
Expand Down Expand Up @@ -176,7 +188,8 @@ def close(self) -> None:

self.ds_thread = False
self.report_thread.join()
self.device.close()
for key in self.devices.keys(): # Close controllers
self.devices[key]["deviceConection"].close()

def __find_device(self) -> Tuple[hidapi.Device, bool]:
"""
Expand All @@ -187,8 +200,8 @@ def __find_device(self) -> Tuple[hidapi.Device, bool]:
Exception: No device detected

Returns:
hid.Device: returns opened controller device
bool: returns true if the device is a DualSense Edge.
hid.Device: returns a dict with opened controllers devices and
if the device is a DualSense Edge.
"""
# TODO: detect connection mode, bluetooth has a bigger write buffer
# TODO: implement multiple controllers working
Expand All @@ -199,19 +212,19 @@ def __find_device(self) -> Tuple[hidapi.Device, bool]:
raise Exception(
"HIDGuardian detected. Delete the controller from HIDGuardian and restart PC to connect to controller"
)
detected_device: hidapi.Device = None

select = {} # Dict of devices connected
numCont = 0 # Counter of devices connected
devices = hidapi.enumerate(vendor_id=0x054C)
for device in devices:
if device.vendor_id == 0x054C and device.product_id in (0x0CE6, 0x0DF2):
detected_device = device
for dev in devices:
if dev.vendor_id == 0x054C and dev.product_id in (0x0CE6, 0x0DF2):
select[f'controller{numCont}'] = {"deviceConection": hidapi.Device(path=dev.path), "isEdge": dev.product_id == 0x0DF2}
numCont = numCont + 1

if detected_device is None:
if len(select) == 0:
raise Exception("No device detected")

dual_sense = hidapi.Device(
vendor_id=detected_device.vendor_id, product_id=detected_device.product_id
)
return dual_sense, detected_device.product_id == 0x0DF2
return select

def setLeftMotor(self, intensity: int) -> None:
"""
Expand Down Expand Up @@ -253,6 +266,10 @@ def sendReport(self) -> None:
"""background thread handling the reading of the device and updating its states"""
while self.ds_thread:
try:
self.device = self.devices[f'controller{self.setController.selectedController}']["deviceConection"] # Controller selector

self.is_edge = self.devices[f'controller{self.setController.selectedController}']["isEdge"] # Check if is an edge and enable the reading of edge buttons

# read data from the input report of the controller
inReport = self.device.read(self.input_report_length)
if self.verbose:
Expand All @@ -265,13 +282,18 @@ def sendReport(self) -> None:

# write the report to the device
self.writeReport(outReport)

except IOError:
self.connected = False
break

except AttributeError:
self.connected = False
break

except KeyError:
self.connected = False
break

def readInput(self, inReport : List[int]) -> None:
"""
Expand Down Expand Up @@ -665,6 +687,12 @@ def __init__(self) -> None:
self.X = 0
self.Y = 0

class selectController:
def __init__(self) -> None:
self.selectedController = 0

def choose(self, controllerNum=0):
self.selectedController = controllerNum

class DSState:
def __init__(self) -> None:
Expand Down Expand Up @@ -963,6 +991,15 @@ def setMode(self, mode: TriggerModes) -> None:

self.mode = mode

def setEffect(self, effect: TriggersEffects) -> None:
"""
Select effect
"""
if not isinstance(effect, TriggersEffects):
raise TypeError("Triggers effects parameter needs to be of type 'TriggersEffects'")

self.mode, self.forces = effect.value


class DSGyro:
"""
Expand Down