Instrument data

Read current driving data and historical samples via $data and use the results for text, graphics and curves. This article explains the data configuration, reading structure, OBD parameters and update rules.

Data interface

The instrument uses a defined input via $data. $data.current provides the current input and $data.history provides the sampling sequence of the last 30 seconds of the current trip. The field name comes from the instrument's input identifier; for example, name the basic speed input speed, read through $data.current.speed; name the OBD input coolant, read through $data.current.coolant.

Speed, distance, time and OBD quantities include value and unit. For example, { value: 42.3, unit: 'km/h' } contains numerical values and units; fields such as position and G value provide their respective components. Numeric values, text, switches and selection items directly return numbers, strings or Boolean values without an extra layer of value. See the $data API for complete rules.

Define inputs and local bindings

Click the instrument name at the top of the editor to open Instrument settings → Input bindings. Use the gear to open Parameter configuration and define the input’s name, identifier and type. Return to Input bindings to enter local values or select your own vehicle parameters for an OBD input. Basic data is provided automatically by the app.

type Configuration and what the script reads
Number Set the default value and optional upper and lower limits; the script reads the number directly, such as $data.current.limit.
Text Set the default text; the script reads the string directly, such as $data.current.label.
Switch Set the default switch state; the script reads the Boolean value directly, such as $data.current.showUnit.
Selection Define optional text and defaults; the script reads the selected string, such as $data.current.style.
Basic data Select a source such as speed, G-force, position, or time; the readout structure depends on the source.
OBD data Define the expected value type and select the device and vehicle parameters in Input bindings; the readout is { value, unit } or null.

The input identifier is 1-100 English letters and numbers, starting with a letter, and cannot be repeated in the same instrument; each instrument has a maximum of 256 inputs. The name is used for interface display and the identifier is used for code. Modifying the display name does not change the access path, but modifying the identifier requires modifying the code simultaneously.

For example, after using coolant to represent the coolant temperature and binding the corresponding parameters of different devices, the code still reads $data.current.coolant. The editor provides expression and type hints based on the input definition. The numerical default value must fall within the upper and lower limits, and the selection items must have non-duplicate non-empty options and valid default items; correct the corresponding fields first when saving prompts for errors.

Current reading

The new instrument already contains the basic data input with identifier speed and source Speed; existing instruments can check this definition in Parameter configuration. Obtain $data.current.speed in main(), and you can use the value and unit of this speed. The following example shows the rounded value at the top and the unit at the bottom; when no speed is available, — is displayed.

Correctly configured basic speed inputs always retain the object, value is null when missing, and unit remains km/h or mph from the setup. == null Check whether the value is missing. When the speed is zero, it still displays 0. The field name is determined by the input identifier; if it is changed to vehicleSpeed, the code should also be changed to $data.current.vehicleSpeed.

function main() {
  const speed = $data.current.speed;
  return (
    <VStack spacing={8}>
      <Text fontSize={64} color={$env.foreground}>
        {speed?.value == null ? '—' : Math.round(speed.value)}
      </Text>
      <Text fontSize={16} color={$env.accent}>
        {speed?.unit ?? $env.speedUnit}
      </Text>
    </VStack>
  );
}

OBD parameter

First connect the OBD device and verify that the target parameters produce valid readings. Go to Parameter configuration and add the OBD data input, e.g. name "Coolant Temperature", identifier coolant, expected type select Numeric. Return to Input bindings and select the coolant temperature parameter under Devices for it. The following example reads via $data.current.coolant without using the vehicle parameter UUID as the script field name.

Reads that are bound and configured always contain value and unit. When the device is not connected, the reading expires, or the query fails, value is null, and unit still takes the parameter configuration saved by the selected device; unitless parameters use unit: null. When it is not bound or the device and parameter configuration no longer exist, the entire input is null.

value can be a number or a text-mapped string. The expected type is used for editing prompts; a binding may warn that the selected parameter may return a different type, but will still be bindable, and the actual value will not be cast as a result. The type should be checked before number formatting. After changing the adapter or importing someone else’s instrument, rebind the corresponding input to reuse the original code.

function main() {
  const reading = $data.current.coolant;
  const value = reading?.value;
  const text =
    value == null
      ? '—'
      : typeof value === 'number'
        ? value.toFixed(1)
        : value.toString();
  return (
    <VStack spacing={8}>
      <Text fontSize={48} color={$env.foreground}>
        {text}
      </Text>
      <Text fontSize={16} color={$env.accent}>
        {reading?.unit ?? ''}
      </Text>
    </VStack>
  );
}

Updates and historical sampling

Custom instruments do not have a uniformly fixed refresh rate. When the current input, input definition, or display environment changes, CapDrive re-executes the script and calls main(); identical inputs are skipped. The script is executed serially, and only the latest pending input is retained during processing. Animation is performed independently by the draw side, without running scripts frame by frame, and without increasing the sampling frequency of the sensor or OBD.

The App uniformly maintains the collected data of the last 30 seconds of the current trip, sampling and retaining up to 151 items at 5 Hz. Open the instrument to read the existing window; pause drawing or rebuild the running instance without losing the window, and reset it when the trip ends, switches trips or running modes. Historical growth or expiration cleanup will not trigger the script separately; when you want the curve to advance over time, you can define a basic input with the identifier elapsed and the source being the recorded duration.

After defining speed and elapsed input, elapsed.value of each historical sample can be used as the abscissa and speed.value as the ordinate; the timestamp uses milliseconds and the recorded duration uses seconds. Keep gaps in readings without backfilling history with subsequent OBD values. Previews use simulated input and dashboards use real data.

Data validity

null means there are currently no readings available and 0 is a valid value. By retaining these two states, the instrument can truly display parking, connection interruption, or temporary absence of testing. You can use value == null ? '—' : value to handle values ​​to avoid displaying all missing measurements as zero via value || 0.

Text mappings may produce an empty string, or may return a number or null if there is no match, depending on the parameter configuration. View that accepts literals can display strings; values ​​involved in calculations and number formatting should first check the type.

When troubleshooting missing readings, check according to the data source corresponding to the field: basic speed to view the positioning status; OBD parameter to view the current device, test reading results and parameter configuration. After the data source is normal, check the fields and code expressions selected by the instrument.

Continue with