base#

Authorable base classes for non-Qt hardware devices.

Layered hierarchy that lets a user write a new device with as little boilerplate as possible:

BaseDevice

Lifecycle skeleton, self.signals (DeviceSignals), logger, cfg storage, is_primary / is_running tracking, default status/metadata.

BaseDataProducer(BaseDevice)

Adds the DataProducer surface: output_path/metadata_path, a thread-safe in-memory buffer, a record(payload, ts=None) helper that timestamps + buffers + emits signals.data in one call, and a default CSV save_data / get_data.

BaseSerialDevice(BaseDataProducer)

Specialization for line-oriented serial protocols (Teensy / Arduino). Owns its own daemon read thread; subclasses only need to override parse_line(line). A development_mode flag skips opening the port so authors can iterate without hardware connected.

Qt devices (QThread subclasses) cannot inherit from these due to metaclass conflicts; they continue to duck-type the contract by instantiating self.signals = DeviceSignals() directly.

class mesofield.devices.base.BaseDevice[source]#

Bases: object

Default lifecycle skeleton for non-Qt hardware devices.

Constructor accepts an optional cfg mapping (the YAML stanza for this device). Common keys are auto-extracted:

  • id / device_id -> self.device_id

  • primary: true -> self.is_primary

A logger is created automatically as f"{module}.{class}[{device_id}]".

__init__(cfg=None, **kwargs)[source]#
Parameters:
Return type:

None

arm(config)[source]#

Per-run preparation. No-op by default.

Parameters:

config (ExperimentConfig)

Return type:

None

property calibration: Dict[str, Any]#

Device-specific constants worth recording with the data.

Default: everything in cfg that isn’t an orchestration key. Override on a subclass to curate the list explicitly.

sidecars()[source]#

Auxiliary files this device writes alongside its primary output.

Default: none. Override to declare extra sidecars (masks, regions, derived parameter files) so they ride in the manifest with a role and schema_version instead of being discovered by glob.

The camera classes’ per-frame metadata JSON is the primary sidecar and lives on self.metadata_path – not here. Use this method for the extra files only.

Returns a list of mesokit_schema.SidecarEntry-shaped mappings or instances. The Procedure relativises any absolute paths.

Return type:

list

class mesofield.devices.base.BaseDataProducer[source]#

Bases: BaseDevice

Base class for devices that stream samples to the DataQueue.

Subclasses produce data by calling record(), which timestamps, appends to an in-memory buffer, and emits signals.data(payload, ts) in one step.

The default save_data() writes the buffer as a two-column CSV (timestamp,payload). Override for binary or domain-specific formats.

__init__(cfg=None, **kwargs)[source]#
Parameters:
Return type:

None

record(payload, ts=None)[source]#

Buffer payload and emit signals.data.

Returns the timestamp used.

Parameters:
Return type:

float

arm(config)[source]#

Default arm: clear buffer and resolve output_path.

config is expected to expose make_path(name, ext, bids) (see mesofield.config.ExperimentConfig).

Parameters:

config (ExperimentConfig)

Return type:

None

class mesofield.devices.base.BaseSerialDevice[source]#

Bases: BaseDataProducer

Polling device for line-based serial protocols (Arduino/Teensy/etc.).

Subclasses override parse_line(). Optionally override setup_serial() for post-open initialisation (handshakes, buffer drain, configuring device-side parameters).

Configuration keys read from cfg:

  • port (str, required when development_mode=False)

  • baudrate (int, default 115200)

  • timeout (float, default 0.1) — pyserial readline timeout.

  • dtr (bool | None, default None) — set to False to suppress Arduino auto-reset on connect. None keeps the OS default.

  • connect_delay (float, default 0.0) — seconds to wait after opening the port before reads begin; common Arduinos need ~2.0. The input buffer is flushed after the delay.

  • development_mode (bool, default False) — skip opening the port so the GUI / Procedure can launch without hardware. send_line becomes a no-op; the polling thread idles.

__init__(cfg=None, **kwargs)[source]#
Parameters:
Return type:

None

parse_line(line)[source]#

Decode one raw serial line into (payload, ts) or None.

Parameters:

line (bytes)

Return type:

Tuple[Any, float | None] | None

setup_serial()[source]#

Hook called once after the port is opened. Default no-op.

Override to send a handshake, query firmware version, configure device-side parameters, etc.

Return type:

None

send_line(payload, *, newline=b'\n')[source]#

Write a command to the device. Thread-safe with the reader.

Accepts str (UTF-8 encoded) or bytes. newline is appended unless payload already ends with it. In development_mode the bytes are logged and discarded. Returns the bytes that were written (or would have been).

Parameters:
Return type:

bytes