Unit 07 · lab
Lab: Build and Audit a Robot Component Inventory
This lab combines the Java collection skills from the week into one system.
You will build a SensorInventory, add several RangeSensor objects, search the inventory by ID, deliberately introduce a duplicate-ID failure, repair it, and compare the finished Java inventory with a controlled ROS 2 graph snapshot.
Your final artifact is a Component Inventory Record. It should let another student see what the Java program knows, what the ROS graph shows, and where those two evidence sources stop.
Starting component class
Use this simplified component model:
class RangeSensor {
private final String id;
private final String location;
private double rangeMeters;
RangeSensor(
String id,
String location,
double initialRangeMeters
) {
if (id == null || id.isBlank()) {
throw new IllegalArgumentException("ID cannot be blank");
}
if (location == null || location.isBlank()) {
throw new IllegalArgumentException(
"Location cannot be blank"
);
}
if (initialRangeMeters < 0) {
throw new IllegalArgumentException(
"Range cannot be negative"
);
}
this.id = id;
this.location = location;
this.rangeMeters = initialRangeMeters;
}
String getId() {
return id;
}
String getLocation() {
return location;
}
double getRangeMeters() {
return rangeMeters;
}
void updateRange(double newRangeMeters) {
if (newRangeMeters < 0) {
throw new IllegalArgumentException(
"Range cannot be negative"
);
}
rangeMeters = newRangeMeters;
}
}
The class protects rules about one modeled sensor. Your inventory will add a new rule involving the group: sensor IDs must be unique.
Guided example: detect one duplicate before building your inventory
Suppose the list currently contains:
List<RangeSensor> sensors = new ArrayList<>();
sensors.add(
new RangeSensor("range-front", "front", 0.42)
);
sensors.add(
new RangeSensor("range-left", "left", 0.66)
);
A new object arrives:
RangeSensor candidate =
new RangeSensor("range-left", "right", 0.71);
The candidate object is individually valid. Its ID is not blank, its location is not blank, and its reading is nonnegative.
But the inventory already contains range-left.
Use this search:
boolean duplicate = false;
for (RangeSensor sensor : sensors) {
if (sensor.getId().equals(candidate.getId())) {
duplicate = true;
break;
}
}
The result is:
duplicate=true
A defensible response is to reject the candidate before adding it:
if (duplicate) {
throw new IllegalArgumentException(
"Duplicate sensor ID: " + candidate.getId()
);
}
The important reasoning is that the defect exists between objects, not inside either object by itself.
You will now build that rule into an inventory class.
Part 1: Create SensorInventory
Create a class that owns the list:
import java.util.ArrayList;
import java.util.List;
class SensorInventory {
private final List<RangeSensor> sensors =
new ArrayList<>();
void addSensor(RangeSensor newSensor) {
if (findById(newSensor.getId()) != null) {
throw new IllegalArgumentException(
"Duplicate sensor ID: " + newSensor.getId()
);
}
sensors.add(newSensor);
}
RangeSensor findById(String wantedId) {
for (RangeSensor sensor : sensors) {
if (sensor.getId().equals(wantedId)) {
return sensor;
}
}
return null;
}
int size() {
return sensors.size();
}
void printSummary() {
for (RangeSensor sensor : sensors) {
System.out.println(
sensor.getId()
+ " location=" + sensor.getLocation()
+ " range=" + sensor.getRangeMeters()
+ " m"
);
}
}
}
Before continuing, explain why sensors is private instead of public.
Your answer should mention that outside code should not bypass the duplicate-ID rule by modifying the list directly.
Part 2: Build a valid four-sensor inventory
Create:
SensorInventory inventory = new SensorInventory();
Add these four objects:
| ID | Location | Initial reading |
|---|---|---|
range-front | front | 0.42 m |
range-rear | rear | 1.10 m |
range-left | left | 0.66 m |
range-right | right | 0.71 m |
Then record the expected result of:
inventory.size()
and the expected lines from:
inventory.printSummary();
Do not skip the units in your written record.
Part 3: Search by identity
Find:
inventory.findById("range-left")
Record:
- whether an object was returned;
- its location;
- its reading.
Then search for:
inventory.findById("range-top")
This ID does not exist.
Your code must handle the null result instead of calling a method on it.
Write the output you would want a user or log to receive, such as:
sensor not found: range-top
Part 4: Introduce a failure on purpose
Attempt to add:
inventory.addSensor(
new RangeSensor("range-left", "spare", 0.80)
);
Do not "fix" the input before recording what should happen.
Your Component Inventory Record must preserve:
- the candidate ID;
- the existing conflicting ID;
- the expected exception type;
- the expected message;
- the inventory rule that was violated.
Then repair the candidate by giving it a unique ID:
range-spare
Add it successfully and record the new inventory size.
Part 5: Inspect current state separately from identity
Update only the front sensor:
RangeSensor front =
inventory.findById("range-front");
if (front != null) {
front.updateRange(0.28);
}
Then print the inventory again.
Explain why these two facts can both be true:
ID remained range-front
reading changed from 0.42 m to 0.28 m
Your explanation should distinguish stable identity from mutable state.
Part 6: Inspect the controlled ROS 2 graph
The terminal below is deterministic. It does not inspect your Java objects and it does not connect to a physical robot.
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.
echo $ROS_DISTROsource /opt/ros/jazzy/setup.bashecho $ROS_DISTROros2 --helpros2 node listros2 node info /sensor_bridgeros2 node info /motor_guardros2 topic listros2 param get /motor_guard caution_distance_m
Run:
source /opt/ros/jazzy/setup.bash
ros2 node list
Record the node names shown.
Now make two separate inventories in your artifact:
Java component inventory
Record the IDs currently stored in your SensorInventory.
ROS graph snapshot
Record the node names returned by ros2 node list.
Do not merge the names into one table that implies a one-to-one mapping.
Part 7: Diagnose a misleading conclusion
A teammate writes:
The Java inventory contains five sensors and ROS shows two nodes, so seven robot components are online.
Repair that statement.
Your corrected explanation must address all three errors:
- Java object membership is not physical-device discovery.
- A ROS node is not automatically a physical component.
- The two inventories cannot be added together as if they counted the same kind of thing.
Part 8: Produce the Component Inventory Record
Your final record should contain:
Java model
- inventory size;
- every component ID;
- location and current reading for each sensor;
- the code or method used to search by ID.
Inventory invariant
- the unique-ID rule;
- the deliberately rejected duplicate case;
- the repaired candidate.
State change
- the front sensor's original and updated reading;
- a sentence explaining why the ID stayed constant.
ROS 2 runtime evidence
- the exact
ros2 node listoutput from the controlled terminal; - a sentence explaining what that graph snapshot supports.
Boundary statement
Write the strongest conclusion your combined evidence supports without claiming physical hardware health or a Java-to-ROS connection that was not demonstrated.
Success criteria
Your lab is complete when another student can inspect the record and answer:
- How many Java sensor objects are in the final inventory?
- Which rule prevents duplicate IDs?
- How does the program find a sensor without assuming its list index?
- Which field changed when the front reading changed?
- Which field preserved the component's identity?
- Which names came from Java and which came from ROS 2?
- Why does neither list prove that physical sensors are online?
The important outcome is not that the inventories look neat. It is that every name, state value, and runtime observation is labeled by the mechanism that produced it.