Instrument Interfaces
Reads & Instrument Measurements describes the capability check, which CHESSCore performs in memory with no persistence. This page describes how CHESSDatabase records which instrument performed a persisted operation.
Package responsibilities
CHESSCore owns instrument capability, meaning performable_operations and the check that uses it. It has no concept of a database, an InstrumentID column, or a ledger. CHESSDatabase has no capability logic. It never checks performable_operations. It records which instrument performed an operation, in the InstrumentID and InstrumentTime columns of Transfers, Movements, EnvironmentAttributes, and Reads.
The instrument argument
upload takes fun, args...; instrument=... and is where the two packages meet. The same instrument value is used twice in one call, for two unrelated purposes:
function upload(fun::Function, args...; instrument=nothing, kwargs...)
...
instrument_id = isnothing(instrument) ? nothing : location_id(instrument)
...
fun(args...; instrument=instrument) # gates capability -- inside CHESSCore
up_fun(args...; instrument_id=instrument_id, ...) # records attribution -- inside CHESSDatabase
...
endfun(args...; instrument=instrument) runs the CHESSCore operation, which checks capability. If the instrument cannot perform fun, the call throws ArgumentError. The in-memory change and the database write are one step (see Committing & Uploading), so nothing is written and no InstrumentID is recorded. Separately, upload computes location_id(instrument) and passes it to the matching upload_* function, which writes the row.
The examples use a new database with a plate, CHESS's Epoch2 plate reader, and an Autoclave, which cannot record reads:
julia> path = joinpath(mktempdir(), "lab.db");
julia> create_db(path);
julia> connect_SQLite(path)
julia> well = generate_location(loc"WP96", "Plate 1")["A1"];
julia> reader = generate_location(loc"Epoch2", "Reader 1");
julia> autoclave = generate_location(loc"Autoclave", "Autoclave 1");
julia> upload(record_read!, well, read"Fluorescence"(50u"RFU"); instrument=autoclave)
ERROR: ArgumentError: Autoclave 1 cannot perform record_read!The check covers only performable_operations. The readable_types field is descriptive and is not enforced (see Reads & Instrument Measurements), so a capable instrument can record any registered ReadKind. Epoch2 lists only :Absorbance and :Fluorescence, but it can still record a free-text note:
julia> upload(record_read!, well, read"Fluorescence"(50u"RFU"); instrument=reader)
3
julia> @read ReaderNote
ReadKind(ReaderNote)
julia> upload(record_read!, well, ReaderNote("condensation on lid"); instrument=reader)
4Instrument settings
get_instrument_settings takes instrument_id, sequence_id=..., time=.... It is unrelated to capability. It returns the configuration of one instrument, which changes over time, as free-text Setting and Value pairs such as "Gain" and "2.0". Each setting holds its latest value, like an Attribute and unlike the accumulating history of Reads:
julia> upload_instrument_setting(reader, "Gain", 1.5);
julia> upload_instrument_setting(reader, "Gain", 2.0);
julia> get_instrument_settings(CHESSCore.location_id(reader))
1×3 DataFrame
Row │ Setting Value SequenceID
│ String String Int64
─────┼─────────────────────────────
1 │ Gain 2.0 6performable_operations, actuatable_attributes, and readable_types are fixed capability data on a LocationKind. Only the first is checked at call time, and none is persisted as changing state. InstrumentSettings is the opposite: it is persisted in ledger order and has no capability meaning.
Interop describes the formats that CHESSCore uses to exchange Location and Stock data with tools outside CHESS.