Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions app/autonomy/LocalizationMeasurements.js
Original file line number Diff line number Diff line change
Expand Up @@ -296,8 +296,17 @@ export function buildGnssMeasurement(snapshot, sensorConfig, rng, state = {}) {
}

const outageProbability = Number(faults.outageProbability ?? 0);
const inOutage = state.inOutage || (outageProbability > 0 && rng.next() < outageProbability);
if (inOutage) {
// A missing or zero duration means the outage lasts to the end of the run.
const outageDurationNs = Math.max(0, Number(faults.outageDurationSec || 0)) * 1e9;
const outageEnded = state.inOutage
&& state.outageEndNs != null
&& snapshot.captureTimeNs >= state.outageEndNs;
const continuing = state.inOutage && !outageEnded;
const starting = !continuing && outageProbability > 0 && rng.next() < outageProbability;
if (continuing || starting) {
const outageEndNs = starting
? (outageDurationNs > 0 ? snapshot.captureTimeNs + outageDurationNs : null)
: state.outageEndNs ?? null;
return {
measurement: {
status: -1,
Expand All @@ -309,7 +318,7 @@ export function buildGnssMeasurement(snapshot, sensorConfig, rng, state = {}) {
positionCovarianceType: 0,
noFix: true,
},
nextState: { ...state, multipath: nextMultipath, inOutage: true, lastCaptureTimeNs: snapshot.captureTimeNs },
nextState: { ...state, multipath: nextMultipath, inOutage: true, outageEndNs, lastCaptureTimeNs: snapshot.captureTimeNs },
};
}

Expand Down Expand Up @@ -338,7 +347,7 @@ export function buildGnssMeasurement(snapshot, sensorConfig, rng, state = {}) {
positionCovarianceType: 2,
noFix: false,
},
nextState: { ...state, multipath: nextMultipath, inOutage: false, lastCaptureTimeNs: snapshot.captureTimeNs, dropped: false },
nextState: { ...state, multipath: nextMultipath, inOutage: false, outageEndNs: null, lastCaptureTimeNs: snapshot.captureTimeNs, dropped: false },
};
}

Expand Down
1 change: 1 addition & 0 deletions app/simulation/sensors/SensorTypeRegistry.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ const gnssRunFields = [
{ label: "Position noise N (m)", path: ["calibration", "positionNoiseEnu", "y"], control: "number", min: 0, step: 0.001, advanced: true },
{ label: "Dropout probability", path: ["calibration", "faults", "dropoutProbability"], control: "number", min: 0, max: 1, step: 0.001, advanced: true },
{ label: "Outage probability", path: ["calibration", "faults", "outageProbability"], control: "number", min: 0, max: 1, step: 0.001, advanced: true },
{ label: "Outage duration (s, 0 = rest of run)", path: ["calibration", "faults", "outageDurationSec"], control: "number", min: 0, step: 0.1, advanced: true },
];

const wheelOdometryRunFields = [
Expand Down
2 changes: 1 addition & 1 deletion docs/run-manifests.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ appearance/analytic readback.
| Sensor | Rate | Key calibration | Units |
| --- | --- | --- | --- |
| IMU | 100 Hz | `gravity`, per-axis `angularVelocityStdDev`, `linearAccelerationStdDev`, drift τ, saturation | rad/s, m/s² |
| GNSS | 10 Hz | `datum` (lat/lng/alt), `positionNoiseEnu`, `faults.dropoutProbability`, `faults.outageProbability`, multipath τ | deg, m |
| GNSS | 10 Hz | `datum` (lat/lng/alt), `positionNoiseEnu`, `faults.dropoutProbability`, `faults.outageProbability`, `faults.outageDurationSec` (0 or absent: outage lasts to the end of the run), multipath τ | deg, m |
| Wheel odometry | 50 Hz | `wheelRadius`, `ticksPerRevolution`, `trackWidth`, `slipFactor`, pose/twist noise | m, ticks/rev |

GNSS treats map/odom position as ENU offset from the manifest datum. Wheel odometry integrates an independent measured `odom → base_link` estimate; oracle truth on `/oracle/vehicle/odometry` remains isolated for scoring.
Expand Down
24 changes: 24 additions & 0 deletions tests/localization-sensors.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,30 @@ test("WGS84 conversion returns datum at zero ENU offset", () => {
assert.ok(Math.abs(converted.lng - datum.lng) < 1e-4);
});

test("GNSS outage ends after outageDurationSec and is permanent without it", () => {
const run = (faults) => {
const start = normalizeRunSensor({ type: "gnss", calibration: { faults: { ...faults, outageProbability: 1 } } });
const after = normalizeRunSensor({ type: "gnss", calibration: { faults: { ...faults, outageProbability: 0 } } });
const rng = new SeededRNG("gnss-outage-duration");
let state = createMeasurementSeedState(start, rng);
const noFix = [];
// 10 Hz fixes. The outage starts on the first fix only.
for (let index = 1; index <= 20; index += 1) {
const snapshot = captureVehicleSnapshot(mockVehicle(), index * 100_000_000);
const result = buildGnssMeasurement(snapshot, index === 1 ? start : after, rng, state);
state = { ...state, ...result.nextState };
noFix.push(result.measurement.noFix);
}
return noFix;
};
// Starts at 0.1 s and ends at 0.6 s: fixes at 0.1 through 0.5 s have no fix.
const timed = run({ outageDurationSec: 0.5 });
assert.deepEqual(timed.slice(0, 5), [true, true, true, true, true]);
assert.ok(timed.slice(5).every((value) => value === false));
assert.ok(run({}).every((value) => value === true));
assert.ok(run({ outageDurationSec: 0 }).every((value) => value === true));
});

test("GNSS dropout returns no sample and outage emits no-fix status", () => {
const config = normalizeRunSensor({
type: "gnss",
Expand Down
Loading