Files
byonoy_devices_sdk/SDK_NATIVE.md
T

11 KiB

Byonoy Device Library — Native SDK

Read the SDK guide 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:

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:

# 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:

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

#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:

#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 (value[0] is A1, value[12] is B1), as is selected_wells. The capability table in the Python document 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:

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:

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 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; 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.