Error Handling
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:
- Design-time, using the SiLA framework's built-in constraint system.
- 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.
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.
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.