Skip to content

Commit

Permalink
Control the Arm (#66)
Browse files Browse the repository at this point in the history
Controls the arm and adds a list of controls to the UI

Closes #53
  • Loading branch information
Levi-Lesches authored Feb 27, 2023
1 parent 590ee4a commit d6ce255
Show file tree
Hide file tree
Showing 16 changed files with 454 additions and 493 deletions.
1 change: 1 addition & 0 deletions lib/data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
/// library should import any other library.
library data;

export "src/data/generated/arm.pb.dart";
export "src/data/generated/core.pb.dart";
export "src/data/generated/drive.pb.dart";
export "src/data/generated/electrical.pb.dart";
Expand Down
2 changes: 1 addition & 1 deletion lib/src/data/Protobuf
Submodule Protobuf updated 1 files
+20 −29 arm.proto
516 changes: 167 additions & 349 deletions lib/src/data/generated/arm.pb.dart

Large diffs are not rendered by default.

74 changes: 24 additions & 50 deletions lib/src/data/generated/arm.pbjson.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,6 @@ const Position$json = const {

/// Descriptor for `Position`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List positionDescriptor = $convert.base64Decode('CghQb3NpdGlvbhIMCgF4GAEgASgFUgF4EgwKAXkYAiABKAVSAXkSDAoBehgDIAEoBVIBeg==');
@$core.Deprecated('Use armCommandDescriptor instead')
const ArmCommand$json = const {
'1': 'ArmCommand',
'2': const [
const {'1': 'move_to', '3': 1, '4': 1, '5': 11, '6': '.Position', '10': 'moveTo'},
const {'1': 'calibrate', '3': 2, '4': 1, '5': 8, '10': 'calibrate'},
const {'1': 'swivel', '3': 3, '4': 1, '5': 2, '10': 'swivel'},
const {'1': 'extend', '3': 4, '4': 1, '5': 2, '10': 'extend'},
const {'1': 'lift', '3': 5, '4': 1, '5': 2, '10': 'lift'},
const {'1': 'precise_swivel', '3': 6, '4': 1, '5': 2, '10': 'preciseSwivel'},
const {'1': 'precise_lift', '3': 7, '4': 1, '5': 2, '10': 'preciseLift'},
const {'1': 'precise_extend', '3': 8, '4': 1, '5': 2, '10': 'preciseExtend'},
],
};

/// Descriptor for `ArmCommand`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List armCommandDescriptor = $convert.base64Decode('CgpBcm1Db21tYW5kEiIKB21vdmVfdG8YASABKAsyCS5Qb3NpdGlvblIGbW92ZVRvEhwKCWNhbGlicmF0ZRgCIAEoCFIJY2FsaWJyYXRlEhYKBnN3aXZlbBgDIAEoAlIGc3dpdmVsEhYKBmV4dGVuZBgEIAEoAlIGZXh0ZW5kEhIKBGxpZnQYBSABKAJSBGxpZnQSJQoOcHJlY2lzZV9zd2l2ZWwYBiABKAJSDXByZWNpc2VTd2l2ZWwSIQoMcHJlY2lzZV9saWZ0GAcgASgCUgtwcmVjaXNlTGlmdBIlCg5wcmVjaXNlX2V4dGVuZBgIIAEoAlINcHJlY2lzZUV4dGVuZA==');
@$core.Deprecated('Use motorStatusDescriptor instead')
const MotorStatus$json = const {
'1': 'MotorStatus',
Expand All @@ -63,52 +46,43 @@ const ArmData$json = const {

/// Descriptor for `ArmData`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List armDataDescriptor = $convert.base64Decode('CgdBcm1EYXRhEjMKD2N1cnJlbnRQb3NpdGlvbhgBIAEoCzIJLlBvc2l0aW9uUg9jdXJyZW50UG9zaXRpb24SMQoOdGFyZ2V0UG9zaXRpb24YAiABKAsyCS5Qb3NpdGlvblIOdGFyZ2V0UG9zaXRpb24SIAoEYmFzZRgDIAEoCzIMLk1vdG9yU3RhdHVzUgRiYXNlEigKCHNob3VsZGVyGAQgASgLMgwuTW90b3JTdGF0dXNSCHNob3VsZGVyEiIKBWVsYm93GAUgASgLMgwuTW90b3JTdGF0dXNSBWVsYm93');
@$core.Deprecated('Use armCommandDescriptor instead')
const ArmCommand$json = const {
'1': 'ArmCommand',
'2': const [
const {'1': 'stop', '3': 1, '4': 1, '5': 8, '10': 'stop'},
const {'1': 'calibrate', '3': 2, '4': 1, '5': 8, '10': 'calibrate'},
const {'1': 'move_swivel', '3': 3, '4': 1, '5': 2, '10': 'moveSwivel'},
const {'1': 'move_shoulder', '3': 4, '4': 1, '5': 2, '10': 'moveShoulder'},
const {'1': 'move_elbow', '3': 5, '4': 1, '5': 2, '10': 'moveElbow'},
],
};

/// Descriptor for `ArmCommand`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List armCommandDescriptor = $convert.base64Decode('CgpBcm1Db21tYW5kEhIKBHN0b3AYASABKAhSBHN0b3ASHAoJY2FsaWJyYXRlGAIgASgIUgljYWxpYnJhdGUSHwoLbW92ZV9zd2l2ZWwYAyABKAJSCm1vdmVTd2l2ZWwSIwoNbW92ZV9zaG91bGRlchgEIAEoAlIMbW92ZVNob3VsZGVyEh0KCm1vdmVfZWxib3cYBSABKAJSCW1vdmVFbGJvdw==');
@$core.Deprecated('Use gripperDataDescriptor instead')
const GripperData$json = const {
'1': 'GripperData',
'2': const [
const {'1': 'rotation', '3': 1, '4': 1, '5': 2, '10': 'rotation'},
const {'1': 'swivel', '3': 2, '4': 1, '5': 2, '10': 'swivel'},
const {'1': 'pinch', '3': 3, '4': 1, '5': 2, '10': 'pinch'},
const {'1': 'motor_temperature', '3': 4, '4': 1, '5': 2, '10': 'motorTemperature'},
const {'1': 'rotate', '3': 1, '4': 1, '5': 11, '6': '.MotorStatus', '10': 'rotate'},
const {'1': 'lift', '3': 2, '4': 1, '5': 11, '6': '.MotorStatus', '10': 'lift'},
const {'1': 'pinch', '3': 3, '4': 1, '5': 11, '6': '.MotorStatus', '10': 'pinch'},
],
};

/// Descriptor for `GripperData`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List gripperDataDescriptor = $convert.base64Decode('CgtHcmlwcGVyRGF0YRIaCghyb3RhdGlvbhgBIAEoAlIIcm90YXRpb24SFgoGc3dpdmVsGAIgASgCUgZzd2l2ZWwSFAoFcGluY2gYAyABKAJSBXBpbmNoEisKEW1vdG9yX3RlbXBlcmF0dXJlGAQgASgCUhBtb3RvclRlbXBlcmF0dXJl');
final $typed_data.Uint8List gripperDataDescriptor = $convert.base64Decode('CgtHcmlwcGVyRGF0YRIkCgZyb3RhdGUYASABKAsyDC5Nb3RvclN0YXR1c1IGcm90YXRlEiAKBGxpZnQYAiABKAsyDC5Nb3RvclN0YXR1c1IEbGlmdBIiCgVwaW5jaBgDIAEoCzIMLk1vdG9yU3RhdHVzUgVwaW5jaA==');
@$core.Deprecated('Use gripperCommandDescriptor instead')
const GripperCommand$json = const {
'1': 'GripperCommand',
'2': const [
const {'1': 'calibrate', '3': 1, '4': 1, '5': 8, '10': 'calibrate'},
const {'1': 'pinch', '3': 2, '4': 1, '5': 2, '10': 'pinch'},
const {'1': 'rotate', '3': 3, '4': 1, '5': 2, '10': 'rotate'},
const {'1': 'precise_pinch', '3': 4, '4': 1, '5': 2, '10': 'precisePinch'},
const {'1': 'precise_rotate', '3': 5, '4': 1, '5': 2, '10': 'preciseRotate'},
const {'1': 'stop', '3': 1, '4': 1, '5': 8, '10': 'stop'},
const {'1': 'calibrate', '3': 2, '4': 1, '5': 8, '10': 'calibrate'},
const {'1': 'move_rotate', '3': 3, '4': 1, '5': 2, '10': 'moveRotate'},
const {'1': 'move_lift', '3': 4, '4': 1, '5': 2, '10': 'moveLift'},
const {'1': 'move_gripper', '3': 5, '4': 1, '5': 2, '10': 'moveGripper'},
],
};

/// Descriptor for `GripperCommand`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List gripperCommandDescriptor = $convert.base64Decode('Cg5HcmlwcGVyQ29tbWFuZBIcCgljYWxpYnJhdGUYASABKAhSCWNhbGlicmF0ZRIUCgVwaW5jaBgCIAEoAlIFcGluY2gSFgoGcm90YXRlGAMgASgCUgZyb3RhdGUSIwoNcHJlY2lzZV9waW5jaBgEIAEoAlIMcHJlY2lzZVBpbmNoEiUKDnByZWNpc2Vfcm90YXRlGAUgASgCUg1wcmVjaXNlUm90YXRl');
@$core.Deprecated('Use hreiDataDescriptor instead')
const HreiData$json = const {
'1': 'HreiData',
'2': const [
const {'1': 'arm_data', '3': 1, '4': 1, '5': 11, '6': '.ArmData', '10': 'armData'},
const {'1': 'gripper_data', '3': 2, '4': 1, '5': 11, '6': '.GripperData', '10': 'gripperData'},
],
};

/// Descriptor for `HreiData`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List hreiDataDescriptor = $convert.base64Decode('CghIcmVpRGF0YRIjCghhcm1fZGF0YRgBIAEoCzIILkFybURhdGFSB2FybURhdGESLwoMZ3JpcHBlcl9kYXRhGAIgASgLMgwuR3JpcHBlckRhdGFSC2dyaXBwZXJEYXRh');
@$core.Deprecated('Use hreiCommandDescriptor instead')
const HreiCommand$json = const {
'1': 'HreiCommand',
'2': const [
const {'1': 'arm_command', '3': 1, '4': 1, '5': 11, '6': '.ArmCommand', '10': 'armCommand'},
const {'1': 'gripper_command', '3': 2, '4': 1, '5': 11, '6': '.GripperCommand', '10': 'gripperCommand'},
],
};

/// Descriptor for `HreiCommand`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List hreiCommandDescriptor = $convert.base64Decode('CgtIcmVpQ29tbWFuZBIsCgthcm1fY29tbWFuZBgBIAEoCzILLkFybUNvbW1hbmRSCmFybUNvbW1hbmQSOAoPZ3JpcHBlcl9jb21tYW5kGAIgASgLMg8uR3JpcHBlckNvbW1hbmRSDmdyaXBwZXJDb21tYW5k');
final $typed_data.Uint8List gripperCommandDescriptor = $convert.base64Decode('Cg5HcmlwcGVyQ29tbWFuZBISCgRzdG9wGAEgASgIUgRzdG9wEhwKCWNhbGlicmF0ZRgCIAEoCFIJY2FsaWJyYXRlEh8KC21vdmVfcm90YXRlGAMgASgCUgptb3ZlUm90YXRlEhsKCW1vdmVfbGlmdBgEIAEoAlIIbW92ZUxpZnQSIQoMbW92ZV9ncmlwcGVyGAUgASgCUgttb3ZlR3JpcHBlcg==');
15 changes: 7 additions & 8 deletions lib/src/models/data/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,18 @@ class HomeModel extends Model {
_messageTimer = Timer(const Duration(seconds: 5), () { message = null; notifyListeners(); });
}

/// The list of controls for this mode.
List<String> get controls => [ // TODO: replace with actual backend
"Start dig sequence: START",
"Change operation mode: BACK",
"Move Auger: D-pad Up/Down",
"...",
];

/// Changes the mode based on an index.
void changeMode(int index) {
mode = OperatingMode.values[index];
models.rover.updateMode(mode);
services.gamepad.vibrate();
notifyListeners();
}

/// Switches to the next mode.
void nextMode() {
int index = mode.index + 1;
if (index == OperatingMode.values.length) index = 0;
changeMode(index);
}
}
2 changes: 1 addition & 1 deletion lib/src/models/data/video.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class VideoModel extends Model {
handler: updateFrame,
);
frameUpdater = Timer.periodic(
const Duration(milliseconds: 17), // 60 FPS
const Duration(milliseconds: 42), // 24 FPS
(_) => notifyListeners()
);
// TODO: Read the layout from Settings
Expand Down
41 changes: 41 additions & 0 deletions lib/src/models/rover/arm.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import "controller.dart";

/// Controls the arm.
class ArmController extends Controller {
@override
List<Message> parseInputs(GamepadState state) => [
// ARM
if (state.leftShoulder) ArmCommand(moveSwivel: -0.25),
if (state.rightShoulder) ArmCommand(moveSwivel: 0.25),
if (state.normalRightY != 0) ArmCommand(moveElbow: state.normalRightY),
if (state.normalLeftY != 0) ArmCommand(moveShoulder: -state.normalLeftY),

// GRIPPER
if (state.leftTrigger > 0) GripperCommand(moveGripper: -state.normalLeftTrigger),
if (state.rightTrigger > 0) GripperCommand(moveGripper: state.normalRightTrigger),
if (state.dpadUp) GripperCommand(moveLift: 1),
if (state.dpadDown) GripperCommand(moveLift: -1),
if (state.normalRightX != 0) GripperCommand(moveRotate: 1.25*state.normalRightX),

if (state.buttonBack) ArmCommand(stop: true),
if (state.buttonBack) GripperCommand(stop: true),
// if (state.buttonStart) ArmCommand(calibrate: true),
// if (state.buttonStart) GripperCommand(calibrate: true),
];

@override
List<Message> get onDispose => [
ArmCommand(stop: true),
GripperCommand(stop: true),
];

@override
Map<String, String> get controls => {
"Swivel": "Bumpers",
"Shoulder": "Left joystick (vertical)",
"Elbow": "Right joystick (vertical)",
"Gripper Lift": "D-pad up/down",
"Gripper rotate": "Right joystick (horizontal)",
"Pinch": "Triggers",
};
}
18 changes: 16 additions & 2 deletions lib/src/models/rover/controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "package:rover_dashboard/models.dart";
import "package:rover_dashboard/services.dart";

import "../model.dart";
import "arm.dart";
import "drive.dart";
import "stub.dart";

Expand All @@ -26,13 +27,18 @@ abstract class Controller extends Model {
/// Reads the gamepad and controls the rover when triggered.
late final Timer gamepadTimer;

/// Whether the start button has been pressed.
///
/// When the start button is released, the dashboard will switch to the next mode.
bool isStartPressed = false;

/// Allows this class to be subclassed.
Controller();

/// Constructs the appropriate [Controller] for each mode.
factory Controller.forMode(OperatingMode mode) {
switch (mode) {
case OperatingMode.arm: return StubController();
case OperatingMode.arm: return ArmController();
case OperatingMode.science: return StubController();
case OperatingMode.autonomy: return StubController();
case OperatingMode.drive: return DriveController();
Expand Down Expand Up @@ -62,6 +68,9 @@ abstract class Controller extends Model {
/// Use this to stop the rover when the user switches modes.
Iterable<Message> get onDispose;

/// A human-readable list of controls.
Map<String, String> get controls;

/// Sends a command over the network or over Serial.
Future<void> sendMessage(Message message) async {
if (models.serial.isConnected) {
Expand All @@ -72,8 +81,13 @@ abstract class Controller extends Model {
}

/// Reads the gamepad, chooses commands, and sends them to the rover.
void _update([_]) {
Future<void> _update([_]) async {
services.gamepad.update();
if (services.gamepad.state.buttonStart) {
isStartPressed = true;
} else if (isStartPressed) {
models.home.nextMode();
}
final messages = parseInputs(services.gamepad.state);
messages.forEach(sendMessage);
}
Expand Down
4 changes: 2 additions & 2 deletions lib/src/models/rover/core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import "../model.dart";
const connectionIncrement = 0.2;

/// How long to wait between handshakes.
const handshakeInterval = Duration(seconds: 1);
const handshakeInterval = Duration(milliseconds: 200);

/// How long to wait for incoming handshakes after sending them out.
const handshakeWaitDelay = Duration(milliseconds: 200);
const handshakeWaitDelay = Duration(milliseconds: 50);

/// Monitors the connection to the rover.
class RoverCore extends Model {
Expand Down
28 changes: 27 additions & 1 deletion lib/src/models/rover/drive.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,27 @@ class DriveController extends Controller {

@override
List<Message> parseInputs(GamepadState state) => [
DriveCommand(setLeft: true, left: state.leftThumbstickY.normalizeJoystick.clamp(-1, 1)),
// Manual controls
DriveCommand(setLeft: true, left: -1*state.leftThumbstickY.normalizeJoystick.clamp(-1, 1)),
DriveCommand(setRight: true, right: state.rightThumbstickY.normalizeJoystick.clamp(-1, 1)),
if (state.dpadUp) updateThrottle(throttleIncrement),
if (state.dpadDown) updateThrottle(-throttleIncrement),

// More intuitive controls
if (state.leftShoulder) ...[
DriveCommand(setLeft: true, left: 1),
DriveCommand(setRight: true, right: 1),
] else if (state.rightShoulder)...[
DriveCommand(setLeft: true, left: -1),
DriveCommand(setRight: true, right: -1),
],
if (state.leftTrigger > 0) ...[
DriveCommand(setLeft: true, left: state.normalLeftTrigger),
DriveCommand(setRight: true, right: -1*state.normalRightTrigger),
] else if (state.rightTrigger > 0)...[
DriveCommand(setLeft: true, left: -1*state.normalLeftTrigger),
DriveCommand(setRight: true, right: state.normalRightTrigger),
]
];

/// Updates the throttle by [throttleIncrement], clamping at [0, 1].
Expand All @@ -29,4 +46,13 @@ class DriveController extends Controller {
DriveCommand(setLeft: true, left: 0),
DriveCommand(setLeft: false, left: 0),
];

@override
Map<String, String> get controls => {
"Left Throttle": "Left joystick (vertical)",
"Right Throttle": "Right joystick (vertical)",
"Drive Straight": "Triggers",
"Turn in place": "Bumpers",
"Adjust speed": "D-pad up/down",
};
}
3 changes: 3 additions & 0 deletions lib/src/models/rover/stub.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@ class StubController extends Controller {

@override
List<Message> get onDispose => [];

@override
Map<String, String> get controls => {};
}
Loading

0 comments on commit d6ce255

Please sign in to comment.