We have written a software library of java classes and many are exported to Blocks for our rookie team members.
We have noticed that if we run a java OpMode as the first OpMode after boot up we receive Java initialization errors when the first java class that has an Export To Blocks declaration. We do not get this error from the same Java OpMode AFTER we run a Blocks OpMode.
Are we missing something that we need to declare or initialize in the Java OpMode that the Blocks OpModes are doing?
The Java Classes were originally developed under OBJ and were only called from Blocks OpModes. When we transferred all the classes to Android Studio, even the OpModes, we noticed this problem.
No code changes in the library Java classes though. Only noticed because we didn’t have a Blocks OpMode sample to test so ran our Java OpMode and kept getting this error…until we saw the pattern that if we ran a Blocks OpMode BEFORE we didn’t have a problem. Code works fine.
That log file says you’re using a hardwareMap.get() call with an incorrect (or blank) hardware description string, one that doesn’t exist in the hardware map. That should have nothing to do with Java/Blocks. Look in your PixelEjector.init() method for the offending call.
Agree it is using and looking up a servo in the hardware map.
With no code change, if we run any Blocks OpMode before we run this Java OpMode the Java OpMode does not complain. By itself the hardware map is not loading for the Java OpMode even though we are extending the Java OpMode from a LinearOpMode.
Then I think you’re doing it wrong. If you load a blocks opmode and that solves your problem, that’s because blocks is populating the hardware map for you - you probably can’t load the hardware map in Java because the hardware map is not being loaded properly using the process you’re using.
Java only loads the hardware map in the background for the main opmode class before RunOpMode() is called, and that process doesn’t “automatically” happen for any other class. You’d need to pass the hardware map to your extra class (if you’re using one) and set the classes hardwareMap to the passed in hardwareMap.
You’re getting the crash because the hardwareMap in your extra class that is trying to access “a” hardwareMap (but it’s not the same as your main program hardwareMap) is empty, it doesn’t have the configuration information loaded.
Even after declaring our library modules and explicitly constructing the java class instance, we are having to run a Blocks OpMode to not get an initialization error on the hardware map.
Shown below is an example of our declaration of two key shared library java classes:
DrivetrainMecanumWithSmarts ourDrivetrain = new DrivetrainMecanumWithSmarts();
Vision ourVision = new Vision();
Actually, as I read the log it appears to me that .get() is being called on a null pointer – i.e., as if the hardwareMap variable itself has not been initialized yet.
The log points to this line in the code:
and I think it’s likely that hardwareMap itself isn’t initialized yet. I presume the hardwareMap variable here is coming from the base class (BlocksOpModeCompanion), and it probably doesn’t initialize its fields until a Blocks opmode is requested.
Those are my guesses, at least. The suggested solution – explicitly get the hardwareMap from somewhere not directly related to Blocks – is almost certainly the correct one.
Your entire library is built on top of the BlocksOpModeCompanion class, which in and of itself isn’t necessarily a problem. The BlocksOpModeCompanion class has a hardwareMap member, meant to be used by a Blocks OpMode to hold a copy of the main HardwareMap. When you use the hardwareMap from within your library, you’re NOT directly accessing the hardwareMap defined/scoped within your main OpMode, you’re instead using the hardwareMap scoped within the BlocksOpModeCompanion class. The problem you’re running into is that the hardwareMap member isn’t actually being loaded with a hardwareMap until a Blocks OpMode copies the hardwareMap into it from within the runOpMode function.
What you essentially want to do is create a public setHardwareMap() method within your library where you pass in your main opMode’s hardwareMap, or you want a constructor to your library class that takes in a hardwareMap so you can initialize the BlocksOpModeCompanion class’ hardwareMap before you try to use it.
The same will go for telemetry and gamepads if you choose to access them in your library.
You can do that, but why? This is going to be an issue for any library trying to use an OpMode’s HardwareMap - libraries don’t belong to the instance of the program class, you’re going to have the exact same problem if you extend a LinearOpMode or any class that also contains a HardwareMap member. You either have to remove all references to HardwareMaps, Gamepads, and Telemetry in your library (and take them all in as parameters) or you can bite the bullet and initialize the objects on construction. At least this way you support both languages.
Sure. Currently you do not define a constructor in any of your library classes - as such, you’re just depending on the default constructor. You should set up a constructor that takes in a HardwareMap. For example, your DrivetrainMecanumWithSmarts class should have this:
import com.qualcomm.robotcore.hardware.HardwareMap;
...
public class DrivetrainMecanumWithSmarts extends BlocksOpModeCompanion {
...
public DrivetrainMecanumWithSmarts( HardwareMap hardwareMapParam ) {
BlocksOpModeCompanion.hardwareMap = hardwareMapParam;
}
...
}
and then in order to create your class, you use:
DrivetrainMecanumWithSmarts ourDrivetrain = new DrivetrainMecanumWithSmarts( hardwareMap );
And then you can reference the hardwareMap within the DrivetrainMecanumWithSmarts class to your heart’s content. Technically since you’re using a static member of the class it only has to be done one time ever, but since you never know which class with the same base will be constructed first it’s just easier to initialize each time.
Thanks, @ddiaz, for the clarification! In the process of converting some core classes to Java that our veteran team intended to focus on using for Autonomous, we started down this path you mention above of passing the Hardware handle.