> ## Documentation Index
> Fetch the complete documentation index at: https://docs.embedder.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Live plots and math channels

> Stream serial telemetry into the Monitor, configure real-time line and XY channels, and derive new signals with math expressions on live data.

Embedder plots numeric telemetry from serial or RTT streams in the Monitor. You can view live time series, pair channels in an XY plot, show text status channels, and compute derived channels without changing firmware.

The live plot UI requires the Embedder VS Code extension.

## Emit Teleplot data

Prefix each telemetry line with `>`:

```c theme={"system"}
Serial.printf(">die_temp:%.2f§°C\r\n", die_temp);
Serial.printf(">motor_rpm:%d§RPM\r\n", motor_rpm);
Serial.printf(">accel_x:%lu:%.4f§g\r\n", millis(), accel_x);
Serial.printf(">fsm_state:%s|t\r\n", state_name);
```

The accepted forms are:

```txt theme={"system"}
>channel:value
>channel:timestamp:value
>channel:value§unit
>channel:textValue|t
```

* `channel` identifies the source channel.
* `timestamp` is an optional monotonic millisecond value.
* `unit` labels the Y axis.
* `|t` marks a text or status channel.

Lines without `>` remain normal serial output and are ignored by the plot parser. Emit one channel per line, and log from the main loop or a task rather than a tight interrupt handler.

Device timestamps preserve sample spacing when the serial transport batches data. The plotter anchors the first device timestamp to wall time. A backward jump greater than one second creates a new anchor, which handles device restarts.

## Start and inspect a plot

Ask Embedder to plot the required signals:

```txt theme={"system"}
Plot battery_voltage and charge_current from the connected device.
Put charge_current on the right axis.
```

The agent reads the firmware or serial history, starts the stream with `plot_start`, and checks the discovered names with `plot_status`. Starting a new live plot requires hardware confirmation and holds an exclusive live-session hardware lease for that target.

`plot_status` reports:

* Every active port
* Discovered numeric channels and units
* The saved session ID
* The complete math-channel list for each port

With one active port, later plot operations can omit `port`. With several active ports, specify it.

## Parse another serial format

If you cannot change the firmware, pass a JavaScript regular expression to `plot_start`. It must contain named `channel` and `value` groups:

```json theme={"system"}
{
  "transform_regex": "^\\[SENSOR\\] (?<channel>\\w+)=(?<value>[\\d.]+)(?<unit>[A-Za-z°]+)?$"
}
```

You can also capture `timestamp` in milliseconds and `unit`. Python-style `(?P<name>...)` groups are not accepted.

When no Teleplot channels are detected, Embedder captures sample unmatched lines and asks the agent to prepare or correct the expression. It tests a new expression against buffered samples and reports whether it matched.

## Configure source channels

`plot_start` also updates an active plot without restarting it. You can configure one channel or a batch with:

* Visibility
* Color
* Left or right Y axis
* Display label
* Unit override
* Y-axis minimum and maximum

You can filter incoming data to named channels when starting the stream. Text channels appear in a status panel rather than on a numeric axis.

The plot toolbar lets you:

* Pause or resume rendering
* Clear the current data
* Select all data, 30 seconds, 1 minute, or 5 minutes
* Toggle source and math channel visibility
* Rename the recording
* Export CSV data
* Save the current plot configuration

## Use XY mode

XY mode aligns independently emitted channels by timestamp and plots one variable against another:

```json theme={"system"}
{
  "plot_mode": "xy",
  "xy_pairs": [
    {
      "x_channel": "voltage",
      "y_channel": "current",
      "label": "I-V curve"
    }
  ]
}
```

Only paired channels appear in XY mode. Switch back with:

```json theme={"system"}
{
  "plot_mode": "timeSeries"
}
```

For a pair across ports, use the stored channel names:

```json theme={"system"}
{
  "plot_mode": "xy",
  "xy_pairs": [
    {
      "x_channel": "COM3/voltage",
      "y_channel": "COM4/current"
    }
  ]
}
```

## Create math channels

Math channels are evaluated in the plot view from the latest source values. Call `plot_status` first because references are case-sensitive and must use names the plot has discovered.

```json theme={"system"}
{
  "channels": [
    {
      "name": "power",
      "expression": "voltage * current",
      "unit": "W",
      "axis": "right"
    },
    {
      "name": "temp_smooth",
      "expression": "lowpass(temp, 5)",
      "unit": "°C"
    }
  ]
}
```

Each `plot_set_math_channels` call replaces the full list. Read the existing list, modify it, and send every channel you want to keep. Pass an empty list to clear all math channels.

Math channel names must be unique and should not reuse a source channel name. A missing source produces a warning from the tool and a gap in the chart until all dependencies have data.

### Expression grammar

Expressions accept:

* Operators: `+`, `-`, `*`, `/`, unary `+` and `-`, and parentheses
* Numeric literals: integers, decimals, and scientific notation
* Bare channel names, such as `voltage`
* Port-qualified names with a dot, such as `COM3.voltage`
* Nested function calls

Conditions, comparisons, strings, assignments, and arbitrary JavaScript are rejected.

The stateless functions are:

* `abs(x)`
* `sqrt(x)`
* `pow(x, n)`
* `exp(x)`
* `log(x)`
* `min(a, b, ...)`
* `max(a, b, ...)`

The stateful functions are:

* `lowpass(x, cutoffHz)` and `highpass(x, cutoffHz)`, with a positive literal cutoff
* `ema(x, alpha)`, with a literal alpha from 0 to 1
* `moving_avg(x, window)` and `median(x, window)`, with a positive integer window
* `derivative(x)` and `integral(x)`
* `rolling_mean(x, window)` and `rolling_std(x, window)`
* `rolling_min(x, window)` and `rolling_max(x, window)`
* `rolling_p2p(x, window)` and `rolling_rms(x, window)`

Each stateful function occurrence owns separate state. Replacing the math-channel list resets all filter state. `derivative` uses seconds between source timestamps and starts with a gap. `integral` uses trapezoidal accumulation.

In a multi-port plot, a bare name resolves to a source with that name. If more than one port emits the same name, qualify it with `PORT.channel`. A slash remains the division operator inside expressions.

## Save and load recordings

Live numeric points are recorded under:

```txt theme={"system"}
.embedder/plots
```

Each recording has tab-separated CSV data and JSON metadata with its port, baud rate, channels, timestamps, and point count. `plot_load` can list recordings or find one by session name, session ID, or channel name. The store keeps the 50 most recent completed sessions.

`plot_stop` ends one port or every active plot. It finalizes the recording, clears its math-channel cache, releases the hardware activity, and can copy a single recording to a requested CSV path.
