Add SDK guide for the Byonoy device library

This commit is contained in:
2026-10-01 10:04:20 +02:00
commit 4864916a3a
4 changed files with 2321 additions and 0 deletions
+311
View File
@@ -0,0 +1,311 @@
# Byonoy Device Library — Native SDK
Read the **[SDK guide](README.md)** first: it explains the two
variants, how the library behaves, and the error codes. This document is the
C/C++ specifics.
Everything up to section 6 is the **public** variant. Section 6 covers what the
internal variant adds.
---
## 1. Getting the SDK
**The native SDK is available on request, in either variant.** It is not
published anywhere you can fetch it from without Byonoy providing it. Ask for
the variant you need and you will be given an archive,
`byonoy-devices-public-sdk-<version>.zip` or
`byonoy-devices-internal-sdk-<version>.zip`.
If you have been given access to the internal repository's releases, the archive
is attached to each release and can be downloaded with your token:
```bash
TAG=$(curl -fsS -H "Authorization: token $BYONOY_TOKEN" \
'https://git.byonoy.com/api/v1/repos/sw/byonoy_device_library/releases?limit=1' \
| python3 -c 'import json,sys; print(json.load(sys.stdin)[0]["tag_name"])')
curl -fsSL -H "Authorization: token $BYONOY_TOKEN" -o sdk.zip \
"https://git.byonoy.com/sw/byonoy_device_library/releases/download/${TAG}/byonoy-devices-public-sdk-${TAG}.zip"
unzip -q sdk.zip -d sdk
```
Use that `releases/download/...` URL; the `/api/v1/.../releases/assets/<id>`
endpoint returns `404` on this instance.
`limit=1` takes the newest release, which may be a pre-release (a tag with a
`-suffix`). Public archives are attached to final tags; a recent pre-release tag
carries internal artifacts only. Check what a release actually has rather than
assuming.
---
## 2. Layout
One archive carries every platform the release was built for; take the files for
yours, and check yours is present before building.
```
sdk/include/byonoy_device_library.h the API
sdk/lib/libbyonoy_device_library.so Linux
sdk/lib/libbyonoy_device_library.dylib macOS
sdk/bin/libbyonoy_device_library.dll Windows runtime
sdk/lib/libbyonoy_device_library.dll.a Windows import library
sdk/lib/libhidapi* dependency, ships alongside
sdk/examples/C, sdk/examples/C++ compilable references
sdk/third-party-licenses/ notices for bundled dependencies
```
The internal archive is the same with `_internal` appended to every library
name, plus a second header. `bin/` holds the Windows runtime only — on macOS and
Linux everything you need is in `lib/`.
---
## 3. Build and run
Compiling is not enough: **you must also tell your binary where to find the
library at run time.** The shipped library's install name is
`@rpath/libbyonoy_device_library.dylib`, and its own RPATH entries only cover how
*it* finds hidapi — they do nothing for your executable. Link without an RPATH of
your own and you get a clean compile followed by:
```
dyld: Library not loaded: @rpath/libbyonoy_device_library.dylib
Reason: no LC_RPATH's found
```
Give the executable an RPATH, relative to itself so the result stays portable:
```bash
# macOS
cc -I sdk/include main.c -L sdk/lib -lbyonoy_device_library \
-Wl,-rpath,@executable_path/sdk/lib -o demo
# Linux
cc -I sdk/include main.c -L sdk/lib -lbyonoy_device_library \
-Wl,-rpath,'$ORIGIN/sdk/lib' -o demo
```
Adjust the RPATH to wherever `lib/` sits relative to the finished binary. As a
throwaway alternative you can set the loader path in the environment, but this
does not travel with the binary:
```bash
DYLD_LIBRARY_PATH=sdk/lib ./demo # macOS
LD_LIBRARY_PATH=sdk/lib ./demo # Linux
```
On Windows, put `sdk/bin` on `PATH`; the DLLs there must travel with the
executable.
The examples below use `uint32_t` and `true`, which the SDK header provides on a
current compiler. Add `#include <stdint.h>` and `<stdbool.h>` if yours is older
than C23.
---
## 4. Smallest complete program
```c
#include "byonoy_device_library.h"
#include <stdio.h>
int main(void) {
byonoy_device_t* devices = NULL;
uint32_t count = 0;
byonoy_available_devices(&devices, &count); /* returns void */
if (count == 0) {
printf("no device\n");
byonoy_free_available_devices(); /* allocated even when empty */
return 1;
}
byonoy_device_handle_t handle;
byonoy_error_code rc = byonoy_open_device(devices, &handle);
byonoy_free_available_devices(); /* list is dead once opened */
if (rc != BYONOY_ERROR_NO_ERROR) {
printf("open failed: 0x%04x\n", rc);
return 1;
}
byonoy_device_info_t* info = NULL;
rc = byonoy_create_device_information(&info);
if (rc != BYONOY_ERROR_NO_ERROR) { /* create/free pair */
printf("allocation failed: 0x%04x\n", rc);
byonoy_free_device(handle);
return 1;
}
rc = byonoy_get_device_information(handle, info);
if (rc == BYONOY_ERROR_NO_ERROR) {
printf("%s %s %s\n", info->ref_no, info->sn, info->version);
} else {
printf("device info failed: 0x%04x\n", rc);
}
byonoy_free_device_information(info);
byonoy_free_device(handle);
return rc == BYONOY_ERROR_NO_ERROR ? 0 : 1;
}
```
`byonoy_device_info_t` carries `sn`, `ref_no` and `version` as `const char*`,
and `type` as a `byonoy_device_types` enum.
### Memory rules
- Anything returned through a pointer has a **`create`/`free` pair**. Call
`create` first, `free` exactly once, and never free something you did not
create.
- Functions returning `int`, `float` or `bool` through a pointer allocate
nothing.
- `byonoy_available_devices()` allocates a list; release it with
`byonoy_free_available_devices()` once you have opened what you need. The
`byonoy_device_t*` entries are invalid afterwards — the **handle** is what
stays valid.
- `byonoy_free_device(handle)` closes the device. The handle is invalid after
that, and calls with it return `BYONOY_ERROR_INVALID_ARGUMENT` (`0x0003`) —
*not* `DEVICE_CLOSED`, which is what a disconnected but still-open device
gives you.
---
## 5. Taking a measurement
Gate on the predicate, `create` the config and result, measure, free both. A
complete program, so it can be compiled as it stands:
```c
#include "byonoy_device_library.h"
#include <stdio.h>
int main(void) {
byonoy_device_t* devices = NULL;
uint32_t count = 0;
byonoy_available_devices(&devices, &count);
if (count == 0) { printf("no device\n"); byonoy_free_available_devices(); return 1; }
byonoy_device_handle_t handle;
byonoy_error_code rc = byonoy_open_device(devices, &handle);
byonoy_free_available_devices();
if (rc != BYONOY_ERROR_NO_ERROR) { printf("open failed: 0x%04x\n", rc); return 1; }
if (!byonoy_lum96_measurement_supported(handle)) {
printf("this device does not do 96-well luminescence\n");
byonoy_free_device(handle);
return 1;
}
byonoy_lum96_measurement_config_t* config = NULL;
byonoy_lum96_measurement_result_t* result = NULL;
if (byonoy_create_lum96_measurement_config(&config) != BYONOY_ERROR_NO_ERROR ||
byonoy_create_lum96_measurement_result(&result) != BYONOY_ERROR_NO_ERROR) {
printf("allocation failed\n");
byonoy_free_device(handle);
return 1;
}
config->mode = BYONOY_LUM96_INTEGRATION_RAPID; /* ~10 s; SENSITIVE is ~60 s */
for (int i = 0; i < 96; ++i) config->selected_wells[i] = true;
rc = byonoy_lum96_measure(handle, config, result);
if (rc == BYONOY_ERROR_NO_ERROR) {
for (int i = 0; i < 96; ++i) {
printf("%8.1f", result->value[i]);
if ((i + 1) % 12 == 0) printf("\n");
}
} else {
printf("measurement failed: 0x%04x\n", rc);
}
byonoy_free_lum96_measurement_result(result);
byonoy_free_lum96_measurement_config(config);
byonoy_free_device(handle);
return rc == BYONOY_ERROR_NO_ERROR ? 0 : 1;
}
```
**Set both `mode` and `selected_wells` explicitly.** A freshly created config
has every well deselected, and measuring with it *succeeds* — returning
instantly with 96 zeros and no error. Its default mode is
`BYONOY_LUM96_INTEGRATION_RAPID` (0), which differs from the Python binding's
default; do not rely on either.
The mode enum is
`BYONOY_LUM96_INTEGRATION_{RAPID,SENSITIVE,ULTRA_SENSITIVE,CUSTOM}`, and
`result->value` is a fixed `float[96]` in row-major
[well order](README.md#well-order) (`value[0]` is A1, `value[12]` is B1), as
is `selected_wells`. The capability table in the
[Python document](SDK_PYTHON.md#which-modality-does-this-device-support) is also
the C capability table, with `byonoy_` prefixes.
### Bundled examples
`sdk/examples/` holds compilable references, one directory each with a `main.c`
or `main.cpp` and a `CMakeLists.txt`:
| Path | Shows |
|---|---|
| `examples/C/device-info` | open, read information, close |
| `examples/C/lum96-measurement` | 96-well luminescence |
| `examples/C/abs96-multi-measurement` | multi-wavelength absorbance |
| `examples/C/absone-measurement` | single-cuvette absorbance |
| `examples/C/device-update` | firmware update |
| `examples/C++/async` | asynchronous measurement (internal variant) |
| `examples/C++/rpc` | device RPC (internal variant) |
They build with CMake, and CMake sets the RPATH for you — which makes this the
safer route than the `cc` line above:
```bash
cmake -S sdk/examples/C/device-info -B build-example
cmake --build build-example
./build-example/device-information
```
Against the **internal** archive their `find_library` call fails, because they
look for the public library name while the internal archive ships
`libbyonoy_device_library_internal.*`. Point the cache variable at the real file:
```bash
cmake -S sdk/examples/C/device-info -B build-example \
-DBYONOY_DEVICE_LIBRARY="$PWD/sdk/lib/libbyonoy_device_library_internal.dylib"
```
---
## 6. The internal variant
Available on request; see [the SDK guide](README.md#1-which-variant-public-or-internal)
for when you should want it.
**The C API is identical.** Everything above applies unchanged except the
library name — link `-lbyonoy_device_library_internal` instead. The extra
functionality arrives as a *second* header:
```
sdk/include/byonoy_device_library.h same as public
sdk/include/byonoy_device_library_internal.h the additions
```
`byonoy_device_library_internal.h` is **C++, not C**. It declares
`namespace byonoy::device::library::internal` and uses `std::vector`,
`std::optional`, `std::filesystem` and `std::chrono` in its signatures, so a
translation unit including it must be compiled as C++. The public header remains
usable from C either way — the internal header wraps its include in
`extern "C"`.
It adds its own error enum, `byonoy_internal_error_code`, based at `0x10000`
(`NOT_ENUMERATED`, `UNKNOWN_NAME`, `UNKNOWN_ID`, `REQUEST_FAILED`, `WRONG_TYPE`,
`READ_ONLY`, `WRITE_ONLY`, `FILE_READ_FAILED`, `FILE_WRITE_FAILED`,
`ASYNC_OPERATION_ALREADY_RUNNING`, …). These are distinct from the public codes
in the SDK guide's table, not additions to them.
The feature areas — asynchronous measurements, data fields, files, RPC,
diagnostics, LEDs, bootloader and flashing, reboot — are listed with their entry
points in the [Python document](SDK_PYTHON.md#what-it-adds); the C++ names match
apart from the namespace. Each is gated on its own `*_supported` predicate.
`sdk/examples/C++/async` and `sdk/examples/C++/rpc` are worked examples of two
of these, and are the best starting point for the C++ header.