Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions examples/configs/signal-generator.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
- type: tickit.devices.signal_generator.EpicsSignalGenerator
name: gen
inputs: {}
- type: tickit.devices.sink.Sink
name: sink
inputs:
input:
component: gen
port: value
- type: tickit.devices.sink.Sink
name: sink
inputs:
input:
component: gen
port: gate
97 changes: 96 additions & 1 deletion src/tickit/adapters/epics.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import logging
from abc import abstractmethod
from dataclasses import dataclass
from typing import Any, Callable, Dict, Set
from enum import Enum
from typing import Any, Awaitable, Callable, Dict, Optional, Set, TypeVar

from softioc import asyncio_dispatcher, builder, softioc

Expand All @@ -17,6 +18,8 @@
#: Ids of all adapters currently registered but not ready.
_REGISTERED_ADAPTER_IDS: Set[int] = set()

_REGISTERED_IOC_BACKGROUND_TASKS: Set[Awaitable[None]] = set()

#: Iterator of unique IDs for new adapters
_ID_COUNTER: itertools.count = itertools.count()

Expand All @@ -36,6 +39,9 @@ def register_adapter() -> int:
return adapter_id


def register_background_task(task: Awaitable[None]) -> None:
_REGISTERED_IOC_BACKGROUND_TASKS.add(task)

def notify_adapter_ready(adapter_id: int) -> None:
"""Notify the builder that a particular adapter has made all the records it needs.

Expand Down Expand Up @@ -64,6 +70,13 @@ def _build_and_run_ioc() -> None:
event_loop = asyncio.get_event_loop()
dispatcher = asyncio_dispatcher.AsyncioDispatcher(event_loop)
softioc.iocInit(dispatcher)

async def run_background_tasks() -> None:
if len(_REGISTERED_IOC_BACKGROUND_TASKS) > 0:
await asyncio.wait(_REGISTERED_IOC_BACKGROUND_TASKS)

dispatcher(run_background_tasks)

# dbl directly prints out all record names, so we have to check
# the log level in order to only do it in DEBUG.
if LOGGER.level <= logging.DEBUG:
Expand Down Expand Up @@ -93,6 +106,80 @@ class EpicsAdapter:
interrupt_records: Dict[InputRecord, Callable[[], Any]] = {}
interrupt: RaiseInterrupt

def float_rbv(
self,
name: str,
getter: Callable[[], float],
setter: Callable[[float], None],
rbv_name: Optional[str] = None,
):
rbv_name = rbv_name or f"{name}_RBV"
builder.aOut(
name,
initial_value=getter(),
on_update=self.interrupting_callback(setter),
)
rbv = builder.aIn(rbv_name, initial_value=getter())
self.link_input_on_interrupt(rbv, getter)

def int_rbv(
self,
name: str,
getter: Callable[[], int],
setter: Callable[[int], None],
rbv_name: Optional[str] = None,
):
rbv_name = rbv_name or f"{name}_RBV"
builder.mbbOut(
name,
initial_value=getter(),
on_update=self.interrupting_callback(setter),
)
rbv = builder.mbbIn(rbv_name, initial_value=getter())
self.link_input_on_interrupt(rbv, getter)

def bool_rbv(
self,
name: str,
getter: Callable[[], bool],
setter: Callable[[bool], None],
rbv_name: Optional[str] = None,
):
rbv_name = rbv_name or f"{name}_RBV"
builder.boolOut(
name,
initial_value=getter(),
on_update=self.interrupting_callback(setter),
)
rbv = builder.boolIn(rbv_name, initial_value=getter())
self.link_input_on_interrupt(rbv, getter)

def bool_rbv(
self,
name: str,
getter: Callable[[], bool],
setter: Callable[[bool], None],
rbv_name: Optional[str] = None,
):
rbv_name = rbv_name or f"{name}_RBV"
builder.boolOut(
name,
initial_value=getter(),
on_update=self.interrupting_callback(setter),
)
rbv = builder.boolIn(rbv_name, initial_value=getter())
self.link_input_on_interrupt(rbv, getter)


def interrupting_callback(
self, action: Callable[[Any], None]
) -> Callable[[Any], Awaitable[None]]:
async def callback(value: Any) -> None:
action(value)
await self.interrupt()

return callback

def link_input_on_interrupt(
self, record: InputRecord, getter: Callable[[], Any]
) -> None:
Expand All @@ -115,3 +202,11 @@ def after_update(self) -> None:
current_value = getter()
record.set(current_value)
print(f"Record {record.name} updated to : {current_value}")

def polling_interrupt(self, interval: float) -> None:
async def polling_task() -> None:
while True:
await asyncio.sleep(interval)
await self.interrupt()

register_background_task(polling_task())
Loading