Unit 10 · lab

Lab: Reconstruct a Range Failure From Tests, Logs, and Runtime Evidence

This lab treats debugging as an evidence problem.

You will begin with a Java component whose boundary tests already pass, inject one invalid range value into a controlled runtime case, preserve the exception and WPILib-style log evidence, verify the component's modeled recovery state, inspect separate ROS 2 graph/configuration evidence, and write a diagnosis that does not invent a cause.

Your final artifact is a Controlled Diagnosis Record.

Evidence lanes

You do not need physical robot hardware for this lab.

Local WPILib project lane

If you have a local WPILib Java project, use your Week 9 test suite for the JUnit evidence. You may also add the logging calls from this lab to a desktop/simulation project if your environment supports the required WPILib runtime.

Label exactly what you executed.

Reader evidence lane

If you do not have that environment, use the supplied JUnit and WPILib log records in this lab. Label them supplied evidence.

The ROS 2 terminal in the reader remains deterministic and is labeled separately.

Guided example: do not turn a rejection into a diagnosis

Evidence:

raw value: 3.70 m
allowed interval: 0.10..2.00 m
result: IllegalArgumentException
last valid state: 0.42 m

A weak conclusion is:

The sensor is broken.

A defensible conclusion is:

The Java component rejected 3.70 m because it exceeded the configured 2.00 m maximum. The object preserved its prior valid state of 0.42 m. The source of the invalid input is not established by this evidence.

The second version uses every available fact without inventing a physical cause.

That is the standard for the rest of the lab.

Part 1: Preserve the known-good test baseline

Your Week 9 suite should include the strict caution-boundary case:

0.49 m → stop=true
0.50 m → stop=false
0.51 m → stop=false

and validated sensor cases around:

0.10 m minimum
2.00 m maximum

If you have local test output, preserve it.

Otherwise use this supplied record:

ObstacleGuardTest
  stopsImmediatelyBelowBoundary        PASSED
  continuesExactlyAtBoundary           PASSED
  continuesImmediatelyAboveBoundary    PASSED

ValidatedRangeSensorTest
  acceptsExactMinimum                   PASSED
  acceptsExactMaximum                   PASSED
  rejectsReadingBelowMinimum            PASSED
  rejectsReadingAboveMaximum            PASSED
  rejectedUpdatePreservesLastValidReading PASSED

What does this baseline establish?

It supports claims about the tested Java code and those supplied cases. It does not establish that the later runtime input source is healthy.

Part 2: Define the runtime logging records

A WPILib application could start logging with:

DataLogManager.start();

For the range case, define named entries such as:

DataLog log = DataLogManager.getLog();

DoubleLogEntry rawRangeLog =
    new DoubleLogEntry(
        log,
        "/sensors/front/rawRangeMeters"
    );

DoubleLogEntry acceptedRangeLog =
    new DoubleLogEntry(
        log,
        "/sensors/front/acceptedRangeMeters"
    );

BooleanLogEntry healthyLog =
    new BooleanLogEntry(
        log,
        "/sensors/front/healthy"
    );

The exact class organization is up to your project. Your diagnostic record must preserve the meaning of each entry.

Part 3: Process a controlled sequence

Use this input series:

double[] runtimeInputs = {
    0.42,
    0.38,
    3.70,
    0.41
};

For every input:

  1. preserve the raw value;
  2. attempt the validated update;
  3. if accepted, preserve the new accepted state and mark modeled health true;
  4. if rejected, preserve the exception context and mark modeled health false;
  5. do not replace the invalid value with a made-up fallback number.

A simplified implementation might contain:

for (double value : runtimeInputs) {
  rawRangeLog.append(value);

  try {
    sensor.updateRange(value);

    acceptedRangeLog.append(
        sensor.getRangeMeters()
    );

    healthyLog.append(true);

    DataLogManager.log(
        "range accepted"
        + " id=" + sensor.getId()
        + " value=" + value + " m"
    );
  } catch (IllegalArgumentException error) {
    healthyLog.append(false);

    DataLogManager.log(
        "range rejected"
        + " id=" + sensor.getId()
        + " value=" + value + " m"
        + " reason=" + error.getMessage()
    );
  }
}

The teaching point is the record design: raw input, accepted state, health state, and failure context remain distinguishable.

Part 4: Use or reconstruct the WPILib log evidence

If you executed the logging locally, use your own results.

Otherwise use this supplied timeline:

TimeEntryValue/event
42.100/sensors/front/rawRangeMeters0.42
42.101/sensors/front/acceptedRangeMeters0.42
42.101/sensors/front/healthytrue
42.120/sensors/front/rawRangeMeters0.38
42.121/sensors/front/acceptedRangeMeters0.38
42.121/sensors/front/healthytrue
42.140/sensors/front/rawRangeMeters3.70
42.141messagesrange rejected id=range-front value=3.70 m allowed=0.10..2.00 m
42.141/sensors/front/healthyfalse
42.160/sensors/front/rawRangeMeters0.41
42.161/sensors/front/acceptedRangeMeters0.41
42.161/sensors/front/healthytrue

Label the table supplied WPILib log evidence if you did not produce it locally.

Part 5: State exactly what happened to component state

Before the invalid input, the last accepted value was:

0.38 m

The 3.70 m request was rejected.

Therefore the component should not adopt 3.70 m as accepted state.

The next accepted value is:

0.41 m

Your record must answer:

  • What was the last valid reading before the failure?
  • Which raw value was rejected?
  • Did the rejected value become accepted state?
  • What input returned the modeled health state to true?

Do not write simply "sensor recovered."

Write what the software state actually did.

Part 6: Inspect independent ROS 2 runtime evidence

The reader terminal is a controlled ROS 2 simulation. It is not connected to your Java or WPILib code.

xterm.js terminal simulation

Inspect a Java application beside a ROS 2 Jazzy graph

Source a simulated Jazzy shell, verify the active distribution, inspect nodes and their graph relationships, and keep runtime observations separate from Java and hardware claims.

This is a controlled command simulator. It does not execute Java, ROS 2, shell commands, or network requests on your device.

Commands worth trying
  • echo $ROS_DISTRO
  • source /opt/ros/jazzy/setup.bash
  • echo $ROS_DISTRO
  • ros2 --help
  • ros2 node list
  • ros2 node info /sensor_bridge
  • ros2 node info /motor_guard
  • ros2 topic list
  • ros2 param get /motor_guard caution_distance_m

Run:

source /opt/ros/jazzy/setup.bash
ros2 node list
ros2 param get /motor_guard caution_distance_m

Record the exact output under a heading:

Controlled ROS 2 runtime evidence

The node list and parameter value are useful observations.

They do not prove that the ROS graph produced the Java runtime input 3.70 m.

Part 7: Add a supplied ROS logging example without merging it into the Java incident

ROS 2 Jazzy supports log severity levels and logger identity. For comparison, use this supplied independent ROS log record:

42.130 WARN sensor_bridge "one range source timeout observed"

Label it:

supplied ROS logging example

Do not write:

The ROS timeout caused the Java range rejection.

The course has not demonstrated that data path.

You may write:

The supplied ROS warning occurs near the Java-side rejection in the comparison timeline, but no causal link has been established.

Part 8: Build the evidence timeline

Create one timeline with a Source column.

Include:

  • JUnit baseline;
  • raw WPILib inputs;
  • accepted WPILib state;
  • rejection event;
  • modeled health changes;
  • controlled ROS graph snapshot;
  • ROS parameter observation;
  • supplied ROS warning.

If two records do not share a trustworthy clock, do not pretend they do. Mark their timing relationship as unknown or approximate.

Part 9: Write competing hypotheses

Write at least three possible explanations for the 3.70 m input.

Examples include:

  • unit conversion error;
  • isolated source anomaly;
  • configured range mismatch;
  • test or injected data;
  • incorrect upstream field mapping.

For each hypothesis, name one observation that would make it stronger or weaker.

Do not select a winner without evidence.

Part 10: Choose the next diagnostic action

Pick the single next observation you would collect.

A strong next action answers a specific uncertainty, such as:

inspect the raw source before Java unit conversion

or:

verify whether a documented Java-to-ROS bridge actually feeds this component

A weak next action is:

look at more logs

because it does not say what question the additional record should answer.

Part 11: Write the diagnosis in four sections

Observed

State only facts directly supported by the test, log, and runtime records.

Hypotheses

List plausible explanations still under investigation.

Modeled recovery state

State what the Java/WPILib application did after rejection and what later event changed its modeled state.

Not yet proven

List claims that remain unsupported, including physical sensor failure/recovery and any Java-to-ROS causal connection not demonstrated by architecture evidence.

Success criteria

Your Controlled Diagnosis Record is complete when another student can answer:

  • Which software rules were already protected by tests?
  • Which runtime value violated the range rule?
  • Which record preserved the raw value?
  • Which record preserved the rejection context?
  • What happened to accepted state after the invalid request?
  • What event returned modeled health to true?
  • Which evidence came from WPILib and which came from ROS 2?
  • Which causal relationship remains unproven?
  • What next observation would reduce the uncertainty?

The goal is not to tell the most convincing failure story. It is to tell the strongest story the evidence can actually support.