Source code for resonance.api.core.dio
from __future__ import annotations
from typing import TYPE_CHECKING, get_args
from resonance.api.types import DIO, ShutterError
if TYPE_CHECKING:
from bcs import BCSz
_valid_channels: tuple[str, ...] = get_args(DIO.__value__)
__all__ = ["DIOAccessor"]
[docs]
class DIOAccessor:
"""
Async interface for reading and writing BCS digital I/O channels.
Parameters
----------
conn : BCSz.BCSServer
Active BCSz server connection.
Notes
-----
Wraps BCSz digital I/O channels. `read` reads digital inputs; `set` writes
digital outputs. The shutter channel ("Light Output", "Shutter Output", etc.)
is the most common use case. Values are always coerced to bool.
Examples
--------
>>> state = await bl.dio.read("Shutter Output", "Light Output")
>>> print(state)
{'Shutter Output': True, 'Light Output': True}
>>> await bl.dio.set("Shutter Output", False)
>>> await bl.dio.set("Light Output", True)
>>> state = await bl.dio.read("Shutter Output", "Light Output")
>>> print(state)
{'Shutter Output': False, 'Light Output': True}
"""
def __init__(self, conn: BCSz.BCSServer) -> None:
self._conn = conn
[docs]
async def read(self, *channels: str) -> dict[str, bool]:
"""
Read current state of one or more digital I/O channels.
Parameters
----------
*channels : str
One or more DIO channel names to read. Must be members of the
``DIO`` literal type.
Returns
-------
dict[str, bool]
Mapping of channel name to its current boolean state.
Raises
------
KeyError
If any channel name is not a valid DIO channel.
Notes
-----
Channels are read in a single BCSz ``get_di`` call. The response
``data`` field is coerced to bool for each channel.
"""
invalid = [c for c in channels if c not in _valid_channels]
if invalid:
raise KeyError(
f"Unknown DIO channel(s): {invalid}. Valid channels: {list(_valid_channels)}"
)
response: dict = await self._conn.get_di(chans=list(channels))
return {
chan: bool(data) for chan, data in zip(response["chans"], response["data"])
}
[docs]
async def set(self, channel: str, value: bool | int) -> None:
"""
Set a digital output channel to True (on) or False (off).
Parameters
----------
channel : str
DIO channel name to write. Must be a member of the ``DIO`` literal type.
value : bool or int
Output value to set. Non-zero integers map to True, zero maps to False.
Returns
-------
None
Raises
------
KeyError
If ``channel`` is not a valid DIO channel name.
ValueError
If ``value`` is not a bool or int (e.g. a float is passed).
ShutterError
If the BCSz ``set_do`` call raises an exception.
Notes
-----
Value is coerced to bool: any non-zero int is True, 0 is False.
Typical use is controlling the beamline shutter (e.g. channel="Light Output").
"""
if channel not in _valid_channels:
raise KeyError(
f"Unknown DIO channel: {channel!r}. Valid channels: {list(_valid_channels)}"
)
if not isinstance(value, bool | int):
raise ValueError(f"value must be bool or int, got {type(value).__name__!r}")
bool_value = bool(value)
try:
await self._conn.set_do(chan=channel, value=bool_value)
except Exception as exc:
raise ShutterError(
f"Failed to set DIO channel {channel!r} to {bool_value}: {exc}"
) from exc
# TODO: add set_many for batch digital output (e.g. enable multiple outputs atomically)