Skip to content

Commit

Permalink
Use package:sdl_gamepad instead of win32_gamepad (#163)
Browse files Browse the repository at this point in the history
See more details at https://pub.dev/packages/sdl_gamepad

Tested on:

- [x] Windows (@Levi-Lesches)
- [ ] MacOS (unplanned because we need new gamepads)
- [x] Linux/Pi (@Levi-Lesches)

---------

Co-authored-by: BinghamtonRover <[email protected]>
  • Loading branch information
Levi-Lesches and Bing-Rover authored Sep 25, 2024
1 parent 4226b63 commit 9c7b0d6
Show file tree
Hide file tree
Showing 15 changed files with 433 additions and 299 deletions.
Binary file added SDL3.dll
Binary file not shown.
6 changes: 4 additions & 2 deletions lib/services.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@
library services;

import "src/services/files.dart";
import "src/services/gamepad.dart";
import "src/services/gamepad/service.dart";
import "src/services/service.dart";

export "src/services/socket.dart";
export "src/services/files.dart";
export "src/services/gamepad.dart";
export "src/services/gamepad/gamepad.dart";
export "src/services/gamepad/service.dart";
export "src/services/gamepad/state.dart";
export "src/services/serial.dart";

/// A dependency injection service that manages the lifecycle of other services.
Expand Down
142 changes: 64 additions & 78 deletions lib/src/models/rover/controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,104 +13,90 @@ import "package:rover_dashboard/services.dart";
/// call [RoverControls.parseInputs] to see what actions can be done. Each command is then sent
/// with [MessagesModel.sendMessage], which will either send them over the network or via serial.
class Controller extends Model {
/// Reads the gamepad and controls the rover when triggered.
late final Timer gamepadTimer;
/// Reads the gamepad and controls the rover when triggered.
late final Timer gamepadTimer;

/// The index of the gamepad to read from. Does not match [Gamepad.controller].
final int gamepadIndex;
/// The index of the gamepad to read from. Does not match [Gamepad.controllerIndex].
final int index;

/// Defines what the current controls are for the current mode.
RoverControls controls;
/// Defines what the current controls are for the current mode.
RoverControls controls;

/// Maps button presses on [gamepad] to [controls].
Controller(this.gamepadIndex, this.controls);
/// Maps button presses on [gamepad] to [controls].
Controller(this.index, this.controls);

/// The gamepad to read from.
Gamepad get gamepad => services.gamepad.gamepads[gamepadIndex];
/// The gamepad to read from.
Gamepad get gamepad => services.gamepad.gamepads[index];

@override
Future<void> init() async {
gamepadTimer = Timer.periodic(gamepadDelay, _update);
models.settings.addListener(notifyListeners);
}
@override
Future<void> init() async {
gamepadTimer = Timer.periodic(gamepadDelay, _update);
models.settings.addListener(notifyListeners);
}

@override
void dispose() {
gamepadTimer.cancel();
models.settings.removeListener(notifyListeners);
controls.onDispose.forEach(models.messages.sendMessage);
super.dispose();
}
@override
void dispose() {
gamepadTimer.cancel();
models.settings.removeListener(notifyListeners);
controls.onDispose.forEach(models.messages.sendMessage);
super.dispose();
}

/// The current operating mode.
OperatingMode get mode => controls.mode;
/// The current operating mode.
OperatingMode get mode => controls.mode;

/// Whether this controller is ready to use.
bool get isConnected => gamepad.isConnected;
/// Whether this controller is ready to use.
bool get isConnected => gamepad.isConnected;

/// Changes the current mode this [gamepad] is controlling, and chooses a new [RoverControls].
void setMode(OperatingMode? mode) {
if (mode == null) return;
if (
mode != OperatingMode.none
&& models.rover.controllers.any(
(other) => other.gamepadIndex != gamepadIndex && other.mode == mode,
)
) {
/// Returns Whether another controller is set to the given mode.
bool otherControllerIs(OperatingMode mode) => models.rover.controllers
.any((other) => other.index != index && other.mode == mode);

/// Changes the current mode this [gamepad] is controlling, and chooses a new [RoverControls].
void setMode(OperatingMode? mode) {
if (mode == null) return;
if (mode != OperatingMode.none && otherControllerIs(mode)) {
models.home.setMessage(severity: Severity.error, text: "Another controller is set to that mode");
return;
}
if (
mode == OperatingMode.drive
&& models.rover.controllers.any(
(other) => other.gamepadIndex != gamepadIndex && other.mode == OperatingMode.modernDrive,
)
) {
} else if (mode == OperatingMode.drive && otherControllerIs(OperatingMode.modernDrive)) {
models.home.setMessage(severity: Severity.error, text: "Cannot use both tank and drive controls");
return;
}
if (
mode == OperatingMode.modernDrive
&& models.rover.controllers.any(
(other) => other.gamepadIndex != gamepadIndex && other.mode == OperatingMode.drive,
)
) {
} else if (mode == OperatingMode.modernDrive && otherControllerIs(OperatingMode.drive)) {
models.home.setMessage(severity: Severity.error, text: "Cannot use both tank and drive controls");
return;
}
if (mode == OperatingMode.cameras && !models.settings.dashboard.splitCameras) {
} else if (mode == OperatingMode.cameras && !models.settings.dashboard.splitCameras) {
models.home.setMessage(severity: Severity.error, text: "Enable split camera controls in the settings");
return;
}
controls.onDispose.forEach(models.messages.sendMessage);
controls = RoverControls.forMode(mode);
gamepad.pulse();
notifyListeners();
}
controls.onDispose.forEach(models.messages.sendMessage);
controls = RoverControls.forMode(mode);
gamepad.pulse();
notifyListeners();
}

/// Connects the [gamepad] to the user's device.
Future<void> connect() async {
await services.gamepad.connect(gamepadIndex);
if (gamepad.isConnected) {
models.home.setMessage(severity: Severity.info, text: "Connected to gamepad");
} else {
models.home.setMessage(severity: Severity.error, text: "No gamepad connected");
}
notifyListeners();
}
/// Connects the [gamepad] to the user's device.
Future<void> connect() async {
await services.gamepad.connect(index);
if (gamepad.isConnected) {
models.home.setMessage(severity: Severity.info, text: "Connected to gamepad");
} else {
models.home.setMessage(severity: Severity.error, text: "No gamepad connected");
}
notifyListeners();
}

/// Same as [setMode], but uses [OperatingMode.index] instead.
void setModeIndex(int index) => setMode(OperatingMode.values[index]);
/// Same as [setMode], but uses [OperatingMode.index] instead.
void setModeIndex(int index) => setMode(OperatingMode.values[index]);

/// Reads the gamepad, chooses commands, and sends them to the rover.
Future<void> _update([_]) async {
services.gamepad.update();
if (!gamepad.isConnected) return;
controls.updateState(gamepad.state);
final messages = controls.parseInputs(gamepad.state);
for (final message in messages) {
/// Reads the gamepad, chooses commands, and sends them to the rover.
Future<void> _update([_]) async {
final state = gamepad.getState();
if (!gamepad.isConnected || state == null) return;
controls.updateState(state);
final messages = controls.parseInputs(state);
for (final message in messages) {
// print(message.toProto3Json());
models.messages.sendMessage(message);
}
}
models.messages.sendMessage(message);
}
}
}
186 changes: 0 additions & 186 deletions lib/src/services/gamepad.dart

This file was deleted.

Loading

0 comments on commit 9c7b0d6

Please sign in to comment.