Simulatable SparkMax

So… I got bored (check out the motivation section in the readme for a rough idea of how long this probably took me)… I really love the new desktop simulation GUI, but more than that, I love not getting my console flooded with HAL/CAN errors when running the sim with REV devices in code…

So without further ado, allow me to present, the SparkMaxSim library!

I’m open for questions/comments/complaints, either here or in github issues.

I’m also fully expecting a cease&desist from REV… Please be kind…

4 Likes

Sweet. I’ll be checking this out for sure. I’ve got lots of thoughts on what the “proper” way to do simulation is, and this definitely aligns with our team’s goals.

The only hangup: we’re at the part of the season where software is actually testing on the real robot, so the need for simulation has decreased quite a bit.

Agreed, it’s definitely a little late in the season for this. However, that also means many teams will have functional code they can run in the simulator with this and show that it works.

I’d say it’ll be something they can add to their toolsuite for next year, but I honestly hope REV will have added this capability by then, making this useless.

The other feature I really wanted to add with this, but couldn’t find an easy way to do it, was to make the sensor types compatible with their wpilib equivalents. Unfortunately, wpilib doesn’t provide interfaces for DigitalInput(though there is DigitalSource), Encoder, etc. They’re concrete classes that I would have to extend directly, which caused a few issues when I attempted it. I think I read somewhere that making those types into interfaces (like what happened with command based for this year) was something they planned to get done for next year, so I’m keeping my fingers crossed.

1 Like

Sweet, mostly makes sense to me (though you’re much further on the investigation than I was).

My thoughts at a high level: There’s two things needed for me to hit my “blue-dot” of simulation abilities:

  1. All physical devices (3rd party included) show up in the SIM gui.
  2. Sim GUI has a “closed loop” mode where I can custom-define plant model code to “hook up” various inputs and outputs to each other.

With both of these in place, we’d have all the framework we need to have a single codebase that runs on a real robot, and in simulation, and is fully useful for test and debug in both spots.

I’m not 100% sure of my summer plans yet, but some of them may involve poking at that sim gui…

These classes are concrete because they implement access to the RoboRIO hardware. The general trend we are going in is to not create specific WPILib-specific interfaces but instead use generic functional interfaces which better decouple and don’t require vendors depend on WPILib classes. This is a lesson learned from SpeedController (this interface is the sole reason WPI_TalonSRX exists). For example, the RamseteController uses functional interfaces, which allows teams to use any sensor or motor controller without worrying about specific interface compatibility.

The simulator GUI this year was added pretty late, which didn’t give vendors much time to add their own GUI bits. I’m hopeful vendors will have the time this summer to do this.

You can do this today (except for most 3rd party libraries). It really doesn’t have anything to do with the GUI, all of the features to do this are in the hal.sim package (and equivalent in C++). You can write the “closed loop” simulation portion in your main robot program and just conditionalize it on RobotBase.isSimulation().

Again, the challenge is third party libraries, as it’s really up to each vendor to provide appropriate simulation support in the same way WPILib does for the RoboRIO. We added the SimDevice framework for 2020 to try to make this easier for vendors (especially for simple devices), but as with the GUI, it was added a little on the late side for good vendor support this year. Vendors with more complex devices (CTRE and REV) will probably need to roll their own thing anyway, as SimDevice is a fairly basic framework.

If you’re looking for a concrete example of how to interact with things like a DigitalInput/etc, I’ve published a python version of how one can do it (which uses the C++ objects, so you just need to find the equivalent package in Java/C++ in the hal.sim or hal.simulation packages).

Ah, I guess I misread or otherwise misunderstood that intent. Thanks for the insight!

Sweeet, thanks. I’ll be looking into this more (not much time as of yet :smiley: ). The big holdup for us with 2020 is just the sheer number of Spark Max’s we ended up using. Without these in the architecture, the usefulness of that architecture goes down quite a bit.

For 2020 so far we were able to draw the boundary diagram a layer higher for drivetrain and shooter (two higher priority subsystems), and punted on the rest. It works just fine, but involves some infrastructure complexities I’d be looking to avoid in the future.

Again, I can’t say thank you enough for the work so far - I’m well aware of the challenges of wrangling multiple vendors into a common platform (much less getting it all to simulate nicely together). Even with the extra infrastructure, we did almost all our autonomous validation in simulation this year. About four hours on the robot total to get to a 5-ball auto routine. Everything else was done with sim. When I say this is a game-changer for our development cycle, I really mean it!!!

2 Likes

As far as the rest of the simulation goes, your way may be better I haven’t looked into it.
However it got me thinking, why can’t I just override the methods in CANSparkMax, and whenever it tries to access the physical device, just don’t.
eg

@Override
public double get() {
    if (simulated) {
        return 0.0;
    } else {
        return super.get();
    }
}

I didn’t go and override every single method however, only the ones I was currently using.
This removed basically all but one HAL/CAN error (probably because I forgot to override a method, or there is a heartbeat), but it was infrequent.

It was then just as simple as replacing all the CANSparkMax's with CANSparkMaxSim's, not needing to update the CANEncoder's or CANPIDController's.

I cover that to an extent in the readme file. I did attempt that first, and there are two primary reasons it failed.

  1. The CANSparkMax object needs to call the super() in its constructor, which calls the CANSparkMaxLowLevel constructor, which has JNI call(s] in it. So you’ll never be able to fully get rid of those calls in sim
  2. The secondary objects (CANEncoder, CANDigitalInput, etc) are all created in respective methods of CANSparkMax. For that not to fail blow up somewhere, you can’t just return null. Returning an object means calling the constructor, some of which had JNI calls. Also, a couple of them extended CANSensor, which had private members that appeared core to its functionality, so it’s not like you could just extend it.

My goal wasn’t just to hide the errors, but to make the sim work as much as possible. I didn’t test all of those secondary classes before switching to just rewriting it, but every step I took always left me with “just one more” HAL/CAN error

1 Like

Ah ok, that makes sense. I hadn’t looked that deeply into the code as we weren’t using a lot of that functionality.
Thanks for the clarification of the readme though.