484 lines
24 KiB
Markdown
484 lines
24 KiB
Markdown
# Byonoy Device Library — SDK guide
|
||||
|
|
|
|||
|
|
How to obtain and safely use the Byonoy device SDKs. Start here, then follow the
|
|||
|
|
link for the form you need:
|
|||
|
|
|
|||
|
|
- **[Python SDK](SDK_PYTHON.md)** — a wheel you `import`. Scripting, tests, automation.
|
|||
|
|
- **[Native SDK](SDK_NATIVE.md)** — headers and a shared library. C, C++, or any FFI.
|
|||
|
|
|
|||
|
|
Both drive the same library and the same devices.
|
|||
|
|
|
|||
|
|
These documents are deliberately explicit: they spell out defaults, return
|
|||
|
|
conventions and failure modes that are easy to get wrong, so that they can be
|
|||
|
|
followed without any other context — whether you are a developer integrating a
|
|||
|
|
device or a coding agent writing code against it. If something you need is not
|
|||
|
|
covered here, ask Byonoy rather than inferring it.
|
|||
|
|
|
|||
|
|
> **Manual Absorbance 96: use the serial protocol, not this SDK.** The manual
|
|||
|
|
> variant of Absorbance 96 is driven **only** through a plain-text serial
|
|||
|
|
> protocol, documented publicly in
|
|||
|
|
> **[abs96serial](https://git.byonoy.com/public/abs96serial)**; the SDK does not
|
|||
|
|
> support it. That protocol is used by no other Byonoy device — the Absorbance
|
|||
|
|
> 96 Automate and every other device are driven through the SDK described here.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. Which variant: public or internal
|
|||
|
|
|
|||
|
|
The library ships in two variants.
|
|||
|
|
|
|||
|
|
**The public variant is what is generally available outside Byonoy, and it is
|
|||
|
|
enough for everyday operation.** It covers the whole normal working life of an
|
|||
|
|
instrument:
|
|||
|
|
|
|||
|
|
- discovering connected devices and opening them
|
|||
|
|
- device information, status, errors, temperature, humidity, uptime, slot and
|
|||
|
|
alignment state
|
|||
|
|
- every measurement modality the hardware offers — absorbance (Absorbance One
|
|||
|
|
and Absorbance 96 Automate), luminescence, fluorescence
|
|||
|
|
- firmware update
|
|||
|
|
|
|||
|
|
If your job is "talk to the instrument and take readings", the public variant is
|
|||
|
|
the right answer and the rest of this section does not apply to you.
|
|||
|
|
|
|||
|
|
**The internal variant is available on request.** Ask Byonoy for it if you run
|
|||
|
|
into a specific limitation the public variant cannot express — not as a default
|
|||
|
|
choice. It is a narrower, less travelled path, and its extra surface can change
|
|||
|
|
without the consideration given to the public API.
|
|||
|
|
|
|||
|
|
What it adds is described where it is relevant: see the internal chapter of the
|
|||
|
|
[Python](SDK_PYTHON.md) or [Native](SDK_NATIVE.md) document.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. How to get it
|
|||
|
|
|
|||
|
|
| What | How |
|
|||
|
|
|---|---|
|
|||
|
|
| **Public Python wheel** | Self-serve, no credentials — the public package registry |
|
|||
|
|
| **Internal Python wheel** | On request from Byonoy; installed from the internal registry with a token |
|
|||
|
|
| **Native SDK, either variant** | On request from Byonoy |
|
|||
|
|
|
|||
|
|
Only the public Python wheel is self-serve today. The native archive is not
|
|||
|
|
currently published to a location you can reach without Byonoy handing it to
|
|||
|
|
you, whichever variant you need.
|
|||
|
|
|
|||
|
|
The per-form documents cover each of these in detail.
|
|||
|
|
|
|||
|
|
### Credentials
|
|||
|
|
|
|||
|
|
Nothing you install anonymously needs a token. For anything Byonoy provides from
|
|||
|
|
the internal registry you will be given a `git.byonoy.com` account or token; the
|
|||
|
|
per-form documents show where it goes. Export it as:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
export BYONOY_USER='<your gitea username>'
|
|||
|
|
export BYONOY_TOKEN='<your gitea token>'
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```powershell
|
|||
|
|
$env:BYONOY_USER='<your gitea username>'
|
|||
|
|
$env:BYONOY_TOKEN='<your gitea token>'
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Commands in these documents are written for **bash** (Linux, macOS). Where
|
|||
|
|
Windows differs, the Python document gives the PowerShell equivalent.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. How the library works
|
|||
|
|
|
|||
|
|
These apply to both forms and both variants. The per-form documents show the
|
|||
|
|
code; this is the behaviour behind it.
|
|||
|
|
|
|||
|
|
**A device must be physically attached over USB.** There is no simulator. An
|
|||
|
|
empty device list is the normal answer when nothing is plugged in, not an error.
|
|||
|
|
For prototyping in Python, a small mock can stand in for a device — see
|
|||
|
|
[Prototyping without a device](SDK_PYTHON.md#7-prototyping-without-a-device).
|
|||
|
|
|
|||
|
|
**The lifecycle is discover → open → use → close.** Opening gives you a handle;
|
|||
|
|
everything else takes that handle; closing invalidates it. Using a closed handle
|
|||
|
|
returns `INVALID_ARGUMENT`, not `DEVICE_CLOSED`.
|
|||
|
|
|
|||
|
|
**Check every return code.** Almost every call reports one. A value handed back
|
|||
|
|
alongside a non-`NO_ERROR` code is meaningless — and it is not empty: a failed
|
|||
|
|
96-well measurement still hands back 96 zeros, so testing the result's length
|
|||
|
|
or truthiness does not tell you it worked.
|
|||
|
|
|
|||
|
|
**Ask what the device supports before asking it to do something.** Each
|
|||
|
|
modality and feature has a `*_supported` predicate; calling something the device
|
|||
|
|
does not support gives `UNSUPPORTED_OPERATION`. Do not branch on the model name.
|
|||
|
|
|
|||
|
|
- **Decide the modality from the `*_measurement_supported` predicates only.**
|
|||
|
|
Helper predicates can answer `True` on a device that cannot perform the
|
|||
|
|
modality: an Absorbance One reports `abs96_available_wavelengths_supported`
|
|||
|
|
as `True`, and an Absorbance 96 Automate reports
|
|||
|
|
`absone_available_wavelength_supported` as `True`, while the corresponding
|
|||
|
|
`*_measurement_supported` is `False`.
|
|||
|
|
- `abs96_modules_supported`, `abs96_get_modules` and `abs96_setup_modules` concern
|
|||
|
|
the Absorbance 96's wavelength modules and are not needed for measuring. Do
|
|||
|
|
not call `abs96_setup_modules` unless Byonoy asks you to.
|
|||
|
|
- **Discovery and device information can name different types.** Discovery
|
|||
|
|
(`available_devices()`) reports the type from the USB IDs alone, and every
|
|||
|
|
Absorbance One is reported there as `AbsorbanceOneOr96`. Once the device is
|
|||
|
|
open, `get_device_information()` reports what it actually is
|
|||
|
|
(`AbsorbanceOne`). Treat the latter as authoritative.
|
|||
|
|
|
|||
|
|
**One open handle per device.** What happens when you try a second one
|
|||
|
|
depends on who holds the first:
|
|||
|
|
|
|||
|
|
- another handle in **your own process** → `DEVICE_ALREADY_OPEN` (`0x0104`)
|
|||
|
|
- the device held by **another process**, on Linux and macOS →
|
|||
|
|
`DEVICE_COMMUNICATION_FAILURE` (`0x0006`)
|
|||
|
|
- the device held by **another process, on Windows** → the second open
|
|||
|
|
**succeeds**, and both processes can talk to the device at once. Nothing
|
|||
|
|
stops them interleaving commands. On Windows, making sure only one program
|
|||
|
|
uses a device is your job.
|
|||
|
|
|
|||
|
|
`0x0006` does not name its cause. Before treating it as a hardware fault, check
|
|||
|
|
the usual ones: another program — a stray REPL, a previous run, another tool —
|
|||
|
|
still has the device open; on Linux, the device is not accessible to your user
|
|||
|
|
(see [Linux permissions](#linux-permissions)); on macOS, nobody is logged in at
|
|||
|
|
the machine (see below).
|
|||
|
|
|
|||
|
|
**Measurements block, and can take a long time.** Ctrl-C will not interrupt a
|
|||
|
|
measurement in progress — SIGINT is ignored while the call is inside the
|
|||
|
|
library. Size timeouts from the mode you actually use.
|
|||
|
|
|
|||
|
|
The luminescence and fluorescence modes set an integration time:
|
|||
|
|
`RAPID` 100 ms, `SENSITIVE` 2 s, `ULTRA_SENSITIVE` 20 s, or
|
|||
|
|
`custom_integration_time_ms` for `CUSTOM` (at least 50). How that turns into a
|
|||
|
|
duration differs between the two devices.
|
|||
|
|
|
|||
|
|
**Luminescence 96** reads the plate through 16 detector channels of 6 wells
|
|||
|
|
each, one channel after another, and spends the integration time — but at
|
|||
|
|
least 300 ms — on each channel in use. With the device's sampling overhead a
|
|||
|
|
run takes roughly *(channels used) × max(integration time, 300 ms) × 1.9*, so a
|
|||
|
|
full plate is about 30 × the integration time, and `RAPID` is limited by the
|
|||
|
|
300 ms floor. A channel counts as used when any of its 6 wells is selected;
|
|||
|
|
which wells share a channel is fixed by the hardware, so selecting fewer wells
|
|||
|
|
saves time only when it frees whole channels:
|
|||
|
|
|
|||
|
|
| Mode | Integration time | Full plate |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `RAPID` | 100 ms | ~10 s |
|
|||
|
|
| `SENSITIVE` | 2 s | ~60 s |
|
|||
|
|
| `ULTRA_SENSITIVE` | 20 s | **~10 min** |
|
|||
|
|
| `CUSTOM` | `custom_integration_time_ms`, at least 50 | ~30 × that, plus overhead |
|
|||
|
|
|
|||
|
|
**Fluorescence 96** steps across the plate one column at a time and measures
|
|||
|
|
every column that contains **at least one selected well**; columns with no
|
|||
|
|
selected well are skipped entirely. A run therefore takes roughly
|
|||
|
|
*(number of columns used) × (time per column)*, plus a short start-up, and the
|
|||
|
|
time per column grows with the integration time. Deselecting wells within a
|
|||
|
|
column that is measured anyway saves nothing — to shorten a run, leave out
|
|||
|
|
whole columns. The exact time per column depends on the device's calibration,
|
|||
|
|
so it differs between units.
|
|||
|
|
|
|||
|
|
**Absorbance:**
|
|||
|
|
|
|||
|
|
| Device | Call | Duration |
|
|||
|
|
|---|---|---|
|
|||
|
|
| Absorbance 96 Automate | initialise, one wavelength | 1.3–2 s (~2.7 s with a reference wavelength) |
|
|||
|
|
| Absorbance 96 Automate | initialise, two wavelengths | ~3 s |
|
|||
|
|
| Absorbance 96 Automate | measure, one wavelength | ~1.2 s (~0.7 s in rapid mode) |
|
|||
|
|
| Absorbance 96 Automate | measure, two wavelengths | ~2.8 s |
|
|||
|
|
| Absorbance One | measure | under 5 s |
|
|||
|
|
|
|||
|
|
- **Do not stop a long measurement early.** An `ULTRA_SENSITIVE` read of a full
|
|||
|
|
plate takes about 10 minutes. On a test unit, one that was ended after 9
|
|||
|
|
minutes left the device unable to measure until it was power-cycled (see
|
|||
|
|
[When the device stops measuring](#when-the-device-stops-measuring)). If
|
|||
|
|
10 minutes per read is too long for you, use `SENSITIVE` or select fewer
|
|||
|
|
wells.
|
|||
|
|
- **`CUSTOM` is rejected below 50 ms** (`INVALID_ARGUMENT`). There is no upper
|
|||
|
|
limit — the device splits a long integration into shorter steps — and the
|
|||
|
|
duration follows the formulas above.
|
|||
|
|
- **A measurement can block indefinitely instead of failing.** On an Absorbance
|
|||
|
|
One in an error state, `absone_measure` never returned — without an
|
|||
|
|
initialise it did **not** return `NOT_INITIALIZED` — whether or not
|
|||
|
|
initialisation had been attempted. Treat it as a rule: call `absone_measure`
|
|||
|
|
only when the device status is `OK` and `absone_is_initialized` reports
|
|||
|
|
`True` (see [Device status and device error](#device-status-and-device-error)).
|
|||
|
|
|
|||
|
|
**Stopping a blocked measurement is a last resort.** The only way out of a call
|
|||
|
|
that does not return is to end the process; the library's own internal waits
|
|||
|
|
do not make it return. Ending the process works — SIGTERM or SIGKILL on Linux
|
|||
|
|
and macOS, `Stop-Process` on Windows. The library gets no chance to
|
|||
|
|
clean up, and the device may be left unable to measure, or may reset itself
|
|||
|
|
— dropping off USB and re-enumerating a few seconds to a minute later. Plan for
|
|||
|
|
a replug afterwards. In a virtual machine with USB passthrough, a device that
|
|||
|
|
re-enumerates comes back to the **host**, not the VM, and has to be attached
|
|||
|
|
to the VM again. If you run unattended, run each
|
|||
|
|
measurement in a child process under a timeout (the Python document shows a
|
|||
|
|
portable way) so that a hang costs you the measurement, not the whole program.
|
|||
|
|
|
|||
|
|
**The library does not check for a plate,** and no device refuses to measure
|
|||
|
|
without one. Absorbance measurements on an empty slot return `NO_ERROR` and
|
|||
|
|
near-zero values that mean nothing; a Luminescence 96 cannot even tell whether
|
|||
|
|
a plate is inserted, and without one returns background noise around zero.
|
|||
|
|
Where the device can sense the slot, checking it before measuring is your job;
|
|||
|
|
the complete Python example shows how.
|
|||
|
|
|
|||
|
|
**A measurement returns a fixed-size result.** A 96-well read gives 96 values
|
|||
|
|
whatever you selected; index `i` of the result corresponds to index `i` of your
|
|||
|
|
selection. Deselecting wells does not shorten the list.
|
|||
|
|
|
|||
|
|
### Well order
|
|||
|
|
|
|||
|
|
**Results are in row-major order.** On a 96-well plate (8 rows × 12 columns),
|
|||
|
|
index `i` is row `i // 12` and column `i % 12`:
|
|||
|
|
|
|||
|
|
| Index | 0 | 1 | … | 11 | 12 | … | 95 |
|
|||
|
|
|---|---|---|---|---|---|---|---|
|
|||
|
|
| Well | A1 | A2 | … | A12 | B1 | … | H12 |
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
def well_name(i, columns=12):
|
|||
|
|
return f"{chr(ord('A') + i // columns)}{i % columns + 1}"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
The same order applies to `selected_wells`: entry `i` selects well `i` in the
|
|||
|
|
table above. It holds for every 96-well modality (absorbance, luminescence,
|
|||
|
|
fluorescence). The device sends one plate row at a time and nothing between it
|
|||
|
|
and your code reorders the values, so the order is fixed by the plate, not by
|
|||
|
|
the readout.
|
|||
|
|
|
|||
|
|
- **Bottom readout needs no correction.** A device that reads the plate from
|
|||
|
|
below mirrors the data itself before sending it. The result is already in
|
|||
|
|
plate orientation; do not flip it again because `is_bottom_readout` is set.
|
|||
|
|
|
|||
|
|
### Deselected wells
|
|||
|
|
|
|||
|
|
A deselected well keeps its place in the result, but what it contains differs
|
|||
|
|
by modality — do not treat it as a reading:
|
|||
|
|
|
|||
|
|
| Modality | Deselected well contains |
|
|||
|
|
|---|---|
|
|||
|
|
| Luminescence 96 | `0` |
|
|||
|
|
| Fluorescence 96 | `NaN` |
|
|||
|
|
| Absorbance 96 Automate | no well selection; every well is measured |
|
|||
|
|
|
|||
|
|
Because Luminescence 96 reports deselected wells as `0`, you cannot tell one
|
|||
|
|
from a genuine dark reading by value. Keep your own selection and use it to
|
|||
|
|
filter the result.
|
|||
|
|
|
|||
|
|
### Units
|
|||
|
|
|
|||
|
|
| Modality | Unit | Meaning |
|
|||
|
|
|---|---|---|
|
|||
|
|
| Absorbance (Absorbance 96 Automate, Absorbance One) | OD — optical density | how much light the sample absorbs, on a log₁₀ scale: 0 lets all light through, 1 lets 10 % through, 2 lets 1 % through |
|
|||
|
|
| Luminescence | RLU — relative light units | how much light the sample emits, on the instrument's own scale |
|
|||
|
|
| Fluorescence | RFU — relative fluorescence units | how much light the sample emits when excited, on the instrument's own scale |
|
|||
|
|
|
|||
|
|
RLU and RFU are not calibrated physical units: compare readings taken on the
|
|||
|
|
same device, in the same mode (and for fluorescence, the same filter set),
|
|||
|
|
rather than across devices or settings.
|
|||
|
|
|
|||
|
|
### Device status and device error
|
|||
|
|
|
|||
|
|
Two calls describe the device's own health, separately from the return code of
|
|||
|
|
any single call:
|
|||
|
|
|
|||
|
|
**`get_device_status`** asks the device and gives a state:
|
|||
|
|
|
|||
|
|
| State | Meaning |
|
|||
|
|
|---|---|
|
|||
|
|
| `OK` | the device reports no error |
|
|||
|
|
| `ERROR` | the device reports an error — see `get_device_error` |
|
|||
|
|
| `BROKEN_FW` | the firmware is corrupted or unidentifiable; the device needs a firmware update |
|
|||
|
|
| `UNKNOWN` | the device did not answer the status request |
|
|||
|
|
|
|||
|
|
Anything but `OK` means: do not measure.
|
|||
|
|
|
|||
|
|
**`get_device_error`** gives the last error recorded for the device, from a
|
|||
|
|
small set shared by all devices. It does not ask the device itself — call
|
|||
|
|
`get_device_status` first for a current value.
|
|||
|
|
|
|||
|
|
| Device error | Meaning |
|
|||
|
|
|---|---|
|
|||
|
|
| `0` | no error |
|
|||
|
|
| `0x8001` (32769) | the device reported an error of its own — the common case; see below |
|
|||
|
|
| `0x8004` (32772) | the device did not respond |
|
|||
|
|
| `0x8009` (32777) | the device was disconnected |
|
|||
|
|
| `0x8000` (32768) | unknown error |
|
|||
|
|
|
|||
|
|
These numbers are **not** the library's return codes listed under
|
|||
|
|
[Error codes worth recognising](#error-codes-worth-recognising), even where
|
|||
|
|
they coincide (`0x8001` there is `MEASUREMENT_SLOT_NOT_EMPTY`).
|
|||
|
|
|
|||
|
|
**What the device actually reported** is not available through the API. It
|
|||
|
|
appears only in the protocol log (`enable_logging`), as a text ID such as
|
|||
|
|
`com.byonoy-AbsOne-MIN_LIGHT_ERROR`. For the two absorbance devices, these mean:
|
|||
|
|
|
|||
|
|
| Text ID | Meaning |
|
|||
|
|
|---|---|
|
|||
|
|
| `AbsOne-AMBIENT_LIGHT_ERROR` | too much ambient light is reaching the device, or it is defective |
|
|||
|
|
| `AbsOne-MIN_LIGHT_ERROR` | too little light: the device is dirty, the slot is occupied, or it is defective |
|
|||
|
|
| `AbsOne-TIMEOUT_ERROR` | the measurement was disrupted, e.g. by a shadow in the device |
|
|||
|
|
| `AbsOne-NOISE_LIMIT_ERROR` | USB cable or hub problem, or a defective device |
|
|||
|
|
| `AbsOne-HARDWARE_ERROR`, `AbsOne-UNRECOVERABLE_ERROR` | the device is defective; nothing the user can do |
|
|||
|
|
| Absorbance 96: calibration failed | initialise failed — usually a plate in the slot or a dirty slot |
|
|||
|
|
| Absorbance 96: ambient light | too much light is entering the device |
|
|||
|
|
| Absorbance 96: USB power | insufficient USB power — use another port or a powered hub |
|
|||
|
|
| Absorbance 96: temperature | the device temperature is outside its specified range |
|
|||
|
|
| Absorbance 96: measurement unit | no contact with the upper section — make sure the device parts are aligned and seated |
|
|||
|
|
| Absorbance 96: hardware | hardware defect — contact support |
|
|||
|
|
|
|||
|
|
For the luminescence and fluorescence devices the log shows only the raw
|
|||
|
|
firmware code, as `errorCode=0x…`. On a **Luminescence 96** it is a single
|
|||
|
|
value:
|
|||
|
|
|
|||
|
|
| Code | Meaning | Cleared by |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `0x1` | calibration missing — the detector calibration could not be loaded | a power cycle; report it |
|
|||
|
|
| `0x7` | shutter error — the shutter failed to move after repeated retries during a measurement | a power cycle |
|
|||
|
|
|
|||
|
|
On a **Fluorescence 96** it is a set of flags, several of which can be set at
|
|||
|
|
once:
|
|||
|
|
|
|||
|
|
| Bit | Value | Meaning |
|
|||
|
|
|---|---|---|
|
|||
|
|
| 0 | `0x01` | calibration missing |
|
|||
|
|
| 1 | `0x02` | configuration — the device's identity or board revision could not be read |
|
|||
|
|
| 2 | `0x04` | homing — the slide is not homed; check that it moves freely and the transport lock is out |
|
|||
|
|
| 3 | `0x08` | detector — usually the detector sled's supply or connection rather than the photodiodes |
|
|||
|
|
| 4 | `0x10` | hall position — the position sensor does not answer |
|
|||
|
|
| 5 | `0x20` | accelerometer — the accelerometer does not answer |
|
|||
|
|
| 6 | `0x40` | photodiode compensation — no excitation light seen |
|
|||
|
|
| 7 | `0x80` | motion system — the slide stalled, or homing never found its endstop |
|
|||
|
|
|
|||
|
|
A Fluorescence 96 clears all flags at the start of every measurement and on a
|
|||
|
|
restart, so a code seen after a failed run describes that run. Any flag
|
|||
|
|
aborts a running measurement. Report codes to Byonoy support with the serial
|
|||
|
|
number and firmware version.
|
|||
|
|
|
|||
|
|
`ERROR` clears by itself once the device stops reporting the condition; no
|
|||
|
|
reconnect is needed. A device that keeps reporting one — such as an Absorbance
|
|||
|
|
One with a dirty optical path — stays in `ERROR` across closing, reopening and
|
|||
|
|
new processes. It still answers queries, but its measurements fail or hang.
|
|||
|
|
The opposite case — status `OK`, error `0`, yet every measurement fails — is
|
|||
|
|
described under [When the device stops measuring](#when-the-device-stops-measuring).
|
|||
|
|
|
|||
|
|
### Error codes worth recognising
|
|||
|
|
|
|||
|
|
The library defines 25 error codes; these are the ones you are most likely to
|
|||
|
|
meet. Print the name and number (`rc.name`, `hex(rc.value)` in Python) rather
|
|||
|
|
than assuming an unlisted value is impossible.
|
|||
|
|
|
|||
|
|
| Code | Name | Usual cause |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `0x0000` | `NO_ERROR` | success |
|
|||
|
|
| `0x0002` | `DEVICE_CLOSED` | device unplugged or lost while open |
|
|||
|
|
| `0x0003` | `INVALID_ARGUMENT` | a handle you already freed, a config you did not create, or a value the device does not accept (see also the Absorbance 96 initialise note in the Python document) |
|
|||
|
|
| `0x0005` | `UNSUPPORTED_OPERATION` | wrong modality or feature for this device |
|
|||
|
|
| `0x0006` | `DEVICE_COMMUNICATION_FAILURE` | another process holds the device; on Linux, missing permissions; on macOS, nobody logged in to a desktop session; otherwise cable, power, or a device mid-reset |
|
|||
|
|
| `0x0007` | `DEVICE_OPERATION_FAILED` | the device refused the operation. If the device status is `ERROR`, that is the cause; if it is `OK` and every measurement fails this way, see [When the device stops measuring](#when-the-device-stops-measuring) |
|
|||
|
|
| `0x0101` | `DEVICE_NOT_FOUND` | nothing matching attached |
|
|||
|
|
| `0x0104` | `DEVICE_ALREADY_OPEN` | your own process already has it open |
|
|||
|
|
| `0x8001` | `MEASUREMENT_SLOT_NOT_EMPTY` | absorbance: initialising with a plate or cuvette in the slot |
|
|||
|
|
| `0x8002` | `NOT_INITIALIZED` | absorbance: measuring a wavelength that was not initialised **on this handle** — initialisation does not survive closing the device |
|
|||
|
|
| `0x8003` | `MEASUREMENT_PARTS_NOT_ALIGNED` | the device's parts are not seated correctly |
|
|||
|
|
|
|||
|
|
### Linux permissions
|
|||
|
|
|
|||
|
|
USB access is root-only by default. Without a udev rule the device still
|
|||
|
|
appears in `available_devices()`, but with an **empty serial number**, and
|
|||
|
|
opening it fails with `DEVICE_COMMUNICATION_FAILURE` (`0x0006`). Rather than
|
|||
|
|
running as root, install the suggested rules in
|
|||
|
|
**[`70-byonoy.rules`](70-byonoy.rules)** — the same rules the Byonoy desktop
|
|||
|
|
app installs. The file sits next to this guide; copy it onto the machine, then:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
sudo install -m 0644 70-byonoy.rules /etc/udev/rules.d/70-byonoy.rules
|
|||
|
|
sudo udevadm control --reload-rules
|
|||
|
|
sudo udevadm trigger --subsystem-match=usb --subsystem-match=hidraw --subsystem-match=tty
|
|||
|
|
sudo usermod -aG dialout "$USER" # needed for SSH, services and CI; then log in again
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Check that it took effect, in a new login:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
id | grep -o dialout # your account is in the group
|
|||
|
|
lsusb -d 16d0: # e.g. "Bus 003 Device 006: ID 16d0:106a …"
|
|||
|
|
ls -l /dev/bus/usb/003/006 # the Bus/Device numbers from lsusb: group dialout
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
The library talks to the device through libusb, so the USB node under
|
|||
|
|
`/dev/bus/usb` is the one whose permissions matter. The device's
|
|||
|
|
`/dev/hidraw*` node is not a reliable check: it disappears while a program has
|
|||
|
|
the device open, and can stay missing after a program was killed even though
|
|||
|
|
the device works. If the USB node's group is not `dialout`, unplug the device
|
|||
|
|
and plug it back in.
|
|||
|
|
|
|||
|
|
An empty serial number is not always a permissions problem. After a process
|
|||
|
|
was ended in the middle of a measurement, a device can be listed with an empty
|
|||
|
|
serial number while opening it returns `DEVICE_COMMUNICATION_FAILURE`
|
|||
|
|
(`0x0006`) or `DEVICE_NOT_FOUND` (`0x0101`) — typically because it is resetting
|
|||
|
|
and about to drop off USB and re-enumerate. If permissions were fine before,
|
|||
|
|
wait a minute and look again; if it does not come back, replug it.
|
|||
|
|
|
|||
|
|
The rules cover Byonoy devices by USB vendor and product ID (vendor `16d0`,
|
|||
|
|
plus `0483:ab12`), for both the raw USB and the `hidraw` device nodes. They
|
|||
|
|
grant access in two ways:
|
|||
|
|
|
|||
|
|
- `TAG+="uaccess"` gives the user logged in at the machine's own seat access
|
|||
|
|
automatically. This does **not** apply to SSH sessions, containers, CI
|
|||
|
|
runners or services.
|
|||
|
|
- `GROUP="dialout"` gives access to members of the `dialout` group — the
|
|||
|
|
`usermod` line above. Anything that is not a local desktop session depends on
|
|||
|
|
it.
|
|||
|
|
|
|||
|
|
The last two lines match the manual Absorbance 96's serial adapter (vendor
|
|||
|
|
`0403`, manufacturer `Byonoy GmbH`) and stop ModemManager from probing it. They
|
|||
|
|
matter only for the [serial protocol](https://git.byonoy.com/public/abs96serial),
|
|||
|
|
not for the SDK.
|
|||
|
|
|
|||
|
|
### macOS and Windows
|
|||
|
|
|
|||
|
|
Neither needs a driver or a permission step. On **macOS**, though, the device
|
|||
|
|
can only be opened while a user is logged in to a desktop session on the Mac.
|
|||
|
|
With nobody logged in — for example over SSH right after a restart — the device
|
|||
|
|
is still listed by `available_devices()`, but opening it fails with
|
|||
|
|
`DEVICE_COMMUNICATION_FAILURE` (`0x0006`). For an unattended Mac, enable
|
|||
|
|
automatic login.
|
|||
|
|
|
|||
|
|
### When the device stops measuring
|
|||
|
|
|
|||
|
|
**This is not normal operation.** A healthy instrument measures every time, and
|
|||
|
|
you should not need this section. It is here so that if you meet the state, you
|
|||
|
|
recognise it rather than concluding your code is wrong.
|
|||
|
|
|
|||
|
|
A device can end up unable to measure while every diagnostic insists it is fine:
|
|||
|
|
**every** measurement fails immediately with `DEVICE_OPERATION_FAILED`, in fresh
|
|||
|
|
processes too, yet the device status reads OK, the handle reports open, and the
|
|||
|
|
device error accessor reports nothing. With protocol logging on, the device
|
|||
|
|
refuses each measurement request with return code 3.
|
|||
|
|
|
|||
|
|
To confirm the state quickly on a luminescence or fluorescence device, measure
|
|||
|
|
with **no wells selected**: a healthy device returns `NO_ERROR` within a
|
|||
|
|
fraction of a second; a device in this state returns `DEVICE_OPERATION_FAILED`
|
|||
|
|
just as fast.
|
|||
|
|
|
|||
|
|
It has been seen after a measurement was stopped by ending the process —
|
|||
|
|
including a long `ULTRA_SENSITIVE` read stopped before it finished. Waiting
|
|||
|
|
does not clear it; a power cycle does.
|
|||
|
|
|
|||
|
|
Recovery is a device reboot. The internal variant can do this from software
|
|||
|
|
(`reboot()`); **the public variant cannot** — unplug the device and plug it back
|
|||
|
|
in. Either way it re-enumerates within a few seconds and you open it again.
|
|||
|
|
|
|||
|
|
> **Report it if it recurs.** A one-off on an engineering or pre-production unit
|
|||
|
|
> is not remarkable. A device that needs rebooting repeatedly, or one on which a
|
|||
|
|
> particular mode fails consistently, is a fault worth reporting to Byonoy
|
|||
|
|
> support with the serial number, the firmware version from the device
|
|||
|
|
> information call, the mode and well selection you used, and how often it
|
|||
|
|
> happens. Do not work around it with a reboot loop — that hides a hardware
|
|||
|
|
> problem behind software.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. Next
|
|||
|
|
|
|||
|
|
- **[Python SDK →](SDK_PYTHON.md)** — including a [complete measure-to-CSV program](SDK_PYTHON.md#6-complete-example-measure-and-export-csv) covering every device
|
|||
|
|
- **[Native SDK →](SDK_NATIVE.md)**
|