The camera keywords every camerad camera has, as a service object any libby daemon can compose.
One process owns one camera, holding its controller connection in process
through the camera_interface extension by way of
pycamerad. There is no camerad
process in the loop.
Each project has its own libby base that fixes the transport and broker, such
as HispecDaemon or Lris2Daemon, so a shared daemon class would have to pick
one. CameraService has no transport opinion and is composed instead.
from camera_daemon import CameraService
class CameraDaemon(MyProjectDaemon):
"""Daemon for one camerad camera."""
def __init__(self):
super().__init__()
self.camera = None
self.instrument = None
self.service = CameraService(self)
def on_start(self, _libby):
self.instrument = self._load_instrument()
self.service.register_keywords(self.keyword_registry)
if self.instrument is not None:
self.instrument.register_keywords(self.keyword_registry)
self.service.connect()
def on_stop(self, _libby=None):
self.service.on_stop()The instrument is loaded by the project, because it resolves a dotted path into
the project's own namespace. It must be in place before connect(), which
reads camera_class from it.
The service reads four attributes from the daemon it is given and writes one:
| Attribute | Direction | Purpose |
|---|---|---|
get_config |
read | camera.config_file and camera.nframes |
logger |
read | hardware failures are logged, not raised, so the daemon still serves |
keyword_registry |
read | where keywords are registered |
instrument |
read | supplies camera_class; may be None |
camera |
write | the built camera, so instrument objects can reach it |
camera is assigned on the host rather than kept private so an instrument
object reads self.daemon.camera, needing no knowledge of the service.
| Keyword | Type | Access |
|---|---|---|
state |
string | R |
isconnected |
bool | R |
isopen |
bool | R/W |
ispowered |
bool | R/W |
initialize |
trigger | W |
loadfirmware |
trigger | W |
readacf |
trigger | W |
exptime |
float, s | R/W |
nframes |
int | R/W |
datacube |
bool | R/W |
expose |
trigger | W |
abort |
trigger | W |
lastframe |
string | R |
state is one of nocamera, idle, exposing, aborting or error.
expose returns at once and runs on a worker thread, so a long run does not
block the daemon; watch state for progress.
Instrument-specific keywords, such as readout modes and subframe geometry, are registered by the instrument object, not here.
A camera that is absent, unreachable or uninitialised does not stop the daemon:
connect() logs the failure and state stays nocamera, so keywords are still
served. Hardware initialisation happens only through the initialize trigger.
initialize gets a 300 second timeout, since it opens the controller, loads
firmware and powers the detector.
The three steps are separable, which matters for a cryostat. Writing isopen
opens the link alone, enough to read module-specific keywords such as
temperatures with the detector unpowered. loadfirmware writes the ACF to the
controller without powering it, which a keyword write needs, since a write
patches a single configuration line and the controller has to hold the
configuration camerad parsed. readacf parses the ACF into the host without
writing the controller, for recovering after a restart when the controller is
already configured.
BiasService and HeaterService are separate, and composed the same way. They
are Archon-specific, and every channel is declared in the host's config, so no
module number or label is assumed:
biases:
- {module: 9, channel: 4, keyword: hvxbiasoutputdraina}
- {module: 4, channel: 28, keyword: lvxbiasccdplus5v, maxvolts: 5.0}
heaters:
- {module: 11, channel: A, keyword: heaterxdetheater}
sensors:
- {module: 11, channel: A, keyword: heaterxdettemp}Each bias channel gets its voltage, order, label, enable and current limit.
Which attributes a channel actually has depends on its bank, so enable and
ilim are nullable and read null where the bank carries none. minvolts and
maxvolts are optional; a write past either is refused before it reaches the
controller.
A bias write applies that module alone and never the whole system, so it does not disturb the detector's power. The controller does refuse to apply a bias change while the detector is unpowered, so a write needs power on to take effect.