0.0.6 #5
2
.gitignore
vendored
@@ -168,4 +168,4 @@ cython_debug/
|
|||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
#.idea/
|
||||||
|
|
||||||
testing/
|
testing/*
|
||||||
|
@@ -25,5 +25,6 @@ Højre - <kbd>d</kbd> eller <kbd>→</kbd>
|
|||||||
Venstre - <kbd>a</kbd> eller <kbd>←</kbd>
|
Venstre - <kbd>a</kbd> eller <kbd>←</kbd>
|
||||||
Op - <kbd>w</kbd> eller <kbd>↑</kbd>
|
Op - <kbd>w</kbd> eller <kbd>↑</kbd>
|
||||||
Ned - <kbd>s</kbd> eller <kbd>↓</kbd>
|
Ned - <kbd>s</kbd> eller <kbd>↓</kbd>
|
||||||
|
Pause - <kbd>p</kbd>
|
||||||
|
|
||||||
Alternativt, kan et joystick :joystick: eller gamepad :video_game: bruges.
|
Alternativt, kan et joystick :joystick: eller gamepad :video_game: bruges.
|
@@ -1,76 +1,27 @@
|
|||||||
import pygame
|
import pygame
|
||||||
from controls.controlsbase import ControlsBase
|
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):
|
def __init__(self, joy):
|
||||||
self.device = joy
|
self.controllers = []
|
||||||
self.instance_id: int = self.device.get_instance_id()
|
if not joy.get_name() in CONTROLLERS:
|
||||||
self.name = self.device.get_name()
|
self.controllers.append(GenericController(joy))
|
||||||
self.numaxis: int = self.device.get_numaxis()
|
else:
|
||||||
self.axis: list = []
|
self.controllers.append(CONTROLLERS[joy.get_name()](joy))
|
||||||
self.numhats: int = self.device.get_numhats()
|
|
||||||
self.hats: list = []
|
|
||||||
self.numbuttons: int = self.device.get_numbuttons()
|
|
||||||
self.buttons: list = []
|
|
||||||
self.power_level: str = ""
|
|
||||||
|
|
||||||
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()
|
|
@@ -6,4 +6,28 @@ from abc import ABC, abstractmethod
|
|||||||
class ControlsBase(ABC):
|
class ControlsBase(ABC):
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def handle_input(self, event):
|
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
|
pass
|
70
controls/dualsense_audio.py
Normal 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})")
|
||||||
|
|
199
controls/dualsense_controller.py
Normal 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()
|
199
controls/dualsense_edge_controller.py
Normal 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()
|
68
controls/generic_controller.py
Normal 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)]
|
||||||
|
|
108
controls/logitech_f310_controller.py
Normal 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
|
108
controls/logitech_f510_controller.py
Normal 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
|
108
controls/logitech_f710_controller.py
Normal 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
@@ -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
@@ -3,6 +3,16 @@ import time
|
|||||||
import controls
|
import controls
|
||||||
from screens.startscreen import StartScreen
|
from screens.startscreen import StartScreen
|
||||||
from tilemap.playground import PlayGround
|
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:
|
class Snake:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -15,7 +25,7 @@ class Snake:
|
|||||||
self.windowed: bool = True
|
self.windowed: bool = True
|
||||||
self.width: int = 800
|
self.width: int = 800
|
||||||
self.height: int =600
|
self.height: int =600
|
||||||
self.startscreen = None
|
self.screen = None
|
||||||
self.icon = pygame.image.load("snake.webp")
|
self.icon = pygame.image.load("snake.webp")
|
||||||
pygame.display.set_icon(self.icon)
|
pygame.display.set_icon(self.icon)
|
||||||
|
|
||||||
@@ -29,7 +39,20 @@ class Snake:
|
|||||||
while self.running:
|
while self.running:
|
||||||
match self.state:
|
match self.state:
|
||||||
case 0:
|
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:
|
case 1:
|
||||||
pass
|
pass
|
||||||
case 2:
|
case 2:
|
||||||
|
@@ -1,5 +1,27 @@
|
|||||||
|
from enum import Enum
|
||||||
|
from controls.controller import Controllers
|
||||||
|
|
||||||
|
class Players(Enum):
|
||||||
|
UP1 = 1
|
||||||
|
UP2 = 2
|
||||||
|
|
||||||
class Player:
|
class Player:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.controls = None
|
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
|
||||||
|
|
Before Width: | Height: | Size: 327 KiB After Width: | Height: | Size: 327 KiB |
Before Width: | Height: | Size: 245 KiB After Width: | Height: | Size: 245 KiB |
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 115 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 665 KiB After Width: | Height: | Size: 665 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 129 KiB |
@@ -8,4 +8,7 @@ class StartScreen:
|
|||||||
self.banner = pygame.transform.smoothscale(self.banner, (self.banner_width, int(self.banner_width*0.4)))
|
self.banner = pygame.transform.smoothscale(self.banner, (self.banner_width, int(self.banner_width*0.4)))
|
||||||
|
|
||||||
self.parent.screen.blit(self.banner, (0, 0))
|
self.parent.screen.blit(self.banner, (0, 0))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@@ -26,9 +26,9 @@ def load_image(path, max_size):
|
|||||||
new_size = (int(image_rect.width * scale_factor), int(image_rect.height * scale_factor))
|
new_size = (int(image_rect.width * scale_factor), int(image_rect.height * scale_factor))
|
||||||
return pygame.transform.smoothscale(image, new_size)
|
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("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
|
# Abstract Control Class
|
||||||
class Control(ABC):
|
class Control(ABC):
|
||||||
|
@@ -57,7 +57,7 @@ def main():
|
|||||||
print("Joystick button pressed.")
|
print("Joystick button pressed.")
|
||||||
if event.button == 0:
|
if event.button == 0:
|
||||||
joystick = joysticks[event.instance_id]
|
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}")
|
print(f"Rumble effect played on joystick {event.instance_id}")
|
||||||
|
|
||||||
if event.type == pygame.JOYBUTTONUP:
|
if event.type == pygame.JOYBUTTONUP:
|
||||||
|