fleetlessfleetlessdocs
Reference/fleetless.yaml/Datapoints

📈 Datapoints

A datapoint is one field of one topic — or the whole topic. Never several topics.

yaml
datapoints:
  battery_soc:
    topic: /battery
    type: sensor_msgs/msg/BatteryState
    field: percentage
    rate_throttle_hz: 1
    description: |
      Main battery charge.
      Below 15 % the robot returns to its dock on its own.

    numeric:
      scale: 100
      unit: "%"
      decimals: 1

    retention:
      enabled: true
      interval_seconds: 60
      max_buffer_values: 5000

    chart:
      y_min: 0
      y_max: 100
      style: line
      default_window_minutes: 1440

    alerts:
      low_battery:
        name: Battery low
        severity: warning
        condition: { fire_at: 15, resolve_at: 18 }
field type bounds absent means
topic ROS graph name absolute, at most 255 characters required
type ROS type name pkg/msg/Type, at most 255 characters required
field field path at most 255 characters, one array index per segment the whole message
rate_throttle_hz number 0 to 20 no throttling
low_bandwidth keep — rate-capped by low_bandwidth when active; keep exempts the rate only — its backfill still pauses
description text 1 to 2000 characters offered as description: null
numeric.scale number — no factor
numeric.offset number — no offset
numeric.unit text at most 32 characters no unit shown
numeric.decimals integer 0 to 6 the console’s own default
retention.enabled boolean — false, live only
retention.interval_seconds integer 1 to 3600 300
retention.max_buffer_values integer 1 to 100000 no buffer on the robot
chart.y_min number finite, not above y_max the axis scales to the data
chart.y_max number finite the axis scales to the data
chart.style line or step — the console decides
chart.default_window_minutes integer 1 to 43200 60
alerts map of slugs — nothing watches this value

topic and type are required, and so is nothing else. Leaving field out means the whole message, and the value is then an object rather than a scalar. There is no whole_message: true — omission already says it.

Without field, the numeric, chart and alerts groups are refused as requires_single_field. All three need one comparable value: a scale factor on a whole message means nothing, and neither does a y-axis.

field — a path into the message

Dot-separated field names, each name optionally followed by one array index. All four of these are valid:

  • percentage
  • pose.position.x
  • ranges[0]
  • points[0].x

The rules the path obeys:

  • Names keep ROS’s spelling — lowercase letters, digits and underscores, starting with a letter or an underscore.
  • An index only works on a field the type declares as an array. points[0].x is a path; percentage[0] on a float64 is not.
  • One index per name. ROS 2 has float64[], float64[3] and float64[<=10] and no multi-dimensional arrays at all, so points[0][1] could name nothing on any message.
  • The whole path is at most 255 characters.

Index bounds are not checked, and cannot be — an array’s length is not part of a ROS type. ranges[999] publishes cleanly, and on a robot whose message is shorter the datapoint simply produces no values.

Throttling with rate_throttle_hz

An upper bound, 0 to 20, and it need not be a whole number. Omitted or 0 means no throttling.

Three properties define it:

  • It is a ceiling, not a clock. A topic that publishes more slowly reaches you more slowly, which is correct.
  • A value is never repeated to manufacture a rate. A silent topic produces no traffic.
  • Within a window the newest value wins. What arrives faster is dropped rather than queued, so a subscriber always sees the current state.

The bridge enforces this, not the cloud. Every realtime subscriber therefore sees the same rate and the robot’s bandwidth is genuinely saved; throttling in the cloud would arrive too late to save anything.

The 20 Hz ceiling is a product decision. For an app’s interface it is plenty, and a control loop or millisecond debugging wants a tool that reads directly on the robot.

numeric — needs a numeric field

scale and offset are applied on the robot, before sending: value * scale + offset. That is why REST, realtime and history all carry identical numbers — there is no second place where arithmetic happens.

unit is the unit after conversion. It appears beside the value and is carried into the datasheet robot_describe answers an MCP client with, so a model does not have to guess whether 15 means percent, volts or minutes.

decimals is an exact count of fraction digits, not a ceiling: decimals: 1 renders 18.0 as well as 18.4, which is what makes a column of readings line up. Leaving it out is not the same as decimals: 0 — absent means the console’s own default, while 0 is a request for whole numbers. It is presentation only, and the stored value keeps the precision it arrived with.

retention — what outlives the moment

enabled is off by default and decides whether values are written to the time series and become queryable. Without it the value is live only: if you are not watching when it arrives, you never see it. That is often right, because not every value deserves a past.

interval_seconds is how often a value is written to history, not how often it is sent — that is rate_throttle_hz. Two consequences are worth knowing: a bumper that is true for 200 ms does not appear unless a write falls inside it, and stored points are billed. This interval is the direct lever on what a robot costs.

max_buffer_values acts on the robot: how many values it holds while the bridge is disconnected, pushed after reconnect. The catch-up runs behind live telemetry and job results at a limited rate, so closing a gap never delays the present. Without it the series simply has a gap, which is an honest answer.

chart — display only

y_min and y_max are fixed axis bounds, and omitting them lets the axis scale to the data. y_min: 0 is a real axis floor and is read as 0, never as unset. A y_max below y_min is refused at parse time, because nothing downstream catches it and the chart would render empty.

style is line or step, and it is not a matter of taste. line claims the value moved evenly between two samples, roughly true of a temperature or a charge. step holds the value and then jumps, which for a mode, a switch or a counter is the only honest drawing.

default_window_minutes is only the starting zoom. A viewer may look further back, and nothing about what is stored follows from it.

What only the robot can settle

type is declared rather than discovered, so a configuration can be written for a robot that has never connected. The cost is that some checks have nothing to run against until one has.

finding severity when
unknown_type warning the robot has not introspected this type, so nothing about it can be checked
unknown_topic warning the topic was not seen in the last introspection
unknown_field_path error the type is known and has no such field
requires_numeric_field error numeric or chart sits on a field that is not a number

A warning does not block a publish. An error does, and the editor marks it as you type — long before the robot would have seen it.

Alerts on this datapoint are their own page. The message template that actions and services and publishers send is Messages & Parameters. Cameras have none, and the limits every section shares are on the overview.