UniteLabs
Reference

Device Variants

Show only the endpoints the connected device variant supports, and overload an endpoint per variant.

When one connector covers several hardware variants with different capabilities, each variant should get a clean interface with only the endpoints it actually has.

Hide endpoints a variant doesn't support

Every SiLA decorator takes an enabled argument: a callable that receives the feature instance and returns a bool. The CDK evaluates it once at startup and only registers the endpoint when it returns True.

For a shaker with two variants, BS and TC:

@sila.UnobservableProperty(enabled=lambda self: self.protocol.is_device_variant(["BS"]))
async def get_zero_position_state(self) -> bool:
    """Whether the shaker is at its zero position."""

@sila.UnobservableCommand(enabled=lambda self: self.protocol.is_device_variant(["TC"]))
async def start_with_runtime(
    self,
    target_speed: typing.Annotated[int, RPM],
    duration: typing.Annotated[int, SECOND],
) -> None:
    """
    Start shaking at the given speed for the given duration.

    Args:
      TargetSpeed: The shaking speed.
      Duration: How long to shake.
    """

A client connected to a BS device doesn't see StartWithRuntime; a client connected to a TC device doesn't see ZeroPositionState.

enabled is evaluated once at startup. If detecting the variant needs the device, open the connection before you register the features in create_app. Otherwise the check runs against a disconnected protocol.

Overload an endpoint per variant

When variants support the same operation with different parameters, write one method per variant, give them the same name, and use enabled so only one is registered. The client sees a single Shake command with the parameters its device supports.

@sila.ObservableCommand(name="Shake", enabled=lambda self: not self.protocol.supports_direction)
async def shake_fixed_direction(self, speed: typing.Annotated[int, RPM], *, status: sila.Status) -> None:
    """
    Shake at the given speed.

    Args:
      Speed: The shaking speed.
    """

@sila.ObservableCommand(name="Shake", enabled=lambda self: self.protocol.supports_direction)
async def shake_with_direction(
    self,
    speed: typing.Annotated[int, RPM],
    direction: ShakeDirection,
    *,
    status: sila.Status,
) -> None:
    """
    Shake at the given speed in the given direction.

    Args:
      Speed: The shaking speed.
      Direction: The shaking direction.
    """

Make sure the enabled conditions are mutually exclusive. If both methods are enabled, two endpoints claim the same identifier.

Variant-specific ranges

When variants support the same parameters but with different limits, such as a different maximum speed, you don't need separate endpoints. Query the limit at startup and add it as a constraint. See Constraints that depend on the device.

Last updated