> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wuji.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK Users and Calibration Data

Common device parameters include identity parameters (serial number, firmware version, handedness) and network parameters (IP address, data port). Every device connected through the Wuji SDK (Wuji Hand 2, Wuji Glove, and more) accesses them through the same resource-style API.

This page covers four cross-device mechanisms: parameter read/write, SDK user management with data import/export, local calibration storage with per-user isolation, and resource introspection. For each product's parameter list and device operations, see the product docs linked in each section.

## Reading Parameters

Read device properties through the semantic API:

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    sn = glove.sn().get()                # Serial number
    version = glove.version().get()      # Firmware version
    hand_side = glove.hand_side().get()  # Hand side ("left" or "right")
    ip = glove.ip().get()                # IP address
    port = glove.port().get()            # Data port
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    // String parameters use a two-step size query: probe, then allocate and fill
    size_t needed = 0;
    wuji_glove_get_sn(glove, NULL, 0, &needed);
    char *sn = malloc(needed);
    wuji_glove_get_sn(glove, sn, needed, NULL);   // Serial number

    uint16_t port = 0;
    wuji_glove_get_port(glove, &port);            // Data port

    // Firmware version, hand side, and IP address follow the same two-step query
    // wuji_glove_get_version / wuji_glove_get_hand_side / wuji_glove_get_ip

    free(sn);
    ```
  </Tab>
</Tabs>

## Writing Parameters

Some parameters support writing:

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    glove.ip().set("192.168.1.200")     # Change IP address
    glove.port().set(50001)             # Change data port
    ```

    The IP must be unique on the subnet. Avoid addresses already used by other devices or the host network adapter.
  </Tab>

  <Tab title="C">
    ```c theme={null}
    wuji_glove_set_ip(glove, "192.168.1.200");   // Change IP address
    wuji_glove_set_port(glove, 50001);           // Change data port
    ```
  </Tab>
</Tabs>

The IP must be unique on the subnet. Avoid addresses already used by other devices or the host network adapter.

<Warning>
  Changing network parameters (IP address, port) may require reconnecting to the device.
</Warning>

## Device Operations

Parameter persistence, reboot, and other device operations vary by product:

| Product | Device operations |
| - | - |
| Wuji Hand 2 | [Wuji Hand 2 SDK reference — reboot](/en/docs/wuji-hand/latest/sdk-reference/device-operations/#reboot-exec) |
| Wuji Glove | [Wuji Glove device operations](/en/docs/wuji-glove/sdk-data-reference/device-operations/) |
| Wuji Hand | Run through the generic API, such as `hand.execute("reboot")` — enumerate available actions with [`resources()`](#listing-all-resources) (product docs are archived) |

## SDK User Management

When several operators share the same device, calibration and local parameters from one operator overwrite another's. The SDK keeps a local user profile per operator at `~/.wuji/sdk/users/<user_id>/`, isolating each user's parameter and calibration files.

Without a named user, parameter files and tactile calibration land in `~/.wuji/sdk/params/` and `~/.wuji/sdk/tactile/` directly, and pose calibration isn't available at all. Create a named user when you need to isolate by operator, or when you need pose calibration:

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from wuji_sdk import SdkManager

    manager = SdkManager.instance()

    # Create a user
    alice = manager.create_user("Alice", description="right-hand operator")

    # Switch to a specific user
    manager.switch_user(alice["user_id"])

    # Inspect the current user
    print(manager.current_user())

    # List all users
    for user in manager.list_users():
        marker = "*" if user["is_default"] else " "
        print(f"{marker} {user['display_name']} ({user['user_id']})")

    # Switch back to the default user
    manager.switch_to_default_user()
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    // Create a user
    WujiUserInfo alice = {0};
    wuji_create_user("Alice", "right-hand operator", /*external_id=*/NULL, &alice);

    // Switch to a specific user
    WujiUserInfo current = {0};
    wuji_switch_user(alice.user_id, &current);

    // List all users
    WujiUserInfo *users = NULL;
    size_t n = 0;
    wuji_list_users(&users, &n);
    for (size_t i = 0; i < n; i++) {
        printf("%c %s (%s)\n", users[i].is_default ? '*' : ' ',
               users[i].display_name, users[i].user_id);
    }
    wuji_user_info_array_free(users, n);

    // Switch back to the default user
    WujiUserInfo def = {0};
    wuji_switch_to_default_user(&def);

    wuji_user_info_free(&alice);
    wuji_user_info_free(&current);
    wuji_user_info_free(&def);
    ```
  </Tab>
</Tabs>

Switching reloads calibration parameters for connected devices immediately, no reconnect needed. Deleting the current user automatically switches back to the default user.

For measured edges around creating, updating, switching, and deleting users, and how the change persists across processes and languages, see [Local SDK User Management](/en/docs/wuji-sdk/sdk-practice/user-management).

## User Data Export and Import

Pack everything an SDK user owns into a single zip file to back it up or move it to another host. A bundle holds the user's hand calibration models, the tactile model for each device, and the latest complete tactile calibration run.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    # Export: write everything the user owns into one zip file
    report = manager.export_user_data(alice["user_id"], "alice.zip")
    print(report["out_path"], report["device_sns"])

    # Preview: validate a bundle and list its contents without writing anything
    preview = manager.preview_user_data("alice.zip")
    print(preview["owner_display_name"], preview["created_at"])
    for component in preview["components"]:
        print(component["name"], component["state"], component.get("reason"))

    # Import: restore the bundle to the user who created it
    result = manager.import_user_data("alice.zip")
    print(result["owner_user_id"], result["imported_at"])
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    // Export: pass "" as user_id for the default user. The path must end in .zip
    if (wuji_user_data_export(alice.user_id, "alice.zip") != WUJI_STATUS_OK) {
        fprintf(stderr, "export failed: %s\n", wuji_last_error());
    }

    // Preview: run the full validation without writing. OK means the bundle imports
    if (wuji_user_data_preview("alice.zip") != WUJI_STATUS_OK) {
        fprintf(stderr, "bundle rejected: %s\n", wuji_last_error());
    }

    // Import: restore the bundle to the user who created it
    wuji_user_data_import("alice.zip");
    ```
  </Tab>
</Tabs>

In Python, all three methods return a report dict whose `components` list the state of each component:

| Field | Description |
| - | - |
| `name` | Component name, such as `hand_model.left`, `tactile.model`, or `tactile.run` |
| `state` | `present` if the bundle carries it and it was written, `missing` if the user has no such data, `skipped` if the bundle carries it but it wasn't written |
| `reason` | Why a component is `missing` or `skipped` |
| `device_sn` | Serial number of the device a per-device component belongs to |
| `path` | Where the component sits in the bundle or on disk |

Import behavior:

* The owner recorded in the bundle is created if it doesn't exist, and the current user stays the same
* Every `present` component that isn't skipped is written and overwrites that user's existing file
* Skipping wins over writing: a skipped component is never written and never overwrites an existing file, and its `state` in the report is `skipped`. Hand models are skipped this way when the owner is the default user (which doesn't load local hand calibration), while the other components are still written
* Validation (path safety, size caps, sha256, data format, and owner id) finishes before anything is written. Any failure raises an exception and leaves no file behind

<Info>
  The export path must end in `.zip`, and the SDK creates the parent directory if it's missing. The three C calls return only a status code, because the per-component detail doesn't cross the FFI boundary — use the Python API when you need per-component results. A missing bundle returns `WUJI_STATUS_ERR_NOT_FOUND`, and a bundle that fails validation returns `WUJI_STATUS_ERR_INVALID_DATA`.
</Info>

### Tactile Data Paths

The tactile model is trained in [Wuji CLI tactile contact calibration](/en/docs/wuji-cli/tactile-calibration/). Models and calibration data are scoped by SDK user and device serial number:

| SDK user | Tactile directory |
| - | - |
| Default user | `~/.wuji/sdk/tactile/<sn>/` |
| Named user | `~/.wuji/sdk/users/<user_id>/tactile/<sn>/` |

`model/` holds the trained tactile model (`contact.safetensors` and `contact.npz`), and `calibration-data/` holds the raw calibration runs, one subdirectory per run. A complete run has one data file per guided motion, 4 in total. Export takes the latest complete run only.

```python theme={null}
sn = "WG1JA00XXXXXXXXX"

print(manager.tactile_dir(alice["user_id"], sn))
print(manager.tactile_model_paths(alice["user_id"], sn))  # dir / safetensors / npz
print(manager.tactile_run_dir(alice["user_id"], sn))
```

## Calibration

Calibration fits the SDK's algorithms to a specific physical device. The results are written to SDK-local parameter and model files and loaded automatically when the device subscribes to them.

Calibration files are scoped by the current SDK user first, then split into three resource types:

| SDK user | Parameter file (per serial number) | Tactile directory (per serial number) | Pose hand model (per hand side) |
| - | - | - | - |
| Default user | `~/.wuji/sdk/params/<sn>.toml` | `~/.wuji/sdk/tactile/<sn>/` | Not saved, not loaded — the built-in default model is used |
| Named user | `~/.wuji/sdk/users/<user_id>/params/<sn>.toml` | `~/.wuji/sdk/users/<user_id>/tactile/<sn>/` | `~/.wuji/sdk/users/<user_id>/models/` |

Parameter files and the tactile directory are still keyed by device serial number. The tactile directory holds the trained model under `model/` and the latest calibration run under `calibration-data/`.

The pose hand URDF is stored per SDK user and hand side only (such as `left_hand.urdf` / `right_hand.urdf`), without the serial number or hand-profile dimension, so any device of the same side under the same user shares one model. The default user has no pose hand model at all: calibration and import never write one, and real-time IK always falls back to the built-in default. For Wuji Glove specifics, see [Wuji Glove Calibration](/en/docs/wuji-glove/sdk-data-reference/calibration/).

Switching the SDK user reloads the matching files for connected devices automatically, no reconnect required.

<Warning>
  Calibration is limited under the default user. When multiple operators share a device, switch to a named user first:

  * Running `calibrate` as the default user fails immediately with `ExecuteError(operation="calibrate", code=0x4108)`. Call `SdkManager.create_user(...)` and `switch_user(...)` to move to a named user before calibrating, so the generated URDFs are scoped to that user.
  * Setting a custom hand URDF as the default user fails immediately: the semantic API `glove.hand_model_path().set(path)` and the generic write paths (`device.set`, `manager.set`) all reject writes to `calibration.hand_model_path` with `UnsupportedOperation` and leave the parameter store untouched. Switch to a named user first. The semantic API also validates the path and rejects an unreadable file with `InvalidConfig` without saving it.
  * Under the default user, IK-derived streams (`hand_skeleton`, `hand_joint_angles`, `tip_poses`) always use the SDK's built-in default URDF and ignore any stored `calibration.hand_model_path`. This prevents accidentally reusing another operator's calibration when the SDK user wasn't explicitly switched.
</Warning>

<Info>
  * **When to calibrate**: first time you use a device, a different wearer puts on a wearable device, higher-level model or algorithm generation changes, or calibration accuracy has visibly degraded. The exact triggers depend on the product
  * **Failure behavior**: when calibration fails, the SDK preserves the last successful calibration and rolls back the new files automatically, so you can retry directly. Typical failure modes: timeout, solver fails to converge, file persistence fails, or the SDK user switches mid-calibration. Exact exception types vary by product
  * **Backup and migration**: use [user data export and import](#user-data-export-and-import) to pack a whole user's calibration results — on import the SDK rebuilds the paths under the target host's user directory. Copying the directory works too, but the parameter file can store absolute paths pointing to other files under `~/.wuji/sdk/` (such as model files). When migrating to a host with a different home directory, update those paths or run calibration again
</Info>

For each product's calibration procedure, see the product docs:

* Wuji Hand 2 fingertip tactile calibration (runs on the device and doesn't write the SDK-local calibration files described in this section): [Wuji Hand 2 Fingertip Tactile — Zero-Baseline Calibration and Status](/en/docs/wuji-sdk/wuji-hand-2-tactile/#zero-baseline-calibration-and-status)
* Wuji Glove calibration procedure and APIs: [Wuji Glove Calibration](/en/docs/wuji-glove/sdk-data-reference/calibration/)

## Available Parameters

| Product | Parameter list |
| - | - |
| Wuji Hand 2 | [Wuji Hand 2 SDK reference — Device Operations](/en/docs/wuji-hand/latest/sdk-reference/device-operations/) |
| Wuji Glove | [Wuji Glove available parameters](/en/docs/wuji-glove/sdk-data-reference/device-operations/#available-parameters) |
| Wuji Hand | Enumerate with [`params()`](#listing-all-resources) on this page (product docs are archived) |

## Listing All Resources

List all available resources, parameters, and topics on a device:

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    # All resources
    for res in glove.resources():
        print(res.path)

    # Read/write parameters
    for param in glove.params():
        print(param.path)

    # Subscribable topics
    for topic in glove.topics():
        print(topic.path)
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    // Read/write parameters
    WujiParam *params = NULL;
    size_t n = 0;
    wuji_dev_list_params(glove, &params, &n);
    for (size_t i = 0; i < n; i++) printf("%s\n", params[i].path);
    wuji_param_array_free(params, n);

    // Subscribable topics
    WujiTopic *topics = NULL;
    size_t m = 0;
    wuji_dev_list_topics(glove, &topics, &m);
    for (size_t i = 0; i < m; i++) printf("%s\n", topics[i].path);
    wuji_topic_array_free(topics, m);
    ```
  </Tab>
</Tabs>

## Interface List

| Operation | Python | C |
| - | - | - |
| Read Wuji Glove identity and network parameters | `glove.sn().get()`, `glove.version().get()`, `glove.hand_side().get()`, `glove.ip().get()`, `glove.port().get()` | `wuji_glove_get_sn()`, `wuji_glove_get_version()`, `wuji_glove_get_hand_side()`, `wuji_glove_get_ip()`, `wuji_glove_get_port()` |
| Write Wuji Glove network parameters | `glove.ip().set(...)`, `glove.port().set(...)` | `wuji_glove_set_ip()`, `wuji_glove_set_port()` |
| Read or write any parameter by path | `device.get(path)`, `device.set(path, value)` | `wuji_dev_get()`, `wuji_dev_set()` exchange raw protocol bytes — release the read buffer with `wuji_buffer_free()` |
| Run a device action | `device.execute(path)` | `wuji_dev_execute()`, plus `wuji_glove_reboot()` and `wuji_hand_2_reboot()` for reboots |
| Create and delete a user | `manager.create_user(...)`, `manager.delete_user(user_id)` | `wuji_create_user()`, `wuji_delete_user()` |
| Switch and query users | `manager.switch_user(...)`, `manager.switch_to_default_user()`, `manager.current_user()`, `manager.list_users()` | `wuji_switch_user()`, `wuji_switch_to_default_user()`, `wuji_current_user()`, `wuji_list_users()` |
| Update user details | `manager.update_user(...)` | `wuji_update_user()`, which takes only the changed fields through `WujiUserUpdate` |
| Export, preview, and import user data | `manager.export_user_data(...)`, `manager.preview_user_data(...)`, `manager.import_user_data(...)` | `wuji_user_data_export()`, `wuji_user_data_preview()`, `wuji_user_data_import()` |
| Look up tactile data paths | `manager.tactile_dir(...)`, `manager.tactile_model_paths(...)`, `manager.tactile_run_dir(...)` | No equivalent — build the paths from the directory layout on this page |
| Read or write the custom hand model path | `glove.hand_model_path().get()`, `glove.hand_model_path().set(path)` | `wuji_glove_get_hand_model_path()`, `wuji_glove_set_hand_model_path()` |
| Enumerate resources, parameters, and topics | `device.resources()`, `device.params()`, `device.topics()` | Enumerate parameters and topics separately with `wuji_dev_list_params()` and `wuji_dev_list_topics()`. There's no combined resource listing |

Every C string getter uses a two-step size query: pass `buf = NULL` and `buf_len = 0` to learn the required byte count, then allocate and call again to fill the buffer. APIs that return a report dictionary in Python return only a `WujiStatus` in C, because the per-component detail doesn't cross the FFI boundary. For the `WujiUserInfo` and `WujiUserUpdate` fields, memory ownership, and the release functions, see the [C Interface Reference](/en/docs/wuji-sdk/data-reference/).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.