Skip to main content
A sensor binding links one sensor slot of a digital twin to one channel of a time-series dataset. The dataset page explains how to package and search the data and how to attach a single channel by hand; the recorded data feed page explains how bound channels are played. This page covers what makes bindings work for a whole facility rather than one twin: sensors declared once on an asset, the type check on every binding, the identifier that tells a twin which channels are its own, Bind from dataset, the slot template in a recipe, and the Python helpers that build a rack of twenty servers in one script.

Sensors declared on an asset

A sensor slot does not have to be added to every twin by hand. An asset can declare scalar sensors in its universal schema (the JSON that describes what the asset is; see Editing a universal schema), and every twin created from that asset inherits them through its capabilities (capabilities.sensors, see Sensor capabilities). A scalar sensor reports one number per reading: a temperature, a power draw, a fan speed. Unlike a camera or a lidar it has no field of view and needs no pose, so the 3D view draws nothing for it, and the twin editor’s sensor list shows it by name with its unit and without field-of-view controls. It does not block a simulation either: the checks that run before a simulation starts skip scalar sensors, since there is nothing to simulate for them. An entry in the asset’s sensors list carries a type from the scalar vocabulary (the list on the dataset page), the slot id under parameters.id and, optionally, a unit under parameters.unit. A position inside the asset is optional:
The catalog asset cyberwave/server-2u, a generic two-unit rack server, declares fourteen such slots: The names are generic on purpose: they say what a server measures, not what a vendor calls it. A twin made from the asset lists all fourteen slots in its Data sources and Live values sections from the start; the translation from a dataset’s channel names to these slots happens in the binding (see Bind from dataset). A twin can still add slots of its own in metadata.sensors for readings its asset does not declare (see Declare sensor slots on the twin); both sets are merged into the twin’s sensor list.

The type gate

Both sides of a binding carry a type: the slot (declared on the asset or the twin) and the channel (from the recipe’s signals annotations, generic when none matched). Cyberwave accepts a binding only when the two types are the same, or when one side is generic or has no type. A temperature slot takes a temperature channel and refuses a power channel, so a wrong pick in a list of hundreds of channels does not go unnoticed. A mismatch is sometimes intended, for example a ratio channel that should drive a utilization slot. It can be allowed explicitly, and only explicitly:
  • In the Data sources section the channel search is filtered to the slot’s type by default; All types lifts the filter. Choosing a channel of another type asks for confirmation: <channel> carries <type>, this sensor measures <type>. Bind anyway? Once confirmed, the binding is stored with the override and the row shows an amber type mismatch badge from then on.
  • Over the API and from Python, the binding entry carries "allow_type_mismatch": true.
Without the override the binding is refused. PUT /api/v1/twins/{uuid} with data_sources in the metadata answers 422 with the reason for each failing slot, named as <twin>.<slot>, for example rack-48-s00.inlet_temperature: slot 'inlet_temperature' measures temperature but channel 'ipmi/total_power/node=960' carries power; choose a channel of the same type or allow the mismatch explicitly. The same request also checks that the dataset exists and has finished processing (422, dataset missing) and that the channel is in the dataset’s catalog (422, unknown channel). Nothing is written when one entry fails. Entries in the legacy shape and entries without a dataset are left as they are. Units are not converted. The check compares kinds, not units: a channel in °F on a slot declared in °C passes, and its values are stored and shown as they come. When the units differ, the row says channel unit <u1>, sensor unit <u2>, values are not converted. Convert at import instead, with scale and offset in the recipe’s signal annotations, or declare the slot in the channel’s unit. The environment’s sensor-sources report, GET /api/v1/environments/{uuid}/sensor-sources, lists every bound slot with type_mismatch (the types differ and the mismatch was allowed), unit_mismatch and allow_type_mismatch, and each twin’s identifier (thing_id). A switched-on binding whose types differ without the override, which is only possible for bindings made before the check existed, is refused by the recorded data feed: Play answers with a message naming the sensor as <twin>.<slot>. Attach the channel again in Edit mode, confirming the mismatch, or pick a channel of the slot’s type.

Identifier in datasets

In a dataset, a channel id is the signal followed by the thing: what is measured, then where (ipmi/ambient/node=960, see the vocabulary). A twin that stands for one such thing gets that string as its identifier in datasets (metadata.thing_id). With it, Cyberwave knows that ipmi/ambient/node=960 is the inlet temperature of this server and ipmi/ambient/node=961 belongs to its neighbour, and it can bind a twin’s slots without you searching every channel. Set it in Edit mode in the twin’s Data sources section, field Identifier in datasets, or over the API with PUT /api/v1/twins/{uuid} and the body {"metadata": {"thing_id": "node=960"}} (top-level metadata keys are merged, so the other keys are kept). From Python, cw.twins.set_thing_id(twin.uuid, "node=960") does the same. The build script in From Python sets it for every server it creates. The identifier is free text and must match the thing part of the channel ids exactly; the report of GET /api/v1/environments/{uuid}/sensor-sources shows it per twin. A dataset whose data has no entity column names its channels by the signal alone (weather/temp). The template below still binds such a channel: when the signal joined with the twin’s identifier is not in the catalog but the signal alone is, the signal alone is used. The twin still needs an identifier to take part.

Bind from dataset

Once a twin has an identifier, one click binds every slot the dataset can serve. In Edit mode, the Data sources section has a Bind from dataset button: pick one of your time-series datasets and, when the twin has docked children (twins attached to one of its links, such as the servers in a rack), tick Also docked children to bind them in the same go. The button uses the dataset’s slot template (next section); a dataset without one says so, and you attach channels by hand or pass a template over the API. For every twin, and for every scalar slot the template names, Cyberwave joins the template’s signal and the twin’s identifier into a channel id (ipmi/ambient and node=960 give ipmi/ambient/node=960), looks it up in the dataset’s catalog, applies the type gate and writes the binding. Each twin’s bindings are saved once, as a whole. The report, shown under the button, lists per twin its identifier, how many slots were bound and the slots skipped with the reason (the API response below also names the channel of every bound slot): Slots the template does not mention are left as they are. The totals at the end of the report count bound and skipped slots over all twins. The form’s Use recorded data box, ticked by default, sets the switch of every new binding (use_recorded over the API); look over the rows afterwards and switch off the ones that should keep their live input. The same operation over the API, with edit access to the environment like editing a twin:

Slot template in the recipe

The template that Bind from dataset uses is part of the dataset: the optional slots entry of cyberwave-timeseries.json (see the recipe reference) maps slot ids to signals. Signals, not channels: the thing part comes from each twin’s identifier, so one template serves every server of every rack.
The import stores it with the dataset as metadata.timeseries.slot_template. A dataset imported without one can still be bound over the API or from Python by passing template in the request. The reference recipe for the Marconi100 export, cyberwave-python/examples/timeseries_replay/recipes/marconi100.json, maps the slots of cyberwave/server-2u this way: outlet_temperature has no Marconi counterpart and stays unbound.

From Python

The Python SDK wraps the calls above. Twins, datasets, environments and feeds are given by id (the uuid of the object). Errors raise the SDK’s usual API exception with the backend’s message, for example the reason of a refused binding. Together they build a rack in a few lines. The script below creates rack 48 of the Marconi100 data: the rack twin from cyberwave/server-rack-42u, twenty cyberwave/server-2u twins docked to its server_rack_42u_link link at 2U pitch from the bottom up, each with its node number as identifier, and binds them with the dataset’s slot template. The API key comes from CYBERWAVE_API_KEY.
Running it again reuses the twins (the SDK returns an existing twin of the same asset and name instead of creating another) and skips the slots that are already bound. The SDK ships the same build as a script with more checks, cyberwave-python/examples/datacenter_rack/build_rack.py, which finds the rack and the servers by name and, on a re-run, also re-docks the servers and rewrites their identifiers:
It creates rack-48 and rack-48-s00 to rack-48-s19, docked bottom to top, sets the identifiers node=960 to node=979, binds the dataset with the chosen template (the Marconi100 one, a JSON file of your own, or else the dataset’s) and prints the report. --as-kit also registers the kit cyberwave/server-rack-42u-20x2u (the rack with twenty servers at the same offsets) in the Catalog.
If you prefer the app, add the kit with Add to environment from the Catalog (Asset kits), give each server its Identifier in datasets, and press Bind from dataset on the rack with Also docked children ticked.

Next steps

Time-series datasets

Package sensor logs, search channels and attach a channel by hand.

Recorded data feed

Play the bound channels into the twins in Live and Simulate.

Asset kits

Reuse a rack with its servers as one docked assembly.

Python SDK

Install the SDK and authenticate.