Skip to main content
A time-series dataset holds numeric readings over time: temperatures, power draw, fan speeds, humidity, anything a sensor logs as a value at a point in time. It is the dataset kind for facility and data-center telemetry, plant monitoring exports, and any Parquet or CSV log that is not a robot trajectory. After import, every channel of the dataset can be searched over the API, bound to a sensor slot of a digital twin, and replayed in the environment at any speed.

Vocabulary

Types are free-form strings. These are the ones the UI knows; a twin sensor slot can be bound to a channel only when its type is one of them: temperature, humidity, pressure, power, energy, voltage, current, frequency, fan_speed, speed, flow, utilization, load, memory, network, count, status, position, direction, ratio, index, distance, generic.

Package a dataset

A time-series upload is a zip that contains the data files and one recipe file named cyberwave-timeseries.json. The recipe may sit at the zip root or up to two directory levels below it. Its directory is the dataset root; every file glob in the recipe is relative to that directory. Data files are Parquet or CSV.
1

Upload the zip

Open Datasets, click Upload Dataset, choose Upload zip and select the file (max 10 GB). From Python, cw.datasets.add("./marconi100_rack48_2022-02_timeseries.zip", name="marconi100-rack48") uploads the same file; a folder is zipped for you.
2

Wait for processing

Cyberwave finds the recipe, validates it, reads the data files one at a time and then checks them for repeated readings in passes of a fixed size, so memory use is bounded by the largest single file plus a fixed amount for that check, however many readings there are. The dataset record gets source_format: timeseries. A recipe that fails validation fails the import with a message that names the problem; data files that do not match path_pattern are skipped with a warning. The import also fails, naming the file, when a column it reads appears twice in that file, or when two columns (or two signal values) of one file that both have readings would become the same signal.
3

Check the overview

Open the dataset and switch to its Playground tab: it shows the title and description from the recipe, the time range, the number of rows, channels, signals and things, the types found, a table of signals and the first channels. On the Datasets page the card carries the kind Time series.
The SDK examples folder cyberwave-python/examples/timeseries_replay/ ships make_zip.py <data-folder> <recipe.json> <out.zip>, which packs a folder and a recipe into an upload zip.

Recipe reference

Long and wide layouts

Long: one row per reading, one value column. The signal name comes from the signal template, built from path captures, columns, or both. With path_pattern capturing source=ipmi and signal=ambient from the file path, the template {source}/{signal} names the signal ipmi/ambient, and thing.columns: ["node"] produces the channels ipmi/ambient/node=960 and ipmi/ambient/node=970. Wide: one row per time (and per thing), one column per signal. Every column that is not the time column or a thing column is a signal, named after the column. Set signal to a template with {column} to rename them, for example "room/{column}". A template without {column}, such as "{machine}/temperature", suits files with a single numeric column; two numeric columns of one file cannot share a signal name, and the import fails. A column of text values yields no readings and no channel; a column of another non-numeric type, such as a second timestamp column, fails the import. This produces the channels temperature/room=lab, humidity/room=lab, temperature/room=server_room and humidity/room=server_room.

Path captures

path_pattern is matched against each data file’s path relative to the dataset root:
  • * matches any text inside one path segment.
  • **/ matches any number of leading directories.
  • {name} captures the text at that position, inside one path segment, and makes it a path field for the signal template.
With **/plugin={source}_pub/metric={signal}/*.parquet, the file year_month=22-02/plugin=ipmi_pub/metric=ambient/a_0.parquet yields source=ipmi and signal=ambient. Capture names are yours to choose; the {signal} capture above is a path field that happens to share the name of the recipe key. Files that do not match the pattern are skipped with a warning.

Signal annotations

signals maps patterns to annotations. Patterns use shell-style wildcards (*, ?, [..]) and are matched, case-sensitively, against the full signal name. Patterns are applied in file order, and a later match overrides an earlier one key by key, so a broad pattern can set a default that narrower patterns refine:
schneider/pdu_power ends up with scale: 0.1, type: power, unit: W and label: PDU power. schneider/pdu_current only gets scale: 0.1 and keeps the defaults for everything else.

Slot template

slots maps the sensor slot ids of your twins to signals, so that a twin whose Identifier in datasets is node=960 can be bound to ipmi/ambient/node=960, ipmi/total_power/node=960 and so on in one go with Bind from dataset. Signals, not channels: the thing part comes from each twin’s identifier.
The import stores it with the dataset as metadata.timeseries.slot_template. See Sensor bindings for the full Marconi100 template and how binding works.

Examples

Marconi100 export (Parquet, long layout)

The CINECA Marconi100 telemetry export is Hive-partitioned Parquet: one folder per plugin and metric, one row per reading, with the server in the node column (other plugins use device, panel or partition). The recipe below is the reference recipe shipped with the SDK at cyberwave-python/examples/timeseries_replay/recipes/marconi100.json.
The file year_month=22-02/plugin=ipmi_pub/metric=ambient/a_0.parquet becomes one channel per server, such as ipmi/ambient/node=960 with type temperature, unit °C and label Inlet air temperature. The weather files have no entity column, so weather/temp is a channel on its own. Schneider values are divided by ten on import. The slots entry maps the sensor slots of the cyberwave/server-2u asset to these signals, so a server twin with the identifier node=960 is bound in one go; see Sensor bindings.

Room sensor log (CSV, wide layout)

A minimal package for a sensor log with one row per minute and room:
time is the time column and room the thing column, so the remaining columns temperature and humidity are the signals. The import produces four channels: temperature/room=lab, temperature/room=server_room, humidity/room=lab and humidity/room=server_room. Without the thing entry, room would be treated as a signal column (its text values yield no channel) and the readings of both rooms would land in the same temperature and humidity channels: at each time only the reading imported last would be returned, and the import would report the clash under duplicate_readings (see below).

What the import produces

  • Catalog: one row per channel with channel_id, signal, thing, type, unit, label, hidden, first_time, last_time, count and partition (the signal’s chunk directory). Channel search reads it.
  • Store: the readings rewritten sorted by time and chunked by signal and UTC day, with scale and offset already applied. Every signal gets its own chunks, even when two signal names differ only in characters a directory name cannot hold (inlet temp and inlet_temp, or 温度 and 湿度). Hidden channels are stored too. A query reads only the chunks of the requested signals that overlap its time window, so its cost does not grow with the size of the whole dataset.
  • Summary on the dataset record (metadata.timeseries): title, description, time range, counts of rows, channels, hidden channels, signals and things, the number of channels per type, and one entry per signal (type, unit, label, channel count; capped at 500 signals). The dataset card and overview page show it.
  • Repeated readings: two readings of one channel at the same time, for example from exports that overlap, are both stored, and queries and the replay feed use the one imported last (files in path order). The summary then has duplicate_readings: count (readings beyond the first at their time), conflicting (times whose readings disagree) and example (one disagreement with its channel, time, values and files, in import order). Row and channel counts leave the repeats out. Disagreeing repeats usually mean the recipe puts two sources into one channel, such as one file per machine without the machine in the signal name.

Query API

Both endpoints need the same read access as GET /api/v1/datasets/{uuid} and the usual Authorization: Bearer header (see API overview). A dataset that has no time-series data, for example one that is still processing, answers 409 with {"detail": "Dataset has no time-series data"}. All times in responses are ISO-8601 in UTC with a Z suffix.

List and search channels

Results are sorted by channel_id.

Read rows

Rows come back sorted by time, then channel, with at most one row per channel and time (of repeated readings, the one imported last). truncated: true means the limit was hit; request a narrower window.
The same call from Python, with the API key in CYBERWAVE_API_KEY:

Play recorded data on digital twins

Once the import has finished, the channels of the dataset can drive the sensors of your digital twins. Three things are involved:
  • Sensor slots: the sensors a twin declares it has, for example inlet_temperature.
  • Data source: an optional link from one slot to one channel of one dataset. A slot without a data source is fed by the environment: the paired device in Live, the simulation in Simulate.
  • Use recorded data: a switch on each data source. Switched on, the recording is the slot’s only source in Live and Simulate: the recorded data feed plays the channel, and readings the device or the simulation sends for the slot are dropped, whether or not a feed is playing. Switched off, the data source is used by Replay mode only.
1

Declare sensor slots on the twin

Twins made from an asset that declares scalar sensors, such as cyberwave/server-2u, have their slots from the start (see Sensors declared on an asset). Any twin can add slots of its own: a sensor slot is an entry of metadata.sensors on the twin with an id, a name, a scalar type from the vocabulary above and, optionally, a unit.
Add it in Edit mode: select the twin, open its Advanced section and click JSON next to Semantics to open the Edit Twin Metadata dialog. Or write it with PUT /api/v1/twins/{uuid} and the body {"metadata": {"sensors": [...]}}. Slots with a scalar type can take a data source; cameras, depth and lidar sensors from the asset schema cannot.
2

Attach a data source

In Edit mode, select the twin and open its Data sources section. Every slot with a scalar type has a Data source field, empty by default, which reads Environment. To attach a channel, press Attach recorded channel, pick one of your completed time-series datasets and search a channel; the list is filtered to the slot’s type by default, and All types lifts that filter. Change opens the pickers again for an attached slot, and Clear detaches the channel. A channel of another type than the slot asks for confirmation and marks the row with a type mismatch badge; a differing unit is pointed out and never converted (see The type gate). To bind many slots at once, give the twin its Identifier in datasets and press Bind from dataset (see Bind from dataset).Once a channel is attached, the row shows the Use recorded data switch, off by default, with the hint “Replaces the live input in Live and Simulate; the device is muted for this sensor”. The row’s summary reads <dataset> · <channel> · recorded data on (or off). Every change is saved at once. When the switch is on but the dataset behind it is gone (deleted, or not finished processing), the row warns “Recorded data selected, dataset missing — clear it or attach another channel”: the sensor has no source left, and no feed can start until you fix it.Saving writes metadata.data_sources on the twin, one entry per attached slot:
The same map can be written with PUT /api/v1/twins/{uuid} and a body of {"metadata": {"data_sources": {...}}}. Metadata is merged one key deep: each top-level key you send replaces the stored one and the other keys are kept, so always send the complete data_sources map and leave sensors out. Every entry is checked on save: the dataset must exist with finished time-series data, the channel must be in its catalog, and the types must match unless the entry carries "allow_type_mismatch": true; otherwise the request answers 422 with the reason for each failing slot, named as <twin>.<slot>, and nothing is written. A slot without an entry is fed by the environment, so to detach a channel leave its key out. Twins set up before the switch existed may still carry "source": "replay" in an entry: Cyberwave reads it as use_recorded: true and writes the shape above the next time the twin’s data sources are saved.
3

Play the data

Switch to Live or Simulate. As soon as one sensor in the environment has Use recorded data switched on, the right panel shows the Recorded data feed: choose a start time and a speed and press Play. The feed runs on Cyberwave’s servers and plays every switched-on sensor of the environment together; see Recorded data feed. Until you press Play, the switched-on sensors get no fresh values at all, not even the device’s, and the panel says so: “Recorded data selected on N sensors, no feed playing”. A switched-on sensor whose dataset is missing is listed there too, and Play stays disabled until you fix or clear it in Edit mode.To look at the data without feeding it into the live scene, use Replay mode: the calendar marks the days the attached datasets cover, and the Sensors tab shows the values at the playhead; see Sensor data in Replay.
4

Watch the values

In Live or Simulate, select the twin. The Live values section lists every slot with its current value, unit and age, a sparkline of the last values, and a badge that names the source: replay with the run id and the data time while the feed plays, otherwise edge or sim. A switched-on slot gets values from the feed only: while no feed plays, or when its dataset is missing, it shows no fresh value, its age reads stale (a dash when it never had one), and it never falls back to a device reading. The values are stored in the sensor history like device readings, tagged as injected so that Replay mode can tell them apart later. The twin’s metadata.readings holds the latest value per slot, and metadata.replay the run id, the dataset and the data time currently playing, so other tools see the same state.
The SDK examples folder cyberwave-python/examples/timeseries_replay/ still ships player.py, a script that publishes the same messages from your own machine. Use it for offline runs: --from-folder plays a local Parquet folder with its recipe, --dry-run prints the messages instead of publishing them, and --all-attached plays every attached channel regardless of the switch. Its messages carry the same recorded-data tag as the feed’s, so a switched-on sensor accepts them; do not run it next to a running feed, or both write to the same sensors. For the Marconi100 example, make_scene.py --environment <environment-uuid> --dataset <dataset-uuid> in the same folder builds a demo scene: an equipment rack with three server twins (bottom, middle, top) that carry the sensor slots above, with the ipmi/ambient and ipmi/total_power channels of nodes 960, 970 and 979 attached and Use recorded data switched on. For a full rack of twenty cyberwave/server-2u servers bound through the recipe’s slot template, use examples/datacenter_rack/build_rack.py instead; see Sensor bindings.

Next steps

Import datasets

Upload sources, format detection and the full format matrix.

Digital twins

Twin metadata, capabilities and sensors.

Sensor bindings

Sensors declared on assets, the type check, identifiers and Bind from dataset.

API overview

Authentication and base URL for the channel and row endpoints.

Python SDK

Upload datasets and read twins from Python.

Recorded data feed

Play attached channels into the twins in Live and Simulate.

Replay timeline

Sensor data days in the calendar and the Sensors tab at the playhead.