# StreamZet xSpan SDK 0.2.1

Documentation revision: 2026-09-23

https://streamzet.com/docs?lang=en

## xSpan overview

Connect your devices and integrate remote support into your product. Install and integrate the xSpan SDK 0.2.1.

### Choose your starting point

- [Test on a reader first](https://streamzet.com/docs/xspan/quickstart?lang=en)
- [Integrate with a C/C++ product](https://streamzet.com/docs/xspan/integration?lang=en)
- [Review delivery packages and checksums](https://streamzet.com/docs/xspan/packages?lang=en)

Use the prebuilt engine for an installation test. Ship the C adapter and the same engine for product integration. Both modes use persistent per-device state; do not run both against the same state at once.

### xSpan capabilities

| Capability | Current scope |
| --- | --- |
| Auto enrollment | Automatically appears in the account’s Devices after initial enrollment; requires connectivity. |
| Remote terminal | Interactive sh terminal in the browser, running with the installed agent’s OS privileges. |
| Fleet commands | Select multiple or all eligible online readers; inspect status, stdout, stderr and exit code per device. |
| RFID dashboard | Open the original OEM dashboard once it is enabled for the reader. Existing OEM login is required. |

### Compatibility and qualification

| Item | Verified environment |
| --- | --- |
| Reader | Impinj xSpan R660 |
| CPU / ABI | ARMv5TE · soft-float · static musl engine |
| Linux / OEM libc | 2.6.39.4 / glibc 2.19 |
| Octane firmware | 6.2.2.240 |
| OEM application | RFID Overhead 360° 2.22.11.30 |
| SDK / documentation | 0.2.1 / 2026-09-22 |

> Enrollment, terminal, Fleet commands, SDK lifecycle and the web dashboard were verified on one physical R660. Other models/firmware, a full reboot and multi-reader hardware load require separate acceptance tests.

## Quickstart: test a reader

Prepare a unique installation directory on a trusted computer, install it on one xSpan, and verify the connection.

### 1. Prerequisites

- An xSpan R660 matching the qualified environment, with authorized SSH or OEM factory provisioning access.
- The evaluation ZIP, a separately supplied enrollment key file and a StreamZet operator account.
- Python 3 on the provisioning computer; correct reader time, DNS and outbound TCP 443. Python is not required on the reader.

> Paths and READER_HOST below are examples. Use reader-001 for exactly one reader. Do not overwrite an existing installation’s state using this first-install procedure.

### 2. Extract and verify

```sh
shasum -a 256 -c streamzet-xspan-evaluation-0.2.1-20260922.zip.sha256
unzip streamzet-xspan-evaluation-0.2.1-20260922.zip -d xspan-evaluation
cd xspan-evaluation
shasum -a 256 -c SHA256SUMS
```

On Linux, sha256sum -c can replace shasum. Obtain the checksum file together with the ZIP.

### 3. Provision one device

```sh
umask 077
mkdir -p ./provisioning
python3 device/prepare-device.py \
  --registration-key /secure/JCI-production-key.txt \
  --output ./provisioning/reader-001 \
  --name 'xSpan R660 001'
```

The new directory contains the engine, CA bundle, lifecycle scripts, enrollment key and a unique 64-byte entropy seed. The tool refuses an existing output directory. First startup creates state/identity containing the device UUID and secret.

### 4. Transfer and start

Use verified SSH or approved factory provisioning. This example assumes /mnt/spp/streamzet-jci does not already exist on the test reader. Adapt the installation location to the OEM’s persistent-storage policy.

```sh
# On the provisioning computer
scp -pr ./provisioning/reader-001 root@READER_HOST:/mnt/spp/streamzet-jci

# In the reader's authorized shell
cd /mnt/spp/streamzet-jci
chmod 700 . state streamzet-embedded start.sh stop.sh
chmod 600 registration.key cacert.pem device-name state/entropy.seed
./start.sh
```

> Do not pass passwords or enrollment key values as command-line arguments. Never clone an enrolled device’s state directory to another reader.

### 5. Verify the connection

1. Sign in to StreamZet and confirm that the named reader appears online in Devices.
2. Open the terminal and run the read-only commands below. The output should match the actual reader.
3. In Fleet → Linux → sh · xSpan / embedded, select only this test reader and run the same command. Check its result and exit code.

```sh
printf 'STREAMZET_TEST_OK\n'
uname -m
uptime
```

The agent checks the Fleet queue approximately every 30 seconds. Enrollment is asynchronous; successful process startup does not by itself confirm online status.

- [Configure startup and operations](https://streamzet.com/docs/xspan/operations?lang=en)
- [Troubleshoot a missing reader](https://streamzet.com/docs/xspan/troubleshooting?lang=en)

## C/C++ integration

Connect remote support to your product lifecycle. Use the POSIX process adapter for existing glibc applications.

### Integration architecture

```text
OEM application (your ETK / libc)
  └─ streamzet_product.c  [start / poll / stop]
       └─ streamzet-embedded  [static ARMv5 engine]
            └─ outbound TLS :443 → StreamZet
                 └─ enrollment · terminal · Fleet · dashboard
```

Compile the small C adapter into the OEM application and ship the qualified static engine alongside it. TLS and the C runtime remain in a separate process. The engine handles transport, enrollment and network reconnect; the host starts, monitors and stops the child.

### Required files

| File | Purpose |
| --- | --- |
| sdk/product/streamzet_product.h | Public C/C++ interface |
| sdk/product/streamzet_product.c | POSIX adapter compiled with the OEM ETK |
| sdk/product/example.c | Start, monitor, stop and reap example |
| device/streamzet-embedded | Qualified static ARMv5 engine |
| device/cacert.pem | CA bundle for server authentication |
| registration.key + state/ | Provisioned and preserved per device |

### Compile with your product toolchain

```sh
# Set CC to the matching OEM ETK C compiler.
"$CC" -std=c99 -D_POSIX_C_SOURCE=200809L \
  -Isdk/product sdk/product/example.c sdk/product/streamzet_product.c \
  -o streamzet-product-example
```

For C++, compile streamzet_product.c as C, link that object, and include the header. Copy the resulting example to the provisioned reader and run it below. Stop an existing start.sh-managed engine and wait for its full exit before reusing its state.

```sh
./streamzet-product-example \
  /mnt/spp/streamzet-jci/streamzet-embedded \
  /mnt/spp/streamzet-jci/registration.key \
  /mnt/spp/streamzet-jci/cacert.pem \
  /mnt/spp/streamzet-jci/state \
  'xSpan R660 001'
```

### Host lifecycle example

```c
#include "streamzet_product.h"

static stz_product support = {0};

int start_remote_support(void) {
    const stz_product_config config = {
        .engine_path = "/mnt/spp/streamzet-jci/streamzet-embedded",
        .registration_file = "/mnt/spp/streamzet-jci/registration.key",
        .ca_file = "/mnt/spp/streamzet-jci/cacert.pem",
        .state_directory = "/mnt/spp/streamzet-jci/state",
        .device_name = "xSpan R660 001"
    };
    return stz_product_start(&support, &config);
}

/* Call periodically from the same host thread. */
int poll_remote_support(int *wait_status) {
    return stz_product_poll(&support, wait_status);
}

/* Request stop, then keep polling until the child is reaped. */
int stop_remote_support(void) {
    return stz_product_stop(&support);
}
```

- A zero start return value confirms spawn success. Confirm online status in StreamZet.
- Serialize calls on a handle. Do not let another SIGCHLD handler reap this child.
- Mark unrelated host file descriptors close-on-exec. The adapter child inherits stdout/stderr.
- Network reconnect is automatic. Detect process exits through poll and apply the OEM restart policy.
- Do not run start.sh alongside SDK mode. After a stop request, reap via poll before reusing the handle.

- [Fields, return values and errors](https://streamzet.com/docs/xspan/api?lang=en)

### Low-level static library

sdk/musl contains libstreamzet-embedded.a, TLS archives and headers. A new musl ARMv5 application uses stz_create → stz_run on a worker thread → stz_stop → join → stz_destroy, with the documented entropy, persistent identity and single TLS-runtime contracts.

> This is not a drop-in library for an existing glibc app. Direct in-process integration requires the matching ETK, ABI, threading and TLS qualification. Use the process adapter for the initial partner integration.

## C API reference

The public contract in streamzet_product.h. This API controls the engine lifecycle.

### stz_product_config

| Field | Type and requirement |
| --- | --- |
| engine_path | const char* · Absolute engine path |
| registration_file | const char* · Absolute path to an owner-only enrollment key file (0600). |
| ca_file | const char* · Absolute CA PEM path |
| state_directory | const char* · Absolute per-device persistent directory path (0700). |
| device_name | const char* · Nonempty UTF-8 name, at most 128 bytes |

Paths must be shorter than 1,024 bytes. Keep string pointers valid until start returns. The provisioning tool rejects control characters in names. Initialize the stz_product handle to {0}; do not copy it to share ownership.

| Field / API | Type / default | Contract |
| --- | --- | --- |
| stz_product.pid | pid_t · 0 | Owned child PID; initialize the handle to {0}. poll resets it on exit/error. Do not assign, copy ownership or reap this child elsewhere. |

### stz_product_start

```c
int stz_product_start(stz_product *product,
                      const stz_product_config *config);
```

| Return | Meaning |
| --- | --- |
| 0 | Child spawned. Enrollment and network connection proceed asynchronously. |
| EINVAL | Invalid argument, relative/oversized path, or device name |
| EALREADY | This handle already owns a running child |
| ECHILD | Could not inspect/reap the previous child |
| ENOENT / EACCES / other errno | posix_spawn or setup failure. Interpret the return value as an errno code. |

File-content and network-configuration errors can occur after spawn and cause the engine to exit. Keep polling after a successful start.

### stz_product_poll

```c
int stz_product_poll(stz_product *product, int *wait_status);
```

| Return | Meaning |
| --- | --- |
| 1 | Child is still running; nonblocking. |
| 0 | Already stopped, or exited and reaped. The handle PID is reset to zero. |
| -1 | NULL handle or waitpid error |

wait_status may be NULL. When this call reaps a child, inspect the status with WIFEXITED/WEXITSTATUS or WIFSIGNALED/WTERMSIG from sys/wait.h. An already-stopped handle does not write a new exit status.

### stz_product_stop

```c
int stz_product_stop(stz_product *product);
```

Requests SIGTERM if running. Returns zero on success or when already stopped; EINVAL for a NULL handle, ECHILD for a child-state error, or errno for signal delivery failure. This function does not wait for shutdown; reap the child with poll.

### Threading and state ownership

Serialize all calls on a handle using one thread or external locking. The adapter passes credential file paths rather than key values, without evaluating arguments through a shell. The engine’s file lock rejects concurrent use of the same state directory.

### Advanced: stz_config

> The shipped musl archives are an advanced interface for a separately qualified musl host. They are not qualified for direct linkage into the existing glibc OEM application. The process adapter remains the supported initial integration. All pointers are copied by stz_create; a zero-initialized struct has no implicit valid configuration.

| Field / API | Type / default | Contract |
| --- | --- | --- |
| relay_host | const char* · required | 1–253 bytes of hostname characters (letters/digits/.-), no leading/trailing dot. No scheme/port/path; HTTPS/WSS port 443. |
| device_token | const char* · required | Exactly 64 hexadecimal characters: per-device secret, not enrollment or operator token. |
| ca_file | const char* · required | Absolute PEM path shorter than 1024 bytes. Valid CA file required at create time; TLS certificate/hostname checks always enabled. |
| allow_terminal | int · 0 | 0 denies interactive shell; nonzero opts in as the host OS user. |
| allow_commands | int · 0 | 0 denies Fleet commands; nonzero enables bounded execution. |
| device_id | const char* · NULL | Production: canonical 36-character lowercase UUID with hyphens. NULL selects legacy preview mode, not tenant enrollment. |
| registration_key | const char* · NULL | Production enrollment only; NULL/empty allowed after enrollment. Otherwise sz_live_ prefix, ≤128 bytes, alphanumeric/_/-. |
| device_name | const char* · required in production | Nonempty, ≤128 bytes when device_id is set; ignored in preview mode. |
| os_version | const char* · NULL | Production: ≤128 bytes, NULL becomes Linux embedded. Ignored in preview. |

### Advanced lifecycle and bootstrap

| Field / API | Type / default | Contract |
| --- | --- | --- |
| stz_create(config) | stz_client* / NULL | Copies config and initializes entropy/TLS. NULL on invalid config, allocation/entropy/CA/TLS failure. Does not enroll until run. |
| stz_run(client) | int · blocking | Host worker thread. Reconnects until stopped; 0 clean stop, -1 NULL/already running. One run per handle; one active TLS client/runtime per process is the qualified scope. |
| stz_stop(client) | void | Nonblocking wake; NULL is harmless, signal-handler safe. Join run before destroy. A stopped handle is not a fresh reconnectable handle; create a new one for a new run. |
| stz_destroy(client) | void | After run has joined only; frees resources and wipes copied secrets. NULL allowed. No concurrent access after destroy. |
| stz_prepare_entropy(path) | int | CLI bootstrap helper. Absolute seed path <1024 bytes under an owned 0700 directory; owned single-link regular 0600 file exactly 64 bytes. Rotate/fsync before TLS and new identity. 0 success, -1 failure. Never clone a seed. |
| stz_identity_lock(directory) | int fd / -1 | Existing absolute owned private directory; returns lock FD or -1. Hold through run; caller closes FD after shutdown. |
| stz_identity_load(directory, identity) | int | Hold the lock and prepare entropy first. Loads/creates identity, 0 success/-1 failure. identity is a caller-owned stz_identity*. |
| stz_identity.device_id | char[37] | 36-character lowercase UUID plus NUL; stable public ID. |
| stz_identity.secret | char[65] | 64 hexadecimal characters plus NUL; private per-device secret. Never log or export. |

## Operations and Fleet

Configure startup, remote access, per-reader dashboard access and updates that preserve identity.

### Start on boot

For a standalone installation, add the StreamZet start.sh call to /cust/start on the qualified R660. Preserve OEM lines and back up the original. In SDK mode the product calls stz_product_start, so a separate start.sh boot hook is unnecessary.

```sh
# Standalone mode: add once to the OEM-approved startup hook
/mnt/spp/streamzet-jci/start.sh
```

> Do not re-run the entire /cust/start on a live reader; that can start the RFID application again. OEM CAP/firmware updates may replace the hook. Include it in the OEM product package and perform reboot acceptance testing.

### Send commands to multiple readers

1. In Fleet select Linux and sh · xSpan / embedded.
2. Select readers individually or use Select all. Eligible online devices controlled by the current account are targeted.
3. Run the command and review pending, running, completed, failed or expired state and output per reader.

```sh
printf 'FLEET_CHECK_OK\n'
uname -m
uptime
```

> Batch submission does not guarantee synchronized execution. Queue polling and network conditions affect start time. Commands interrupted by an ambiguous disconnect are not silently replayed; inspect results before retrying.

### Open the RFID dashboard

Use Open xSpan dashboard in the device list or terminal to open the original OEM UI in a new tab. Sign in with the OEM administrator account. The dashboard can be used alongside the terminal.

> New readers require dashboard enablement after terminal enrollment. Give your integration contact the StreamZet device UUID; the button appears once the dashboard is enabled. If the reader's HTTPS certificate changes, for example after an IP change, the dashboard keeps working without any action.

Web connections expire after 30 minutes and can be ended immediately with Close dashboard. Long-inactive tabs may need reopening. A shared URL alone does not grant access from another browser.

### Execution limits

| Item | Limit |
| --- | --- |
| Terminal | One per reader, up to 30 minutes per session |
| Concurrent commands | Up to two per reader |
| Command length | 4,096 UTF-8 bytes |
| Command timeout | 100–120,000 ms; current Fleet UI uses 30,000 ms |
| Output | 65,536 bytes combined stdout/stderr; truncation is reported |
| Queue polling | Approximately every 30 seconds |

### Update and remove

1. Verify new artifact checksums and back up the current engine and startup configuration.
2. Stop only StreamZet and confirm it has exited. Stopping the engine closes its remote terminal, so use the OEM deployment path for replacement.
3. Replace only the engine and required CA/scripts. Preserve state/, registration.key, device name and ownership.
4. Restart and confirm the same device UUID returns online. Restore the backed-up executable if needed.

To remove the integration, stop StreamZet and remove only its startup hook. Keep identity state if reinstalling. When retiring a reader, have an administrator revoke that device’s server-side credentials and securely remove local secrets.

## Network and authentication

Network requirements, credential roles and operator privileges.

### Network requirements

| Connection | Requirement |
| --- | --- |
| Reader → relay | streamzet-embedded.plitsoft.workers.dev · TCP 443 · HTTPS/WSS |
| Operator browser | streamzet.com · streamzet-embedded.plitsoft.workers.dev · *.supabase.co · HTTPS/WSS |
| OEM web dashboard | xspan.streamzet.com · HTTPS |
| Inside the reader | Dashboard traffic reaches loopback TLS 9000; no inbound port forwarding required. |

The reader initiates outbound TLS, allowing operation behind NAT. DNS, a correct clock and WebSocket-compatible egress are required. Retain CA and hostname verification.

### Credential roles

| Credential | Purpose |
| --- | --- |
| Enrollment key | Enrolls new readers; does not grant operator terminal or Fleet authority. |
| Device secret | Stored in state/identity; unique per reader and used for its connection. |
| Operator login | Authorizes terminal, commands and dashboard access after ownership checks. |
| OEM login | Separate Basic authentication for the original RFID dashboard. |

> Keep enrollment keys, passwords and device state out of public documentation, source repositories, SDK ZIPs and logs. Use 0700 provisioning directories and 0600 secret files; never clone them between readers.

### Privileges and authentication boundaries

Shells run with the installed agent’s OS privileges. The qualified reader’s OEM environment runs as root. The OEM must choose and qualify the deployment UID and file access scope.

The RFID UI uses a separate web origin from the StreamZet console. A one-use launch ticket and HttpOnly session cookie protect access; operator Bearer tokens are not forwarded to the reader. Dashboard traffic travels only over the reader’s own connection, authenticated with its per-device secret, and changes to the reader’s HTTPS certificate are recorded on the server.

## Troubleshooting and acceptance

Diagnostic steps by symptom and partner acceptance checks.

### Diagnose by symptom

| Symptom | Check |
| --- | --- |
| Reader does not appear | Check time, DNS, outbound 443, enrollment key permission and account capacity. Identify readers by UUID, not name alone. |
| Private state directory is unavailable or already in use | Check state path, ownership and 0700 permissions. Check for simultaneous standalone and SDK processes. |
| Private enrollment configuration, entropy or identity failed | Check key/seed/identity file ownership, 0600 modes and contents. Do not substitute another reader’s state. |
| Invalid configuration, CA bundle or entropy source | Check the shipped CA bundle, seed and engine configuration. Keep certificate verification enabled. |
| Engine exits after successful start | Inspect poll exit status and stderr. start only confirms spawn. |
| Command is pending | Check online state, owner account and approximately 30-second queue interval. |
| Terminal already in use | End the existing session normally. Do not take over another operator’s session. |
| Dashboard button missing | Check online state and ask your contact to verify dashboard enablement. |
| Dashboard 401/expired | Create a new connection using the StreamZet button. Use OEM credentials for an OEM authentication prompt. |
| Dashboard connection error | Check that the reader's OEM port 9000 service is running, then open it again from StreamZet. |

### Foreground diagnostics

start.sh discards output. If diagnostics are needed, first stop StreamZet and confirm exit. Then run the command below in an authorized reader shell to see stderr. Stop with Ctrl+C and restore the normal startup method.

```sh
cd /mnt/spp/streamzet-jci
./streamzet-embedded --production \
  streamzet-embedded.plitsoft.workers.dev \
  /mnt/spp/streamzet-jci/registration.key \
  /mnt/spp/streamzet-jci/cacert.pem \
  /mnt/spp/streamzet-jci/state 'xSpan R660 001'
```

### Partner acceptance checklist

- Does first startup enroll the reader and make it visible to the operator account?
- Have terminal output and successful/failed Fleet results been verified on the physical reader?
- Does the UUID survive app/engine restart, and does connectivity recover after a network outage?
- Do SDK start, poll, graceful stop and child reaping work inside the OEM application?
- During an approved maintenance window, has startup been verified after a full reboot and CAP/firmware update?
- Have CPU, memory, RFID workload and batch execution across multiple physical readers been tested?
- Have dashboard enablement, login, live events and simultaneous terminal use been verified?

When requesting help, provide SDK version, reader model/firmware, UUID, timestamp with timezone, reproduction steps and redacted exit status/logs. Do not send keys, passwords or state/identity.

- [Contact technical support](mailto:plitsoft@plitsoft.com)

## Packages and delivery

Choose the evaluation or product-integration package and verify the delivered files.

### Version 0.2.1 · delivery 2026-09-22

| Package | Contents |
| --- | --- |
| streamzet-xspan-evaluation-0.2.1-20260922.zip | ARMv5 engine, CA bundle, per-device provisioning tool, lifecycle scripts, docs and licenses. Test without compiling. |
| streamzet-jci-xspan-sdk-0.2.1-20260922.zip | Evaluation files plus the C/C++ process adapter, example, musl archives, source, pinned dependencies and build instructions. |
| *.zip.sha256 / SHA256SUMS | SHA256 checksums for archives and files inside them. Compare against the release metadata below. |

> Your integration contact supplies the binary and SDK packages directly. This public documentation contains no operator accounts, enrollment keys or device secrets. Verify delivered artifacts using the checksums below.

### Separately supplied credentials

- StreamZet operator account: device list, remote access and Fleet.
- Enrollment key file: used by prepare-device.py per reader. Do not hardcode it in SDK source.
- OEM dashboard credentials and a contact for enabling each new reader’s dashboard.

The xSpan adapter does not read the JCI-sdk-config.json used by other integrations. It uses the enrollment key file, CA bundle and persistent state path, and connects to the deployed relay.

### Offline documentation

- [Download Korean guide (.md)](https://streamzet.com/docs-assets/xspan/integration-ko.md)
- [Download English guide (.md)](https://streamzet.com/docs-assets/xspan/integration-en.md)
- [Download Japanese guide (.md)](https://streamzet.com/docs-assets/xspan/integration-ja.md)

Both delivery ZIPs include these guides under docs/. The exact API contract is defined by streamzet_product.h in the SDK.

## Releases and verification

Engine versions and documentation/package revisions are tracked separately.

### 2026-09-22 · documentation and example revision

The 0.2.1 engine binary is unchanged. All SDK references now support English, Korean and Japanese. The product example propagates abnormal engine exit codes.

### Completed and remaining qualification

| Validation | Scope |
| --- | --- |
| Physical R660 | Enrollment, terminal, Fleet success/failure/timeout/output limits, SDK lifecycle and same-UUID reconnect |
| Real browser | OEM navigation, reload, SSE events, concurrent terminal and unauthenticated access denial |
| Automation | ARM926 emulation, gateway race/access control, web type/build checks and pagination over 1,101 mock devices |
| Partner acceptance needed | OEM ETK build, other firmware, full reboot, production RFID workload and multiple physical readers |
