Skip to content

Commit

Permalink
Added simulator and real rover logic (#1)
Browse files Browse the repository at this point in the history
This PR splits the code into 3 main categories: 
- interfaces: an interface for each component that defines what it can do
- simulated: a simulated version for each component
- rover: the real implementation of each component

Using this new structure, this PR also adds unit tests that simulate all
but one component at a time. These tests are run for every new PR and
commit.
  • Loading branch information
Levi-Lesches authored May 16, 2024
1 parent a6fec0f commit 9b4bd73
Show file tree
Hide file tree
Showing 56 changed files with 1,876 additions and 226 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/analyze.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ jobs:
# You can specify other versions if desired, see documentation here:
# https://github.com/dart-lang/setup-dart/blob/main/README.md
- uses: dart-lang/setup-dart@v1
with:
sdk: 3.2.2

- name: Install dependencies
run: dart pub get
Expand All @@ -26,5 +28,5 @@ jobs:
# Your project will need to have tests in test/ and a dependency on
# package:test for this step to succeed. Note that Flutter projects will
# want to change this to 'flutter test'.
# - name: Run tests
# run: dart test
- name: Run tests
run: dart test
8 changes: 8 additions & 0 deletions .github/workflows/documentation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ jobs:
- name: Install Dart
uses: dart-lang/setup-dart@v1
with:
sdk: 3.2.2

- name: Install dependencies
run: dart pub get
Expand All @@ -37,6 +39,12 @@ jobs:
- name: Generate documentation
run: dart doc --output=docs

# Your project will need to have tests in test/ and a dependency on
# package:test for this step to succeed. Note that Flutter projects will
# want to change this to 'flutter test'.
- name: Run tests
run: dart test

- name: Commit and push files
run: |
cd docs
Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# These tell your computer or the rover to fetch from the parent folder and not GitHub
pubspec_overrides.yaml

# https://dart.dev/guides/libraries/private-files
# Created by `dart pub`
.dart_tool/

*.dll
*.dll
7 changes: 4 additions & 3 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ analyzer:
strict-raw-types: true

exclude:
- test/**.dart

linter:
rules:
Expand All @@ -44,6 +43,8 @@ linter:
always_use_package_imports: false # not when importing sibling files
sort_constructors_first: false # final properties, then constructor
avoid_dynamic_calls: false # this lint takes over errors in the IDE
cascade_invocations: false # cascades are often less readable

# Temporarily disabled until we are ready to document
# public_member_api_docs: false
# Temporarily disabled lints
public_member_api_docs: false # until we are ready to document
flutter_style_todos: false # until we are ready to address them
10 changes: 4 additions & 6 deletions bin/autonomy.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import "package:autonomy/autonomy.dart";
import "package:burt_network/logging.dart";
import "package:autonomy/rover.dart";

void main(List<String> arguments) async {
final tankMode = arguments.contains("--tank");
if (tankMode) logger.info("Running in tank mode");
await collection.init(tankMode: arguments.contains("--tank"));
void main() async {
final rover = RoverAutonomy();
await rover.init();
}
23 changes: 23 additions & 0 deletions bin/latlong.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// ignore_for_file: avoid_print

import "package:autonomy/interfaces.dart";

const binghamtonLatitude = 42.0877327;
const utahLatitude = 38.406683;

void printInfo(String name, double latitude) {
GpsInterface.currentLatitude = latitude;
print("At $name:");
print(" There are ${GpsUtils.metersPerLongitude.toStringAsFixed(2)} meters per 1 degree of longitude");
print(" Our max error in longitude would be ${GpsUtils.epsilonLongitude} degrees");
final isWithinRange = GpsInterface.gpsError <= GpsUtils.epsilonLongitude;
print(" Our GPS has ${GpsInterface.gpsError} degrees of error, so this would ${isWithinRange ? 'work' : 'not work'}");
}

void main() {
print("There are always ${GpsUtils.metersPerLatitude} meters in 1 degree of latitude");
print(" So our max error in latitude is always ${GpsUtils.epsilonLatitude} degrees");
printInfo("the equator", 0);
printInfo("Binghamton", binghamtonLatitude);
printInfo("Utah", utahLatitude);
}
35 changes: 35 additions & 0 deletions bin/random.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// ignore_for_file: avoid_print

import "package:autonomy/interfaces.dart";
import "package:autonomy/src/rover/corrector.dart";

const maxError = GpsInterface.gpsError;
const maxSamples = 10;
final epsilon = GpsUtils.epsilonLatitude; // we need to be accurate within 1 meter
const n = 1000;
bool verbose = false;

bool test() {
final error = RandomError(maxError);
final corrector = ErrorCorrector(maxSamples: maxSamples);
for (var i = 0; i < 10; i++) {
final value = error.value;
corrector.addValue(value);
if (verbose) {
final calibrated = corrector.calibratedValue;
print("Current value: $value, Corrected value: $calibrated");
print(" Difference: ${calibrated.toStringAsFixed(7)} < ${epsilon.toStringAsFixed(7)}");
}
}
return corrector.calibratedValue.abs() <= epsilon;
}

void main(List<String> args) {
if (args.contains("-v") || args.contains("--verbose")) verbose = true;
var count = 0;
for (var i = 0; i < n; i++) {
if (test()) count++;
}
final percentage = (count / n * 100).toStringAsFixed(2);
print("Average performance: %$percentage");
}
63 changes: 63 additions & 0 deletions bin/sensorless.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import "package:burt_network/logging.dart";
import "package:burt_network/generated.dart";

import "package:autonomy/simulator.dart";
import "package:autonomy/rover.dart";

void main() async {
Logger.level = LogLevel.debug;
final simulator = AutonomySimulator();
simulator.drive = SensorlessDrive(collection: simulator, useGps: false, useImu: false);
await simulator.init();
await simulator.server.waitForConnection();

final message = AutonomyData(
destination: GpsCoordinates(latitude: 0, longitude: 0),
state: AutonomyState.DRIVING,
task: AutonomyTask.GPS_ONLY,
obstacles: [],
path: [
for (double x = 0; x < 3; x++)
for (double y = 0; y < 3; y++)
GpsCoordinates(latitude: y, longitude: x),
],
);
simulator.server.sendMessage(message);

// "Snakes" around a 3x3 meter box. (0, 0), North
await simulator.drive.goForward(); // (1, 0), North
await simulator.drive.goForward(); // (2, 0), North
await simulator.drive.turnRight(); // (2, 0), East
await simulator.drive.goForward(); // (2, 1), East
await simulator.drive.turnRight(); // (2, 1), South
await simulator.drive.goForward(); // (1, 1), South
await simulator.drive.goForward(); // (0, 1), South
await simulator.drive.turnLeft(); // (0, 1), East
await simulator.drive.goForward(); // (0, 2), East
await simulator.drive.turnLeft(); // (0, 2), North
await simulator.drive.goForward(); // (1, 2), North
await simulator.drive.goForward(); // (2, 2), North
await simulator.drive.turnLeft(); // (2, 2), West
await simulator.drive.goForward(); // (2, 1), West
await simulator.drive.goForward(); // (2, 0), West
await simulator.drive.turnLeft(); // (2, 0), South
await simulator.drive.goForward(); // (1, 0), South
await simulator.drive.goForward(); // (0, 0), South
await simulator.drive.turnLeft(); // (0, 0), East
await simulator.drive.turnLeft(); // (0, 0), North

final message2 = AutonomyData(
state: AutonomyState.AT_DESTINATION,
task: AutonomyTask.AUTONOMY_TASK_UNDEFINED,
obstacles: [],
path: [
for (double x = 0; x < 3; x++)
for (double y = 0; y < 3; y++)
GpsCoordinates(latitude: y, longitude: x),
],
);
simulator.server.sendMessage(message2);
simulator.server.sendMessage(message2);

await simulator.dispose();
}
24 changes: 24 additions & 0 deletions bin/task.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import "package:autonomy/interfaces.dart";
import "package:autonomy/simulator.dart";
import "package:autonomy/rover.dart";
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),
];
// Enter in the Dashboard: Destination = (lat=7, long=0);

void main() async {
Logger.level = LogLevel.all;
final simulator = AutonomySimulator();
final detector = DetectorSimulator(collection: simulator, obstacles: obstacles);
simulator.detector = detector;
simulator.pathfinder = RoverPathfinder(collection: simulator);
simulator.orchestrator = RoverOrchestrator(collection: simulator);
simulator.drive = DriveSimulator(collection: simulator, shouldDelay: true);
await simulator.init();
await simulator.server.waitForConnection();
}
4 changes: 0 additions & 4 deletions lib/autonomy.dart

This file was deleted.

13 changes: 13 additions & 0 deletions lib/interfaces.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export "src/interfaces/a_star.dart";
export "src/interfaces/autonomy.dart";
export "src/interfaces/detector.dart";
export "src/interfaces/drive.dart";
export "src/interfaces/error.dart";
export "src/interfaces/gps.dart";
export "src/interfaces/imu.dart";
export "src/interfaces/pathfinding.dart";
export "src/interfaces/server.dart";
export "src/interfaces/realsense.dart";
export "src/interfaces/reporter.dart";
export "src/interfaces/service.dart";
export "src/interfaces/orchestrator.dart";
30 changes: 30 additions & 0 deletions lib/rover.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export "src/rover/drive.dart";
export "src/rover/pathfinding.dart";
export "src/rover/orchestrator.dart";
export "src/rover/sensorless.dart";

import "package:autonomy/interfaces.dart";
import "package:autonomy/simulator.dart";
import "package:burt_network/logging.dart";

import "src/rover/pathfinding.dart";
import "src/rover/server.dart";
import "src/rover/drive.dart";
import "src/rover/gps.dart";
import "src/rover/imu.dart";
import "src/rover/orchestrator.dart";

/// A collection of all the different services used by the autonomy program.
class RoverAutonomy extends AutonomyInterface {
/// A server to communicate with the dashboard and receive data from the subsystems.
@override late final AutonomyServer server = AutonomyServer(collection: this);
/// A helper class to handle driving the rover.
@override late final drive = RoverDrive(collection: this);
@override late final gps = RoverGps(collection: this);
@override late final imu = RoverImu(collection: this);
@override late final logger = BurtLogger(socket: server);
@override late final pathfinder = RoverPathfinder(collection: this);
@override late final detector = DetectorSimulator(collection: this, obstacles: []);
@override late final realsense = RealSenseSimulator(collection: this);
@override late final orchestrator = RoverOrchestrator(collection: this);
}
38 changes: 38 additions & 0 deletions lib/simulator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
export "src/simulator/detector.dart";
export "src/simulator/drive.dart";
export "src/simulator/gps.dart";
export "src/simulator/imu.dart";
export "src/simulator/realsense.dart";
export "src/simulator/server.dart";

import "package:autonomy/interfaces.dart";
import "package:autonomy/src/simulator/orchestrator.dart";
import "package:burt_network/logging.dart";

import "src/simulator/detector.dart";
import "src/simulator/drive.dart";
import "src/simulator/gps.dart";
import "src/simulator/imu.dart";
import "src/simulator/pathfinding.dart";
import "src/simulator/realsense.dart";
import "src/simulator/server.dart";

class AutonomySimulator extends AutonomyInterface {
@override late final logger = BurtLogger(socket: server);
@override late ServerInterface server = SimulatorServer(collection: this);
@override late GpsInterface gps = GpsSimulator(collection: this);
@override late ImuInterface imu = ImuSimulator(collection: this);
@override late DriveInterface drive = DriveSimulator(collection: this);
@override late PathfindingInterface pathfinder = PathfindingSimulator(collection: this);
@override late DetectorInterface detector = DetectorSimulator(collection: this, obstacles: []);
@override late RealSenseInterface realsense = RealSenseSimulator(collection: this);
@override late OrchestratorInterface orchestrator = OrchestratorSimulator(collection: this);

bool isInitialized = false;

@override
Future<bool> init() { isInitialized = true; return super.init(); }

@override
Future<void> dispose() { isInitialized = false; return super.dispose(); }
}
32 changes: 0 additions & 32 deletions lib/src/collection.dart

This file was deleted.

37 changes: 0 additions & 37 deletions lib/src/drive.dart

This file was deleted.

Loading

0 comments on commit 9b4bd73

Please sign in to comment.