Git Product home page Git Product logo

ezauton's Introduction

travis-ci codecov jitpack discord

ezAuton

Target Audience

Here are some use cases where ezAuton would and wouldn't be a good idea to use

  • βœ… Creating a codebase that will run on FTC, FRC, and simulations without modifications
  • βœ… Type safetyβ€”designed around units
  • ❌ Using a Java only codebase. ezAuton uses Kotlin heavily due to the multiple advantages it gives, particularly with suspending functions (approximately equivalent to async/await), and DSLs
  • βœ… Likes the idea of structured concurrency and coroutines
  • βœ… Wanting to work on robot code without a physical robot
  • βœ… Recording/streaming robot telemetry data
  • βœ… Playing back or live viewing robot telemetry data
  • βœ… Using trajectory control algorithms with minimum boilerplate
  • βœ… Programming an FTC Robot
  • βœ… Programming an FRC Robot

Showcase

The following example is all that is needed for a simulation running pure pursuit that is recorded. Note this is very similar for any trajectory algorithm, I am aware pure pursuit is inferior to other methods like ramsete. Example

suspend fun run() {

  // (1) a straight line trajectory starting at (0,0) going to (0,20) with a max speed of 3 m/s.
  val trajectory = trajectory(samplePeriod = 5.ms) {
    point(0.m, 0.m, speed = 0.mps, acceleration = 13.0.mps / s, deceleration = 12.0.mps / s)
    point(0.m, 10.m, speed = 3.mps, acceleration = 13.0.mps / s, deceleration = 12.0.mps / s)
    point(0.m, 20.m, speed = 0.mps, acceleration = 13.0.mps / s, deceleration = 12.0.mps / s)
  }

  // (2) a simulated robot
  val robot = SimulatedTankRobot.create(lateralWheelDistance = 1.m, maxAccel = 14.0.mpss, minVel = 0.3.mps, maxVel = 16.0.mps)

  // (3) a lookahead that scales with the velocity of the robot
  val lookahead = ScalingLookahead(distanceRange = 1.0.m..5.0.m, speedRange = 2.0.mps..10.0.mps, velocityEstimator = robot)

  // (4) pure pursuit
  val purePursuit = robot.purePursuit(period = 10.ms, trajectory, lookahead)

  // (4) the action we will actually be running
  val action = action {

    // (5) record everything inside this
    val recording = recording {

      // (6) include data about the path
      include(trajectory.path.simpleRepr)

      // (7) run pure pursuit in parallel
      parallel(purePursuit)

      // (8) every 10ms sample data from the robot (like location) and include in recording
      sample(10.ms, robot)
    }

    // (9) save the recording to ~/.ezauton/test.json
    recording.save("test.json")
  }

  action.run()
}
  1. Create a trajectory.

    • Note the use of extension properties like .m, .mps, .mps/s = .mpss. The library uses units, and they can usually be multiplied, added, or divided together as one would do in maths or physics. The values stored in each container is the SI value. If someone wanted to use feet instead of meters, they could use .ft and likewise for other units.
    • Trajectories contain a path which is comprised of path segments (in this case linear path segments)
    • Trajectories also contain a speed at any distance along the path
    • We specify starting, stopping points, and acceleration/decelerations.
    • The trajectory will attempt to optimize time for given constraints (deceleration at last moment)
    • The trajectory generates sub-trajectories every 5ms which are linearly interpolated between
  2. Create a simulated robot.

    • A simulated robot implements a lot of interfaces. This is useful because this library is built around interface and not implementation. Trajectory algorithms and other functions almost always accept an interface, so the library is flexible to any implementation. Interfaces

    • Notice that some of the interfaces are oddly named. Generally, abbreviations are used at a minimum, but to avoid extraordinarily long names, a few are used. For example

      • Rot = Rotation
      • Vel = Velocity
      • Trans = Translational
      • Loc = Location
      • Est = Estimator
  3. Create a lookahead that scales with the speed of the robot

    • robot implements TankRobotVelEst, an interface which in turn implements VelocityEst, so it can be passed into velocity estimator
  4. Create a pure pursuit action.

    • This uses a useful extension function to allow for less boilerplate.

    • The extension function depends on any classes which implement both TransLocEst and TransLocDrivable. This means that the robot can estimate its translational location and drive towards any given translational location. The tank robot implementation of TransLocDrivable implements this by driving in arcs.

    • If there is no common interface, it is easy to use the regular non-extension version Common

  5. Record anything inside the scope into a Recording object which can be saved

  6. Include the data of the path. Currently, only a simple representation of the path which is just a list of points can be serialized. This is because paths can contain any type of path segments---including weird shapes such as curves, which might be hard to serialize and even harder to display in a visualizer

  7. Run pure pursuit in parallel (so we can sample and run pure pursuit at the same time).

    • Pure pursuit sees that it is in a recording scope and records data each time it loops
  8. Sample data (such as location) every 10 milliseconds.

    • robot implements Sampler<Data.TankRobotState>, so it can be sampled
  9. Save the data to a json file located in test.json

Modifications β€” Network Tables

  • If we wanted to send over network tables, we would not want to serialize all the data at once. We would instead want to have a packet-approach. In reality, both recording and packet approaches are very similar, as the way this library is designed the recording is just a list of packets. Network Tables

ezauton's People

Contributors

andrewda avatar andrewgazelka avatar ritikmishra avatar themdev avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

ezauton's Issues

Change Type Parameter for BinarySearch

Change it from BinarySearch<T> to BinarySearch<T implements Comparable>. This way, instead of making BinarySearch#search take an ugly lambda as a parameter, we can have a BinarySearch#searchFor method that uses Comparable#compareTo under the hood to determine if the current object is higher or lower than the target object.

Decide on versioning scheme/system

#15 (comment)
If we are using JitPack (mentioned by @andrewda), we probably want to start tagging commits with the current version. I think we can probably agree Semantic Versioning is the best route, but how should we execute this? Should we have some automatic tag system with Gradle or do it manually? I think manual is probably the best option to avoid mistakes, but I am not completely sure. What are your opinions?

Multiple sub-projects

This should allow for greater separation of the core API from WPILib-specific helper methods and logging utilities.

Ideally, we would have subprojects for

  • The core API
  • WPILib-specific utilities
  • FTC-specific utilities
  • The GUI for viewing PP logs
  • A Path-creation GUI

Should Recording#update() update subrecordings?

I vote no since this unnecessarily intertwines updating when we might not want to update all sub recordings at the same time... the main recording shouldn't know when sub recordings should update. However, is this makes everything a lot cleaner (i.e., in the visualizer) it might make more sense to do this...maybe.

Ramp up motor should be more consistent

Concerns the following class: RampUpSimulatedMotor

Problem
The way ramp-up motors currently work is each time a velocity is set for a motor, it must be within dv of the previous set motor or else the motor is actually set to lastVel + dv or lastVel - dv depending on if the desired velocity is less or greater than the current velocity.

This is mainly for voltage localization where the set velocities need to be continuous-like to be able to accurately localize the robot.

However, this current method creates inconsistent behavior in scenarios with special commands with a period of less than 20ms.

Take an extreme scenario. A command is called once every ms. The motor can ramp up much faster per unit time than in a 20ms loop. Of course if you take the other extreme if you have a 1s loop (for some weird reason) the motor would ramp up extremely slow.

This is partially needed... the motor set velocities wouldn't look that continuous if we asked how much faster can the motor get in one second... the motor never is set at intermediate velocities during that 1s time.

Possible Solution
provide a max dv and a max dv/dt parameter for the motor so we can't get insane acceleration when commands are really fast.

Method overriding issue

PeriodicAction overrides IAction#run()with its loop logic. However, this is kinda bad because some dumbo might not know that he can't override Periodic#run(). Is there a good solution to this?

Fix abstraction for motor classes

Concerns the following classes:

  • InstantSimulatedMotor
  • RampUpSimulatedMotor
  • StaticFrictionSimulatedMotor
  • BoundedSFSimMotor

The line of motor classes extending each other is a little janky right now and does not appropriately encapsulate isolated behaviors for each motor. This could be done a lot better. Possibly by having some motor velocity processor interface.

Simulating two commands same instance sequentially gets into infinite loop

AtomicInteger count = new AtomicInteger(0);

        TimedAction timedAction1 = new TimedAction(5);
        timedAction1.onFinish(() -> count.compareAndSet(3, 4));

        TimedAction timedAction2 = new TimedAction(3);
        timedAction2.onFinish(count::incrementAndGet);

        ActionGroup actionGroup = new ActionGroup()
                .addParallel(timedAction1)
                .addSequential(timedAction2)
                .addParallel(timedAction2);

addParallel cannot come last

Suppose I have the following unit test

   @Test
    public void testActionGroup()
    {
        AtomicInteger atomicInteger = new AtomicInteger(0);

        Simulation simulation = new Simulation(1);
        ActionGroup actionGroup = new ActionGroup();

        DelayedAction delayedAction = new DelayedAction(TimeUnit.SECONDS, 1, ()->atomicInteger.compareAndSet(2, 3));
        delayedAction.onFinish(() -> System.out.println("1 done"));

        DelayedAction delayedAction2 = new DelayedAction(TimeUnit.MILLISECONDS, 10, ()->atomicInteger.compareAndSet(0,1));
        delayedAction2.onFinish(() -> System.out.println("2 done"));

        DelayedAction delayedAction3 = new DelayedAction(TimeUnit.MILLISECONDS, 150, ()->atomicInteger.compareAndSet(1,2));
        delayedAction3.onFinish(() -> System.out.println("3 done"));

        actionGroup.addParallel(delayedAction3); // second
        actionGroup.with(delayedAction2); // first
        actionGroup.addSequential(delayedAction); // last

        simulation.add(actionGroup);
        simulation.run(TimeUnit.SECONDS, 100);
        Assert.assertEquals(3, atomicInteger.get());
    }

In it's current state, it will pass, However, if I move the addParallel call below the addSequential call like so

        actionGroup.with(delayedAction2); // first
        actionGroup.addSequential(delayedAction); // last
        actionGroup.addParallel(delayedAction3); // second

then the test will fail, because the last action doesn't run

Is this a problem? I understand that there should exist at least some rules for the ordering (e.g with should go before the addSequential that it runs with, but delayedAction3 should still run.

Comb through TODO's

at the time of writing, there were 30 TODO's and I expect the number to increase as I continue writing documentation and noticing things that are off

Fix .idea/

We should figure out a good way to keep the default code format in .idea/ but also make it so people's IDEs don't keep having "commit wars" on what .idea/ should be.

Look at "plugins" ability

It might make sense for people to add plugins to the Visualizer module rather than having to modify source. Why?

  • The visualizer will be the only module run directly from a client (i.e., it isn't some part of a larger jar like ezAuton Recorder/Core/WPILib...)

  • it would be pretty πŸ”₯for people to be able to add plugins to easily get more visualizers. This allows for easily adding/removing like plugins on mc servers.

Create Kotlin API

Although we should probably keep the main Core and API code in Java, it might be interesting to add another Kotlin module which adds extension functions and utility functions (i.e., operator overloading) to be able to use ezAuton easier w/ the best language in the world.

PathGenerator consider acceleration between multiple points

Right now if the magnitude of acceleration/deceleration from point n to point n+1 is too low to complete the segment, the code will just throw an exception. We should add implementation for smoothing acceleration/deceleration between multiple waypoints, so we can easily have the fastest motion possible between more than two waypoints.

Potential Issue: Scheduling a task, then changing the timestamp of the TimeWarpedClock

The timestamp that is read on a TimeWarpedClock can be changed by calling TimeWarpedClock#setStartTime. This may create issues where

  1. A task is scheduled for timestamp X
  2. Before timestamp X, TimeWarpedClock#setStartTime is called
  3. Instead of running at timestamp X as expected, the task is run at some other timestamp Y.

A solution would be to only set the starting timestamp when calling the constructor, or only allowing setStartTime to be called once.

Default implementation of IClock#wait raises IllegalMonitorStateException

To reproduce

import com.team2502.ezauton.utils.RealClock;

import java.util.concurrent.TimeUnit;

class Scratch
{
    public static void main(String[] args)
    {
        long init = System.currentTimeMillis();

        RealClock.CLOCK.wait(TimeUnit.MILLISECONDS, 5000);

        System.out.println("(System.currentTimeMillis() - init) = " + (System.currentTimeMillis() - init));
    }
}

Expected behavior: Main waits for 5 seconds, and then a number around 5000 is printed

Actual behavior: The number of milliseconds it took for RealClock.CLOCK.wait() to happen is printed, then some time happens, then an IMSE is thrown.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.