Driver Logic V1 vs V2

How the PS HOS app turns engine data into automatic duty-status changes (Driving, On Duty, Personal Conveyance, Yard Move), and how the 2022 rewrite (V2) changed that from V1. The driver sees nearly the same thing in both versions, but the code underneath is very different.

The driver flow didn't change

Both versions use ≥5 mph to start Driving, 0 mph for 3 s to stop, a 5-min idle before “Are you still driving?”, and a 1-min countdown before an automatic On Duty.

V1 one large class

DriveLogicFeature.java (2,355 lines): a re-entrant queue, a two-field motion status, and the same stop logic copied across the active-driver, unidentified and store-and-forward paths.

V2 a pipeline of state machines

DriveProcessor runs Ignition, DriveTime, Vehicle and Recovery state machines in a fixed order on one serial queue. DriveLogicFeatureV2 then turns their events into HOS records.

What V2 added

It tags every event with why it happened (EventOrigin), recovers after a crash or data wipe, backfills hourly events, and adds geofence Yard Move, PC 75/95/100% alerts and stuck-in-drive detection.

History

What the driver experiences (same in both versions)

The timers are the same in both versions: V1 used NOT_CONFIRMED_DRIVE_TIME_THRESHOLD = 5 min + NOT_CONFIRMED_STATUS_CHANGE_TIME_THRESHOLD = 1 min, and V2 uses VEHICLE_IDLING_START_DURATION = 5 min + VEHICLE_IDLING_END_DURATION = 1 min.

moving · Driving idling · 5 min, no prompt yet 1-min countdown t₀ stop +5 min +6 min Speed ≥ 5 mph → Driving (system) 0 mph for 3 s idle timer starts “Are you still driving?” dialog + 1-min countdown No answer → On Duty (system) SYSTEM DRIVER / OUTCOME Wheels move again timers cancelled · stays Driving Driver answers 5-min timer restarts Driving ends at… US: the stop · Canada: stop + 6 min
The automatic stop flow. The app only creates On Duty if the driver stays silent for six minutes after stopping. The start time of that On Duty depends on the ruleset (isOnDutyBackdatedToStartOfIdling: true in the US, false in Canada). A Driving status the driver selects manually is never ended automatically.

Architecture: where the logic lives

The biggest difference is in the structure. V1 runs everything through a single class with a lock-protected queue. V2 splits the work into separate decisions that run in a fixed order on one coroutine Channel, so only one piece of work runs at a time.

V1 Engine dataspeed · ign · odo CVD connectionstate flow IModelDelegatesession/status callbacks DriveLogicFeature.java2,355 lines Queue<EngineData> + lock flagspauses while a status/PUN change is in progress; restarted from callbacks WimAdjustHelperwheels in motion at ≥ 5 mph · stopped after 0 mph for 3 s MotionStatus × 2 fieldslastKnown + currentTemp, mirrored to storage Active · Unidentified · S&F branchesthe same stop logic written three times PC / YM, limits, hourly events, monitorschecked inline for every engine sample threads: main Handler · AsyncLoader · coroutines · AlarmManager changeDriveStatusvia modelDelegate saveAndPostELDEventpower-up / shutdown Alerts + alarmsAlertHelper · AlarmManager
V1 All inputs go into one object with shared mutable state. The orange box marks the whole class boundary: everything inside it can read and change the same fields.
V2 Engine data flowCVD / Backbone EngineDataWimAdjusterWIM ≥ config threshold (max 5 mph) DriveProcessorone Channel · strictly in order 1 RecoveryStateMachineafter a data wipe 2 Power unit number change 3 Store-and-forward handling 4 IgnitionStateMachineOn / Off 5 DriveTimeStateMachinewho owns the drive 6 YardMoveEvaluatorspeed + geofence 7 PcEvaluatormay replay the sample 8 VehicleStateMachineStopped/Moving/Idling DriveProcessor.Event + EventOrigin DriveLogicFeatureV2 turns events into HOS records, alarms and dialogs ChangeDutyStatusUseCase (+ UDP) SendEngineHosEventpower-up / shutdown DialogManager→ DialogActivity COMPANION MONITORS HeartbeatMonitorhourly + backfill PcLimitMonitor75 / 95 / 100 % StuckInDriveLoggerchecks every 15 min
V2 The order of the steps matters: drive ownership and the PC/YM rules are settled before the vehicle state machine sees the sample. Results reach the HOS log only through use cases, never by writing it directly.

Motion state: two fields vs. a real state machine

Both versions track the same idea (driving → maybe stopped → asking → On Duty). V1 does this with an enum stored in two fields. V2 uses a three-state machine with named events, and each event carries the reason it happened.

V1 · MotionStatus DRIVING UNKNOWN MAYBE_NOT_DRIVING NOT_DRIVING_CONFIRMATION NOT_DRIVING wheels stop (system drive) 5-min alarm shows dialog driver answers → re-arm 5 min 1 min, no answer → On Duty wheels move
V1 A simplified view: the current value is spread across lastKnownMotionStatus and currentTempMotionStatus, along with flags such as isStatusChangingInProgress.
V2 · VehicleStateMachine Stopped Moving Idling WheelsInMotion / StartMoving driver · duty status · PUN change, YM/PC limit, conflicting timestamp / StopMoving WheelsNotInMotion / StartIdling · 5-min alarm Wheels InMotion /Resume DriverUnresponsive / StopMoving → On Duty also: driver, duty status, PUN, limits
V2 “Asking the driver” is not a state here. The dialog is a side effect of the 5-min alarm, and an unanswered dialog becomes the DriverUnresponsive event. Each move to Stopped records its EventOrigin, which decides whether an On Duty is written: a manual change, a YM/PC limit or a geofence exit does not write one.

State machines that exist only in V2

V1 handled unidentified driving and store-and-forward (data recorded while the tablet was disconnected) in separate copy-pasted branches. V2 represents both as explicit state.

DriveTimeStateMachine · who owns this drive? Unidentified Driver Unconfirmed logged in, live data ⇄ logout / UDT S&F, CVD not connected at ignition-on S&F while stopped live data → ConfirmedDriver backdates Driving to the start
Drive time collected over store-and-forward before anyone is confirmed at the wheel is held as Unconfirmed. If the driver is confirmed, the drive is re-emitted from its original start. If not, it becomes unidentified (UDP) Driving + On Duty.
RecoveryStateMachine · after a data wipe AwaitingData AwaitingHosEvent RecoveryDriving RecoverySpecDriving Inactive first engine data suppress initial ign/drive ELD Driving ELD PC/YM Moving → SuppressDriveStart stationary / reset VIN mismatch, other event
Added in 2026 (PSELD-2496). After the HOS cache is cleared mid-drive, it stops the app from writing a duplicate power-up and Driving record. It is only active when no ignition snapshot exists at startup. V1 had no equivalent.

V2 Who gets the drive time? Login, disconnect, slip-seat & team scenarios

DriveLogic can't see who is sitting in the seat. It decides who owns a drive from three things: whether a driver is logged in, whether the data is live or replayed later from store-and-forward (S&F), and whether the tablet was connected to the CVD when the ignition came on. DriveTimeEventEvaluator checks these on every engine sample, and DriveTimeStateMachine records the result. All scenarios below describe the current V2 behavior.

Is an unidentified drive already in progress (truck hasn't stopped)? Is a driver logged in? Live data, or was the tablet connected to the CVD when the ignition came on? Unconfirmed held — not written to anyone yet UDP keeps it until the truck stops UDP (unidentified) UDT notification + diagnostic Driver (+ co-driver) saved as the drive owner Live data returns while moving → Driver, Driving backdated to start Truck stops while still offline → UDP Driving + UDP On Duty yes no yes no yes no — S&F, offline at ignition-on
This simplifies DriveTimeEventEvaluator. A change of owner only takes effect at a drive boundary: logging in, logging out or switching drivers fires a DriverChanged event, which first closes the current drive (On Duty for whoever started it) before the next sample opens a new one. Engine power-up and shutdown events follow the same rule: if the data is S&F and the tablet wasn't connected when the ignition came on, they go to the UDP.
Driving On Duty arrived later via store-and-forward CVD connected CVD offline UDP = Unidentified Driver Profile (unassigned driving)

1 · Nobody logged in: a mechanic or yard jockey moves the truck

No driver is logged in to PS HOS. The truck is started and driven (for example around the shop or yard).

Driver no one logged in UDP truck moves stops · 6 min, no answer
  • Drive time is Unidentified, so the app writes UDP Driving and shows the UDT notification. The Unidentified Driving Records diagnostic starts.
  • The truck stops and idles. Nobody is logged in to see “Are you still driving?”, so after 5 + 1 min DriverUnresponsive fires and the app writes UDP On Duty (same US/Canada end-time rule).
  • Later, drivers see the time as UDT suggestions (AssignUdtActivity, from the review screen, a forced review, or at logout) and can accept or reject it. Mechanic movement should be rejected, and the carrier explains it in the back office.

2 · Driver still logged in while someone else moves the truck

Driver A drops the truck at the shop or hands it off without logging out. A mechanic or another driver moves it.

Driver A mechanic moves truck stops
  • The driver gets the drive time. A logged-in driver with live data → Driver. DriveLogic has no way to tell who is behind the wheel.
  • The movement is written as System-origin Driving on Driver A's log, and A cannot simply edit automatically recorded Driving away.
  • To prevent it, put the driver in Yard Move (if provisioned) or have them log out before handing over the keys. With geofence Yard Move enabled, the app prompts for Yard Move when the truck enters a configured yard.

3 · Driver logs in after the truck is already rolling

The truck starts moving before anyone logs in (for example, the driver pulls out and then signs in), so an unidentified drive is already open.

UDP Driver truck moves driver logs in stops
  • While a UDT is in progress, the evaluator refuses AssignToDriver. It never silently moves an open UDP drive onto the driver.
  • The login fires DriverChanged, which moves the vehicle state machine to Stopped. The UDP drive closes with UDP On Duty and the UDT-in-progress flag clears.
  • On the next engine sample the drive is assigned to Driver, and because the wheels are still moving, Driving starts on the driver's log. The UDP part stays a UDT suggestion the driver can claim.

4 · Slip seat: Driver A hands the truck to Driver B

Shared truck, different shifts. A finishes and logs out; B logs in later on the same tablet. Switching the active driver in a team (co-driver takes over) follows the same path.

Driver A logged out UDP Driver B A logs out truck repositioned? B logs in
  • When a drive starts, V2 saves an EldDriveOwner (driver + co-driver). The On Duty that closes the drive always goes to whoever started it, even if the logged-in user has changed since.
  • A's logout fires DriverChanged (origin DriverChangeToUnidentified). An open drive closes with On Duty on A's log, and “connected at ignition-on” is reset, so a hand-off can't inherit A's live-connection credit.
  • Any movement before B logs in (repositioning, a yard jockey) goes to the UDP.
  • B's login (or an active-driver switch) fires DriverChanged again, and B's drive starts from the next movement. An active-user switch in progress delays “Are you still driving?” (PSELD-3596).
  • A power unit number change (a different truck) also closes the drive, using the PrePunChange snapshot so the end of the drive keeps the original truck number.

5 · Team driving: two drivers logged in on one tablet

Driver A and Driver B are both logged in. One is designated the driver and the other is the co-driver. Separately, the active user is whoever's screen is currently on the tablet, which can be the co-driver.

Driver A co-driver · hourly events Driver B co-driver · hourly events stop swap driver B moves
  • Drive time follows the designated driver, not the active user. DriveLogic credits GetDriverUserIdUseCase, and the co-driver's ID (GetCoUserIdUseCase) is stamped on the Driving and On Duty records as co-driver.
  • When the drive starts, the app saves an EldDriveOwner (driver + co-driver), so the On Duty that closes the drive goes to the same pair even if someone switches mid-drive.
  • DriveLogic never changes the co-driver's own duty status. Hourly intermediate events (HeartbeatMonitor) are posted for both the driver and the co-driver while moving, including backfilled ones.
  • Swapping seats changes the designated driver and fires DriverChanged (origin DriverChange). The drive closes with On Duty for A, and B's next movement starts a new drive on B's log with A as co-driver.
  • “Are you still driving?” while the co-driver's screen is up: tapping Change Status first runs an active-user guard (PSELD-3596), so the status change is made as the driver. On Plus/Instinct, the app asks the co-driver to confirm switching to the driver (ConfirmBecomeActiveUser). On Enterprise, the PS Core SDK switches users and opens Change Status itself. While the switch is in progress, the 5-min prompt keeps rescheduling, so the 1-min countdown to automatic On Duty doesn't start.
  • Risk: if the team swaps seats without changing the designated driver in the app, the new person's driving goes on the teammate's log. DriveLogic has no way to detect who is at the wheel.

6 · Tablet loses its CVD connection mid-drive

The driver is logged in and the tablet was connected when the ignition came on. Mid-drive the link drops (bad cable, Bluetooth, tablet reboot), and the CVD buffers the data as store-and-forward.

CVD link Driver A connection lost stops reconnects · replay
  • connectedToCvdWhenIgnitionOn = true, so when the S&F data comes in it stays with Driver. Driving and On Duty are written at the original timestamps.
  • If the S&F data shows the truck idled for 6 min or more, the app writes On Duty without asking the driver (DriverUnresponsiveStoreAndForward). If part of the idle is still ahead, the 5-min alarm is shortened by the time already elapsed.
  • When syncing finishes, HeartbeatMonitor backfills the hourly intermediate events missed during the gap, and ValidateDriverStatus re-checks the driver's status.

7 · Tablet wasn't connected when the ignition came on

The driver is logged in, but the tablet only connects (or is plugged in) after the truck was started, so the start of the trip arrives as S&F. The app can't prove the logged-in driver was the one driving.

7a · Reconnects while still moving

CVD link Driver A UDP nothing written moves (offline) live data → confirmed stops

7b · Still offline when the truck stops

CVD link Driver A logged in, but not credited UDP moves (offline) stops reconnects
  • The drive goes to Unconfirmed: it is held and nothing is written yet.
  • 7a: live data arrives while moving → ConfirmedDriver. The app writes Driving for the driver backdated to the original start, then re-validates the status.
  • 7b: the truck stops while data is still S&F → the app writes UDP Driving at the start snapshot and UDP On Duty at the end. The driver can claim it later as a UDT suggestion. The engine power-up event from that ignition-on also goes to the UDP.
  • If someone logs in while the truck is idling with the ignition off during S&F, the app closes the drive as unidentified (DriverChangeWhileIdlingStoreAndForward).

Personal Conveyance & Yard Move

The behavior drivers see stayed close to V1. In V2 the rules moved into their own evaluators and gained new triggers.

BehaviorV1V2
Ignition off ends PC/YMWhen ignitionOffEndsSpecialDriving (US) is set: PC → Off Duty, YM → On Duty unless the UPS exemption applies; the indication is savedSame rule; the saved indication is restored at ignition on
Ignition-on promptAuthorized Personal Use alert or Yard Move alert; moving with the prompt open → DrivingContinueSpecialDriving.{Ym, PcWithAvailableDistanceLimit, PcWithExceededDistanceLimit} in DriveLogicDialogActivity; closes automatically on movement, ignition off or a status change
PC distance limitPcLimitHelper USED_100; switches to Off Duty only in CanadaPcLimitStateManager (daily reset at local midnight) with alerts at 75 / 95 / 100%. Canada: PcEvaluator switches to Driving while moving, Off Duty when not; US: reminder after 5 min
YM speed limitYmLimitHelper → Driving20 mph (US, UPS exemption only) / 32 km/h (Canada, always) → YmSpeedLimitExceeded → Driving; never applied to unidentified (UDP) drive time
Geofence Yard Move—New (PSELD-194, flag YARD_MOVE_GEOFENCE_AUTOMATION_ENABLED, off by default): entering a yard prompts for YM, leaving the yard switches to Driving
Auto-YM on stopStopped in YM for 6 min → user-origin YM eventUnanswered stop in YM (US) → On Duty + YM indication

Full comparison

ConcernV1 · DriveLogicFeatureV2 · DriveLogicFeatureV2 + DriveProcessor
ShapeOne 2,355-line Java classAbout 20 Kotlin classes: processor, 4 state machines, evaluators, monitors
Ordering & concurrencyHand-built queue paused by lock flags and restarted recursively from callbacks; mixes main Handler, AsyncLoader, coroutines and AlarmManagerOne coroutine Channel: engine samples and external inputs (driver change, unresponsive, status change) are processed one at a time, in order
StateMotionStatus split across two fields plus many booleans and countersTinder state machines (Vehicle, Ignition, DriveTime, Recovery) with explicit events and side effects
Why did status change?Only System vs User record originEventOrigin on every event (e.g. DriverUnresponsive, PcDistanceLimitExceeded, YmGeofenceExit); this decides whether On Duty is written
ThresholdsUnchanged: ≥5 mph (V2 can be lowered by config), 0 mph for 3 s, 5 min idle, 1 min countdown, US backdates / Canada +6 min
Unidentified & S&FSeparate copy-pasted branches; S&F stop decided from timestampsDriveTimeStateMachine (Unidentified / Driver / Unconfirmed); S&F shortens the alarm by elapsed time; adds DriverUnresponsiveStoreAndForward
Hourly intermediate eventsTop-of-hour alarm and a separate alarm counting from when the wheels started movingHeartbeatMonitor: top-of-hour + hourly; alarm times persisted; backfills gaps over a 2-day window after S&F
App restart / crashRe-reads stored motion statusRestores state machines from engine-data snapshots, reschedules remaining alarm time, fires DriverUnresponsiveMissed if an alarm is overdue
Data wipe mid-driveNo handlingRecoveryStateMachine suppresses a duplicate power-up and Driving record
DialogsAlert helper overlaysDriveLogicDialogManager → DriveLogicDialogActivity; answers come back as a flow
ObservabilityFirebase S&F event, invalid_odometerStructured state machine logs plus StuckInDriveLogger (stuck_in_drive_anomaly, possible_obc_disconnect)
Status todayDeleted July 2024The only implementation; actively developed (PSELD-*)

Sources: hos/src/main/java/com/pltsci/hos/data/drive/ on develop (V2) and DriveLogicFeature.java as of commit bf66134ae2^ (V1, now deleted). The thresholds listed are code defaults; the ruleset (US/Canada), the user session and remote config can change them per fleet.