# 19. SoftSignalBackend Wrapping Arbitrary Callables ## **Status** Accepted ## **Context** Users working outside of EPICS/Tango ecosystems such as those wrapping third-party Python APIs, calling analysis scripts, or interfacing with devices with their own Python drivers, currently have no supported path to create signals without writing a full custom `SignalBackend`. This was identified as a friction point for smaller lab-based groups during the Bluesky community workshop. To address this, `SoftSignalBackend` was extended to support arbitrary callables for getting, setting, and polling values. This allows users to integrate external systems without implementing a full backend, reducing boilerplate and improving usability. ## **Decision** ### **Extend `SoftSignalBackend` with Callable Support** `SoftSignalBackend` was augmented with three new **keyword-only** parameters: 1. **`getter`**: A callable (`Callable[[], T | Awaitable[T]]`) invoked during `get_value()` and `get_reading()` to fetch the current value from an external source. If `poll_period` is set, the `getter` is called periodically while a subscription is active. 2. **`setter`**: A callable (`Callable[[SignalDatatypeT], SignalDatatypeT | None | Awaitable[SignalDatatypeT | None]]`) invoked during `put()`. It may return a `SignalDatatypeT`; if it returns `None`, the `getter` (if configured) is called immediately to refresh the cache. 3. **`poll_period`**: A float representing the interval (in seconds) at which the `getter` is polled while a subscription is active. Requires `getter` to be set. ### **Design Choices** - All three parameters are **optional**. If none are provided, behavior remains identical to the existing `SoftSignalBackend`. - The internal `self._reading` store remains the **single source of truth**. The `getter` updates this store rather than bypassing it, preserving coherence for subscriptions and cached reads. - The `put` method accepts `SignalDatatypeT` (the same type as the signal's stored value) to maintain type safety and consistency. - Polling tasks are used for subscriptions, starting in `set_callback` and canceling when subscriptions end. - `get_setpoint()` **does not invoke the `getter`**; it returns the last value written to the `setter` or the initial value of. ### **Factory Function Updates** The convenience functions `soft_signal_rw` and `soft_signal_r_and_setter` were updated to accept `getter`, and `poll_period` arguments. `soft_signal_rw` additionally accepts a `setter` argument. These additional arguments are passed to `SoftSignalBackend`. ## **Consequences** **Improved Usability**: Non-EPICS/Tango users can now easily integrate external systems (e.g., Python APIs, scripts) without writing full backends. **No Breaking Changes**: Existing code continues to work unchanged since all new parameters are optional. **Consistent Behavior**: Polling, caching, and subscriptions align with EPICS/Tango backend patterns.