Addiing individual controller support. /JL

This commit is contained in:
2025-04-13 12:55:39 +02:00
parent 724123e569
commit c99c9945cf
15 changed files with 1159 additions and 78 deletions

View File

@@ -1,76 +1,27 @@
import pygame
from controls.controlsbase import ControlsBase
from controls.dualsense_controller import DualSenseController
from controls.dualsense_edge_controller import DualSenseEdgeController
from controls.logitech_f310_controller import LogitechF310Controller
from controls.logitech_f510_controller import LogitechF510Controller
from controls.logitech_f710_controller import LogitechF710Controller
from controls.xbox_controller import XboxController
from controls.generic_controller import GenericController
class ControllerControls(ControlsBase):
CONTROLLERS = {
"DualSense Wireless Controller": DualSenseController,
"DualSense Edge Wireless Controller": DualSenseEdgeController,
"Logitech Gamepad F310": LogitechF310Controller,
"Logitech Gamepad F510": LogitechF510Controller,
"Logitech Gamepad F710": LogitechF710Controller,
"Xbox": XboxController
}
class Controllers:
def __init__(self, joy):
self.device = joy
self.instance_id: int = self.device.get_instance_id()
self.name = self.device.get_name()
self.numaxis: int = self.device.get_numaxis()
self.axis: list = []
self.numhats: int = self.device.get_numhats()
self.hats: list = []
self.numbuttons: int = self.device.get_numbuttons()
self.buttons: list = []
self.power_level: str = ""
self.controllers = []
if not joy.get_name() in CONTROLLERS:
self.controllers.append(GenericController(joy))
else:
self.controllers.append(CONTROLLERS[joy.get_name()](joy))
def handle_input(self, event):
pass
def left(self):
pass
def right(self):
pass
def up(self):
pass
def down(self):
pass
def pause(self):
pass
def rumble(self):
pass
@property
def name(self) -> str:
return self._name
@name.setter
def name(self, name: str) -> None:
self._name = name
@property
def axis(self) -> list:
return self._axis
@axis.setter
def axis(self) -> None:
self._axis = [self.device.get_axis(a) for a in range(self.numaxis)]
@property
def hats(self) -> list:
return self._hats
@hats.setter
def hats(self) -> None:
self.hats = [self.device.get_hats(h) for h in range(self.numhats)]
@property
def buttons(self) -> list:
return self._buttons
@buttons.setter
def buttons(self) -> None:
self._buttons = [self.device.get_buttons(b) for b in range(self.numbuttons)]
@property
def power_level(self) -> str:
return self._power_level
@power_level.setter
def power_level(self) -> None:
self._power_level = self.device.get_power_level()

View File

@@ -7,3 +7,27 @@ class ControlsBase(ABC):
@abstractmethod
def handle_input(self, event):
pass
@abstractmethod
def left(self):
pass
@abstractmethod
def right(self):
pass
@abstractmethod
def up(self):
pass
@abstractmethod
def down(self):
pass
@abstractmethod
def pause(self):
pass
@abstractmethod
def rumble(self):
pass

View File

@@ -0,0 +1,70 @@
import os
import time
import numpy as np
import sounddevice as sd
import alsaaudio
import pulsectl
class DualSenseAudio:
def __init__(self):
self.alsa_devices = self._get_alsa_devices()
self.pulse_devices = self._get_pulseaudio_devices()
self.dualsense_device = self._detect_dualsense()
def _get_alsa_devices(self):
try:
cards = alsaaudio.cards()
return cards
except Exception as e:
print("ALSA detection failed:", e)
return []
def _get_pulseaudio_devices(self):
try:
pulse = pulsectl.Pulse("dualsense-audio")
sinks = pulse.sink_list()
return sinks
except Exception as e:
print("PulseAudio detection failed:", e)
return []
def _detect_dualsense(self):
# Check ALSA names
for card in self.alsa_devices:
if "DualSense" in card:
return {'type': 'alsa', 'name': card}
# Check PulseAudio sinks
for sink in self.pulse_devices:
if "dualsense" in sink.description.lower():
return {'type': 'pulse', 'name': sink.name}
return None
def play_tone(self, frequency=440.0, duration=2.0, volume=0.5):
if not self.dualsense_device:
print("DualSense speaker not found.")
return
print(f"Playing tone on DualSense ({self.dualsense_device['type']})...")
fs = 48000 # Sample rate
t = np.linspace(0, duration, int(fs * duration), False)
tone = np.sin(frequency * 2 * np.pi * t) * volume
audio = tone.astype(np.float32)
if self.dualsense_device['type'] == 'pulse':
sd.play(audio, samplerate=fs, device=self.dualsense_device['name'])
elif self.dualsense_device['type'] == 'alsa':
device_index = self.alsa_devices.index(self.dualsense_device['name'])
sd.play(audio, samplerate=fs, device=device_index)
sd.wait()
def list_devices(self):
print("ALSA Devices:")
for card in self.alsa_devices:
print(f" - {card}")
print("\nPulseAudio Devices:")
for sink in self.pulse_devices:
print(f" - {sink.name} ({sink.description})")

View File

@@ -0,0 +1,199 @@
import time
import threading
import numpy as np
import sounddevice as sd
import alsaaudio
import pulsectl
from pydualsense import *
class DualSenseController:
def __init__(self):
# DualSense input/output interface
self.ds = pydualsense()
self.ds.init()
self._listening = False
self._bindings = {}
# Audio detection
self.alsa_devices = self._get_alsa_devices()
self.pulse_devices = self._get_pulseaudio_devices()
self.dualsense_audio_device = self._detect_dualsense_audio()
print("DualSense initialized.")
# ---------------------- Device Controls ----------------------
def set_rumble(self, small_motor: int, big_motor: int):
self.ds.setRumble(small_motor, big_motor)
def stop_rumble(self):
self.set_rumble(0, 0)
def set_led_color(self, r: int, g: int, b: int):
self.ds.setLightBarColor(r, g, b)
def set_trigger_effects(self, left_mode='Off', right_mode='Off', force=0):
left = getattr(TriggerModes, left_mode.upper(), TriggerModes.Off)
right = getattr(TriggerModes, right_mode.upper(), TriggerModes.Off)
self.ds.triggerL.setMode(left)
self.ds.triggerR.setMode(right)
if force > 0:
self.ds.triggerL.setForce(force)
self.ds.triggerR.setForce(force)
# ---------------------- Predefined Rumble Patterns ----------------------
def rumble_pattern(self, pattern: str, duration: float = 1.0):
patterns = {
"pulse": self._pulse_rumble,
"heartbeat": self._heartbeat_rumble,
"buzz": self._buzz_rumble,
"wave": self._wave_rumble,
"alarm": self._alarm_rumble,
}
if pattern in patterns:
threading.Thread(target=patterns[pattern], args=(duration,), daemon=True).start()
else:
print(f"Unknown rumble pattern: {pattern}")
def _pulse_rumble(self, duration):
end = time.time() + duration
while time.time() < end:
self.set_rumble(50, 150)
time.sleep(0.2)
self.stop_rumble()
time.sleep(0.2)
def _heartbeat_rumble(self, duration):
end = time.time() + duration
while time.time() < end:
self.set_rumble(200, 200)
time.sleep(0.1)
self.stop_rumble()
time.sleep(0.1)
self.set_rumble(100, 100)
time.sleep(0.1)
self.stop_rumble()
time.sleep(0.4)
def _buzz_rumble(self, duration):
self.set_rumble(80, 255)
time.sleep(duration)
self.stop_rumble()
def _wave_rumble(self, duration):
start = time.time()
while time.time() - start < duration:
for i in range(0, 256, 25):
self.set_rumble(i, 255 - i)
time.sleep(0.05)
for i in reversed(range(0, 256, 25)):
self.set_rumble(i, 255 - i)
time.sleep(0.05)
self.stop_rumble()
def _alarm_rumble(self, duration):
end = time.time() + duration
while time.time() < end:
self.set_rumble(255, 0)
time.sleep(0.1)
self.set_rumble(0, 255)
time.sleep(0.1)
self.stop_rumble()
# ---------------------- Input Listener + Bindings ----------------------
def bind(self, button: str, action: callable):
"""Bind a button to a callable. Ex: controller.bind('cross', lambda: rumble_pattern('buzz'))"""
self._bindings[button] = action
def start_input_listener(self):
def listen():
while self._listening:
#self.ds.update()
for button, action in self._bindings.items():
if getattr(self.ds, button, False):
action()
self._listening = True
thread = threading.Thread(target=listen, daemon=True)
thread.start()
def stop_input_listener(self):
self._listening = False
# ---------------------- Audio Output ----------------------
def _get_alsa_devices(self):
try:
return alsaaudio.cards()
except Exception:
return []
def _get_pulseaudio_devices(self):
try:
pulse = pulsectl.Pulse("dualsense-audio")
return pulse.sink_list()
except Exception:
return []
def _detect_dualsense_audio(self):
# Check ALSA names
for card in self.alsa_devices:
if "DualSense" in card:
return {'type': 'alsa', 'name': card}
# Check PulseAudio sinks
for sink in self.pulse_devices:
if "dualsense" in sink.description.lower():
return {'type': 'pulse', 'name': sink.name}
return None
def play_tone(self, frequency=440.0, duration=2.0, volume=0.5):
if not self.dualsense_audio_device:
print("DualSense speaker not detected.")
return
print(f"Playing tone on DualSense ({self.dualsense_audio_device['type']})...")
fs = 48000 # Sample rate
t = np.linspace(0, duration, int(fs * duration), False)
tone = np.sin(frequency * 2 * np.pi * t) * volume
audio = tone.astype(np.float32)
try:
if self.dualsense_audio_device['type'] == 'pulse':
sd.play(audio, samplerate=fs, device=self.dualsense_audio_device['name'])
elif self.dualsense_audio_device['type'] == 'alsa':
device_index = self.alsa_devices.index(self.dualsense_audio_device['name'])
sd.play(audio, samplerate=fs, device=device_index)
sd.wait()
except Exception as e:
print("Failed to play tone:", e)
def list_audio_devices(self):
print("ALSA Devices:")
for card in self.alsa_devices:
print(f" - {card}")
print("\nPulseAudio Devices:")
for sink in self.pulse_devices:
print(f" - {sink.name} ({sink.description})")
# ---------------------- Cleanup ----------------------
def close(self):
self.ds.close()
if __name__ == "__main__":
controller = DualSenseController()
# Bind buttons to patterns
controller.bind("cross", lambda: controller.rumble_pattern("heartbeat", 1.5))
controller.bind("circle", lambda: controller.rumble_pattern("buzz", 0.5))
controller.bind("triangle", lambda: controller.rumble_pattern("pulse", 2))
controller.bind("square", lambda: controller.set_led_color(255, 0, 0))
# Start listening
controller.start_input_listener()

View File

@@ -0,0 +1,199 @@
import time
import threading
import numpy as np
import sounddevice as sd
import alsaaudio
import pulsectl
from pydualsense import *
class DualSenseEdgeController:
def __init__(self):
# DualSense input/output interface
self.ds = pydualsense()
self.ds.init()
self._listening = False
self._bindings = {}
# Audio detection
self.alsa_devices = self._get_alsa_devices()
self.pulse_devices = self._get_pulseaudio_devices()
self.dualsense_audio_device = self._detect_dualsense_audio()
print("DualSense initialized.")
# ---------------------- Device Controls ----------------------
def set_rumble(self, small_motor: int, big_motor: int):
self.ds.setRumble(small_motor, big_motor)
def stop_rumble(self):
self.set_rumble(0, 0)
def set_led_color(self, r: int, g: int, b: int):
self.ds.setLightBarColor(r, g, b)
def set_trigger_effects(self, left_mode='Off', right_mode='Off', force=0):
left = getattr(TriggerModes, left_mode.upper(), TriggerModes.Off)
right = getattr(TriggerModes, right_mode.upper(), TriggerModes.Off)
self.ds.triggerL.setMode(left)
self.ds.triggerR.setMode(right)
if force > 0:
self.ds.triggerL.setForce(force)
self.ds.triggerR.setForce(force)
# ---------------------- Predefined Rumble Patterns ----------------------
def rumble_pattern(self, pattern: str, duration: float = 1.0):
patterns = {
"pulse": self._pulse_rumble,
"heartbeat": self._heartbeat_rumble,
"buzz": self._buzz_rumble,
"wave": self._wave_rumble,
"alarm": self._alarm_rumble,
}
if pattern in patterns:
threading.Thread(target=patterns[pattern], args=(duration,), daemon=True).start()
else:
print(f"Unknown rumble pattern: {pattern}")
def _pulse_rumble(self, duration):
end = time.time() + duration
while time.time() < end:
self.set_rumble(50, 150)
time.sleep(0.2)
self.stop_rumble()
time.sleep(0.2)
def _heartbeat_rumble(self, duration):
end = time.time() + duration
while time.time() < end:
self.set_rumble(200, 200)
time.sleep(0.1)
self.stop_rumble()
time.sleep(0.1)
self.set_rumble(100, 100)
time.sleep(0.1)
self.stop_rumble()
time.sleep(0.4)
def _buzz_rumble(self, duration):
self.set_rumble(80, 255)
time.sleep(duration)
self.stop_rumble()
def _wave_rumble(self, duration):
start = time.time()
while time.time() - start < duration:
for i in range(0, 256, 25):
self.set_rumble(i, 255 - i)
time.sleep(0.05)
for i in reversed(range(0, 256, 25)):
self.set_rumble(i, 255 - i)
time.sleep(0.05)
self.stop_rumble()
def _alarm_rumble(self, duration):
end = time.time() + duration
while time.time() < end:
self.set_rumble(255, 0)
time.sleep(0.1)
self.set_rumble(0, 255)
time.sleep(0.1)
self.stop_rumble()
# ---------------------- Input Listener + Bindings ----------------------
def bind(self, button: str, action: callable):
"""Bind a button to a callable. Ex: controller.bind('cross', lambda: rumble_pattern('buzz'))"""
self._bindings[button] = action
def start_input_listener(self):
def listen():
while self._listening:
#self.ds.update()
for button, action in self._bindings.items():
if getattr(self.ds, button, False):
action()
self._listening = True
thread = threading.Thread(target=listen, daemon=True)
thread.start()
def stop_input_listener(self):
self._listening = False
# ---------------------- Audio Output ----------------------
def _get_alsa_devices(self):
try:
return alsaaudio.cards()
except Exception:
return []
def _get_pulseaudio_devices(self):
try:
pulse = pulsectl.Pulse("dualsense-audio")
return pulse.sink_list()
except Exception:
return []
def _detect_dualsense_audio(self):
# Check ALSA names
for card in self.alsa_devices:
if "DualSense" in card:
return {'type': 'alsa', 'name': card}
# Check PulseAudio sinks
for sink in self.pulse_devices:
if "dualsense" in sink.description.lower():
return {'type': 'pulse', 'name': sink.name}
return None
def play_tone(self, frequency=440.0, duration=2.0, volume=0.5):
if not self.dualsense_audio_device:
print("DualSense speaker not detected.")
return
print(f"Playing tone on DualSense ({self.dualsense_audio_device['type']})...")
fs = 48000 # Sample rate
t = np.linspace(0, duration, int(fs * duration), False)
tone = np.sin(frequency * 2 * np.pi * t) * volume
audio = tone.astype(np.float32)
try:
if self.dualsense_audio_device['type'] == 'pulse':
sd.play(audio, samplerate=fs, device=self.dualsense_audio_device['name'])
elif self.dualsense_audio_device['type'] == 'alsa':
device_index = self.alsa_devices.index(self.dualsense_audio_device['name'])
sd.play(audio, samplerate=fs, device=device_index)
sd.wait()
except Exception as e:
print("Failed to play tone:", e)
def list_audio_devices(self):
print("ALSA Devices:")
for card in self.alsa_devices:
print(f" - {card}")
print("\nPulseAudio Devices:")
for sink in self.pulse_devices:
print(f" - {sink.name} ({sink.description})")
# ---------------------- Cleanup ----------------------
def close(self):
self.ds.close()
if __name__ == "__main__":
controller = DualSenseController()
# Bind buttons to patterns
controller.bind("cross", lambda: controller.rumble_pattern("heartbeat", 1.5))
controller.bind("circle", lambda: controller.rumble_pattern("buzz", 0.5))
controller.bind("triangle", lambda: controller.rumble_pattern("pulse", 2))
controller.bind("square", lambda: controller.set_led_color(255, 0, 0))
# Start listening
controller.start_input_listener()

View File

@@ -0,0 +1,68 @@
import pygame
from controls.controlsbase import ControlsBase
class GenericController(ControlsBase):
def __init__(self, joy):
self.device = joy
self.instance_id: int = self.device.get_instance_id()
self.name = self.device.get_name()
self.numaxis: int = self.device.get_numaxis()
self.axis: list = []
self.numhats: int = self.device.get_numhats()
self.hats: list = []
self.numbuttons: int = self.device.get_numbuttons()
self.buttons: list = []
def handle_input(self, event):
pass
def left(self):
pass
def right(self):
pass
def up(self):
pass
def down(self):
pass
def pause(self):
pass
def rumble(self):
pass
@property
def name(self) -> str:
return self._name
@name.setter
def name(self, name: str) -> None:
self._name = name
@property
def axis(self) -> list:
return self._axis
@axis.setter
def axis(self) -> None:
self._axis = [self.device.get_axis(a) for a in range(self.numaxis)]
@property
def hats(self) -> list:
return self._hats
@hats.setter
def hats(self) -> None:
self.hats = [self.device.get_hats(h) for h in range(self.numhats)]
@property
def buttons(self) -> list:
return self._buttons
@buttons.setter
def buttons(self) -> None:
self._buttons = [self.device.get_buttons(b) for b in range(self.numbuttons)]

View File

@@ -0,0 +1,108 @@
"""
Logitech F310 Controller class.
This controller is a usb controller, with the following features.
(XInput mode)
6 axis
11 buttons
1 hat
(DirectInput mode)
4 axis
12 buttons
1 hat
"""
import pygame
from controls.controlsbase import ControlsBase
from enum import Enum
class InputMode(Enum):
DirectInput = 1
XInput = 2
class ConnectionType(Enum):
WIRED = 1
WIRELESS = 2
class LogitechF310Controller(ControlsBase):
def __init__(self, joy):
self.device = joy
self.instance_id: int = self.device.get_instance_id()
self.name = self.device.get_name()
self.numaxis: int = self.device.get_numaxis()
self.axis: list = []
self.numhats: int = self.device.get_numhats()
self.hats: list = []
self.numbuttons: int = self.device.get_numbuttons()
self.buttons: list = []
self.input_mode: InputMode.DirectInput
self.input_connection: ConnectionType.WIRED
def handle_input(self, event):
pass
def left(self):
pass
def right(self):
pass
def up(self):
pass
def down(self):
pass
def pause(self):
pass
def rumble(self):
pass
@property
def name(self) -> str:
return self._name
@name.setter
def name(self, name: str) -> None:
self._name = name
@property
def axis(self) -> list:
return self._axis
@axis.setter
def axis(self) -> None:
self._axis = [self.device.get_axis(a) for a in range(self.numaxis)]
@property
def hats(self) -> list:
return self._hats
@hats.setter
def hats(self) -> None:
self.hats = [self.device.get_hats(h) for h in range(self.numhats)]
@property
def buttons(self) -> list:
return self._buttons
@buttons.setter
def buttons(self) -> None:
self._buttons = [self.device.get_buttons(b) for b in range(self.numbuttons)]
@property
def input_mode(self) -> int:
return self._inputmode
@input_mode.setter
def input_mode(self, mode: int) -> None:
self._inputmode = mode
@property
def input_connection(self) -> int:
return self._input_connection
@input_connection.setter
def input_connection(self, conn: int) -> None:
self._input_connection = conn

View File

@@ -0,0 +1,108 @@
"""
Logitech F310 Controller class.
This controller is a usb controller, with the following features.
(XInput mode)
6 axis
11 buttons
1 hat
(DirectInput mode)
4 axis
12 buttons
1 hat
"""
import pygame
from controls.controlsbase import ControlsBase
from enum import Enum
class InputMode(Enum):
DirectInput = 1
XInput = 2
class ConnectionType(Enum):
WIRED = 1
WIRELESS = 2
class LogitechF510Controller(ControlsBase):
def __init__(self, joy):
self.device = joy
self.instance_id: int = self.device.get_instance_id()
self.name = self.device.get_name()
self.numaxis: int = self.device.get_numaxis()
self.axis: list = []
self.numhats: int = self.device.get_numhats()
self.hats: list = []
self.numbuttons: int = self.device.get_numbuttons()
self.buttons: list = []
self.input_mode: InputMode.DirectInput
self.input_connection: ConnectionType.WIRED
def handle_input(self, event):
pass
def left(self):
pass
def right(self):
pass
def up(self):
pass
def down(self):
pass
def pause(self):
pass
def rumble(self):
pass
@property
def name(self) -> str:
return self._name
@name.setter
def name(self, name: str) -> None:
self._name = name
@property
def axis(self) -> list:
return self._axis
@axis.setter
def axis(self) -> None:
self._axis = [self.device.get_axis(a) for a in range(self.numaxis)]
@property
def hats(self) -> list:
return self._hats
@hats.setter
def hats(self) -> None:
self.hats = [self.device.get_hats(h) for h in range(self.numhats)]
@property
def buttons(self) -> list:
return self._buttons
@buttons.setter
def buttons(self) -> None:
self._buttons = [self.device.get_buttons(b) for b in range(self.numbuttons)]
@property
def input_mode(self) -> int:
return self._inputmode
@input_mode.setter
def input_mode(self, mode: int) -> None:
self._inputmode = mode
@property
def input_connection(self) -> int:
return self._input_connection
@input_connection.setter
def input_connection(self, conn: int) -> None:
self._input_connection = conn

View File

@@ -0,0 +1,108 @@
"""
Logitech F310 Controller class.
This controller is a usb controller, with the following features.
(XInput mode)
6 axis
11 buttons
1 hat
(DirectInput mode)
4 axis
12 buttons
1 hat
"""
import pygame
from controls.controlsbase import ControlsBase
from enum import Enum
class InputMode(Enum):
DirectInput = 1
XInput = 2
class ConnectionType(Enum):
WIRED = 1
WIRELESS = 2
class LogitechF710Controller(ControlsBase):
def __init__(self, joy):
self.device = joy
self.instance_id: int = self.device.get_instance_id()
self.name = self.device.get_name()
self.numaxis: int = self.device.get_numaxis()
self.axis: list = []
self.numhats: int = self.device.get_numhats()
self.hats: list = []
self.numbuttons: int = self.device.get_numbuttons()
self.buttons: list = []
self.input_mode: InputMode.DirectInput
self.input_connection: ConnectionType.WIRED
def handle_input(self, event):
pass
def left(self):
pass
def right(self):
pass
def up(self):
pass
def down(self):
pass
def pause(self):
pass
def rumble(self):
pass
@property
def name(self) -> str:
return self._name
@name.setter
def name(self, name: str) -> None:
self._name = name
@property
def axis(self) -> list:
return self._axis
@axis.setter
def axis(self) -> None:
self._axis = [self.device.get_axis(a) for a in range(self.numaxis)]
@property
def hats(self) -> list:
return self._hats
@hats.setter
def hats(self) -> None:
self.hats = [self.device.get_hats(h) for h in range(self.numhats)]
@property
def buttons(self) -> list:
return self._buttons
@buttons.setter
def buttons(self) -> None:
self._buttons = [self.device.get_buttons(b) for b in range(self.numbuttons)]
@property
def input_mode(self) -> int:
return self._inputmode
@input_mode.setter
def input_mode(self, mode: int) -> None:
self._inputmode = mode
@property
def input_connection(self) -> int:
return self._input_connection
@input_connection.setter
def input_connection(self, conn: int) -> None:
self._input_connection = conn

199
controls/xbox_controller.py Normal file
View File

@@ -0,0 +1,199 @@
import time
import threading
import numpy as np
import sounddevice as sd
import alsaaudio
import pulsectl
from pydualsense import *
class XboxController:
def __init__(self):
# DualSense input/output interface
self.ds = pydualsense()
self.ds.init()
self._listening = False
self._bindings = {}
# Audio detection
self.alsa_devices = self._get_alsa_devices()
self.pulse_devices = self._get_pulseaudio_devices()
self.dualsense_audio_device = self._detect_dualsense_audio()
print("DualSense initialized.")
# ---------------------- Device Controls ----------------------
def set_rumble(self, small_motor: int, big_motor: int):
self.ds.setRumble(small_motor, big_motor)
def stop_rumble(self):
self.set_rumble(0, 0)
def set_led_color(self, r: int, g: int, b: int):
self.ds.setLightBarColor(r, g, b)
def set_trigger_effects(self, left_mode='Off', right_mode='Off', force=0):
left = getattr(TriggerModes, left_mode.upper(), TriggerModes.Off)
right = getattr(TriggerModes, right_mode.upper(), TriggerModes.Off)
self.ds.triggerL.setMode(left)
self.ds.triggerR.setMode(right)
if force > 0:
self.ds.triggerL.setForce(force)
self.ds.triggerR.setForce(force)
# ---------------------- Predefined Rumble Patterns ----------------------
def rumble_pattern(self, pattern: str, duration: float = 1.0):
patterns = {
"pulse": self._pulse_rumble,
"heartbeat": self._heartbeat_rumble,
"buzz": self._buzz_rumble,
"wave": self._wave_rumble,
"alarm": self._alarm_rumble,
}
if pattern in patterns:
threading.Thread(target=patterns[pattern], args=(duration,), daemon=True).start()
else:
print(f"Unknown rumble pattern: {pattern}")
def _pulse_rumble(self, duration):
end = time.time() + duration
while time.time() < end:
self.set_rumble(50, 150)
time.sleep(0.2)
self.stop_rumble()
time.sleep(0.2)
def _heartbeat_rumble(self, duration):
end = time.time() + duration
while time.time() < end:
self.set_rumble(200, 200)
time.sleep(0.1)
self.stop_rumble()
time.sleep(0.1)
self.set_rumble(100, 100)
time.sleep(0.1)
self.stop_rumble()
time.sleep(0.4)
def _buzz_rumble(self, duration):
self.set_rumble(80, 255)
time.sleep(duration)
self.stop_rumble()
def _wave_rumble(self, duration):
start = time.time()
while time.time() - start < duration:
for i in range(0, 256, 25):
self.set_rumble(i, 255 - i)
time.sleep(0.05)
for i in reversed(range(0, 256, 25)):
self.set_rumble(i, 255 - i)
time.sleep(0.05)
self.stop_rumble()
def _alarm_rumble(self, duration):
end = time.time() + duration
while time.time() < end:
self.set_rumble(255, 0)
time.sleep(0.1)
self.set_rumble(0, 255)
time.sleep(0.1)
self.stop_rumble()
# ---------------------- Input Listener + Bindings ----------------------
def bind(self, button: str, action: callable):
"""Bind a button to a callable. Ex: controller.bind('cross', lambda: rumble_pattern('buzz'))"""
self._bindings[button] = action
def start_input_listener(self):
def listen():
while self._listening:
#self.ds.update()
for button, action in self._bindings.items():
if getattr(self.ds, button, False):
action()
self._listening = True
thread = threading.Thread(target=listen, daemon=True)
thread.start()
def stop_input_listener(self):
self._listening = False
# ---------------------- Audio Output ----------------------
def _get_alsa_devices(self):
try:
return alsaaudio.cards()
except Exception:
return []
def _get_pulseaudio_devices(self):
try:
pulse = pulsectl.Pulse("dualsense-audio")
return pulse.sink_list()
except Exception:
return []
def _detect_dualsense_audio(self):
# Check ALSA names
for card in self.alsa_devices:
if "DualSense" in card:
return {'type': 'alsa', 'name': card}
# Check PulseAudio sinks
for sink in self.pulse_devices:
if "dualsense" in sink.description.lower():
return {'type': 'pulse', 'name': sink.name}
return None
def play_tone(self, frequency=440.0, duration=2.0, volume=0.5):
if not self.dualsense_audio_device:
print("DualSense speaker not detected.")
return
print(f"Playing tone on DualSense ({self.dualsense_audio_device['type']})...")
fs = 48000 # Sample rate
t = np.linspace(0, duration, int(fs * duration), False)
tone = np.sin(frequency * 2 * np.pi * t) * volume
audio = tone.astype(np.float32)
try:
if self.dualsense_audio_device['type'] == 'pulse':
sd.play(audio, samplerate=fs, device=self.dualsense_audio_device['name'])
elif self.dualsense_audio_device['type'] == 'alsa':
device_index = self.alsa_devices.index(self.dualsense_audio_device['name'])
sd.play(audio, samplerate=fs, device=device_index)
sd.wait()
except Exception as e:
print("Failed to play tone:", e)
def list_audio_devices(self):
print("ALSA Devices:")
for card in self.alsa_devices:
print(f" - {card}")
print("\nPulseAudio Devices:")
for sink in self.pulse_devices:
print(f" - {sink.name} ({sink.description})")
# ---------------------- Cleanup ----------------------
def close(self):
self.ds.close()
if __name__ == "__main__":
controller = DualSenseController()
# Bind buttons to patterns
controller.bind("cross", lambda: controller.rumble_pattern("heartbeat", 1.5))
controller.bind("circle", lambda: controller.rumble_pattern("buzz", 0.5))
controller.bind("triangle", lambda: controller.rumble_pattern("pulse", 2))
controller.bind("square", lambda: controller.set_led_color(255, 0, 0))
# Start listening
controller.start_input_listener()

27
main.py
View File

@@ -3,6 +3,16 @@ import time
import controls
from screens.startscreen import StartScreen
from tilemap.playground import PlayGround
from player.player import Player, Players
from enum import Enum
class State(Enum):
STARTING = 1
PLAYING = 2
PAUSING = 3
ENDING = 4
SETTING = 5
HIGHSCORE = 6
class Snake:
def __init__(self):
@@ -15,7 +25,7 @@ class Snake:
self.windowed: bool = True
self.width: int = 800
self.height: int =600
self.startscreen = None
self.screen = None
self.icon = pygame.image.load("snake.webp")
pygame.display.set_icon(self.icon)
@@ -29,7 +39,20 @@ class Snake:
while self.running:
match self.state:
case 0:
self.startscreen = StartScreen(self)
self.screen = StartScreen(self)
pygame.display.flip()
waiting = True
while waiting:
for event in pygame.event.get():
if event.type == pygame.QUIT:
waiting = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_F1:
print("F1 pressed!")
waiting = False
elif event.key == pygame.K_F2:
print("F2 pressed!")
waiting = False
case 1:
pass
case 2:

View File

@@ -1,5 +1,27 @@
from enum import Enum
from controls.controller import Controllers
class Players(Enum):
UP1 = 1
UP2 = 2
class Player:
def __init__(self):
self.controls = None
@property
def player(self) -> int:
return self._player
@player.setter
def player(self, plr: int) -> None:
self._player = plr
@property
def controls(self) -> Controllers:
return self._controls
@controls.setter
def controls(self, ctl: Controllers) -> None:
self._controls = ctl

View File

@@ -10,3 +10,5 @@ class StartScreen:
self.parent.screen.blit(self.banner, (0, 0))

View File

@@ -26,9 +26,9 @@ def load_image(path, max_size):
new_size = (int(image_rect.width * scale_factor), int(image_rect.height * scale_factor))
return pygame.transform.smoothscale(image, new_size)
keyboard_image = load_image("keyboard.png", (100, 100))
keyboard_image = load_image("resources/gfx/keyboard.png", (100, 100))
#joystick_image = load_image("ps5-dualsense.png", (100, 100))
joystick_image = load_image("gamepad.png", (100, 100))
joystick_image = load_image("resources/gfx/gamepad.png", (100, 100))
# Abstract Control Class
class Control(ABC):

View File

@@ -57,7 +57,7 @@ def main():
print("Joystick button pressed.")
if event.button == 0:
joystick = joysticks[event.instance_id]
if joystick.rumble(0, 0.7, 500):
if joystick.rumble(0.7, 1.0, 1000):
print(f"Rumble effect played on joystick {event.instance_id}")
if event.type == pygame.JOYBUTTONUP: