30 Commits

Author SHA1 Message Date
Florian Kaiser
330d117340 Fix wrong import v0.6.1 2022-08-14 16:20:15 +02:00
Flo
9d8ab950de Merge pull request #32 from flok/event_system
Event System and Gyro / Accelerometer support
2022-08-14 15:55:03 +02:00
Florian Kaiser
40f74472d7 Update README.md and version to 0.6 2022-08-14 15:51:39 +02:00
Florian Kaiser
b091660130 Add event system for states, added gyro and accelerometer 2022-08-14 15:48:58 +02:00
Florian Kaiser
82d407bfe2 Linting with pep8 2022-08-14 14:08:57 +02:00
Flo
0f279f1ee8 Update hidapi-usb version for linux support 2021-08-05 23:02:39 +02:00
Flo
624d17c919 Update README.md with Linux instructions 2021-08-05 21:55:24 +02:00
Florian Kaiser
24c628a182 Update package version 2021-06-28 23:04:55 +02:00
Florian Kaiser
36e8886754 Update version 2021-06-28 23:03:23 +02:00
Florian Kaiser
28605e0023 Update with linux support over usb 2021-06-28 23:00:22 +02:00
Florian Kaiser
f5529f1463 Add state for microphone button 2021-03-07 21:40:55 +01:00
Florian Kaiser
b3ff9fd375 Update 0.5.2
- Added Microphone mute
- changed MicrphoneLED function to boolean instead of int parameter
- using diffrent hidapi library for interacting with c library to get length of reports from device for bt support later
2021-03-07 21:21:01 +01:00
Flo
98b13798cd Merge pull request #19 from nougator/master
Fixed verbose
2021-01-17 21:10:33 +01:00
Nougator
b51c8b49f6 Fixed verbose. 2021-01-17 20:55:59 +01:00
Nougator
cdbe03ad56 Merge pull request #1 from flok/master
e
2021-01-17 20:55:19 +01:00
Flo
3a14ab3e7a Update setup.py 2021-01-10 14:53:56 +01:00
Flo
32f9042abb Merge pull request #16 from flok/hidapi_rewrite
Refactor hidapi
2021-01-10 14:08:31 +01:00
Florian Kaiser
4c86d71633 Refactor hidapi 2021-01-10 14:06:53 +01:00
Flo
9c79d961f1 Merge pull request #15 from TheComputerDan/master
Adapting Platform Agnostic Practices
2021-01-07 10:13:15 +01:00
Dan
c1c10e4eac Remove self reference 2021-01-06 18:25:15 -05:00
Dan
04ce807bc0 Added platform check for add_dll_directory 2021-01-03 22:16:31 -05:00
Dan
71a49da5d2 Removing accidently merged imports 2021-01-02 23:29:59 -05:00
Dan
f4e1d73dd3 Merge remote-tracking branch 'upstream/master'
Updating Fork with Master
2021-01-02 23:25:19 -05:00
Florian K
e766dca70f Deleting mypy action
Mypy gives weird results on github. Lets only work with it locally
2021-01-01 23:42:29 +01:00
Florian K
1e0b23da41 Update python-mypy.yml 2021-01-01 23:41:15 +01:00
Florian K
ff01788c89 Mypy enums import error fix 2021-01-01 23:39:54 +01:00
Florian K
c07b975bc5 Merge pull request #13 from nougator/patch-1
Update README.md
2021-01-01 23:16:08 +01:00
Nougator
7b0270fa7d Update README.md 2021-01-01 20:56:57 +01:00
Dan
83a37750d1 linting 2021-01-01 00:28:46 -05:00
Dan
bc0eb35c3c Moving winreg check to support other OSes 2020-12-31 20:39:50 -05:00
11 changed files with 412 additions and 130 deletions

View File

@@ -1,21 +0,0 @@
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/

3
.gitignore vendored
View File

@@ -149,6 +149,9 @@ dmypy.json
!.vscode/extensions.json !.vscode/extensions.json
*.code-workspace *.code-workspace
### pycharm ###
.idea/*
# End of https://www.toptal.com/developers/gitignore/api/python,vscode # End of https://www.toptal.com/developers/gitignore/api/python,vscode
pydualsense/interface.py pydualsense/interface.py

View File

@@ -1,21 +1,45 @@
# pydualsense # pydualsense
control your dualsense through python. using the hid library this package implements the report features for controlling your new PS5 controller. control your dualsense through python. using the hid library this package implements the report features for controlling your new PS5 controller.
# install # Installation
## Windows
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/). 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 ```bash
pip install pydualsense pip install pydualsense
``` ```
## Linux
On Linux based system you first need to install the hidapi through your package manager of your system.
On an Ubuntu system the package ```libhidapi-dev``` is required.
```bash
sudo apt install libhidapi-dev
```
After that install the package from [pypi](https://pypi.org/project/pydualsense/).
```bash
pip install pydualsense
```
# usage # usage
```python ```python
from pydualsense import pydualsense from pydualsense import pydualsense, TriggerModes
def cross_pressed(state):
print(state)
ds = pydualsense() # open controller ds = pydualsense() # open controller
ds.init() # initialize controller ds.init() # initialize controller
ds.cross_pressed += cross_pressed
ds.light.setColorI(255,0,0) # set touchpad color to red ds.light.setColorI(255,0,0) # set touchpad color to red
ds.triggerL.setMode(TriggerModes.Rigid) ds.triggerL.setMode(TriggerModes.Rigid)
ds.triggerL.setForce(1, 255) ds.triggerL.setForce(1, 255)
@@ -30,7 +54,7 @@ Help wanted from people that want to use this and have feature requests. Just op
# dependecies # dependecies
- hid >= 1.0.4 - hidapi-usb >= 0.3
# Credits # Credits

View File

@@ -5,8 +5,8 @@ dualsense = pydualsense()
dualsense.init() dualsense.init()
# set color around touchpad to red # set color around touchpad to red
dualsense.light.setColorI(255,0,0) dualsense.light.setColorI(255,0,0)
# enable microphone indicator # mute microphone
dualsense.audio.setMicrophoneLED(1) dualsense.audio.setMicrophoneMute(True)
# set all player 1 indicator on # set all player 1 indicator on
dualsense.light.setPlayerID(PlayerID.player1) dualsense.light.setPlayerID(PlayerID.player1)
# sleep a little to see the result on the controller # sleep a little to see the result on the controller

View File

@@ -1,15 +1,41 @@
from pydualsense import * from pydualsense import *
def cross_down(state):
print(f'cross {state}')
def circle_down(state):
print(f'circle {state}')
def dpad_down(state):
print(f'dpad {state}')
def joystick(stateX, stateY):
print(f'lj {stateX} {stateY}')
def gyro_changed(pitch, yaw, roll):
print(f'{pitch}, {yaw}, {roll}')
# create dualsense # create dualsense
dualsense = pydualsense() dualsense = pydualsense()
# find device and initialize # find device and initialize
dualsense.init() dualsense.init()
# add events handler functions
dualsense.cross_pressed += cross_down
dualsense.circle_pressed += circle_down
dualsense.dpad_down += dpad_down
dualsense.left_joystick_changed += joystick
dualsense.gyro_changed += gyro_changed
# read controller state until R1 is pressed # read controller state until R1 is pressed
while not dualsense.state.R1: 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 # close device
dualsense.close() dualsense.close()

View File

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

View File

@@ -1,38 +1,46 @@
from enum import IntFlag from enum import IntFlag
class ConnectionType(IntFlag):
BT = 0x0
USB = 0x1
class LedOptions(IntFlag): class LedOptions(IntFlag):
Off=0x0, Off = 0x0
PlayerLedBrightness=0x1, PlayerLedBrightness = 0x1
UninterrumpableLed=0x2, UninterrumpableLed = 0x2
Both=0x01 | 0x02 Both = 0x01 | 0x02
class PulseOptions(IntFlag): class PulseOptions(IntFlag):
Off=0x0, Off = 0x0
FadeBlue=0x1, FadeBlue = 0x1
FadeOut=0x2 FadeOut = 0x2
class Brightness(IntFlag): class Brightness(IntFlag):
high = 0x0, high = 0x0
medium = 0x1, medium = 0x1
low = 0x2 low = 0x2
class PlayerID(IntFlag): class PlayerID(IntFlag):
player1 = 4, PLAYER_1 = 4
player2 = 10, PLAYER_2 = 10
player3 = 21, PLAYER_3 = 21
player4 = 27, PLAYER_4 = 27
all = 31 ALL = 31
class TriggerModes(IntFlag): class TriggerModes(IntFlag):
Off = 0x0, # no resistance Off = 0x0 # no resistance
Rigid = 0x1, # continous resistance Rigid = 0x1 # continous resistance
Pulse = 0x2, # section resistance Pulse = 0x2 # section resistance
Rigid_A = 0x1 | 0x20, Rigid_A = 0x1 | 0x20
Rigid_B = 0x1 | 0x04, Rigid_B = 0x1 | 0x04
Rigid_AB = 0x1 | 0x20 | 0x04, Rigid_AB = 0x1 | 0x20 | 0x04
Pulse_A = 0x2 | 0x20, Pulse_A = 0x2 | 0x20
Pulse_B = 0x2 | 0x04, Pulse_B = 0x2 | 0x04
Pulse_AB = 0x2 | 0x20 | 0x04, Pulse_AB = 0x2 | 0x20 | 0x04
Calibration= 0xFC Calibration = 0xFC

View File

@@ -0,0 +1,60 @@
from collections import defaultdict
class Event(object):
"""
Base class for the event driven system
"""
def __init__(self) -> None:
"""
initialise event system
"""
self._event_handler = []
def subscribe(self, fn):
"""
add a event subscription
Args:
fn (function): _description_
"""
self._event_handler.append(fn)
return self
def unsubscribe(self, fn):
"""
delete event subscription fn
Args:
fn (function): _description_
"""
self._event_handler.remove(fn)
return self
def __iadd__(self, fn):
"""
add event subscription fn
Args:
fn (function): _description_
"""
self._event_handler.append(fn)
return self
def __isub__(self, fn):
"""
delete event subscription fn
Args:
fn (function): _description_
"""
self._event_handler.remove(fn)
return self
def __call__(self, *args, **keywargs):
"""
calls all event subscription functions
"""
for eventhandler in self._event_handler:
eventhandler(*args, **keywargs)

View File

@@ -0,0 +1,20 @@
import winreg
import sys
def check_hide() -> 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, r'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:
pass
return False

View File

@@ -1,44 +1,106 @@
import os
import sys
from sys import platform
# needed for python > 3.8 if platform.startswith('Windows') and sys.version_info >= (3, 8):
import os, sys
if sys.version_info >= (3,8):
os.add_dll_directory(os.getcwd()) os.add_dll_directory(os.getcwd())
import hid # type: ignore import hidapi
from .enums import (LedOptions, PlayerID, from .enums import (LedOptions, PlayerID, PulseOptions, TriggerModes, Brightness, ConnectionType) # type: ignore
PulseOptions, TriggerModes, Brightness)
import threading import threading
import winreg from .event_system import Event
from copy import deepcopy
class pydualsense: class pydualsense:
def __init__(self, verbose: bool = False) -> None:# def __init__(self, verbose: bool = False) -> None:#
# TODO: maybe add a init function to not automatically allocate controller when class is declared # TODO: maybe add a init function to not automatically allocate controller when class is declared
self.verbose = verbose self.verbose = verbose
self.receive_buffer_size = 64
self.send_report_size = 48
self.leftMotor = 0 self.leftMotor = 0
self.rightMotor = 0 self.rightMotor = 0
self.last_states = None
self.register_available_events()
def register_available_events(self):
# button events
self.triangle_pressed = Event()
self.circle_pressed = Event()
self.cross_pressed = Event()
self.square_pressed = Event()
# dpad events
# TODO: add a event that sends the pressed key if any key is pressed
# self.dpad_changed = Event()
self.dpad_up = Event()
self.dpad_down = Event()
self.dpad_left = Event()
self.dpad_right = Event()
# joystick
self.left_joystick_changed = Event()
self.right_joystick_changed = Event()
# trigger back buttons
self.r1_changed = Event()
self.r2_changed = Event()
self.r3_changed = Event()
self.l1_changed = Event()
self.l2_changed = Event()
self.l3_changed = Event()
# misc
self.ps_pressed = Event()
self.touch_pressed = Event()
self.microphone_pressed = Event()
self.share_pressed = Event()
self.option_pressed = Event()
# trackpad touch
# handles 1 or 2 fingers
#self.trackpad_frame_reported = Event()
# gyrometer events
self.gyro_changed = Event()
self.accelerometer_changed = Event()
def init(self): def init(self):
"""initialize module and device states """initialize module and device states
""" """
self.device: hid.Device = self.__find_device() self.device: hidapi.Device = self.__find_device()
self.light = DSLight() # control led light of ds self.light = DSLight() # control led light of ds
self.audio = DSAudio() # ds audio setting self.audio = DSAudio() # ds audio setting
self.triggerL = DSTrigger() # left trigger self.triggerL = DSTrigger() # left trigger
self.triggerR = DSTrigger() # right trigger self.triggerR = DSTrigger() # right trigger
self.state = DSState() # controller states self.state = DSState() # controller states
if platform.startswith('Windows'):
self.conType = self.determineConnectionType() # determine USB or BT connection
else:
# set for usb manually
self.input_report_length = 64
self.output_report_length = 64
# thread for receiving and sending
self.ds_thread = True self.ds_thread = True
self.report_thread = threading.Thread(target=self.sendReport) self.report_thread = threading.Thread(target=self.sendReport)
self.report_thread.start() self.report_thread.start()
self.init = True def determineConnectionType(self) -> ConnectionType:
if self.device._device.input_report_length == 64:
self.input_report_length = 64
self.output_report_length = 64
return ConnectionType.USB
elif self.device._device.input_report_length == 78:
self.input_report_length = 78
self.output_report_length = 78
return ConnectionType.BT
def close(self): def close(self):
""" """
@@ -48,24 +110,7 @@ class pydualsense:
self.report_thread.join() self.report_thread.join()
self.device.close() self.device.close()
def _check_hide(self) -> bool: def __find_device(self) -> hidapi.Device:
"""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 find HID device and open it
@@ -78,19 +123,20 @@ class pydualsense:
""" """
# TODO: detect connection mode, bluetooth has a bigger write buffer # TODO: detect connection mode, bluetooth has a bigger write buffer
# TODO: implement multiple controllers working # TODO: implement multiple controllers working
if self._check_hide(): if sys.platform.startswith('win32'):
raise Exception('HIDGuardian detected. Delete the controller from HIDGuardian and restart PC to connect to controller') import pydualsense.hidguardian as hidguardian
detected_device: hid.Device = None if hidguardian.check_hide():
devices = hid.enumerate(vid=0x054c) raise Exception('HIDGuardian detected. Delete the controller from HIDGuardian and restart PC to connect to controller')
detected_device: hidapi.Device = None
devices = hidapi.enumerate(vendor_id=0x054c)
for device in devices: for device in devices:
if device['vendor_id'] == 0x054c and device['product_id'] == 0x0CE6: if device.vendor_id == 0x054c and device.product_id == 0x0CE6:
detected_device = device detected_device = device
if detected_device is None:
if detected_device == None:
raise Exception('No device detected') raise Exception('No device detected')
dual_sense = hid.Device(vid=detected_device['vendor_id'], pid=detected_device['product_id']) dual_sense = hidapi.Device(vendor_id=detected_device.vendor_id, product_id=detected_device.product_id)
return dual_sense return dual_sense
def setLeftMotor(self, intensity: int): def setLeftMotor(self, intensity: int):
@@ -111,7 +157,6 @@ class pydualsense:
raise Exception('maximum intensity is 255') raise Exception('maximum intensity is 255')
self.leftMotor = intensity self.leftMotor = intensity
def setRightMotor(self, intensity: int): def setRightMotor(self, intensity: int):
""" """
set right motor rumble set right motor rumble
@@ -130,19 +175,17 @@ class pydualsense:
raise Exception('maximum intensity is 255') raise Exception('maximum intensity is 255')
self.rightMotor = intensity self.rightMotor = intensity
def sendReport(self): def sendReport(self):
"""background thread handling the reading of the device and updating its states """background thread handling the reading of the device and updating its states
""" """
while self.ds_thread: while self.ds_thread:
# read data from the input report of the controller # read data from the input report of the controller
inReport = self.device.read(self.receive_buffer_size) inReport = self.device.read(self.input_report_length)
if self.verbose:
print(inReport)
# decrypt the packet and bind the inputs # decrypt the packet and bind the inputs
self.readInput(inReport) self.readInput(inReport)
# prepare new report for device # prepare new report for device
outReport = self.prepareReport() outReport = self.prepareReport()
@@ -173,7 +216,6 @@ class pydualsense:
self.state.cross = (buttonState & (1 << 5)) != 0 self.state.cross = (buttonState & (1 << 5)) != 0
self.state.square = (buttonState & (1 << 4)) != 0 self.state.square = (buttonState & (1 << 4)) != 0
# dpad # dpad
dpad_state = buttonState & 0x0F dpad_state = buttonState & 0x0F
self.state.setDPadState(dpad_state) self.state.setDPadState(dpad_state)
@@ -191,7 +233,7 @@ class pydualsense:
misc2 = states[10] misc2 = states[10]
self.state.ps = (misc2 & (1 << 0)) != 0 self.state.ps = (misc2 & (1 << 0)) != 0
self.state.touchBtn = (misc2 & 0x02) != 0 self.state.touchBtn = (misc2 & 0x02) != 0
self.state.micBtn = (misc2 & 0x04) != 0
# trackpad touch # trackpad touch
self.state.trackPadTouch0.ID = inReport[33] & 0x7F self.state.trackPadTouch0.ID = inReport[33] & 0x7F
@@ -205,17 +247,102 @@ class pydualsense:
self.state.trackPadTouch1.X = ((inReport[39] & 0x0f) << 8) | (inReport[38]) self.state.trackPadTouch1.X = ((inReport[39] & 0x0f) << 8) | (inReport[38])
self.state.trackPadTouch1.Y = ((inReport[40]) << 4) | ((inReport[39] & 0xf0) >> 4) self.state.trackPadTouch1.Y = ((inReport[40]) << 4) | ((inReport[39] & 0xf0) >> 4)
# print(f'1Active = {self.state.trackPadTouch0.isActive}') # accelerometer
# print(f'X1: {self.state.trackPadTouch0.X} Y2: {self.state.trackPadTouch0.Y}') self.state.accelerometer.X = int.from_bytes(([inReport[16], inReport[17]]), byteorder='little', signed=True)
self.state.accelerometer.Y = int.from_bytes(([inReport[18], inReport[19]]), byteorder='little', signed=True)
self.state.accelerometer.Z = int.from_bytes(([inReport[20], inReport[21]]), byteorder='little', signed=True)
# print(f'2Active = {self.state.trackPadTouch1.isActive}') # gyrometer
# print(f'X2: {self.state.trackPadTouch1.X} Y2: {self.state.trackPadTouch1.Y}') self.state.gyro.Pitch = int.from_bytes(([inReport[22], inReport[23]]), byteorder='little', signed=True)
# print(f'DPAD {self.state.DpadLeft} {self.state.DpadUp} {self.state.DpadRight} {self.state.DpadDown}') self.state.gyro.Yaw = int.from_bytes(([inReport[24], inReport[25]]), byteorder='little', signed=True)
self.state.gyro.Roll = int.from_bytes(([inReport[26], inReport[27]]), byteorder='little', signed=True)
# first call we dont have a "last state" so we create if with the first occurence
if self.last_states is None:
self.last_states = deepcopy(self.state)
return
if self.state.circle != self.last_states.circle:
self.circle_pressed(self.state.circle)
if self.state.cross != self.last_states.cross:
self.cross_pressed(self.state.cross)
if self.state.triangle != self.last_states.triangle:
self.triangle_pressed(self.state.triangle)
if self.state.square != self.last_states.square:
self.square_pressed(self.state.square)
if self.state.DpadDown != self.last_states.DpadDown:
self.dpad_down(self.state.DpadDown)
if self.state.DpadLeft != self.last_states.DpadLeft:
self.dpad_left(self.state.DpadLeft)
if self.state.DpadRight != self.last_states.DpadRight:
self.dpad_right(self.state.DpadRight)
if self.state.DpadUp != self.last_states.DpadUp:
self.dpad_up(self.state.DpadUp)
if self.state.LX != self.last_states.LX or self.state.LY != self.last_states.LY:
self.left_joystick_changed(self.state.LX, self.state.LY)
if self.state.RX != self.last_states.RX or self.state.RY != self.last_states.RY:
self.right_joystick_changed(self.state.RX, self.state.RY)
if self.state.R1 != self.last_states.R1:
self.r1_changed(self.state.R1)
if self.state.R2 != self.last_states.R2:
self.r2_changed(self.state.R2)
if self.state.L1 != self.last_states.L1:
self.l1_changed(self.state.L1)
if self.state.L2 != self.last_states.L2:
self.l1_changed(self.state.L2)
if self.state.R3 != self.last_states.R3:
self.r3_changed(self.state.R3)
if self.state.L3 != self.last_states.L3:
self.l3_changed(self.state.L3)
if self.state.ps != self.last_states.ps:
self.ps_pressed(self.state.ps)
if self.state.touchBtn != self.last_states.touchBtn:
self.touch_pressed(self.state.touchBtn)
if self.state.micBtn != self.last_states.micBtn:
self.microphone_pressed(self.state.micBtn)
if self.state.share != self.last_states.share:
self.share_pressed(self.state.share)
if self.state.options != self.last_states.options:
self.option_pressed(self.state.options)
if self.state.accelerometer.X != self.last_states.accelerometer.X or \
self.state.accelerometer.Y != self.last_states.accelerometer.Y or \
self.state.accelerometer.Z != self.last_states.accelerometer.Z:
self.accelerometer_changed(self.state.accelerometer.X, self.state.accelerometer.Y, self.state.accelerometer.Z)
if self.state.gyro.Pitch != self.last_states.gyro.Pitch or \
self.state.gyro.Yaw != self.last_states.gyro.Yaw or \
self.state.gyro.Roll != self.last_states.gyro.Roll:
self.gyro_changed(self.state.gyro.Pitch, self.state.gyro.Yaw, self.state.gyro.Roll)
# copy current state into ltemp object to check next cycle if a change occuret
# and event trigger is needed
self.last_states = deepcopy(self.state) # copy current state into object to check next time
# TODO: implement gyrometer and accelerometer # TODO: implement gyrometer and accelerometer
# TODO: control mouse with touchpad for fun as DS4Windows # TODO: control mouse with touchpad for fun as DS4Windows
def writeReport(self, outReport): def writeReport(self, outReport):
""" """
write the report to the device write the report to the device
@@ -225,7 +352,6 @@ class pydualsense:
""" """
self.device.write(bytes(outReport)) self.device.write(bytes(outReport))
def prepareReport(self): def prepareReport(self):
""" """
prepare the output to be send to the controller prepare the output to be send to the controller
@@ -233,11 +359,11 @@ class pydualsense:
Returns: Returns:
list: report to send to controller list: report to send to controller
""" """
outReport = [0] * 48 # create empty list with range of output report
outReport = [0] * self.output_report_length # create empty list with range of output report
# packet type # packet type
outReport[0] = 0x2 outReport[0] = 0x2
# flags determing what changes this packet will perform # 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. # 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) # 0x02 set the main motors (also requires flag 0x01; without bit 0x01 motors are allowed to time out without re-enabling audio haptics)
@@ -267,6 +393,8 @@ class pydualsense:
# set Micrphone LED, setting doesnt effect microphone settings # set Micrphone LED, setting doesnt effect microphone settings
outReport[9] = self.audio.microphone_led # [9] outReport[9] = self.audio.microphone_led # [9]
outReport[10] = 0x10 if self.audio.microphone_mute is True else 0x00
# add right trigger mode + parameters to packet # add right trigger mode + parameters to packet
outReport[11] = self.triggerR.mode.value outReport[11] = self.triggerR.mode.value
outReport[12] = self.triggerR.forces[0] outReport[12] = self.triggerR.forces[0]
@@ -297,6 +425,7 @@ class pydualsense:
print(outReport) print(outReport)
return outReport return outReport
class DSTouchpad: class DSTouchpad:
def __init__(self) -> None: def __init__(self) -> None:
""" """
@@ -307,6 +436,7 @@ class DSTouchpad:
self.X = 0 self.X = 0
self.Y = 0 self.Y = 0
class DSState: class DSState:
def __init__(self) -> None: def __init__(self) -> None:
@@ -316,8 +446,11 @@ class DSState:
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.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.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.touchFinger1, self.touchFinger2 = False, False
self.RX, self.RY, self.LX, self.LY = 128,128,128,128 self.micBtn = False
self.RX, self.RY, self.LX, self.LY = 128, 128, 128, 128
self.trackPadTouch0, self.trackPadTouch1 = DSTouchpad(), DSTouchpad() self.trackPadTouch0, self.trackPadTouch1 = DSTouchpad(), DSTouchpad()
self.gyro = DSGyro()
self.accelerometer = DSAccelerometer()
def setDPadState(self, dpad_state): def setDPadState(self, dpad_state):
if dpad_state == 0: if dpad_state == 0:
@@ -373,10 +506,10 @@ class DSLight:
""" """
def __init__(self) -> None: def __init__(self) -> None:
self.brightness: Brightness = Brightness.low # sets self.brightness: Brightness = Brightness.low # sets
self.playerNumber: PlayerID = PlayerID.player1 self.playerNumber: PlayerID = PlayerID.PLAYER_1
self.ledOption : LedOptions = LedOptions.Both self.ledOption: LedOptions = LedOptions.Both
self.pulseOptions : PulseOptions = PulseOptions.Off self.pulseOptions: PulseOptions = PulseOptions.Off
self.TouchpadColor = (0,0,255) self.TouchpadColor = (0, 0, 255)
def setLEDOption(self, option: LedOptions): def setLEDOption(self, option: LedOptions):
""" """
@@ -420,7 +553,7 @@ class DSLight:
raise TypeError('Need Brightness type') raise TypeError('Need Brightness type')
self.brightness = brightness self.brightness = brightness
def setPlayerID(self, player : PlayerID): def setPlayerID(self, player: PlayerID):
""" """
Sets the PlayerID of the controller with the choosen LEDs. Sets the PlayerID of the controller with the choosen LEDs.
The controller has 4 Player states The controller has 4 Player states
@@ -435,7 +568,7 @@ class DSLight:
raise TypeError('Need PlayerID type') raise TypeError('Need PlayerID type')
self.playerNumber = player self.playerNumber = player
def setColorI(self, r: int , g: int, b: int) -> None: def setColorI(self, r: int, g: int, b: int) -> None:
""" """
Sets the Color around the Touchpad of the controller Sets the Color around the Touchpad of the controller
@@ -453,8 +586,7 @@ class DSLight:
# check if color is out of bounds # 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): 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') raise Exception('colors have values from 0 to 255 only')
self.TouchpadColor = (r,g,b) self.TouchpadColor = (r, g, b)
def setColorT(self, color: tuple) -> None: def setColorT(self, color: tuple) -> None:
""" """
@@ -470,11 +602,11 @@ class DSLight:
if not isinstance(color, tuple): if not isinstance(color, tuple):
raise TypeError('Color type is tuple') raise TypeError('Color type is tuple')
# unpack for out of bounds check # unpack for out of bounds check
r,g,b = map(int, color) r, g, b = map(int, color)
# check if color is out of bounds # 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): 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') raise Exception('colors have values from 0 to 255 only')
self.TouchpadColor = (r,g,b) self.TouchpadColor = (r, g, b)
class DSAudio: class DSAudio:
@@ -488,19 +620,28 @@ class DSAudio:
This doesnt change the mute/unmutes the microphone itself. This doesnt change the mute/unmutes the microphone itself.
Args: Args:
value (int): On or off microphone LED value (bool): On or off microphone LED
Raises: Raises:
Exception: false state for the led Exception: false state for the led
""" """
if value > 1 or value < 0: if not isinstance(value, bool):
raise Exception('Microphone LED can only be on or off (0 .. 1)') raise TypeError('MicrophoneLED can only be a bool')
self.microphone_led = value self.microphone_led = value
def setMicrophoneMute(self, state):
if not isinstance(state, bool):
raise TypeError('state needs to be bool')
self.setMicrophoneLED(state) # set led accordingly
self.microphone_mute = state
class DSTrigger: class DSTrigger:
def __init__(self) -> None: def __init__(self) -> None:
# trigger modes # trigger modes
self.mode : TriggerModes = TriggerModes.Off self.mode: TriggerModes = TriggerModes.Off
# force parameters for the triggers # force parameters for the triggers
self.forces = [0 for i in range(7)] self.forces = [0 for i in range(7)]
@@ -538,4 +679,24 @@ class DSTrigger:
if not isinstance(mode, TriggerModes): if not isinstance(mode, TriggerModes):
raise TypeError('Trigger mode parameter needs to be of type `TriggerModes`') raise TypeError('Trigger mode parameter needs to be of type `TriggerModes`')
self.mode = mode self.mode = mode
class DSGyro:
def __init__(self) -> None:
"""
Class represents the Gyro of the controller
"""
self.Pitch = 0
self.Yaw = 0
self.Roll = 0
class DSAccelerometer:
def __init__(self) -> None:
"""
Class represents the Accelerometer of the controller
"""
self.X = 0
self.Y = 0
self.Z = 0

View File

@@ -6,7 +6,7 @@ with open("README.md", "r") as fh:
setup( setup(
name='pydualsense', name='pydualsense',
version='0.4.2', version='0.6.1',
description='use your DualSense (PS5) controller with python', description='use your DualSense (PS5) controller with python',
long_description=long_description, long_description=long_description,
long_description_content_type="text/markdown", long_description_content_type="text/markdown",
@@ -14,5 +14,5 @@ setup(
author='Florian K', author='Florian K',
license='MIT License', license='MIT License',
packages=setuptools.find_packages(), packages=setuptools.find_packages(),
install_requires=['hid>=1.0.4'] install_requires=['hidapi-usb>=0.3', 'cffi']
) )