UniteLabs
Guides

Error Handling

Errors happen. This guide shows how to handle them gracefully in your SiLA 2 connector - so clients and users stay informed, not confused.

Error handling is a crucial aspect of developing reliable connectors for laboratory instruments. A connector must anticipate failures from various sources - whether due to invalid user input, communication failures, or hardware malfunctions - and handle them appropriately. This guideline explains how to manage these errors, document them effectively, and expose them via the SiLA 2 interface so that both client applications and users of the interface can understand and respond appropriately.

Errors encountered in connector development typically fall into one of four categories:

  • Communication Errors: Failures in the underlying infrastructure.
  • Framework Errors: Violations of the SiLA 2 protocol or client misuse.
  • Validation Errors: Input that violates constraints, either defined at design-time or evaluated dynamically at runtime.
  • Execution Errors: Unexpected failures during the actual operation of a command or property.

Each of these categories requires a different strategy for detection, reporting, and documentation.

Communication Errors

Communication failures occur at the system or transport layer and often originate from sources beyond your direct control, such as the operating system, network stack, or transport protocol. These failures may include issues like a loss of network connectivity or receiving corrupted or invalid protobuf messages. While they tend to be transient, they must still be reported to the client in a clear and user-friendly manner.

Typically, communication failures are caught and handled by the underlying SiLA framework. However, you should still ensure your connector can recover from such interruptions gracefully and maintain a consistent internal state. For example, if the transport layer fails mid-command, the framework may abort execution, and your connector must avoid side effects from partial execution.

Framework Errors

Framework-level errors result from violations of the SiLA 2 protocol or incorrect client behavior. These may include scenarios where a client sends disallowed metadata or tries to execute a command after the server has explicitly disallowed further execution. In such cases, the SiLA framework automatically detects the violation and raises an appropriate error, requiring little or no intervention from connector developers.

While you do not typically need to handle these errors yourself, understanding their origin can be helpful for diagnosing client-side integration issues. When debugging such problems, check whether the SiLA framework has rejected the request due to a protocol constraint or misuse of the interface.

Validation Errors

Connectors often need to enforce constraints on command input parameters. These constraints can be defined at two levels:

  1. Design-time, using the SiLA framework's built-in constraint system.
  2. Runtime, using application logic that evaluates conditions dynamically.

Design-Time Constraints

Design-time constraints are defined as part of the SiLA interface and automatically enforced by the framework. If a client submits invalid input, a ValidationError is raised with details about the violation.

import typing
from sila.constraints import MinimalInclusive
from unitelabs.cdk import sila

class MyFeature(sila.Feature):
    @sila.UnobservableCommand()
    def my_command(self, my_parameter: typing.Annotated[int, MinimalInclusive(0)]) -> None:
        """
        Execute my command.

        .. parameter:: My constrained input parameter.
        """

        print(my_parameter)

Listing 1: Declaring input parameter constraints at design time so they are exposed via the SiLA interface and visible to clients before command execution.

If the user provides a negative number or omits the parameter, a SiLA ValidationError is raised automatically. These errors are informative and predictable, making them ideal for user guidance.

Runtime Constraints

Sometimes, validation depends on the state or configuration of the connected device. In these cases, you must perform the check manually and raise a ValidationError with a clear explanation.

from sila.errors import ValidationError
from unitelabs.cdk import sila


class MyFeature(sila.Feature):
    async def get_version(self) -> tuple[int, int]:
        return (1, 1)

    @sila.UnobservableCommand()
    async def my_command(self, my_parameter: int) -> None:
        """
        Execute my command.

        .. parameter:: My constrained input parameter. For versions older
          than 2.0, this must be a positive integer.
        """

        version = await self.get_version()

        if version < (2, 0) and my_parameter < 0:
            raise ValidationError(
                message=f"Before version 2.0 `my_parameter` must be a positive integer, received {my_parameter}.",
                parameter=f"{self.fully_qualified_identifier}/Command/MyCommand/Parameter/MyParameter",
            )

        # proceed with validated `my_parameter`

Listing 2: Enforcing input constraints dynamically based on device state or version, when they cannot be expressed in the static SiLA interface.

When constraints cannot be enforced at design time, it becomes your responsibility to clearly document them in the parameter description and raise precise error messages during execution.

Important: If a constraint is not specified in the SiLA interface, users may struggle to understand why their input is rejected. Always document runtime constraints in the parameter description.

Execution Errors

Execution errors are failures that occur at runtime - either during command execution or property evaluation. These may result from hardware malfunctions, device-reported exceptions, or internal logic issues within the connector. The following sections show how to document such errors clearly and how to include meaningful diagnostic information to help users and clients respond appropriately.

SiLA Error Declarations

When you know a specific error might occur during execution, you should define it explicitly in the SiLA interface using the errors argument.

from unitelabs.cdk import sila


class MyError(Exception):
    """My error description."""


class MyFeature(sila.Feature):
    @sila.UnobservableProperty(errors=[ZeroDivisionError])
    async def get_my_property(self) -> float:
        """My property value."""

        return 5 / 0

    @sila.UnobservableCommand(errors=[MyError])
    async def my_command(self) -> None:
        """Execute my command."""

        raise MyError()

Listing 3: Declaring known execution errors in the SiLA interface so that clients can anticipate and handle them proactively.

Note: Only execution errors should be included in a command or property's errors list. Validation errors, even if raised at runtime, must not be listed - since they are not part of the SiLA feature definition and are not exposed in the interface at design time.

Defined Execution Errors

When an exception that is listed in the interface's errors declaration is raised, the client receives a SiLA Defined Execution Error. This error includes a globally unique identifier along with an error message. The message is either the string passed when the exception was raised or, if no message was provided, the docstring of the exception class. Because the error is explicitly defined in the SiLA interface, clients and users can anticipate and handle it in advance.

Undefined Execution Errors

When an exception is raised that is not declared in the interface's errors list, the client receives a SiLA Undefined Execution Error. The message follows the same rules as for defined errors, but no unique identifier is included - only the message is transmitted. Since these errors are not part of the design-time interface, they cannot be anticipated by clients.

Runtime Error Messages

Design-time error descriptions should help users understand why an error might occur and how to avoid it. At runtime, always raise errors with specific and actionable messages.

In the following example, the MyError exception is declared in the SiLA interface for my_command. This means that at design time, the client is already aware that MyError may occur and receives the description "My error description" as guidance for how to handle it. When the error is actually raised at runtime, a more detailed and situation-specific message is provided - "My error occurred and this is how you solve it: ..." - which is then passed to the client to aid in troubleshooting.

from unitelabs.cdk import sila


class MyError(Exception):
    """My error description."""


class MyFeature(sila.Feature):
    @sila.UnobservableCommand(errors=[MyError])
    async def my_command(self) -> None:
        """Execute my command."""

        raise MyError("My error occured and this is how you solve it: ...")

Listing 4: Raising meaningful error messages at runtime to help users understand the cause and resolution of a failure.

Providing clear, actionable error messages both in the interface definition and at runtime improves usability and reduces support overhead.


Copyright © 2025