# 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-.zip` or `byonoy-devices-internal-sdk-.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/` 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 ` and `` if yours is older than C23. --- ## 4. Smallest complete program ```c #include "byonoy_device_library.h" #include 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 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.