XCAICX docs Product Contact

OEM integration

For camera manufacturers embedding XCAICX in firmware. Assumes you have a batch key and a target board. Ends with a pre-ship checklist worth actually running.

1. Build for your target#

The only hard dependency is OpenSSL, which is already in every BSP. Everything else is in-tree.

bash
cmake -S core -B build-arm64 \
      -DCMAKE_TOOLCHAIN_FILE=/opt/toolchains/aarch64.cmake \
      -DCMAKE_BUILD_TYPE=Release \
      -DXCAICX_WITH_ORT=ON \
      -DXCAICX_WITH_TLS=ON
cmake --build build-arm64 -j
OptionDefaultWhen to change it
XCAICX_WITH_ORTOFFON for production — enables real models
XCAICX_WITH_TLSONOFF if the camera never phones home; drops libssl
XCAICX_BUILD_SHAREDONOFF to static-link into a single firmware image

Internal symbols are hidden (-fvisibility=hidden); only XCAICX_API symbols are exported.

Note

Camera firmware links a lot of static libraries into one image, and leaking our internals — or OpenSSL's, when you static-link it — causes duplicate-symbol grief that lands on your desk, not ours. That is why the visibility is locked down rather than left to the toolchain default.

Yocto/Buildroot recipes need openssl in DEPENDS and nothing else. Full toolchain detail in Cross-compiling.

2. Choose the device fingerprint#

This is the most important decision in the integration, because it determines how hard your product is to clone. The derivation order is documented in Licensing §3.

If your SoC has a fused serial, a secure element or a TPM, read it in your init script and export it:

sh
export XCAICX_DEVICE_ID="$(cat /sys/fsl_otp/HW_OCOTP_CFG0)"

An explicit override wins outright. Fingerprints derived from a MAC address are flagged weak_binding on the activation record and show up in xcaicxctl fleet, so the vendor can see which units are trivially clonable.

Careful

Whatever you choose must be stable across reflashes. A fingerprint that changes when the rootfs is rewritten turns every field firmware update into a new activation — which means every update bills the OEM again, and that is the phone call you least want.

3. Provision units on the line#

Option A — network activation (default)#

Flash every camera with the same batch key. On first boot the SDK calls /v1/activate, exchanges it for a hardware-bound licence, and stores it in state_dir.

c
cfg.license_path   = "/etc/xcaicx/batch.token";
cfg.state_dir      = "/var/lib/xcaicx";
cfg.cloud_endpoint = "https://licensing.xcaicx.com";
cfg.offline        = 0;

Requires the camera to reach the licensing server once, at the factory or at first install.

Option B — offline pre-provisioning#

For air-gapped customers, or if you would rather not have network on the line. Read the fingerprint during test, mint the token, write it into the image:

bash
xcaicxctl mint --sku oem-industrial --customer CUST-CAMCO \
               --device "$FINGERPRINT" \
               --key keys/root_ed25519.key -o unit.token
c
cfg.license_path = "/etc/xcaicx/unit.token";
cfg.offline      = 1;

The camera then never contacts anything. Usage still accrues to state_dir/usage-spool.jsonl for later collection. See Air-gapped deployment.

4. state_dir requirements#

Non-negotiable: writable, and survives reboot.

It holds the activation record, the unit token, the clock high-water mark and usage counters.

Do not

Pointing state_dir at /tmp means every reboot re-activates — burning a unit from your batch each time — and loses usage. This is the most expensive integration mistake available, and it does not fail loudly: it just quietly consumes your batch.

  • Put it on the same partition as your other persistent configuration.
  • Roughly 64 KB, growing only if the camera is offline for months.
  • Mode 700; the SDK creates it if absent.
  • Writes are tmp-file + fsync + rename, so a camera yanked off PoE mid-write does not corrupt it.

Verify it across a real power cut before you ship, not across a clean reboot.

5. Feed frames efficiently#

Pass what your ISP already produces. NV12 and I420 are first-class:

c
xcaicx_frame f = {
    .struct_size = sizeof f,
    .data     = isp_buffer,        /* borrowed, never freed by us */
    .data_len = isp_buffer_len,
    .width    = 1920, .height = 1080,
    .stride   = isp_stride,        /* 0 if tightly packed */
    .format   = XCAICX_PIX_NV12,
    .pts_us   = frame_pts,
    .frame_id = seq++,
};

Do not convert to RGB first — that doubles the memcpy cost on exactly the boards that can least afford it.

data is borrowed for the duration of the call only. xcaicx_stream_process is synchronous; xcaicx_stream_submit converts on your thread and runs inference on a worker, returning XCAICX_ERR_QUEUE_FULL under backpressure — treat that as flow control, not an error, and drop the frame.

To cut load on a weak SoC, set detect_every_n = 3. Tracking coasts across skipped frames, so events still fire. More in Performance & tuning.

6. Handle licensing failures individually#

The single most common integration mistake is collapsing these into "license bad". The field technician needs to know which one:

StatusMeansDo
LICENSE_MISSINGNo token installedRun provisioning
ACTIVATION_REQUIREDNever activated, offlineConnect once, or pre-provision
LICENSE_WRONG_DEVICEToken belongs elsewhereSD card swapped between units?
LICENSE_EXPIREDPast term + graceRenew
LICENSE_CLOCK_ROLLBACKClock is behindFix NTP / RTC battery
ENTITLEMENT_DENIEDModule not in SKUUpgrade, or drop it from cfg.modules
LIMIT_EXCEEDEDStream/FPS cap hitClose a stream or upgrade

xcaicx_last_error() returns a human-readable detail for the last failing call on this thread. Surface it in your diagnostics page — it is written to be read by a technician.

Degrade, do not die#

c
if (ps == XCAICX_ERR_LICENSE_EXPIRED || ps == XCAICX_ERR_LICENSE_REVOKED) {
    disable_analytics();     /* keep the video path alive */
    raise_maintenance_alert();
}

Do not

A camera that stops recording because a licence lapsed is a warranty claim. Keep the video path alive under every licensing failure.

XCAICX_LIC_GRACE means expired but inside the grace window — everything still works. Surface it as a warning so the customer renews before the hard stop.

7. Ship the heartbeat#

If your product is online, let the SDK heartbeat (default hourly). It ships metered usage and pulls the revocation list. If you would rather control the timing — and most firmware does, so the radio wakes once for everything — set cfg.heartbeat_seconds high and call xcaicx_engine_heartbeat() from your own scheduler.

Call xcaicx_engine_destroy() on shutdown. It flushes metering to disk; skipping it loses the session's usage.

8. Pre-ship checklist#

  • [ ] XCAICX_DEVICE_ID wired to fused silicon, not a MAC address
  • [ ] Fingerprint verified stable across a firmware reflash
  • [ ] state_dir on persistent storage, verified across a real power cut
  • [ ] Every licensing status has a distinct user-visible message
  • [ ] Analytics failure degrades gracefully; video keeps recording
  • [ ] XCAICX_WITH_ORT=ON and the model bundle is in the image
  • [ ] xcaicx_engine_destroy() runs on clean shutdown and on your signal path
  • [ ] Activation tested on a factory-fresh unit against the real server
  • [ ] Grace-period behaviour verified with a short-term test token
  • [ ] Quota-exhausted behaviour verified with a small --api-quota token
  • [ ] xcaicxctl fleet shows your pilot units with no WEAK-BINDING flags
  • [ ] Clock rollback tested: set the RTC back a day and confirm the message is sensible

Next#

Commercial software. Use requires a valid XCAICX licence token. Questions an integrator cannot answer from this page belong in an email to [email protected] — and, usually, in a fix to this page.

© 2026 AZMX AI · xcaicx.com