Skip to content

Commit

Permalink
Merge pull request #47 from flok/revert-45-bluetooth_impl
Browse files Browse the repository at this point in the history
Revert "Bluetooth implementation"
  • Loading branch information
flok committed Feb 24, 2023
2 parents 875b5f6 + bb3ab10 commit c0797ce
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 148 deletions.
12 changes: 0 additions & 12 deletions pydualsense/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,3 @@ class TriggerModes(IntFlag):
Pulse_B = 0x2 | 0x04
Pulse_AB = 0x2 | 0x20 | 0x04
Calibration = 0xFC


class BatteryState(IntFlag):
POWER_SUPPLY_STATUS_DISCHARGING = 0x0
POWER_SUPPLY_STATUS_CHARGING = 0x1
POWER_SUPPLY_STATUS_FULL = 0x2
POWER_SUPPLY_STATUS_NOT_CHARGING = 0xb
POWER_SUPPLY_STATUS_ERROR = 0xf
POWER_SUPPLY_TEMP_OR_VOLTAGE_OUT_OF_RANGE = 0xa
POWER_SUPPLY_STATUS_UNKNOWN = 0x0


183 changes: 47 additions & 136 deletions pydualsense/pydualsense.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
os.add_dll_directory(os.getcwd())

import hidapi
from .enums import (LedOptions, PlayerID, PulseOptions, TriggerModes, Brightness, ConnectionType, BatteryState) # type: ignore
from .enums import (LedOptions, PlayerID, PulseOptions, TriggerModes, Brightness, ConnectionType) # type: ignore
import threading
from .event_system import Event
from copy import deepcopy
Expand All @@ -19,17 +19,6 @@
logger.setLevel(logging.INFO)

class pydualsense:
# Bluetooth Checksum seeds
INPUT_CRC_SEED = b'\xa1'
OUTPUT_CRC_SEED = b'\xa2'
# Feature report not implemented (I don't think?)
FEATURE_CRC_SEED = b'\xa2'

# Report ID Constants
INPUT_REPORT_USB = 0x01
INPUT_REPORT_BT = 0x31
OUTPUT_REPORT_USB = 0x02
OUTPUT_REPORT_BT = 0x31

def __init__(self, verbose: bool = False) -> None:
"""
Expand Down Expand Up @@ -110,7 +99,6 @@ def init(self) -> None:
self.triggerR = DSTrigger() # right trigger
self.state = DSState() # controller states
self.conType = self.determineConnectionType() # determine USB or BT connection
self.battery = DSBattery()
self.ds_thread = True
self.report_thread = threading.Thread(target=self.sendReport)
self.report_thread.start()
Expand Down Expand Up @@ -140,7 +128,6 @@ def determineConnectionType(self) -> ConnectionType:
elif input_report_length == 78:
self.input_report_length = 78
self.output_report_length = 78
self.output_report_seq_id = 0x0
return ConnectionType.BT

def close(self) -> None:
Expand Down Expand Up @@ -182,23 +169,6 @@ def __find_device(self) -> hidapi.Device:
dual_sense = hidapi.Device(vendor_id=detected_device.vendor_id, product_id=detected_device.product_id)
return dual_sense

def add_checksum(self, data: list) -> list:
from binascii import crc32
crc32_result = crc32(pydualsense.OUTPUT_CRC_SEED)
crc32_result = crc32(bytearray(data[:-4]), crc32_result)
checksum_bytes = crc32_result.to_bytes(4, byteorder='little')
data[-4:] = checksum_bytes
return data

def validate_checksum(self, data: list | bytearray) -> bool:
from binascii import crc32
if isinstance(data, list):
data = bytearray(data)
crc32_result = crc32(pydualsense.INPUT_CRC_SEED)
crc32_result = crc32(data[:-4], crc32_result)
checksum_bytes = crc32_result.to_bytes(4, byteorder='little')
return checksum_bytes == bytes(data[-4:])

def setLeftMotor(self, intensity: int) -> None:
"""
set left motor rumble
Expand Down Expand Up @@ -253,27 +223,22 @@ def sendReport(self) -> None:
self.writeReport(outReport)

def readInput(self, inReport) -> None:
if self.conType == ConnectionType.BT:
# First validate the report and skip if it's invalid
if not self.validate_checksum(inReport):
logger.warning('checksum failed')
return
# Bluetooth report comes prefixed with a tag byte. The meaning is unclear.
# Dropping the byte shifts us to the same common report format as USB
states = list(inReport)[1:] # convert bytes to list

else: # USB
states = list(inReport) # convert bytes to list

# Common report size is USB report size less the report ID (i.e. 62 bytes)
self.states = states
# states 0 is always 1
"""
read the input from the controller and assign the states
Args:
inReport (bytearray): read bytearray containing the state of the whole controller
"""
if self.conType == ConnectionType.BT:
# the reports for BT and USB are structured the same,
# but there is one more byte at the start of the bluetooth report.
# We drop that byte, so that the format matches up again.
states = list(inReport)[1:] # convert bytes to list
else: # USB
states = list(inReport) # convert bytes to list

self.states = states
# states 0 is always 1
self.state.LX = states[1] - 127
self.state.LY = states[2] - 127
self.state.RX = states[3] - 127
Expand Down Expand Up @@ -330,10 +295,6 @@ def readInput(self, inReport) -> None:
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)

battery = states[53]
self.battery.State = BatteryState((battery & 0xF0) >> 4)
self.battery.Level = min((battery & 0x0F) * 10 + 5, 100)

# 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)
Expand Down Expand Up @@ -430,28 +391,6 @@ def writeReport(self, outReport) -> None:
"""
self.device.write(bytes(outReport))

def flag1(self, muteMicEnable=False, powerSaveEnable=False, lightBarControl=False, releaseLEDs=False,
playerIndicatorControl=False):
output = 0
output = output | (muteMicEnable << 0)
output = output | (powerSaveEnable << 1)
output = output | (lightBarControl << 2)
output = output | (releaseLEDs << 3)
output = output | (playerIndicatorControl << 4)
return output

def flag2(self, lightBarSetupEnable=False, compatibleVibration=False):
output = 0
output = output | (lightBarSetupEnable << 0)
output = output | (compatibleVibration << 1)
return output

def flag0(self, compatibleVibration=False, hapticsSelect=False):
output = 0
output = output | (compatibleVibration << 1)
output = output | (hapticsSelect << 2)
return output

def prepareReport(self) -> None:
"""
prepare the output to be send to the controller
Expand All @@ -460,20 +399,10 @@ def prepareReport(self) -> None:
list: report to send to controller
"""

outReport = [0] * self.output_report_length # create empty list with range of output report
outReport = [0] * self.output_report_length # create empty list with range of output report
# packet type
if self.conType == ConnectionType.BT:
# ReportID
outReport[0] = pydualsense.OUTPUT_REPORT_BT
# Sequence Tag
outReport[1] = (self.output_report_seq_id << 4) | 0x00
self.output_report_seq_id = (self.output_report_seq_id + 1) % 16
# Tag. Note: This is just a magic number :(
outReport[2] = 0x10
else:
outReport[0] = pydualsense.OUTPUT_REPORT_USB
outReport[0] = 0x2

outReportCommon = [0] * 47
# 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)
Expand All @@ -482,7 +411,7 @@ def prepareReport(self) -> None:
# 0x10 modification of audio volume
# 0x20 toggling of internal speaker while headset is connected
# 0x40 modification of microphone volume
outReportCommon[0] = self.flag0(True, True)
outReport[1] = 0xff # [1]

# further flags determining what changes this packet will perform
# 0x01 toggling microphone LED
Expand All @@ -493,54 +422,45 @@ def prepareReport(self) -> None:
# 0x20 ???
# 0x40 adjustment of overall motor/effect power (index 37 - read note on triggers)
# 0x80 ???
outReportCommon[1] = 0x1 | 0x2 | 0x4 | 0x10 | 0x40 # [2]
# Function below should control the flags, but probably makes sense to properly implement
# rather than just toggling everything on?
# outReportCommon[1] = self.flag1(True, True, True, False, True)
outReportCommon[2] = self.rightMotor # right low freq motor 0-255 # [3]
outReportCommon[3] = self.leftMotor # left low freq motor 0-255 # [4]
outReport[2] = 0x1 | 0x2 | 0x4 | 0x10 | 0x40 # [2]

outReport[3] = self.rightMotor # right low freq motor 0-255 # [3]
outReport[4] = self.leftMotor # left low freq motor 0-255 # [4]

# outReport[4] - outReport[7] Audio Reserved
# outReport[5] - outReport[8] audio related

# set Micrphone LED, setting doesnt effect microphone settings
outReportCommon[8] = self.audio.microphone_led # [9]
# outReportCommon[9] Power save control? Whatever that is...
outReportCommon[11] = 0x10 if self.audio.microphone_mute is True else 0x00
outReport[9] = self.audio.microphone_led # [9]

# add right trigger mode + parameters to packet
outReportCommon[12] = self.triggerR.mode.value
outReportCommon[13] = self.triggerR.forces[0]
outReportCommon[14] = self.triggerR.forces[1]
outReportCommon[15] = self.triggerR.forces[2]
outReportCommon[16] = self.triggerR.forces[3]
outReportCommon[17] = self.triggerR.forces[4]
outReportCommon[18] = self.triggerR.forces[5]
outReportCommon[21] = self.triggerR.forces[6]

outReportCommon[23] = self.triggerL.mode.value
outReportCommon[24] = self.triggerL.forces[0]
outReportCommon[25] = self.triggerL.forces[1]
outReportCommon[26] = self.triggerL.forces[2]
outReportCommon[27] = self.triggerL.forces[3]
outReportCommon[28] = self.triggerL.forces[4]
outReportCommon[29] = self.triggerL.forces[5]
outReportCommon[32] = self.triggerL.forces[6]

outReportCommon[39] = self.flag2(True, True)
outReportCommon[40] = self.light.ledOption.value
outReportCommon[41] = self.light.pulseOptions.value
outReportCommon[42] = self.light.brightness.value
outReportCommon[43] = self.light.playerNumber.value
outReportCommon[44] = self.light.TouchpadColor[0]
outReportCommon[45] = self.light.TouchpadColor[1]
outReportCommon[46] = self.light.TouchpadColor[2]
outReport[10] = 0x10 if self.audio.microphone_mute is True else 0x00

if self.conType == ConnectionType.BT:
outReport[3:50] = outReportCommon
outReport = self.add_checksum(outReport)
# 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]

else:
outReport[1:48] = outReportCommon
if self.verbose:
logger.debug(outReport)

Expand Down Expand Up @@ -845,16 +765,7 @@ class DSAccelerometer:
"""
Class representing the Accelerometer of the controller
"""

def __init__(self) -> None:
self.X = 0
self.Y = 0
self.Z = 0

class DSBattery:
"""
Class representing the Battery of the controller
"""
def __init__(self) -> None:
self.State = BatteryState.POWER_SUPPLY_STATUS_UNKNOWN
self.Level = 0

0 comments on commit c0797ce

Please sign in to comment.