Firmware C++ Core

Firmware Runtime Reference

OpenHaldex Firmware Core and Control Path

This page documents the OpenHaldex-S3 C++ control path from CAN receive to final generation-specific transmit frames. It covers runtime ownership, mapped signal binding, fallback behavior, mode calculations, interpolation, release smoothing, diagnostics visibility, and validation priorities for firmware contributors.

Architecture Ownership Map

Layer Primary files Responsibility
CAN ingestion + decode src/functions/can/can_rx.cpp Consume chassis/haldex frames, apply mapped decode, update fallback telemetry when stale.
Runtime state src/functions/core/state.* Own active mode, telemetry fields, map/curve arrays, and runtime gates.
Control calculations src/functions/core/calcs.cpp Generate lock request from mode + gates + interpolation + release-rate smoothing.
Frame mutation + TX src/functions/can/frames.cpp, can_bus.cpp Apply lock request to generation-aware frame format and transmit to Haldex path.
HTTP control surface src/functions/api/api.cpp Route registration, payload validation, and endpoint write/read behavior.
Persistence + logs src/functions/storage/storage.cpp, filelog.cpp Persist settings/maps + provide runtime log list/read/delete/clear operations.

Runtime Dataflow (CAN RX -> Lock Request -> TX)

CAN RX frame
-> mapped decode (if configured)
-> mapped freshness check (1000 ms window)
-> fallback decode if mapped channel stale/missing
-> received_vehicle_speed / received_pedal_value / received_vehicle_rpm update
-> mode + gate evaluation in calcs.cpp
-> lock_target percent (0..100) + release smoothing
-> generation-specific byte shaping in frames.cpp
-> Haldex TX + optional rebroadcast + diagnostics cache

  1. Load current input mapping keys from persisted runtime settings.
  2. Resolve mappings into DBC signal bindings and mux requirements.
  3. Cache incoming frame for CAN View decoded/raw streams.
  4. Apply mapped decode for throttle/RPM/speed; update freshness timestamps.
  5. If mapped values stale, apply fallback decode from known chassis frames.
  6. When controller active and generation selected, compute lock and mutate output frame.

  1. Cache Haldex bus traffic for diagnostics.
  2. Decode mapped telemetry when mapping targets Haldex source signals.
  3. Interpret generation-specific state/engagement bits for UI visibility.
  4. Bridge status traffic when broadcast settings require cross-bus visibility.

Execution Notes

  • Control loop authority is firmware-side, not browser-side.
  • Mapped telemetry timeouts prevent stale-input lock behavior.
  • Generated frame tagging helps debug mutated vs pass-through traffic.
  • Standalone generation modes keep required diagnostics traffic alive.

Mapped Signal Contract and Fallback Semantics

Input mapping keys use a normalized 4-token format:

bus|frameId|signalName|unit

Binding checks before accepting a mapped value

  • Binding parsed successfully and DBC signal descriptor exists.
  • Bus match (all, chassis, haldex) for current loop.
  • Frame identifier match after ID masking.
  • Mux selector match for multiplexed signals.
  • Decoded value is finite and clamped to valid numeric range.

Fallback behavior when mapped inputs go stale

  • Mapped channels are considered fresh for ~1000 ms.
  • Throttle fallback: chassis engine frame decode path.
  • RPM fallback: chassis engine frame decode path.
  • Speed fallback: ABS speed first, then alternate speed fallback if ABS stale.
  • Result: control loop keeps coherent telemetry during transient mapping loss.

Control Logic Model in calcs.cpp

Mode family Target source Gate behavior
Preset (FWD, 50:50, 60:40, 70:30, 80:20, 90:10) Fixed lock percentages Uses global preset gate (disableSpeed, pedal threshold).
SPEED / THROTTLE / RPM Curve interpolation Uses dynamic mode disengage-under-speed + launch override at full pedal.
MAP 2D throttle/speed bilinear interpolation Uses map-specific disengage-under-speed + launch override.
// Simplified control path
raw_target = mode_target();
raw_target = apply_speed_gates(raw_target);
smoothed_target = smooth_lock_release(raw_target);
lock_target = clamp(smoothed_target, 0, 100);

Interpolation and Release-Rate Smoothing

  • Curve points must be strictly ascending by x.
  • Linear interpolation is applied between adjacent bins.
  • Out-of-range inputs clamp to first/last point value.
  • Ranges enforced in API: speed 0..300, throttle 0..100, RPM 0..10000, lock 0..100.

  1. Locate surrounding throttle bins and throttle ratio.
  2. Locate surrounding speed bins and speed ratio.
  3. Interpolate speed on both throttle rows.
  4. Blend row outputs by throttle ratio.
  5. Clamp result to 0..100 lock.

  • Upshifts (higher lock requests) apply immediately.
  • Downshifts are rate-limited by lockReleaseRatePctPerSec.
  • 0 disables smoothing (immediate release).
  • Large timing jumps are handled defensively to avoid stale ramp artifacts.
step = release_rate_pct_per_sec * dt_ms / 1000
smoothed = max(raw_target, smoothed - step)

Lock Conversion and Frame Shaping

  • get_lock_target_adjustment() produces final requested lock percent.
  • get_lock_target_adjusted_value() converts lock percent to generation byte semantics.
  • Near-full requests are snapped to full scale to avoid 99.x vs 100 discontinuity.
  • invert=true handles frames where lower encoded values mean higher lock.
  • Controller-off path forces neutral output regardless of prior request.

Final byte mutation and generation-specific frame layout are applied in the CAN frame layer before transmit to the Haldex bus.

Mutation Engine Deep Dive (getLockData)

OpenHaldex mutates chassis-origin frames in place before forwarding them to the Haldex bus. The mutation engine is deterministic: same generation + mode + telemetry inputs always yields the same output bytes. This section explains exactly how a requested lock value becomes concrete CAN byte changes.

if (state.mode == MODE_STOCK) {
  // pass-through, no mutation
} else if (haldexGeneration in {1,2,4}) {
  getLockData(frame); // may mutate bytes based on ID switch-case
}
  • Mutation is attempted only when mode is not STOCK.
  • Only generations 1, 2, and 4 run mutation logic.
  • Inside getLockData, only explicit switch-case IDs are changed. All other IDs pass through unchanged.

The engine first computes requested lock percent in get_lock_target_adjustment(), then converts it per-byte with get_lock_target_adjusted_value(base, invert).

// lock_target conversion (simplified)
requested_lock = clamp(lock_target, 0, 100)
if (requested_lock >= 99.5) requested_lock = 100

if (requested_lock >= 100) return invert ? (0xFE - base) : base
if (requested_lock <= 0)   return invert ? 0xFE : 0x00

factor = (requested_lock * 0.5) + 20
corrected = uint8_t(base * (factor / 100))
return invert ? (0xFE - corrected) : corrected
Input Example Result
requested_lock = 50%, base = 0xFE, invert = false factor = 45, corrected = 254 * 0.45 0x72 (114 decimal)
requested_lock = 50%, base = 0xFE, invert = true 0xFE - 0x72 0x8C (140 decimal)
requested_lock = 0% neutral output 0x00 (or 0xFE when inverted)
requested_lock = 100% full-scale output base (or 0xFE - base when inverted)

Important: this is an empirically tuned command-level abstraction. A requested 50% lock is not guaranteed to equal an exact physical 50:50 torque split under all driveline conditions.

  • Active runtime mode (preset, speed, throttle, RPM, or map).
  • Mapped or fallback telemetry: speed, pedal/throttle, and RPM.
  • Preset gates: pedal threshold + disable speed.
  • Dynamic gates: per-mode disengage-under-speed with full-throttle launch override.
  • Release-rate down-ramp smoothing via lockReleaseRatePctPerSec.
  • Controller enable state (stock/disabled path forces pass-through behavior).

4) Frame ownership and pass-through rules

  • parseCAN_chs always forwards chassis traffic to the Haldex bus.
  • If state.mode == MODE_STOCK, bytes are forwarded unchanged.
  • If generation is not 1/2/4, bytes are forwarded unchanged.
  • Even in active modes, only frame IDs present in getLockData are mutated.
  • All non-target IDs remain byte-identical pass-through frames.
  • generatedFrame is set only when post-mutation bytes differ from original payload.

Mutation decision truth table

Condition Byte mutation Forwarding
MODE_STOCK No Pass-through chassis frame
Mode active + unsupported generation No Pass-through chassis frame
Mode active + supported gen + unmatched ID No Pass-through chassis frame
Mode active + supported gen + matched ID Yes (switch-case byte edits) Mutated frame forwarded to Haldex bus

Generation Mutation Matrix (Frame IDs and Byte Ownership)

Generation Frame ID Mutated bytes Mutation notes
Gen1 0x280 (MOTOR1_ID) D0..D7 Primary Gen1 lock-path shaping; byte 6 uses mode-dependent applied torque strategy.
Gen1 0x380 (MOTOR3_ID) D2, D7 Secondary pedal/throttle-related shaping.
Gen1 0x1A0 (BRAKES1_ID) D1, D2, D3 Brake/slip-influenced control modifiers.
Gen1 0x4A0 (BRAKES3_ID) D0..D7 Wheel/brake byte shaping set to tuned baseline + scaled bytes.
Gen2 0x280 (MOTOR1_ID) D1, D2, D3, D6 Core lock request shaping (Gen2 baseline currently mirrors Gen4 philosophy).
Gen2 0x380 (MOTOR3_ID) D2, D7 Supplemental request shaping bytes.
Gen2 0x488 (MOTOR6_ID) none Case present for expansion; currently no byte edits.
Gen2 0x1A0 (BRAKES1_ID) D0, D1, D2, D3 Brake-path lock modifiers.
Gen2 0x5A0 (BRAKES2_ID) D4, D5 High-impact brake/torque influence bytes.
Gen2 0x4A0 (BRAKES3_ID) D0..D7 Wheel/brake shaping baseline with scaled low-byte channels.
Gen4 0x0C2 (mLW_1) D0..D7 Steering-angle/rate frame loaded from precomputed table with rolling index.
Gen4 0x280 (MOTOR1_ID) D1, D2, D3, D4, D5, D6, D7 Main Gen4 torque request shaping path.
Gen4 0x380 (MOTOR3_ID) none Case currently retained as reference; byte writes are intentionally disabled.
Gen4 0x1A0 (BRAKES1_ID) D0, D1, D4, D5 ASR/brake state shaping plus scaled channels.
Gen4 0x5A0 (BRAKES2_ID) D4 Single high-impact lock-influence byte.
Gen4 0x4A0 (BRAKES3_ID) D0..D7 Wheel/brake shaping with mixed fixed and scaled channels.
Gen4 0x2A0 (BRAKES4_ID) D0..D7 Includes rolling counter and XOR checksum regeneration after mutation.

End-to-End 50% Lock Trace (Worked Example)

Step Computation Result
Raw lock request get_lock_target_adjustment() from active mode/curve/map 50.0
Release smoothing smooth_lock_release(raw_target) lock_target = 50.0 (steady-state)
Scale factor (lock_target * 0.5) + 20 45.0%
Gen4 MOTOR1 D1 (base 0xFE) 254 * 0.45 = 114.3 -> 114 0x72
Gen4 BRAKES2 D4 (base 0x7F) 127 * 0.45 = 57.15 -> 57 0x39
Inverted channel example (base 0xFE) 0xFE - 0x72 0x8C

How mutation targets are chosen in practice

  1. Record baseline pass-through CAN while running stock behavior.
  2. Apply one frame mutation candidate and compare engagement + telemetry deltas.
  3. Classify bytes as high/medium/low influence and remove no-effect edits.
  4. Preserve counters/checksums where required (for example BRAKES4_ID rolling counter + XOR).
  5. Keep per-generation byte ownership documented in the matrix above.

Practical Tuning Workflow for New Byte Candidates

  1. Start in pass-through baseline and capture CAN + engagement telemetry for comparison.
  2. Change one frame case at a time in getLockData; do not fan out simultaneous multi-ID edits.
  3. Use generated-frame markers and /api/canview dump to verify only intended bytes changed.
  4. Compare requested lock vs actual engagement trend to classify influence strength and nonlinearity.
  5. Document each accepted byte mutation in this matrix with generation scope and rationale.

Diagnostics and CAN View Internals

  • Per-bus frame cache supports decoded and raw diagnostics views.
  • /api/canview serves bounded decoded/raw windows with bus filtering.
  • /api/canview/dump exports one-shot text capture with time-window limits.
  • Capture mode stores prior state and enforces a safe stock/controller-off profile while active.
  • Generated frame flags help identify firmware-mutated traffic during investigations.

Logging and Persistence Notes

  • Log scopes support all, can, and error workflows.
  • Debug capture profile can force file logging for supportability.
  • State writes are persisted through dirty-mark storage updates.
  • Map/curve payload changes update runtime arrays immediately and persist via storage layer.

Validation Checklist for Firmware-Core Changes

  1. Verify mapped telemetry and fallback telemetry produce consistent live values.
  2. Test mode transitions at low, medium, and high speed with controlled throttle changes.
  3. Confirm release-rate smoothing eliminates abrupt lock drop artifacts without under-reacting.
  4. Validate generation-specific output frames on the selected Haldex generation profile.
  5. Use /api/canview and log files to capture evidence before and after changes.
  6. Re-run integration tests for settings/map/curve writes and post-write status reconciliation.

Related Technical References

© Bored Systems
Theme Settings
Color Scheme
Light
Dark
Layout Mode
Fluid
Boxed
Topbar Color
Light
Dark
Menu Color
Light
Dark
Layout Position