Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add diagonal movement #4

Draft
wants to merge 22 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions bin/task.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import "package:burt_network/logging.dart";

final chair = (2, 0).toGps();
final obstacles = <SimulatedObstacle>[
SimulatedObstacle(coordinates: (2, 0).toGps(), radius: 3),
SimulatedObstacle(coordinates: (6, -1).toGps(), radius: 3),
SimulatedObstacle(coordinates: (6, 1).toGps(), radius: 3),
];
Expand All @@ -17,9 +16,10 @@ void main() async {
simulator.detector = DetectorSimulator(collection: simulator, obstacles: obstacles);
simulator.pathfinder = RoverPathfinder(collection: simulator);
simulator.orchestrator = RoverOrchestrator(collection: simulator);
simulator.drive = RoverDrive(collection: simulator, useImu: true, useGps: false);
// simulator.drive = RoverDrive(collection: simulator, useImu: true, useGps: false);
simulator.gps = GpsSimulator(collection: simulator);
// simulator.drive = DriveSimulator(collection: simulator);
simulator.imu = ImuSimulator(collection: simulator);
simulator.drive = DriveSimulator(collection: simulator, shouldDelay: true);
await simulator.init();
await simulator.imu.waitForValue();
// await simulator.drive.faceNorth();
Expand Down
91 changes: 85 additions & 6 deletions lib/src/interfaces/a_star.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import "dart:math";

import "package:a_star/a_star.dart";

import "package:burt_network/generated.dart";
Expand Down Expand Up @@ -37,10 +39,12 @@ class AutonomyAStarState extends AStarState<AutonomyAStarState> {
DriveDirection.left => "Turn left to face $direction",
DriveDirection.right => "Turn right to face $direction",
DriveDirection.stop => "Start/Stop at ${position.prettyPrint()}",
DriveDirection.forwardLeft => "Turn 45 degrees left to face $direction",
DriveDirection.forwardRight => "Turn 45 degrees right to face $direction",
};

@override
double heuristic() => position.manhattanDistance(goal);
double heuristic() => position.distanceTo(goal);

@override
String hash() => "${position.prettyPrint()} ($orientation)";
Expand All @@ -54,13 +58,88 @@ class AutonomyAStarState extends AStarState<AutonomyAStarState> {
orientation: orientation,
direction: direction,
goal: goal,
depth: direction == DriveDirection.forward ? depth + 1 : depth + 2,
depth: (direction == DriveDirection.forward)
? depth + 1
: (direction == DriveDirection.forwardLeft || direction == DriveDirection.forwardRight)
? depth + sqrt2
: depth + 2,
Levi-Lesches marked this conversation as resolved.
Show resolved Hide resolved
);

bool drivingThroughObstacle(AutonomyAStarState state) {
final isTurn = state.direction != DriveDirection.forward;
final isQuarterTurn = state.direction == DriveDirection.forwardLeft || state.direction == DriveDirection.forwardRight;

// Forward drive across the perpendicular axis
if (!isTurn && state.orientation.angle.abs() % 90 == 0) {
Gold872 marked this conversation as resolved.
Show resolved Hide resolved
return false;
}

// Not encountering any sort of diagonal angle
if (isTurn && isQuarterTurn && state.orientation.angle.abs() % 90 == 0) {
return false;
}

// No diagonal movement, won't drive between obstacles
if (!isQuarterTurn && orientation.angle.abs() % 90 == 0) {
return false;
}

DriveOrientation orientation1;
DriveOrientation orientation2;
Gold872 marked this conversation as resolved.
Show resolved Hide resolved

if (!isTurn) {
orientation1 = state.orientation.turnQuarterLeft();
orientation2 = state.orientation.turnQuarterRight();
} else if (isQuarterTurn) {
orientation1 = orientation;
orientation2 = (state.direction == DriveDirection.forwardLeft)
? orientation1.turnLeft()
: orientation1.turnRight();
} else {
orientation1 = (state.direction == DriveDirection.left)
? orientation.turnQuarterLeft()
: orientation.turnQuarterRight();
orientation2 = (state.direction == DriveDirection.left)
? state.orientation.turnQuarterLeft()
: state.orientation.turnQuarterRight();
}

// Since the state being passed has a position of moving after the
// turn, we have to check the position of where it started
return collection.pathfinder.isObstacle(
position.goForward(orientation1),
) ||
collection.pathfinder.isObstacle(
position.goForward(orientation2),
);
}

@override
Iterable<AutonomyAStarState> expand() => [
copyWith(direction: DriveDirection.forward, orientation: orientation, position: position.goForward(orientation)),
copyWith(direction: DriveDirection.left, orientation: orientation.turnLeft(), position: position),
copyWith(direction: DriveDirection.right, orientation: orientation.turnRight(), position: position),
].where((state) => !collection.pathfinder.isObstacle(state.position));
copyWith(
direction: DriveDirection.forward,
orientation: orientation,
position: position.goForward(orientation),
),
copyWith(
direction: DriveDirection.left,
orientation: orientation.turnLeft(),
position: position,
),
copyWith(
direction: DriveDirection.right,
orientation: orientation.turnRight(),
position: position,
),
copyWith(
direction: DriveDirection.forwardLeft,
orientation: orientation.turnQuarterLeft(),
position: position,
),
copyWith(
direction: DriveDirection.forwardRight,
orientation: orientation.turnQuarterRight(),
position: position,
),
].where((state) => !collection.pathfinder.isObstacle(state.position) && !drivingThroughObstacle(state));
}
46 changes: 44 additions & 2 deletions lib/src/interfaces/drive.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,22 @@ enum DriveDirection {
forward,
left,
right,
stop,
forwardLeft,
forwardRight,
stop;

bool get isTurn => this != forward && this != stop;
}

enum DriveOrientation {
north(0),
west(90),
south(180),
east(270);
east(270),
northEast(360 - 45),
northWest(45),
southEast(180 + 45),
southWest(180 - 45);

final int angle;
const DriveOrientation(this.angle);
Expand All @@ -30,13 +38,43 @@ enum DriveOrientation {
west => south,
south => east,
east => north,
northEast => northWest,
northWest => southWest,
southWest => southEast,
southEast => northEast,
};

DriveOrientation turnRight() => switch (this) {
north => east,
west => north,
south => west,
east => south,
northEast => southEast,
southEast => southWest,
southWest => northWest,
northWest => northEast,
};

DriveOrientation turnQuarterLeft() => switch (this) {
north => northWest,
northWest => west,
west => southWest,
southWest => south,
south => southEast,
southEast => east,
east => northEast,
northEast => north,
};

DriveOrientation turnQuarterRight() => switch (this) {
north => northEast,
northEast => east,
east => southEast,
southEast => south,
south => southWest,
southWest => west,
west => northWest,
northWest => north,
};
}

Expand All @@ -49,6 +87,8 @@ abstract class DriveInterface extends Service {
DriveDirection.forward => await goForward(),
DriveDirection.left => await turnLeft(),
DriveDirection.right => await turnRight(),
DriveDirection.forwardLeft => await turnQuarterLeft(),
DriveDirection.forwardRight => await turnQuarterRight(),
DriveDirection.stop => await stop(),
};

Expand All @@ -57,6 +97,8 @@ abstract class DriveInterface extends Service {
Future<void> goForward();
Future<void> turnLeft();
Future<void> turnRight();
Future<void> turnQuarterLeft();
Future<void> turnQuarterRight();
Future<void> stop();

Future<void> faceDirection(DriveOrientation orientation) async {
Expand Down
12 changes: 12 additions & 0 deletions lib/src/interfaces/gps_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ extension GpsUtils on GpsCoordinates {
GpsCoordinates(latitude: 1 * latitudePerMeter);
static GpsCoordinates get south =>
GpsCoordinates(latitude: -1 * latitudePerMeter);
static GpsCoordinates get northEast => GpsCoordinates(
latitude: 1 * latitudePerMeter, longitude: -1 / metersPerLongitude);
static GpsCoordinates get northWest => GpsCoordinates(
latitude: 1 * latitudePerMeter, longitude: 1 / metersPerLongitude);
static GpsCoordinates get southEast => GpsCoordinates(
latitude: -1 * latitudePerMeter, longitude: -1 / metersPerLongitude);
static GpsCoordinates get southWest => GpsCoordinates(
latitude: -1 * latitudePerMeter, longitude: 1 / metersPerLongitude);

// Taken from https://stackoverflow.com/a/39540339/9392211
// static const metersPerLatitude = 111.32 * 1000; // 111.32 km
Expand Down Expand Up @@ -58,5 +66,9 @@ extension GpsUtils on GpsCoordinates {
DriveOrientation.south => GpsUtils.south,
DriveOrientation.west => GpsUtils.west,
DriveOrientation.east => GpsUtils.east,
DriveOrientation.northEast => GpsUtils.northEast,
DriveOrientation.northWest => GpsUtils.northWest,
DriveOrientation.southEast => GpsUtils.southEast,
DriveOrientation.southWest => GpsUtils.southWest,
};
}
6 changes: 6 additions & 0 deletions lib/src/rover/drive.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,10 @@ class RoverDrive extends DriveInterface {

@override
Future<void> turnRight() => useImu ? sensorDrive.turnRight() : timedDrive.turnRight();

@override
Future<void> turnQuarterLeft() => useImu ? sensorDrive.turnQuarterLeft() : timedDrive.turnQuarterLeft();

@override
Future<void> turnQuarterRight() => useImu ? sensorDrive.turnQuarterRight() : timedDrive.turnQuarterRight();
}
12 changes: 12 additions & 0 deletions lib/src/rover/drive/sensor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,18 @@ class SensorDrive extends DriveInterface with RoverMotors {
this.orientation = this.orientation.turnRight();
}

@override
Future<void> turnQuarterLeft() {
// TODO: implement turnQuarterLeft
throw UnimplementedError();
}

@override
Future<void> turnQuarterRight() {
// TODO: implement turnQuarterRight
throw UnimplementedError();
}

@override
Future<bool> spinForAruco() async {
for (var i = 0; i < 16; i++) {
Expand Down
12 changes: 12 additions & 0 deletions lib/src/rover/drive/timed.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,16 @@ class TimedDrive extends DriveInterface with RoverMotors {
await Future<void>.delayed(turnDelay);
await stop();
}

@override
Future<void> turnQuarterLeft() {
// TODO: implement turnQuarterLeft
throw UnimplementedError();
}

@override
Future<void> turnQuarterRight() {
// TODO: implement turnQuarterRight
throw UnimplementedError();
}
}
10 changes: 6 additions & 4 deletions lib/src/rover/orchestrator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,13 @@ class RoverOrchestrator extends OrchestratorInterface with ValueReporter {
collection.logger.info("Got GPS Task: Go to ${destination.prettyPrint()}");
collection.logger.debug("Currently at ${collection.gps.coordinates.prettyPrint()}");
collection.drive.setLedStrip(ProtoColor.RED);
collection.detector.findObstacles();
// await collection.drive.faceNorth();
Levi-Lesches marked this conversation as resolved.
Show resolved Hide resolved
while (!collection.gps.coordinates.isNear(destination)) {
// Calculate a path
collection.logger.debug("Finding a path");
currentState = AutonomyState.PATHING;
await collection.drive.faceNorth();
// await collection.drive.faceNorth();
Levi-Lesches marked this conversation as resolved.
Show resolved Hide resolved
final path = collection.pathfinder.getPath(destination);
currentPath = path; // also use local variable path for promotion
if (path == null) {
Expand All @@ -61,13 +63,13 @@ class RoverOrchestrator extends OrchestratorInterface with ValueReporter {
collection.logger.debug(step.toString());
}
currentState = AutonomyState.DRIVING;
var count = 0;
// var count = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was good because it catches small drifts in the GPS and IMU, allowing us to be more lenient about errors in everything. Maybe let's keep it and try removing it later when we do more physical tests

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe instead of replanning after a set amount of steps, have it check after each step is completed if it has drifted too far and replan?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Planning is very cheap -- not noticeable during operation. If there is no drift, the new plan == the old plan. So this is pretty much just as efficient as that, without the extra logic

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't replanning stop the rover suddenly? I'd rather not replan if it will cause a visible hiccup

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not if we move faceNorth outside the while loop, Then there is no stop command and the re-planning will happen while the rover is still executing/finishing the last command. Technically there is a very slight delay but we're still unsure exactly what accuracy we will end up having. Keep in mind that we still don't have RTK so our GPS will have significant error.

for (final state in path) {
collection.logger.debug(state.toString());
await collection.drive.goDirection(state.direction);
traversed.add(state.position);
if (state.direction != DriveDirection.forward) continue;
if (count++ == 5) break;
// if (state.direction != DriveDirection.forward) continue;
// if (count++ == 5) break;
final foundObstacle = collection.detector.findObstacles();
if (foundObstacle) {
collection.logger.debug("Found an obstacle. Recalculating path...");
Expand Down
12 changes: 12 additions & 0 deletions lib/src/rover/sensorless.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,16 @@ class SensorlessDrive extends DriveInterface {
await simulatedDrive.turnRight();
await realDrive.turnRight();
}

@override
Future<void> turnQuarterLeft() {
// TODO: implement turnQuarterLeft
throw UnimplementedError();
}

@override
Future<void> turnQuarterRight() {
// TODO: implement turnQuarterRight
throw UnimplementedError();
}
}
19 changes: 19 additions & 0 deletions lib/src/simulator/drive.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class DriveSimulator extends DriveInterface {
@override
Future<void> faceNorth() async {
collection.imu.update(OrientationUtils.north);
if (shouldDelay) await Future<void>.delayed(const Duration(milliseconds: 500));
}

@override
Expand All @@ -49,6 +50,24 @@ class DriveSimulator extends DriveInterface {
if (shouldDelay) await Future<void>.delayed(const Duration(milliseconds: 500));
}

@override
Future<void> turnQuarterLeft() async {
collection.logger.debug("Turning quarter left");
final heading = collection.imu.heading;
final orientation = Orientation(z: heading + 45).clampHeading();
collection.imu.update(orientation);
if (shouldDelay) await Future<void>.delayed(const Duration(milliseconds: 500));
}

@override
Future<void> turnQuarterRight() async {
collection.logger.debug("Turning quarter right");
final heading = collection.imu.heading;
final orientation = Orientation(z: heading - 45).clampHeading();
collection.imu.update(orientation);
if (shouldDelay) await Future<void>.delayed(const Duration(milliseconds: 500));
}

@override
Future<void> stop() async => collection.logger.debug("Stopping");
}
Loading
Loading