datum, roboRIO, and Java

This tutorial shows how to use the datum-Distance sensor with the roboRIO and Java. It covers configuring the sensor, setting up the build environment, and how to get the data from the sensor.

sensor configuration

Out of the box compact reports are disabled by default. This is a great format for humans to read but not the best for robots to parse. An example of what this looks like is shown below. Take note of how the data packet is structured. JSON uses a"key":valueformat. That becomes important later.

{
  "timestamp": 88.306,
  "distance": {
    "time": [88.3],
    "distance": [1794],
    "status": ["RANGE_VALID"],
    "signalRateReturn": [3.992188],
    "ambientRateReturn": [2.671875]
  }
}

Before plugging the datum sensor into the roboRIO check out the getting started guide. This is a great introduction on how you can interact with the sensor directly. We’re going to start by changing the default report format.

Plug the sensor into your computer, launch your favorite terminal program, and cut and paste the command below into the serial terminal and hit enter. This command tells the sensor to return all the data on one line.

set /config?compactReport=true

After entering the command the output looks like this. Not quite as human readable but much easier to handle programmatically.

{"timestamp":712.206,"distance":{"time":[712.2],"distance":[1783],"status":["RANGE_VALID"],"signalRateReturn":[3.445313],"ambientRateReturn":[2.71875]}}

This is also a great time to adjust the sample, data, and report rates. Filters can be changed and units can be adjusted too. All the settings are saved on the sensor itself so you won’t have to reconfigure it when it’s plugged into the roboRIO. There’s more information about all of these parameters here and on the product pages.

When you’re done configuring the sensor unplug it from the computer and plug it into the roboRIO.

project template

This example is built on theTimedRobotexample included in the WPILib New Project Creator. Any of the styles, Timed, Iterative, or Command, will work just fine but for this tutorial creating a newTimedRobotproject will make it easier to follow along. After you create the project open upbuild.gradleand Robot.java. All the changes we need to make will be inside these files.

alt text

build environment

Before jumping into the code we need to make one change to the build environment. We’re going to add the org.json JSON-java dependency to help with parsing the incoming data. It’s not strictly necessary but it makes working with the JSON data packets much easier. Any parser such as GSON or Jackson could also be used and they will be featured in future tutorials.

update build.gradle

To add the JSON-java dependency add the following line to the dependencies section ofbuild.gradle.

compile ‘org.json:json:20180813’

Here’s what the dependencies section looks like now. Note that there may be other entries here depending on your specific build environment.

// Defining my dependencies. In this case, WPILib (+ friends), and vendor libraries.
// Also defines JUnit 4.
dependencies {
    compile wpi.deps.wpilib()
    compile wpi.deps.vendor.java()
    compile 'org.json:json:20180813'
    nativeZip wpi.deps.vendor.jni(wpi.platforms.roborio)
    nativeDesktopZip wpi.deps.vendor.jni(wpi.platforms.desktop)
    testCompile 'junit:junit:4.12'
}

That’s it for modifications to the build environment. Next up is a simple example to show how to get data from the sensor.

code

There’s just a few things we need to do to get data from the sensor. First is importing the functions we’ll be using, then creating and initializing the serial port, and finally reading in any data that’s available and parsing the results.

imports

Add the following lines at the beginning of the file. This tells the compiler which packages and functions we’re going to be using.

org.json.JSONObject;
org.json.JSONArray;
org.json.JSONException;

package frc.robot;

import edu.wpi.first.wpilibj.Joystick;
import edu.wpi.first.wpilibj.PWMVictorSPX;
import edu.wpi.first.wpilibj.TimedRobot;
import edu.wpi.first.wpilibj.Timer;
import edu.wpi.first.wpilibj.drive.DifferentialDrive;
import edu.wpi.first.wpilibj.SerialPort;

import org.json.JSONObject;
import org.json.JSONArray;
import org.json.JSONException;

instantiate the port

Next we need to create the variable to represent the serial port that data will be received on. Add the line below at the beginning of the class definition.

private SerialPort serialPort0;

Your project should look something like this.

public class Robot extends TimedRobot {
  private final DifferentialDrive m_robotDrive
      = new DifferentialDrive(new PWMVictorSPX(0), new PWMVictorSPX(1));
  private final Joystick m_stick = new Joystick(0);
  private final Timer m_timer = new Timer();
  
  private SerialPort serialPort0;

initialize the port

We need to initialize the serial port duringrobotInit(). This tells the roboRIO what the name of the serial port is. Note that it doesn’t matter which physical port the sensor is plugged into. They just get named sequentially as kUSB, kUSB1, and kUSB2 if you have more than one.

Add the line below to theroboInit() function as shown.

serialPort0 = new SerialPort(921600, SerialPort.Port.kUSB);

/**
 * This function is run when the robot is first started up and should be
 * used for any initialization code.
 */
@Override
public void robotInit() {
  serialPort0 = new SerialPort(921600, SerialPort.Port.kUSB);
}

check for new data and parse the results

Next add the block of code below toteleopPeriodic. This block of code grabs any data that’s been sent to the serial port and stores it inresponse. If there’s data there the response is parsed during the creation of theresponseObject. Once the response has been successfully parsed we can extract the data from it. The data is extracted using the keys in the JSON response. The comments in the code block explain how this is done.

  /**
   * This function is called periodically during teleoperated mode.
   */
  @Override
  public void teleopPeriodic() {
    m_robotDrive.arcadeDrive(0.0, 0.0);
    
    // Get any data that has been trasnmitted from the sensor
    String response = serialPort0.readString();

    // If data has been receive the length of the response will be greater than 1.
    if (response.length() > 1){
      try {
        // Data has been received, try parsing the response.
        JSONObject responseObject = new JSONObject(response);
        
        // Read the timestamp data from the response.
        double timestamp = responseObject.getDouble("timestamp");
        
        // The distance information is stored in a nested structure.  That has
        // to be extracted first, then the data arrays can be extracted next.
        JSONObject distanceObject = responseObject.getJSONObject("distance");
        JSONArray timeArray = distanceObject.getJSONArray("time");
        JSONArray distanceArray = distanceObject.getJSONArray("distance");

        // Grab the first values in the arrays.
        double time = timeArray.getDouble(0);
        double distance = distanceArray.getDouble(0);

        // Print out the values.
        System.out.print("timestamp: " + timestamp + "\t");
        System.out.print("time: " + time + "\t");
        System.out.println("distance: " + distance);           
      } catch (JSONException ex) {
        ex.printStackTrace();
      }
    }
  }

We’re not doing any checking to make sure the communcation was complete so it’s possible the parsing operation may fail. If that happens it just creates an exception and we move on to the next transmission from the sensor. Future tutorials will cover how to make this process more robust.

Click here for the full code listing.
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2017-2018 FIRST. All Rights Reserved.                        */
/* Open Source Software - may be modified and shared by FRC teams. The code   */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project.                                                               */
/*----------------------------------------------------------------------------*/

package frc.robot;

import edu.wpi.first.wpilibj.Joystick;
import edu.wpi.first.wpilibj.PWMVictorSPX;
import edu.wpi.first.wpilibj.TimedRobot;
import edu.wpi.first.wpilibj.Timer;
import edu.wpi.first.wpilibj.drive.DifferentialDrive;
import edu.wpi.first.wpilibj.SerialPort;

import org.json.JSONObject;
import org.json.JSONArray;
import org.json.JSONException;

/**
 * The VM is configured to automatically run this class, and to call the
 * functions corresponding to each mode, as described in the TimedRobot
 * documentation. If you change the name of this class or the package after
 * creating this project, you must also update the manifest file in the resource
 * directory.
 */
public class Robot extends TimedRobot {
  private final DifferentialDrive m_robotDrive
      = new DifferentialDrive(new PWMVictorSPX(0), new PWMVictorSPX(1));
  private final Joystick m_stick = new Joystick(0);
  private final Timer m_timer = new Timer();
  
  private SerialPort serialPort0;

  /**
   * This function is run when the robot is first started up and should be
   * used for any initialization code.
   */
  @Override
  public void robotInit() {
    serialPort0 = new SerialPort(921600, SerialPort.Port.kUSB);
  }

  /**
   * This function is run once each time the robot enters autonomous mode.
   */
  @Override
  public void autonomousInit() {
    m_timer.reset();
    m_timer.start();
  }

  /**
   * This function is called periodically during autonomous.
   */
  @Override
  public void autonomousPeriodic() {
    // Drive for 2 seconds
    if (m_timer.get() < 2.0) {
      m_robotDrive.arcadeDrive(0.5, 0.0); // drive forwards half speed
    } else {
      m_robotDrive.stopMotor(); // stop robot
    }
  }

  /**
   * This function is called once each time the robot enters teleoperated mode.
   */
  @Override
  public void teleopInit() {
  }

  /**
   * This function is called periodically during teleoperated mode.
   */
  @Override
  public void teleopPeriodic() {
    m_robotDrive.arcadeDrive(0.0, 0.0);
    
    // Check to see if data has been trasnmitted from the sensor
    String response = serialPort0.readString();

    if (response.length() > 1){
      try {
        // Data has been received, try parsing the response.
        JSONObject responseObject = new JSONObject(response);
        
        // Read the timestamp data from the response.
        double timestamp = responseObject.getDouble("timestamp");
        
        // The distance information is stored in a nested structure.  That has
        // to be extracted first, then the data arrays can be extracted next.
        JSONObject distanceObject = responseObject.getJSONObject("distance");
        JSONArray timeArray = distanceObject.getJSONArray("time");
        JSONArray distanceArray = distanceObject.getJSONArray("distance");

        // Grab the first values in the arrays.
        double time = timeArray.getDouble(0);
        double distance = distanceArray.getDouble(0);

        // Print out the values.
        System.out.print("timestamp: " + timestamp + "\t");
        System.out.print("time: " + time + "\t");
        System.out.println("distance: " + distance);        

      } catch (JSONException ex) {
        ex.printStackTrace();
      }
    }
  }

  /**
   * This function is called periodically during test mode.
   */
  @Override
  public void testPeriodic() {
  }
}

deploy the code

Now build and deploy the code to the roboRIO. Open the console and you should see data streaming in like the example below. If not you can always print the fullresponseto make sure you’re receiving data from the sensor.

 timestamp: 238.706	time: 238.675	distance: 1696.0 
 timestamp: 238.806	time: 238.775	distance: 1706.0 
 timestamp: 238.906	time: 238.875	distance: 1708.0 
 timestamp: 239.006	time: 238.975	distance: 1699.0 
 timestamp: 239.106	time: 239.075	distance: 1699.0 
 timestamp: 239.206	time: 239.175	distance: 1694.0 
 timestamp: 239.306	time: 239.275	distance: 1701.0 

Updated: