Skip to content

C++ SDK - Revo 2 Dexterous Hand

System Requirements

  • Linux: Ubuntu 20.04/22.04 LTS (x86_64/aarch64), glibc ≥ 2.31
  • macOS: 10.15+
  • Windows: 10/11

Quick Start

Clone Repository

shell
# Use HTTPS method
git clone https://github.com/BrainCoTech/brainco-hand-sdk.git

# Or use SSH method
git clone git@github.com:BrainCoTech/brainco-hand-sdk.git

Installation & Configuration

shell
# Install dependencies
# Required: build-essential, cmake, make, gcc, g++, etc.

# Install SDK dependencies
./download-lib.sh

# Configure device permissions
# In Linux, serial devices (e.g., /dev/ttyUSB0) typically belong to dialout or tty group
# Only group members can access the device, for example:
# crw-rw---- 1 root dialout 188, 0 Aug 14 12:00 /dev/ttyUSB0

# Add current user to dialout group
sudo usermod -aG dialout $USER
# Note: You need to log out and log back in for this to take effect

# Build and run example (unified cross-platform directory)
cd c && make
cd demo && ./hand_demo.exe    # Auto-detect device and run
shell
# Install Xcode or Xcode Command Line Tools
xcode-select --install

# Install SDK dependencies
./download-lib.sh

# Build and run example (unified cross-platform directory)
cd c && make
cd demo && ./hand_demo.exe    # Auto-detect device and run
shell
# Requires MinGW or MSVC build environment

# Install SDK dependencies
./download-lib.sh

# Build and run example (unified cross-platform directory)
cd c && make
cd demo && hand_demo.exe      # Auto-detect device and run

Example Code

The SDK provides comprehensive example code covering different communication protocols and application scenarios.

Unified cross-platform examples with auto-detection and multi-protocol support.

shell
cd c
make
cd demo
./hand_demo.exe       # Auto-detect device and run demo
ExampleDescription
hand_demo.cppComprehensive demo (8 modes: position/speed/current control, action sequences, touch sensors, etc.)
hand_monitor.cppReal-time data monitor (motor status, touch data)
hand_dfu.cppFirmware upgrade
auto_detect.cppDevice auto-detection

Supported initialization methods:

shell
./hand_demo.exe                                          # Auto-detect (recommended)
./hand_demo.exe -m /dev/ttyUSB0 460800 127               # Modbus
./hand_demo.exe -c /dev/cu.usbmodem14201 1000000 1       # CAN 2.0 (ZQWL)
./hand_demo.exe -f /dev/cu.usbmodem14201 1000000 5000000 127  # CANFD (ZQWL)
./hand_demo.exe -s can0 1                                # SocketCAN CAN 2.0 (external adapter)
./hand_demo.exe -S can0 127                              # SocketCAN CANFD (external adapter)
./hand_demo.exe -b can0 1                                # SocketCAN CAN 2.0 (SDK built-in)
./hand_demo.exe -B can0 127                              # SocketCAN CANFD (SDK built-in)
./hand_demo.exe -z 2                                     # ZLG CAN 2.0
./hand_demo.exe -Z 127                                   # ZLG CANFD

Legacy Protocol Examples (Archived)

⚠️ The following examples have been archived to archive/. We recommend using the Cross-Platform Examples (c/demo/) above, which support auto-detection of all protocols and devices.

Modbus-RTU Protocol

CANFD Protocol

EtherCAT Protocol

API Reference

For C/C++ API lookup, use the complete C ABI header bundled with the SDK 2.x package.

View C/C++ header (stark-sdk.h)
c
#ifndef STARK_SDK_H
#define STARK_SDK_H

/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */

#include <cstdarg>
#include <cstdint>
#include <cstdlib>
#include <ostream>
#include <new>

/// Built‑in gestures 1~6: open hand, fist, two‑finger pinch, three‑finger pinch,
/// side pinch, single‑finger point.
/// Revo1 supports up to 6 custom action sequences: IDs 10~15.
/// Revo2 supports up to 24 custom action sequences: IDs 7~30.
enum ActionSequenceId : uint8_t {
  ACTION_SEQUENCE_ID_DEFAULT_GESTURE_OPEN = 1,
  ACTION_SEQUENCE_ID_DEFAULT_GESTURE_FIST = 2,
  ACTION_SEQUENCE_ID_DEFAULT_GESTURE_PINCH_TWO = 3,
  ACTION_SEQUENCE_ID_DEFAULT_GESTURE_PINCH_THREE = 4,
  ACTION_SEQUENCE_ID_DEFAULT_GESTURE_PINCH_SIDE = 5,
  ACTION_SEQUENCE_ID_DEFAULT_GESTURE_POINT = 6,
  ACTION_SEQUENCE_ID_CUSTOM_GESTURE22 = 7,
  ACTION_SEQUENCE_ID_CUSTOM_GESTURE23 = 8,
  ACTION_SEQUENCE_ID_CUSTOM_GESTURE24 = 9,
  ACTION_SEQUENCE_ID_CUSTOM_GESTURE1 = 10,
  ACTION_SEQUENCE_ID_CUSTOM_GESTURE2 = 11,
  ACTION_SEQUENCE_ID_CUSTOM_GESTURE3 = 12,
  ACTION_SEQUENCE_ID_CUSTOM_GESTURE4 = 13,
  ACTION_SEQUENCE_ID_CUSTOM_GESTURE5 = 14,
  ACTION_SEQUENCE_ID_CUSTOM_GESTURE6 = 15,
  ACTION_SEQUENCE_ID_CUSTOM_GESTURE7 = 16,
  ACTION_SEQUENCE_ID_CUSTOM_GESTURE8 = 17,
  ACTION_SEQUENCE_ID_CUSTOM_GESTURE9 = 18,
  ACTION_SEQUENCE_ID_CUSTOM_GESTURE10 = 19,
  ACTION_SEQUENCE_ID_CUSTOM_GESTURE11 = 20,
  ACTION_SEQUENCE_ID_CUSTOM_GESTURE12 = 21,
  ACTION_SEQUENCE_ID_CUSTOM_GESTURE13 = 22,
  ACTION_SEQUENCE_ID_CUSTOM_GESTURE14 = 23,
  ACTION_SEQUENCE_ID_CUSTOM_GESTURE15 = 24,
  ACTION_SEQUENCE_ID_CUSTOM_GESTURE16 = 25,
  ACTION_SEQUENCE_ID_CUSTOM_GESTURE17 = 26,
  ACTION_SEQUENCE_ID_CUSTOM_GESTURE18 = 27,
  ACTION_SEQUENCE_ID_CUSTOM_GESTURE19 = 28,
  ACTION_SEQUENCE_ID_CUSTOM_GESTURE20 = 29,
  ACTION_SEQUENCE_ID_CUSTOM_GESTURE21 = 30,
};

/// DFU (Device Firmware Update) state
/// Used in DFU state callback to indicate current upgrade status
enum DfuState : uint8_t {
  /// DFU not started
  DFU_STATE_IDLE = 0,
  /// Preparing to start DFU
  DFU_STATE_STARTING = 1,
  /// DFU started, ready to transfer
  DFU_STATE_STARTED = 2,
  /// Transferring firmware data
  DFU_STATE_TRANSFER = 3,
  /// DFU completed successfully
  DFU_STATE_COMPLETED = 4,
  /// DFU aborted or failed
  DFU_STATE_ABORTED = 5,
};

/// EtherCAT FoE (File over EtherCAT) firmware type
/// Used for firmware upgrade over EtherCAT
enum EtherCATFoeType : uint8_t {
  /// Wrist module firmware
  ETHER_CAT_FOE_TYPE_WRIST = 1,
  /// Main controller firmware
  ETHER_CAT_FOE_TYPE_CONTROL = 2,
};

/// Finger control unit mode
/// Determines how position/speed/current values are interpreted
enum FingerUnitMode : uint8_t {
  /// Normalized mode: values in range 0-1000 (default)
  FINGER_UNIT_MODE_NORMALIZED = 0,
  /// Physical mode: values in physical units (degrees, °/s, mA)
  FINGER_UNIT_MODE_PHYSICAL = 1,
};

enum ForceLevel : uint8_t {
  FORCE_LEVEL_SMALL = 1,
  FORCE_LEVEL_NORMAL = 2,
  FORCE_LEVEL_FULL = 3,
};
enum HandType : uint8_t {
  HAND_TYPE_LEFT = 0,
  HAND_TYPE_RIGHT = 1,
};
enum LedColor : uint8_t {
  LED_COLOR_UNCHANGED = 0,
  LED_COLOR_R = 1,
  LED_COLOR_G = 2,
  LED_COLOR_RG = 3,
  LED_COLOR_B = 4,
  LED_COLOR_RB = 5,
  LED_COLOR_GB = 6,
  LED_COLOR_RGB = 7,
};
enum LedMode : uint8_t {
  LED_MODE_NONE = 0,
  LED_MODE_SHUTDOWN = 1,
  LED_MODE_KEEP = 2,
  LED_MODE_BLINK = 3,
  LED_MODE_ONE_SHOT = 4,
  LED_MODE_BLINK0_5HZ = 5,
  LED_MODE_BLINK2HZ = 6,
};
enum LogLevel : uint8_t {
  LOG_LEVEL_ERROR = 0,
  LOG_LEVEL_WARN = 1,
  LOG_LEVEL_INFO = 2,
  LOG_LEVEL_DEBUG = 3,
  LOG_LEVEL_TRACE = 4,
};

/// Motor state
/// Indicates the current operating state of a finger motor
enum MotorState : uint8_t {
  /// Motor is idle (not moving)
  MOTOR_STATE_IDLE = 0,
  /// Motor is running (moving)
  MOTOR_STATE_RUNNING = 1,
  /// Motor is stalled (blocked or reached limit)
  MOTOR_STATE_STALL = 2,
  /// Motor is in turbo mode (continuous force)
  MOTOR_STATE_TURBO = 3,
  /// Unknown state
  MOTOR_STATE_UNKNOWN = 255,
};

/// Button press state
/// Used in button event callbacks
enum PressState : uint8_t {
  /// No button event
  PRESS_STATE_NONE = 0,
  /// Button pressed down
  PRESS_STATE_DOWN = 1,
  /// Button released
  PRESS_STATE_UP = 2,
};

/// SKU type (device hand type)
/// Identifies the hand side (left/right). Size is determined by hardware_type.
/// Note: Currently only Medium size is available. Small size values are reserved.
enum SkuType : uint8_t {
  /// Medium size, right hand
  SKU_TYPE_MEDIUM_RIGHT = 1,
  /// Medium size, left hand
  SKU_TYPE_MEDIUM_LEFT = 2,
  /// Small size, right hand (reserved, not currently used)
  SKU_TYPE_SMALL_RIGHT = 3,
  /// Small size, left hand (reserved, not currently used)
  SKU_TYPE_SMALL_LEFT = 4,
};

/// Finger identifier
/// Used to specify which finger to control or query
enum StarkFingerId : uint8_t {
  /// Thumb (main joint)
  STARK_FINGER_ID_THUMB = 1,
  /// Thumb auxiliary (rotation joint, Revo2 only)
  STARK_FINGER_ID_THUMB_AUX = 2,
  /// Index finger
  STARK_FINGER_ID_INDEX = 3,
  /// Middle finger
  STARK_FINGER_ID_MIDDLE = 4,
  /// Ring finger
  STARK_FINGER_ID_RING = 5,
  /// Pinky finger
  STARK_FINGER_ID_PINKY = 6,
};

/// Stark hardware type
/// Identifies the device generation and capabilities
///
/// Value ranges:
///   Revo1: 0~9
///   Revo2: 10~19
enum StarkHardwareType : uint8_t {
  /// Revo1 with legacy Protobuf protocol
  STARK_HARDWARE_TYPE_REVO1_PROTOBUF = 0,
  /// Revo1 Basic (no touch sensor)
  STARK_HARDWARE_TYPE_REVO1_BASIC = 1,
  /// Revo1 with capacitive touch sensor
  STARK_HARDWARE_TYPE_REVO1_TOUCH = 2,
  /// Revo1 Advanced: wide voltage, multi-interface support
  STARK_HARDWARE_TYPE_REVO1_ADVANCED = 3,
  /// Revo1 Advanced with capacitive touch sensor
  STARK_HARDWARE_TYPE_REVO1_ADVANCED_TOUCH = 4,
  /// Revo2 Basic (no touch sensor)
  STARK_HARDWARE_TYPE_REVO2_BASIC = 10,
  /// Revo2 with capacitive touch sensor
  STARK_HARDWARE_TYPE_REVO2_TOUCH = 11,
  /// Revo2 with pressure/modulus touch sensor
  STARK_HARDWARE_TYPE_REVO2_TOUCH_PRESSURE = 12,
  /// Revo2 with 3D force matrix touch sensor
  STARK_HARDWARE_TYPE_REVO2_TOUCH_FORCE3D = 13,
  /// Revo2 with array pressure touch sensor
  STARK_HARDWARE_TYPE_REVO2_TOUCH_ARRAY_PRESSURE = 14,
};

/// Communication protocol type
/// Determines how the SDK communicates with the device
enum StarkProtocolType : uint8_t {
  /// Auto-detect protocol (used in stark_auto_detect)
  STARK_PROTOCOL_TYPE_AUTO = 0,
  /// Modbus RTU over RS485 serial
  STARK_PROTOCOL_TYPE_MODBUS = 1,
  /// CAN 2.0 protocol (1Mbps)
  STARK_PROTOCOL_TYPE_CAN = 2,
  /// CAN FD protocol (1Mbps arbitration, 5Mbps data)
  STARK_PROTOCOL_TYPE_CAN_FD = 3,
  /// EtherCAT protocol (Linux only)
  STARK_PROTOCOL_TYPE_ETHER_CAT = 4,
  /// Legacy Protobuf protocol (Revo1 only)
  STARK_PROTOCOL_TYPE_PROTOBUF = 5,
};

/// Touch sensor status
/// Indicates the health status of a touch sensor
enum TouchSensorStatus : uint8_t {
  /// Sensor data is valid
  TOUCH_SENSOR_STATUS_NORMAL = 0,
  /// Sensor data is abnormal
  TOUCH_SENSOR_STATUS_ABNORMAL = 1,
  /// Communication error with sensor
  TOUCH_SENSOR_STATUS_COMMUNICATION_ERROR = 2,
  /// Unknown state
  TOUCH_SENSOR_STATUS_UNKNOWN = 255,
};

/// Opaque handle for ArrayPressureTouchDataBuffer
struct CArrayPressureTouchItemBuffer;

/// Opaque handle for DataCollector
struct CDataCollector;

/// Opaque handle for MotorStatusBuffer
struct CMotorStatusBuffer;

/// Opaque handle for PressureDetailedBuffer
struct CPressureDetailedBuffer;

/// Opaque handle for PressureSummaryBuffer
struct CPressureSummaryBuffer;

/// Opaque handle for TouchStatusBuffer
struct CTouchStatusBuffer;

/// Device handler for C API
/// This is an opaque handle that wraps the internal device context.
/// Use `modbus_open` or `init_device_handler` to create, and `modbus_close`
/// or `close_device_handler` to release.
struct DeviceHandler;

/// Motor status data for all 6 finger joints.
/// Returned by `stark_get_motor_status`. Call `free_motor_status_data` to release.
/// Array order: [Thumb, ThumbAux, Index, Middle, Ring, Pinky]
struct CMotorStatusData {
  /// Position values in unified range 0-1000 (0=open, 1000=closed)
  uint16_t positions[6];
  /// Speed values in unified range -1000 to +1000 (positive=closing)
  int16_t speeds[6];
  /// Current values in unified range -1000 to +1000 (positive=closing)
  int16_t currents[6];
  /// Motor states (see MotorState enum)
  uint8_t states[6];
};

/// Tactile sensor data (C API)
/// 3D force values, self-capacitance, mutual-capacitance values, and sensor status.
/// Revo2 touch hand only has normal force, tangential force, tangential force
/// direction, and proximity; other fields are zero.
struct CTouchFingerItem {
  uint16_t normal_force1;
  uint16_t normal_force2;
  uint16_t normal_force3;
  uint16_t tangential_force1;
  uint16_t tangential_force2;
  uint16_t tangential_force3;
  uint16_t tangential_direction1;
  uint16_t tangential_direction2;
  uint16_t tangential_direction3;
  uint32_t self_proximity1;
  uint32_t self_proximity2;
  uint32_t mutual_proximity;
  uint16_t status;
};

/// Pressure sensor detailed data (Detailed mode)
///
/// For Modulus pressure touch sensor detailed sensor point data
/// - Fingers: 9 sensor points
/// - Palm: 46 sensor points
struct PressureDetailedItem {
  /// Number of sensor points (thumb/index/middle/ring/pinky: 9, palm: 46)
  uint8_t sensor_count;
  /// Sensor data (up to 46 sensor points)
  uint16_t sensors_data[46];
};

/// ArrayPressure sensor status.
struct CArrayPressureStatus {
  /// Sensor status (5 bits, one per finger, 1=abnormal)
  uint16_t sensor_status;
  /// Warmup status (0=warming up, 1=complete)
  uint8_t warmup_complete;
};

/// ArrayPressure touch data (25 registers = 50 bytes).
/// Call `free_array_pressure_touch_data` to release.
struct CArrayPressureTouchData {
  CArrayPressureStatus status;
  uint16_t data[25];
  uint16_t data_len;
};

/// Device configuration returned by auto-detect functions.
/// Contains the detected protocol, port, baudrate, and slave ID.
/// Call `free_device_config` to release memory.
struct CDeviceConfig {
  /// Communication protocol type
  StarkProtocolType protocol;
  /// Serial port name (e.g., "/dev/ttyUSB0" or "COM3")
  const char *port_name;
  /// Baud rate in bps
  uint32_t baudrate;
  /// Slave ID on the bus
  uint8_t slave_id;
};

/// ZQWL device info struct (C interface)
struct CZqwlDeviceInfo {
  /// Device type (0x0100=dual-channel CANFD, 0x0101=single-channel CANFD, 0x0102=dual-channel CAN, etc.)
  uint16_t device_type;
  /// Serial port name
  char *port_name;
  /// USB VID
  uint16_t vid;
  /// USB PID
  uint16_t pid;
  /// Whether CANFD is supported
  bool supports_canfd;
  /// Number of channels
  uint8_t channel_count;
};

/// ZQWL device list struct (C interface)
struct ZqwlDeviceList {
  /// Device array pointer
  CZqwlDeviceInfo *devices;
  /// Device count
  uintptr_t count;
};

/// SocketCAN device info struct (C interface)
struct CSocketCanDeviceInfo {
  /// Interface name (e.g. "can0")
  char *iface;
  /// Whether CANFD is supported
  bool supports_canfd;
  /// Whether interface is up
  bool is_up;
};

/// SocketCAN device list struct (C interface)
struct SocketCanDeviceList {
  /// Device array pointer
  CSocketCanDeviceInfo *devices;
  /// Device count
  uintptr_t count;
};

/// Detected device information (C interface)
struct CDetectedDevice {
  /// Protocol type
  StarkProtocolType protocol;
  /// Port name (serial port or CAN adapter port)
  char *port_name;
  /// Slave ID on the bus
  uint8_t slave_id;
  /// Baudrate (for Modbus/serial protocols, or CAN arbitration baudrate)
  uint32_t baudrate;
  /// Data baudrate (for CANFD only, 0 for other protocols)
  uint32_t data_baudrate;
  /// Hardware type
  StarkHardwareType hardware_type;
  /// SKU type
  SkuType sku_type;
  /// Serial number (NULL if not detected)
  char *serial_number;
  /// Firmware version (NULL if not detected)
  char *firmware_version;
};

/// Detected device list (C interface)
struct CDetectedDeviceList {
  /// Device array pointer
  CDetectedDevice *devices;
  /// Device count
  uintptr_t count;
};

/// Modbus async read/write result callback.
/// This callback processes results of asynchronous Modbus operations.
/// Return value: 0 on success, non‑zero on failure.
using ModbusOperationResultCallback = void(*)(uint8_t*, int, int, void*);

/// Modbus async read/write callback type.
/// Return value: 0 on success, non‑zero on failure.
using ModbusOperationCallback = int32_t(*)(const uint8_t *values,
                                           int len,
                                           ModbusOperationResultCallback callback,
                                           void *user_data);

/// Custom Modbus RX callback.
/// Return value: 0 on success, non‑zero on failure.
using ModbusRxCallback = int32_t(*)(uint8_t slave_id,
                                    uint16_t register_address,
                                    uint16_t *data_out,
                                    uint16_t count);

/// Custom Modbus TX callback.
/// Return value: 0 on success, non‑zero on failure.
using ModbusTxCallback = int32_t(*)(uint8_t slave_id,
                                    uint16_t register_address,
                                    const uint16_t *data,
                                    uint16_t count);

/// CAN/CANFD RX callback.
///
/// # Parameters
/// - `slave_id`: Slave ID
/// - `expected_can_id`: Expected CAN ID to filter responses
///   - CAN 2.0: `= 0` for DFU mode (accept any), `> 0` for normal filtering
///   - CANFD: Always `> 0`, matches by slave_id and master_id
/// - `expected_frames`: `0` = auto-detect multi-frame, `> 0` = specific frame count
/// - `can_id_out`: Output CAN ID
/// - `data_out`: Output data buffer (at least 512 bytes for multi-frame)
/// - `data_len_out`: Output data length
///
/// # Return value
/// 0 on success, non‑zero on failure.
using CanRxCallback = int32_t(*)(uint8_t slave_id,
                                 uint32_t expected_can_id,
                                 uint8_t expected_frames,
                                 uint32_t *can_id_out,
                                 uint8_t *data_out,
                                 uintptr_t *data_len_out);

/// CAN/CANFD TX callback.
/// Return value: 0 on success, non‑zero on failure.
using CanTxCallback = int32_t(*)(uint8_t slave_id,
                                 uint32_t can_id,
                                 const uint8_t *data,
                                 uintptr_t data_len);

/// DFU state callback, `state` corresponds to `DfuState`.
using DfuStateCallback = void(*)(uint8_t slave_id, uint8_t state);

/// DFU progress callback.
using DfuProgressCallback = void(*)(uint8_t slave_id, float progress);

/// Device information returned by `stark_get_device_info`.
/// Contains device identification and version information.
/// Call `free_device_info` to release memory.
struct CDeviceInfo {
  /// Device SKU (hand side: left/right) (Deprecated, use hand_type instead)
  SkuType sku_type;
  /// Device hand type (left/right)
  HandType hand_type;
  /// Hardware type (device generation and capabilities)
  StarkHardwareType hardware_type;
  /// Serial number string (null-terminated)
  const char *serial_number;
  /// Firmware version string (null-terminated)
  const char *firmware_version;
  /// Hardware version string (reserved; empty for current devices)
  const char *hardware_version;
};

/// Raw capacitance data from touch sensors.
/// Contains raw ADC values for each sensor channel.
/// Call `free_touch_raw_data` to release.
struct CTouchRawData {
  /// Thumb sensor channels (7 channels)
  uint32_t thumb[7];
  /// Index finger sensor channels (11 channels)
  uint32_t index[11];
  /// Middle finger sensor channels (11 channels)
  uint32_t middle[11];
  /// Ring finger sensor channels (11 channels)
  uint32_t ring[11];
  /// Pinky finger sensor channels (7 channels)
  uint32_t pinky[7];
};

/// Touch data for all 5 fingers.
/// Returned by `stark_get_touch_status`. Call `free_touch_finger_data` to release.
/// Array order: [Thumb, Index, Middle, Ring, Pinky]
struct CTouchFingerData {
  /// Touch data for each finger
  CTouchFingerItem items[5];
};

/// 3D force data for a single measurement point.
/// Fx, Fy are signed (-128 ~ +127), Fz is unsigned (0 ~ 255).
struct CForce3DPoint {
  int8_t fx;
  int8_t fy;
  uint16_t fz;
};

/// Force3D summary data for 4 fingers (no thumb).
/// Call `free_force3d_touch_summary` to release.
struct CForce3DTouchSummary {
  CForce3DPoint index;
  CForce3DPoint middle;
  CForce3DPoint ring;
  CForce3DPoint pinky;
};

/// Force3D array data for a single finger (31 measurement points).
/// Call `free_force3d_finger_array` to release.
struct CForce3DFingerArray {
  CForce3DPoint points[31];
  uint16_t count;
};

/// Turbo mode configuration.
/// Turbo mode enables continuous gripping force.
/// Call `free_turbo_config` to release.
struct CTurboConfig {
  /// Interval between turbo pulses in milliseconds
  uint16_t interval;
  /// Duration of each turbo pulse in milliseconds
  uint16_t duration;
};

/// LED information returned by `stark_get_led_info`.
/// Call `free_led_info` to release.
struct CLedInfo {
  /// Current LED color
  LedColor color;
  /// Current LED mode (blinking pattern)
  LedMode mode;
};

/// Button press event returned by `stark_get_button_event`.
/// Call `free_button_event` to release.
struct CButtonPressEvent {
  /// Event timestamp in milliseconds
  int32_t timestamp;
  /// Button identifier
  int32_t button_id;
  /// Press state (down/up)
  PressState press_state;
};

extern "C" {

/// Create a new motor status buffer
///
/// @param max_size Maximum buffer capacity (default 1000)
/// @return Pointer to CMotorStatusBuffer, or NULL on failure
CMotorStatusBuffer *motor_buffer_new(uintptr_t max_size);

/// Free a motor status buffer
///
/// @param buffer Pointer to CMotorStatusBuffer
void motor_buffer_free(CMotorStatusBuffer *buffer);

/// Get all motor status data from buffer
///
/// @param buffer Pointer to CMotorStatusBuffer
/// @param out_data Pointer to array to store data (caller must allocate, no need to free returned data)
/// @param max_count Maximum number of items to return (size of out_data array)
/// @param out_count Pointer to store the number of items actually returned
/// @return 0 on success, -1 on failure
///
/// @note Data is copied to caller-allocated buffer. Caller is responsible for
///       allocating out_data array before calling, but does NOT need to free
///       the returned data (it's stack/heap memory owned by caller).
int motor_buffer_pop_all(CMotorStatusBuffer *buffer,
                         CMotorStatusData *out_data,
                         uintptr_t max_count,
                         uintptr_t *out_count);

/// Get the number of items in the buffer
///
/// @param buffer Pointer to CMotorStatusBuffer
/// @return Number of items, or 0 if buffer is NULL
uintptr_t motor_buffer_len(const CMotorStatusBuffer *buffer);

/// Check if buffer is empty
///
/// @param buffer Pointer to CMotorStatusBuffer
/// @return 1 if empty, 0 if not empty, -1 if buffer is NULL
int motor_buffer_is_empty(const CMotorStatusBuffer *buffer);

/// Clear all data from buffer
///
/// @param buffer Pointer to CMotorStatusBuffer
void motor_buffer_clear(CMotorStatusBuffer *buffer);

/// Create a new touch status buffer
///
/// @param max_size Maximum buffer capacity per finger (default 1000)
/// @return Pointer to CTouchStatusBuffer, or NULL on failure
CTouchStatusBuffer *touch_buffer_new(uintptr_t max_size);

/// Free a touch status buffer
///
/// @param buffer Pointer to CTouchStatusBuffer
void touch_buffer_free(CTouchStatusBuffer *buffer);

/// Get all touch data for all fingers
///
/// @param buffer Pointer to CTouchStatusBuffer
/// @param out_data Pointer to array of 5 arrays to store data (caller must allocate, no need to free returned data)
/// @param max_counts Pointer to array of 5 max sizes (capacity of each out_data array)
/// @param out_counts Pointer to array of 5 sizes to store actual counts per finger
/// @return 0 on success, -1 on failure
///
/// @note Data is copied to caller-allocated buffers. Caller is responsible for
///       allocating out_data arrays before calling, but does NOT need to free
///       the returned data (it's stack/heap memory owned by caller).
int touch_buffer_pop_all(CTouchStatusBuffer *buffer,
                         CTouchFingerItem **out_data,
                         const uintptr_t *max_counts,
                         uintptr_t *out_counts);

/// Get touch data for a single finger
///
/// @param buffer Pointer to CTouchStatusBuffer
/// @param finger_index Finger index (0-4)
/// @param out_data Pointer to array to store data (caller must allocate, no need to free returned data)
/// @param max_count Maximum number of items to return (size of out_data array)
/// @param out_count Pointer to store the number of items actually returned
/// @return 0 on success, -1 on failure
///
/// @note Data is copied to caller-allocated buffer. Caller is responsible for
///       allocating out_data array before calling, but does NOT need to free
///       the returned data (it's stack/heap memory owned by caller).
int touch_buffer_pop_finger(CTouchStatusBuffer *buffer,
                            uintptr_t finger_index,
                            CTouchFingerItem *out_data,
                            uintptr_t max_count,
                            uintptr_t *out_count);

/// Get buffer lengths for all fingers
///
/// @param buffer Pointer to CTouchStatusBuffer
/// @param out_lengths Pointer to array of 5 sizes
/// @return 0 on success, -1 on failure
int touch_buffer_len_all(const CTouchStatusBuffer *buffer, uintptr_t *out_lengths);

/// Clear all touch buffers
///
/// @param buffer Pointer to CTouchStatusBuffer
void touch_buffer_clear(CTouchStatusBuffer *buffer);

/// Create a new pressure summary buffer
///
/// @param max_size Maximum buffer capacity per finger (default 1000)
/// @return Pointer to CPressureSummaryBuffer, or NULL on failure
CPressureSummaryBuffer *pressure_summary_buffer_new(uintptr_t max_size);

/// Free a pressure summary buffer
///
/// @param buffer Pointer to CPressureSummaryBuffer
void pressure_summary_buffer_free(CPressureSummaryBuffer *buffer);

/// Get all pressure summary data for all fingers
///
/// @param buffer Pointer to CPressureSummaryBuffer
/// @param out_data Pointer to array of 6 arrays to store data (caller must allocate, no need to free returned data)
/// @param max_counts Pointer to array of 6 max sizes (capacity of each out_data array)
/// @param out_counts Pointer to array of 6 sizes to store actual counts per finger
/// @return 0 on success, -1 on failure
///
/// @note Data is copied to caller-allocated buffers. Caller is responsible for
///       allocating out_data arrays before calling, but does NOT need to free
///       the returned data (it's stack/heap memory owned by caller).
int pressure_summary_buffer_pop_all(CPressureSummaryBuffer *buffer,
                                    uint16_t **out_data,
                                    const uintptr_t *max_counts,
                                    uintptr_t *out_counts);

/// Get pressure summary data for a single part (finger or palm)
///
/// @param buffer Pointer to CPressureSummaryBuffer
/// @param finger_index Part index (0-5: 0-4=fingers, 5=palm)
/// @param out_data Pointer to array to store data (caller must allocate, no need to free returned data)
/// @param max_count Maximum number of items to return (size of out_data array)
/// @param out_count Pointer to store the number of items actually returned
/// @return 0 on success, -1 on failure
///
/// @note Data is copied to caller-allocated buffer. Caller is responsible for
///       allocating out_data array before calling, but does NOT need to free
///       the returned data (it's stack/heap memory owned by caller).
int pressure_summary_buffer_pop_finger(CPressureSummaryBuffer *buffer,
                                       uintptr_t finger_index,
                                       uint16_t *out_data,
                                       uintptr_t max_count,
                                       uintptr_t *out_count);

/// Clear all pressure summary buffers
///
/// @param buffer Pointer to CPressureSummaryBuffer
void pressure_summary_buffer_clear(CPressureSummaryBuffer *buffer);

/// Create a new pressure detailed buffer
///
/// @param max_size Maximum buffer capacity per finger (default 1000)
/// @return Pointer to CPressureDetailedBuffer, or NULL on failure
CPressureDetailedBuffer *pressure_detailed_buffer_new(uintptr_t max_size);

/// Free a pressure detailed buffer
///
/// @param buffer Pointer to CPressureDetailedBuffer
void pressure_detailed_buffer_free(CPressureDetailedBuffer *buffer);

/// Get all pressure detailed data for all fingers
///
/// @param buffer Pointer to CPressureDetailedBuffer
/// @param out_data Pointer to array of 6 arrays to store data (caller must allocate, no need to free returned data)
/// @param max_counts Pointer to array of 6 max sizes (capacity of each out_data array)
/// @param out_counts Pointer to array of 6 sizes to store actual counts per finger
/// @return 0 on success, -1 on failure
///
/// @note Data is copied to caller-allocated buffers. Caller is responsible for
///       allocating out_data arrays before calling, but does NOT need to free
///       the returned data (it's stack/heap memory owned by caller).
int pressure_detailed_buffer_pop_all(CPressureDetailedBuffer *buffer,
                                     PressureDetailedItem **out_data,
                                     const uintptr_t *max_counts,
                                     uintptr_t *out_counts);

/// Get pressure detailed data for a single part (finger or palm)
///
/// @param buffer Pointer to CPressureDetailedBuffer
/// @param finger_index Part index (0-5: 0-4=fingers, 5=palm)
/// @param out_data Pointer to array to store data (caller must allocate, no need to free returned data)
/// @param max_count Maximum number of items to return (size of out_data array)
/// @param out_count Pointer to store the number of items actually returned
/// @return 0 on success, -1 on failure
///
/// @note Data is copied to caller-allocated buffer. Caller is responsible for
///       allocating out_data array before calling, but does NOT need to free
///       the returned data (it's stack/heap memory owned by caller).
int pressure_detailed_buffer_pop_finger(CPressureDetailedBuffer *buffer,
                                        uintptr_t finger_index,
                                        PressureDetailedItem *out_data,
                                        uintptr_t max_count,
                                        uintptr_t *out_count);

/// Clear all pressure detailed buffers
///
/// @param buffer Pointer to CPressureDetailedBuffer
void pressure_detailed_buffer_clear(CPressureDetailedBuffer *buffer);

CArrayPressureTouchItemBuffer *array_pressure_touch_buffer_new(uintptr_t max_size);

void array_pressure_touch_buffer_free(CArrayPressureTouchItemBuffer *buffer);

int array_pressure_touch_buffer_pop_all(CArrayPressureTouchItemBuffer *buffer,
                                        CArrayPressureTouchData *out_data,
                                        uintptr_t max_count);

uintptr_t array_pressure_touch_buffer_len(const CArrayPressureTouchItemBuffer *buffer);

void array_pressure_touch_buffer_clear(CArrayPressureTouchItemBuffer *buffer);

/// Create a new data collector - Basic version (motor only)
///
/// @param handle Device handler
/// @param motor_buffer Motor status buffer
/// @param slave_id Slave ID (default 1)
/// @param motor_frequency Motor sampling frequency in Hz (default 1000)
/// @param enable_stats Whether to print statistics (1=true, 0=false)
/// @return Pointer to CDataCollector, or NULL on failure
CDataCollector *data_collector_new_basic(DeviceHandler *handle,
                                         CMotorStatusBuffer *motor_buffer,
                                         unsigned char slave_id,
                                         uint32_t motor_frequency,
                                         int enable_stats);

/// Create a new data collector - Capacitive touch version
///
/// @param handle Device handler
/// @param motor_buffer Motor status buffer
/// @param touch_buffer Touch status buffer
/// @param slave_id Slave ID (default 1)
/// @param motor_frequency Motor sampling frequency in Hz (default 1000)
/// @param touch_frequency Touch sampling frequency in Hz (default 100)
/// @param enable_stats Whether to print statistics (1=true, 0=false)
/// @return Pointer to CDataCollector, or NULL on failure
CDataCollector *data_collector_new_capacitive(DeviceHandler *handle,
                                              CMotorStatusBuffer *motor_buffer,
                                              CTouchStatusBuffer *touch_buffer,
                                              unsigned char slave_id,
                                              uint32_t motor_frequency,
                                              uint32_t touch_frequency,
                                              int enable_stats);

/// Create a new data collector - Pressure Summary version
///
/// @param handle Device handler
/// @param motor_buffer Motor status buffer
/// @param pressure_summary_buffer Pressure summary buffer
/// @param slave_id Slave ID (default 1)
/// @param motor_frequency Motor sampling frequency in Hz (default 1000)
/// @param touch_frequency Touch sampling frequency in Hz (default 100)
/// @param enable_stats Whether to print statistics (1=true, 0=false)
/// @return Pointer to CDataCollector, or NULL on failure
CDataCollector *data_collector_new_pressure_summary(DeviceHandler *handle,
                                                    CMotorStatusBuffer *motor_buffer,
                                                    CPressureSummaryBuffer *pressure_summary_buffer,
                                                    unsigned char slave_id,
                                                    uint32_t motor_frequency,
                                                    uint32_t touch_frequency,
                                                    int enable_stats);

/// Create a new data collector - Pressure Detailed version
///
/// @param handle Device handler
/// @param motor_buffer Motor status buffer
/// @param pressure_detailed_buffer Pressure detailed buffer
/// @param slave_id Slave ID (default 1)
/// @param motor_frequency Motor sampling frequency in Hz (default 1000)
/// @param touch_frequency Touch sampling frequency in Hz (default 10)
/// @param enable_stats Whether to print statistics (1=true, 0=false)
/// @return Pointer to CDataCollector, or NULL on failure
CDataCollector *data_collector_new_pressure_detailed(DeviceHandler *handle,
                                                     CMotorStatusBuffer *motor_buffer,
                                                     CPressureDetailedBuffer *pressure_detailed_buffer,
                                                     unsigned char slave_id,
                                                     uint32_t motor_frequency,
                                                     uint32_t touch_frequency,
                                                     int enable_stats);

/// Create a new data collector - Pressure Hybrid version (Summary + Detailed)
///
/// @param handle Device handler
/// @param motor_buffer Motor status buffer
/// @param pressure_summary_buffer Pressure summary buffer
/// @param pressure_detailed_buffer Pressure detailed buffer
/// @param slave_id Slave ID (default 1)
/// @param motor_frequency Motor sampling frequency in Hz (default 1000)
/// @param summary_frequency Summary sampling frequency in Hz (default 100)
/// @param detailed_frequency Detailed sampling frequency in Hz (default 10)
/// @param enable_stats Whether to print statistics (1=true, 0=false)
/// @return Pointer to CDataCollector, or NULL on failure
CDataCollector *data_collector_new_pressure_hybrid(DeviceHandler *handle,
                                                   CMotorStatusBuffer *motor_buffer,
                                                   CPressureSummaryBuffer *pressure_summary_buffer,
                                                   CPressureDetailedBuffer *pressure_detailed_buffer,
                                                   unsigned char slave_id,
                                                   uint32_t motor_frequency,
                                                   uint32_t summary_frequency,
                                                   uint32_t detailed_frequency,
                                                   int enable_stats);

/// Create a new data collector - ArrayPressure touch version
CDataCollector *data_collector_new_array_pressure(DeviceHandler *handle,
                                                  CMotorStatusBuffer *motor_buffer,
                                                  CArrayPressureTouchItemBuffer *touch_buffer,
                                                  unsigned char slave_id,
                                                  uint32_t motor_frequency,
                                                  uint32_t touch_frequency,
                                                  int enable_stats);

int data_collector_start(CDataCollector *collector);

int data_collector_stop(CDataCollector *collector);

int data_collector_wait(CDataCollector *collector);

int data_collector_is_running(const CDataCollector *collector);

void data_collector_free(CDataCollector *collector);

/// Initialize SDK logging.
/// log_level: Log level (Debug, Info, Warn, Error). Default is Info.
void init_logging(LogLevel log_level);

/// List all available Stark serial ports.
void list_available_ports();

/// Automatically detect a Stark device on a serial port.
/// It first tries Revo2 hands and then Revo1 hands.
/// port: Serial port name; when NULL, the function will auto‑detect ports.
/// quick: Whether to use quick detection. Default is true, which only checks
///        a subset of baudrates and slave IDs.
/// Returns a pointer to `DeviceConfig` (protocol, port, baudrate, slave ID);
/// call `free_device_config` to free the memory.
/// On failure, returns NULL.
/// Note: When `quick` is false, this function scans slave IDs in [1, 247],
///       which may take a long time.
///
/// @deprecated Use `stark_auto_detect` + `init_from_detected` instead for better
///             multi-protocol support (Modbus, CAN, CANFD).
CDeviceConfig *auto_detect_device(const char *port, bool quick);

/// Automatically detect a Revo1 hand over Modbus.
/// port: Serial port name; when NULL, the function will auto‑detect ports.
/// quick: Whether to use quick detection. Default is true, which only checks
///        a subset of baudrates and slave IDs.
/// Returns a pointer to `DeviceConfig` (protocol, port, baudrate, slave ID);
/// call `free_device_config` to free the memory.
/// On failure, returns NULL.
/// Note: When `quick` is false, this function scans slave IDs in [1, 247],
///       which may take a long time.
///
/// @deprecated Use `stark_auto_detect` + `init_from_detected` instead for better
///             multi-protocol support (Modbus, CAN, CANFD).
CDeviceConfig *auto_detect_modbus_revo1(const char *port, bool quick);

/// Automatically detect a Revo2 hand over Modbus.
/// port: Serial port name; when NULL, the function will auto‑detect ports.
/// quick: Whether to use quick detection. Default is true, which only checks
///        a subset of baudrates and slave IDs.
/// Returns a pointer to `DeviceConfig` (protocol, port, baudrate, slave ID);
/// call `free_device_config` to free the memory.
/// On failure, returns NULL.
/// Note: When `quick` is false, this function scans slave IDs in [1, 247],
///       which may take a long time.
///
/// @deprecated Use `stark_auto_detect` + `init_from_detected` instead for better
///             multi-protocol support (Modbus, CAN, CANFD).
CDeviceConfig *auto_detect_modbus_revo2(const char *port, bool quick);

/// Open a serial port.
/// port: Serial port name, for example "/dev/ttyUSB0" or "COM1".
/// baudrate: Baud rate, one of 115200, 57600, 19200, 460800.
///           Revo1 default is 115200; Revo2 default is 460800, and Revo2 also
///           supports 1M, 2M, 5M.
/// Returns a pointer to `DeviceHandler`; call `modbus_close` to free it.
/// On failure, returns NULL.
DeviceHandler *modbus_open(const char *port, uint32_t baudrate);

/// Close serial port
void modbus_close(DeviceHandler *handle);

/// Open a Protobuf protocol serial port.
///
/// Protobuf protocol uses default baudrate 115200 and default slave_id 10.
/// Position range is 0-100 (not 0-1000 like Modbus/CAN).
///
/// @param port Serial port name, for example "/dev/ttyUSB0" or "COM1".
/// @param slave_id Device slave ID (default is 10)
/// @param baudrate Baudrate (pass 0 to use default 115200)
/// @return Pointer to `DeviceHandler`; call `protobuf_close` to free it.
///         On failure, returns NULL.
DeviceHandler *protobuf_open(const char *port, uint8_t slave_id, uint32_t baudrate);

/// Close Protobuf serial port
void protobuf_close(DeviceHandler *handle);

/// @brief  Create a device handler with specified protocol and optional master ID.
/// @param protocol_type Protocol type (Modbus, CAN, CanFd, EtherCAT, Protobuf)
/// @param master_id Master ID (required for CanFd and EtherCAT, 0 for others)
/// @return Pointer to the newly created `DeviceHandler`. Call
///         `close_device_handler` to release it.
DeviceHandler *init_device_handler(StarkProtocolType protocol_type, uint8_t master_id);

/// @brief  Create a device handler for CAN/CANFD protocol.
///
/// This function creates a device handler and stores the CAN baudrate information.
///
/// @param protocol_type Protocol type (STARK_PROTOCOL_TYPE_CAN or STARK_PROTOCOL_TYPE_CAN_FD)
/// @param master_id Master ID (typically 1)
/// @param arb_baudrate Arbitration baudrate in bps (e.g., 1000000 for 1 Mbps)
/// @param data_baudrate Data baudrate in bps (for CANFD; equals arb_baudrate for CAN 2.0)
/// @return Pointer to the newly created `DeviceHandler`. Call
///         `close_device_handler` to release it.
DeviceHandler *init_device_handler_can(StarkProtocolType protocol_type,
                                       uint8_t master_id,
                                       uint32_t arb_baudrate,
                                       uint32_t data_baudrate);

/// @brief  Create a device handler for CAN/CANFD protocol with hardware type pre-set.
///
/// @param protocol_type Protocol type (STARK_PROTOCOL_TYPE_CAN or STARK_PROTOCOL_TYPE_CAN_FD)
/// @param master_id Master ID (typically 1)
/// @param slave_id Slave ID to set hardware type for
/// @param arb_baudrate Arbitration baudrate in bps (e.g., 1000000 for 1 Mbps)
/// @param data_baudrate Data baudrate in bps (for CANFD; equals arb_baudrate for CAN 2.0)
/// @param hw_type Hardware type (StarkHardwareType enum value)
/// @return Pointer to the newly created `DeviceHandler`. Call
///         `close_device_handler` to release it.
DeviceHandler *init_device_handler_can_with_hw_type(StarkProtocolType protocol_type,
                                                    uint8_t master_id,
                                                    uint8_t slave_id,
                                                    uint32_t arb_baudrate,
                                                    uint32_t data_baudrate,
                                                    StarkHardwareType hw_type);

/// @brief  Create a device handler with hardware type pre-set.
///
/// This is useful when you already know the hardware type (e.g., from auto_detect)
/// and want to avoid an extra get_device_info call.
///
/// @param protocol_type Protocol type (Modbus, CAN, CanFd, EtherCAT, Protobuf)
/// @param master_id Master ID (required for CanFd and EtherCAT, 0 for others)
/// @param slave_id Slave ID to set hardware type for
/// @param hw_type Hardware type (StarkHardwareType enum value)
/// @return Pointer to the newly created `DeviceHandler`. Call
///         `close_device_handler` to release it.
DeviceHandler *init_device_handler_with_hw_type(StarkProtocolType protocol_type,
                                                uint8_t master_id,
                                                uint8_t slave_id,
                                                StarkHardwareType hw_type);

/// List all ZQWL USB CAN/CANFD devices
///
/// Returns device list pointer. Call `free_zqwl_device_list` to free memory after use.
/// Returns list with count=0 if no devices found.
///
/// @note This is a low-level API. Recommend using high-level API `stark_auto_detect` + `init_from_detected`
///       for automatic device detection and initialization. Low-level API is for advanced users
///       who need manual control of CAN adapter.
ZqwlDeviceList *list_zqwl_devices();

/// Free ZQWL device list memory
void free_zqwl_device_list(ZqwlDeviceList *list);

/// Initialize ZQWL CAN device (CAN 2.0 mode)
///
/// For Revo1 devices, baudrate is typically 1Mbps
///
/// @param port_name Serial port name, e.g. "/dev/cu.usbmodem*" (macOS) or "COM3" (Windows)
/// @param arb_baudrate Arbitration baudrate (bps), typically 1000000
/// @return 0: success, -1: failure
///
/// @note This is a low-level API. Recommend using high-level API `stark_auto_detect` + `init_from_detected`
///       for automatic device detection and initialization. Low-level API is for advanced users
///       who need manual control of CAN adapter.
int init_zqwl_can(const char *port_name,
                  uint32_t arb_baudrate);

/// Initialize ZQWL CANFD device
///
/// For Revo2 devices, arbitration baudrate is typically 1Mbps, data baudrate is typically 5Mbps
///
/// @param port_name Serial port name
/// @param arb_baudrate Arbitration baudrate (bps), typically 1000000
/// @param data_baudrate Data baudrate (bps), typically 5000000
/// @return 0: success, -1: failure
///
/// @note This is a low-level API. Recommend using high-level API `stark_auto_detect` + `init_from_detected`
///       for automatic device detection and initialization. Low-level API is for advanced users
///       who need manual control of CAN adapter.
int init_zqwl_canfd(const char *port_name,
                    uint32_t arb_baudrate,
                    uint32_t data_baudrate);

/// Close ZQWL CAN/CANFD device
///
/// @note This is a low-level API. If device was initialized with `init_from_detected`,
///       use `close_device_handler` instead, which automatically handles ZQWL resource cleanup.
void close_zqwl();

/// List all SocketCAN interfaces (Linux only)
///
/// Scans /sys/class/net/ for CAN interfaces.
/// Returns device list pointer. Call `free_socketcan_device_list` to free memory.
/// Returns list with count=0 on non-Linux platforms or if no interfaces found.
SocketCanDeviceList *list_socketcan_devices();

/// Free SocketCAN device list memory
void free_socketcan_device_list(SocketCanDeviceList *list);

/// Initialize SocketCAN CAN 2.0 device (Linux only)
///
/// @param iface Interface name (e.g. "can0"). If NULL, uses STARK_SOCKETCAN_IFACE env var or "can0"
/// @return 0: success, -1: failure (or non-Linux platform)
int init_socketcan_can(const char *iface);

/// Initialize SocketCAN CANFD device (Linux only)
///
/// @param iface Interface name (e.g. "can0"). If NULL, uses STARK_SOCKETCAN_IFACE env var or "can0"
/// @return 0: success, -1: failure (or non-Linux platform)
int init_socketcan_canfd(const char *iface);

/// Close SocketCAN device (Linux only)
void close_socketcan();

/// Check if a SocketCAN interface is available and up (Linux only)
///
/// @param iface Interface name (e.g. "can0")
/// @return true if interface exists and is up, false otherwise
bool is_socketcan_available(const char *iface);

/// Scan CAN bus for devices (simple version)
///
/// Uses get_can_params to read SkuType for device detection
///
/// @param candidate_ids Pointer to array of candidate device IDs
/// @param candidate_count Number of candidate device IDs
/// @param timeout_ms Timeout in milliseconds
/// @return Found device ID (1-255), or 0 if no device found
uint8_t scan_can_devices(const uint8_t *candidate_ids,
                         uintptr_t candidate_count,
                         uint64_t timeout_ms);

/// Auto-detect Stark devices across all protocols
///
/// Scans for devices in priority order:
/// 1. ZQWL CANFD (IDs 0x7E, 0x7F) → CAN 2.0 (IDs 1, 2) (USB CDC, cross-platform)
/// 2. SocketCAN CANFD (IDs 0x7E, 0x7F) → CAN 2.0 (IDs 1, 2) (Linux only)
/// 3. Modbus/RS485 (IDs 0x7E, 0x7F, 1, 2 at 460800/115200/1Mbps/2Mbps)
/// 4. Protobuf (legacy RS485, ID 10, 115200 baud)
///
/// When a specific protocol is requested, only that protocol is scanned.
///
/// # Parameters
/// - scan_all: If true, scan for all devices. If false, stop at first found.
/// - port: Optional port name to scan. If NULL, scans all available ports.
/// - protocol: Protocol filter:
///   - STARK_PROTOCOL_TYPE_AUTO: Auto-detect all protocols (recommended)
///   - STARK_PROTOCOL_TYPE_MODBUS: Modbus only
///   - STARK_PROTOCOL_TYPE_CAN: CAN 2.0 only
///   - STARK_PROTOCOL_TYPE_CAN_FD: CANFD only
///   - STARK_PROTOCOL_TYPE_PROTOBUF: Protobuf only
///
/// # Returns
/// Pointer to CDetectedDeviceList. Call `free_detected_device_list` to free.
/// Returns empty list (count=0) if no devices found.
CDetectedDeviceList *stark_auto_detect(bool scan_all, const char *port, StarkProtocolType protocol);

/// Free detected device list memory
void free_detected_device_list(CDetectedDeviceList *list);

/// Initialize device handler from detected device info.
///
/// This function initializes the appropriate transport (Modbus, CAN, CANFD)
/// based on the detected device's protocol and returns a ready-to-use handler.
///
/// # Parameters
/// - device: Pointer to CDetectedDevice from stark_auto_detect()
///
/// # Returns
/// - Pointer to DeviceHandler on success
/// - NULL on failure
///
/// # Notes
/// - For CAN/CANFD: Initializes BrainCo/ZQWL/SocketCAN adapter automatically
/// - For Modbus: Opens serial port with detected baudrate
/// - Call `close_from_detected` to cleanup
DeviceHandler *init_from_detected(const CDetectedDevice *device);

/// Close device handler based on protocol type.
///
/// Convenience function that handles cleanup for any protocol type.
/// Automatically closes ZQWL adapter for CAN/CANFD protocols.
///
/// # Parameters
/// - handle: Device handler to close
/// - protocol: Protocol type (from CDetectedDevice.protocol)
void close_device_handler(DeviceHandler *handle, uint8_t protocol);

/// Configure SDO for an EtherCAT slave device.
void ethercat_setup_sdo(DeviceHandler *handle, uint16_t slave_pos);

void ethercat_reserve_master(DeviceHandler *handle);

/// Start EtherCAT cyclic loop with PDO communication (control & read).
/// dc_assign_activate: DC flags; 0x0000 means DC is not enabled.
/// sync0_cycle_time: SYNC0 cycle time in nanoseconds. The loop period matches
///                   SYNC0 cycle time.
/// sync0_shift_time: SYNC0 phase shift in nanoseconds.
/// sync1_cycle_time: SYNC1 cycle time in nanoseconds.
/// sync1_shift_time: SYNC1 phase shift in nanoseconds.
void ethercat_start_loop(DeviceHandler *handle,
                         const uint16_t *slave_positions,
                         int count,
                         uint16_t dc_assign_activate,
                         uint32_t sync0_cycle_time,
                         int32_t sync0_shift_time,
                         uint32_t sync1_cycle_time,
                         int32_t sync1_shift_time);

/// Stop the EtherCAT cyclic loop.
void ethercat_stop_loop(DeviceHandler *handle);

/// EtherCAT DFU: upgrade firmware via FoE protocol.
/// slave_pos: Slave position.
/// dfu_type: DFU type, Control or Wrist.
/// file_path: Firmware file path.
void ethercat_start_dfu(DeviceHandler *handle,
                        uint16_t slave_pos,
                        EtherCATFoeType dfu_type,
                        const char *file_path);

/// Set Modbus async read/write callback.
void set_modbus_operation_callback(ModbusOperationCallback cb);

/// Set Modbus read callback for input registers.
void set_modbus_read_input_callback(ModbusRxCallback cb);

/// Set Modbus read callback for holding registers.
void set_modbus_read_holding_callback(ModbusRxCallback cb);

/// Set Modbus write callback.
void set_modbus_write_callback(ModbusTxCallback cb);

/// Set CAN/CANFD RX callback.
void set_can_rx_callback(CanRxCallback cb);

/// Set CAN/CANFD TX callback.
void set_can_tx_callback(CanTxCallback cb);

/// Set DFU state callback.
void set_dfu_state_callback(DfuStateCallback cb);

/// Set DFU progress callback.
void set_dfu_progress_callback(DfuProgressCallback cb);

/// Get device information.
/// Returns a pointer to `DeviceInfo`; you must call `free_device_info` to free
/// it. Returns NULL on failure.
///
/// NOTE: Hardware type is determined by SN prefix. If the SN is not recognized,
/// hardware_type defaults to Revo2Basic. Use `stark_set_hardware_type()` to
/// manually override if needed.
CDeviceInfo *stark_get_device_info(DeviceHandler *handle, uint8_t slave_id);

/// Set hardware type for a specific slave device.
///
/// Use this to manually override the hardware type when SN-based auto-detection
/// in `stark_get_device_info()` returns an incorrect result (e.g. device SN not
/// yet programmed, defaults to Revo2Basic).
///
/// Can also be used to modify `CDetectedDevice.hardware_type` before calling
/// `init_from_detected()`, or to override after initialization.
///
/// # Parameters
/// - handle: Device handler
/// - slave_id: The slave ID
/// - hw_type: StarkHardwareType enum value
void stark_set_hardware_type(DeviceHandler *handle, uint8_t slave_id, StarkHardwareType hw_type);

/// Read holding registers (fast, no retry).
/// address: Start register address.
/// count: Number of registers to read.
/// out_data: Pointer to array to store data (caller must allocate enough space).
/// Returns 0 on success, -1 on failure.
int32_t stark_read_holding_registers(DeviceHandler *handle,
                                     uint8_t slave_id,
                                     uint16_t address,
                                     uint16_t count,
                                     uint16_t *out_data);

/// Read input registers (fast, no retry).
/// address: Start register address.
/// count: Number of registers to read.
/// out_data: Pointer to array to store data (caller must allocate enough space).
/// Returns 0 on success, -1 on failure.
int32_t stark_read_input_registers(DeviceHandler *handle,
                                   uint8_t slave_id,
                                   uint16_t address,
                                   uint16_t count,
                                   uint16_t *out_data);

/// Get the protocol type of the device handler.
/// Returns: StarkProtocolType enum value:
///   - STARK_PROTOCOL_TYPE_MODBUS = 1
///   - STARK_PROTOCOL_TYPE_CAN = 2
///   - STARK_PROTOCOL_TYPE_CAN_FD = 3
///   - STARK_PROTOCOL_TYPE_ETHER_CAT = 4
///   - STARK_PROTOCOL_TYPE_PROTOBUF = 5
StarkProtocolType stark_get_protocol_type(DeviceHandler *handle);

/// Get the serial port name of the device handler.
/// Returns: Port name string (e.g., "/dev/ttyUSB0" or "COM3"), or NULL if not available.
/// The returned string is owned by the DeviceHandler and should not be freed.
/// For CAN/CANFD protocols, this may return an empty string.
const char *stark_get_port_name(DeviceHandler *handle);

/// Get the baudrate of the device handler.
/// Returns: Baudrate in bps (e.g., 115200, 460800), or 0 if not applicable.
/// For Modbus/Protobuf: returns serial baudrate
/// For CAN/CANFD/EtherCAT: returns 0 (use stark_get_can_arb_baudrate/stark_get_can_data_baudrate instead)
///
/// Note: This function only returns serial port baudrate.
/// For CAN baudrate information, use:
///   - stark_get_can_arb_baudrate() - Get CAN arbitration baudrate
///   - stark_get_can_data_baudrate() - Get CAN data baudrate (CANFD only)
uint32_t stark_get_baudrate(DeviceHandler *handle);

/// Get the CAN arbitration baudrate.
/// Returns: Arbitration baudrate in bps (e.g., 1000000 for 1 Mbps), or 0 if not CAN/CANFD protocol.
/// For CAN 2.0: This is the only baudrate
/// For CANFD: This is the arbitration phase baudrate
uint32_t stark_get_can_arb_baudrate(DeviceHandler *handle);

/// Get the CAN data baudrate.
/// Returns: Data baudrate in bps (e.g., 5000000 for 5 Mbps), or 0 if not CANFD protocol.
/// For CAN 2.0: Returns the same as arbitration baudrate
/// For CANFD: Returns the data phase baudrate
uint32_t stark_get_can_data_baudrate(DeviceHandler *handle);

/// Get touch sensor vendor type by reading register 970.
/// Returns: 0=Unknown, 1=Capacitive, 2=Pressure
/// This should be called for Revo2 Touch devices to determine the actual touch sensor type.
uint8_t stark_get_touch_vendor(DeviceHandler *handle, uint8_t slave_id);

/// Set force level (only supported on Revo1 Basic).
///
/// @deprecated This function is deprecated and only works on Revo1 Basic devices.
/// Use `stark_set_finger_current` or `stark_set_finger_currents` instead for
/// more precise force control on all device types.
void stark_set_force_level(DeviceHandler *handle, uint8_t slave_id, ForceLevel level);

/// Get force level (only supported on Revo1 Basic).
///
/// @deprecated This function is deprecated and only works on Revo1 Basic devices.
/// Use `stark_get_motor_status` instead to get current values for all device types.
uint8_t stark_get_force_level(DeviceHandler *handle, uint8_t slave_id);

/// Get device supply voltage in mV.
uint16_t stark_get_voltage(DeviceHandler *handle, uint8_t slave_id);

/// Get LED enabled state.
bool get_led_enabled(DeviceHandler *handle, uint8_t slave_id);

/// Get buzzer enabled state.
bool get_buzzer_enabled(DeviceHandler *handle, uint8_t slave_id);

/// Get vibration enabled state.
bool get_vibration_enabled(DeviceHandler *handle, uint8_t slave_id);

/// Set LED enabled state.
void set_led_enabled(DeviceHandler *handle, uint8_t slave_id, bool enabled);

/// Set buzzer enabled state.
void set_buzzer_enabled(DeviceHandler *handle, uint8_t slave_id, bool enabled);

/// Set vibration enabled state.
void set_vibration_enabled(DeviceHandler *handle, uint8_t slave_id, bool enabled);

/// Configure unit mode and finger parameters for Revo2.
/// This setting is reset after power‑cycle.
/// Parameter ranges are documented here:
/// https://brainco.yuque.com/tykrbo/hws0nr/pynh5qnmfa1bgamc
/// Sets the control unit mode:
/// normalized mode or physical‑quantity mode.
void stark_set_finger_unit_mode(DeviceHandler *handle, uint8_t slave_id, FingerUnitMode mode);

/// Get the current control unit mode.
/// Returns `FingerUnitMode`; on failure returns `FingerUnitMode::Normalized`.
FingerUnitMode stark_get_finger_unit_mode(DeviceHandler *handle, uint8_t slave_id);

/// Set maximum angle for a finger.
/// max_pos: Maximum angle value; default is 60 87 84 84 84 84.
void stark_set_finger_max_position(DeviceHandler *handle,
                                   uint8_t slave_id,
                                   StarkFingerId finger_id,
                                   uint16_t max_pos);

/// Set minimum angle for a finger.
/// min_pos: Minimum angle in degrees; default is 0 0 0 0 0 0.
void stark_set_finger_min_position(DeviceHandler *handle,
                                   uint8_t slave_id,
                                   StarkFingerId finger_id,
                                   uint16_t min_pos);

/// Set maximum speed for a finger.
/// max_speed: Maximum speed in °/s; default is 145 150 130 130 130 130.
void stark_set_finger_max_speed(DeviceHandler *handle,
                                uint8_t slave_id,
                                StarkFingerId finger_id,
                                uint16_t max_speed);

/// Set maximum current for a finger.
/// max_current: Maximum current in mA; default is 1000.
void stark_set_finger_max_current(DeviceHandler *handle,
                                  uint8_t slave_id,
                                  StarkFingerId finger_id,
                                  uint16_t max_current);

/// Set protection current for a finger.
/// protected_current: Protection current in mA, range 100~1500, default
///                    500 500 500 500 500 500.
void stark_set_finger_protected_current(DeviceHandler *handle,
                                        uint8_t slave_id,
                                        StarkFingerId finger_id,
                                        uint16_t protected_current);

/// Read maximum angle for a finger.
/// Returns 0 on failure.
uint16_t stark_get_finger_max_position(DeviceHandler *handle,
                                       uint8_t slave_id,
                                       StarkFingerId finger_id);

/// Read minimum angle for a finger.
/// min_pos: Minimum angle in degrees; default is 0 0 0 0 0 0.
uint16_t stark_get_finger_min_position(DeviceHandler *handle,
                                       uint8_t slave_id,
                                       StarkFingerId finger_id);

/// Read maximum speed for a finger.
/// max_speed: Maximum speed in °/s; default is 145 150 130 130 130 130.
/// Returns 0 on failure.
uint16_t stark_get_finger_max_speed(DeviceHandler *handle,
                                    uint8_t slave_id,
                                    StarkFingerId finger_id);

/// Read maximum current for a finger.
/// max_current: Maximum current in mA; default is 1000.
/// Returns 0 on failure.
uint16_t stark_get_finger_max_current(DeviceHandler *handle,
                                      uint8_t slave_id,
                                      StarkFingerId finger_id);

/// Read protection current for a finger.
/// protected_current: Protection current in mA, range 100~1500, default
///                    500 500 500 500 500 500.
/// Returns 0 on failure.
uint16_t stark_get_finger_protected_current(DeviceHandler *handle,
                                            uint8_t slave_id,
                                            StarkFingerId finger_id);

/// Configure thumb AUX lock current.
/// aux_lock_current: in mA, range 100~500, default 200.
/// Only supported on Revo2.
void stark_set_thumb_aux_lock_current(DeviceHandler *handle,
                                      uint8_t slave_id,
                                      uint16_t aux_lock_current);

/// Read thumb AUX lock current.
/// Only supported on Revo2.
uint16_t stark_get_thumb_aux_lock_current(DeviceHandler *handle, uint8_t slave_id);

/// Set the position of a single finger.
///
/// # Parameters
/// - `position`: Position in the unified range **0~1000** (all devices,
///   all protocols).
///   - The SDK converts this internally based on device type and protocol.
///   - 0 means fully open, 1000 means fully closed.
///
/// # Unified range
/// For both Revo1 and Revo2, and for Modbus, CAN 2.0, and CANFD, the public
/// API always uses the 0~1000 range; the SDK handles conversion internally.
void stark_set_finger_position(DeviceHandler *handle,
                               uint8_t slave_id,
                               StarkFingerId finger_id,
                               uint16_t position);

/// Set the speed of a single finger.
///
/// # Parameters
/// - `speed`: Speed in the unified range **-1000~+1000** (all devices,
///   all protocols).
///   - The SDK converts this internally based on device type and protocol.
///   - Positive: closing direction; negative: opening direction.
///   - 0: stop.
///
/// # Unified range
/// For both Revo1 and Revo2, and for Modbus, CAN 2.0, and CANFD, the public
/// API always uses the -1000~+1000 range; the SDK handles conversion internally.
void stark_set_finger_speed(DeviceHandler *handle,
                            uint8_t slave_id,
                            StarkFingerId finger_id,
                            int16_t speed);

/// Set the current of a single finger.
///
/// # Parameters
/// - `current`: Current in the unified range **-1000~+1000** (all devices,
///   all protocols).
///   - The SDK converts this internally based on device type and protocol.
///   - Positive: closing direction; negative: opening direction.
///
/// # Unified range
/// For both Revo1 and Revo2, and for Modbus, CAN 2.0, and CANFD, the public
/// API always uses the -1000~+1000 range; the SDK handles conversion internally.
void stark_set_finger_current(DeviceHandler *handle,
                              uint8_t slave_id,
                              StarkFingerId finger_id,
                              int16_t current);

/// Set the PWM of a single finger (Revo2 only).
///
/// # Parameters
/// - `pwm`: PWM value in the unified range **-1000~+1000** (all protocols).
///   - The SDK converts this internally based on protocol.
///   - Positive: closing direction; negative: opening direction.
///
/// # Unified range
/// For Modbus, CAN 2.0, and CANFD, the public API always uses the
/// -1000~+1000 range; the SDK handles conversion internally.
void stark_set_finger_pwm(DeviceHandler *handle,
                          uint8_t slave_id,
                          StarkFingerId finger_id,
                          int16_t pwm);

/// Set position + desired time for a single finger (Revo2 only).
///
/// # Parameters
/// - `position`: Position in the unified range **0~1000** (all protocols).
/// - `millis`: Desired time in milliseconds, range 1~2000.
///
/// # Unified range
/// Position always uses the 0~1000 range; the SDK handles conversion internally.
void stark_set_finger_position_with_millis(DeviceHandler *handle,
                                           uint8_t slave_id,
                                           StarkFingerId finger_id,
                                           uint16_t position,
                                           uint16_t millis);

/// Set position + desired speed for a single finger (Revo2 only).
///
/// # Parameters
/// - `position`: Position in the unified range **0~1000** (all protocols).
/// - `speed`: Speed value, range 1~1000.
///
/// # Unified range
/// Position always uses the 0~1000 range; the SDK handles conversion internally.
void stark_set_finger_position_with_speed(DeviceHandler *handle,
                                          uint8_t slave_id,
                                          StarkFingerId finger_id,
                                          uint16_t position,
                                          uint16_t speed);

/// Set positions for multiple fingers.
///
/// # Parameters
/// - `positions`: Array of length 6, positions in the unified range
///   **0~1000** (all devices, all protocols).
///   - The SDK converts this internally based on device type and protocol.
///   - 0 means fully open, 1000 means fully closed.
///
/// # Unified range
/// For both Revo1 and Revo2, and for Modbus, CAN 2.0, and CANFD, the public
/// API always uses the 0~1000 range; the SDK handles conversion internally.
void stark_set_finger_positions(DeviceHandler *handle,
                                uint8_t slave_id,
                                const uint16_t *positions,
                                uintptr_t len);

/// Set speeds for multiple fingers.
///
/// # Parameters
/// - `speeds`: Array of length 6, speeds in the unified range **-1000~+1000**
///   (all devices, all protocols).
///   - The SDK converts this internally based on device type and protocol.
///   - Positive: closing direction; negative: opening direction.
///   - 0: stop.
///
/// # Unified range
/// For both Revo1 and Revo2, and for Modbus, CAN 2.0, and CANFD, the public
/// API always uses the -1000~+1000 range; the SDK handles conversion internally.
void stark_set_finger_speeds(DeviceHandler *handle,
                             uint8_t slave_id,
                             const int16_t *speeds,
                             uintptr_t len);

/// Set currents for multiple fingers.
///
/// # Parameters
/// - `currents`: Array of length 6, currents in the unified range
///   **-1000~+1000** (all devices, all protocols).
///   - The SDK converts this internally based on device type and protocol.
///   - Positive: closing direction; negative: opening direction.
///
/// # Unified range
/// For both Revo1 and Revo2, and for Modbus, CAN 2.0, and CANFD, the public
/// API always uses the -1000~+1000 range; the SDK handles conversion internally.
void stark_set_finger_currents(DeviceHandler *handle,
                               uint8_t slave_id,
                               const int16_t *currents,
                               uintptr_t len);

/// Set PWM values for multiple fingers (Revo2 only).
///
/// # Parameters
/// - `pwms`: Array of length 6, PWM values in the unified range
///   **-1000~+1000** (all protocols).
///   - The SDK converts this internally based on protocol.
///   - Positive: closing direction; negative: opening direction.
///
/// # Unified range
/// For Modbus, CAN 2.0, and CANFD, the public API always uses the
/// -1000~+1000 range; the SDK handles conversion internally.
void stark_set_finger_pwms(DeviceHandler *handle,
                           uint8_t slave_id,
                           const int16_t *pwms,
                           uintptr_t len);

/// Set positions + desired times for multiple fingers (Revo2 only).
///
/// # Parameters
/// - `positions`: Array of length 6, positions in the unified range
///   **0~1000** (all protocols).
/// - `millis`: Array of length 6, desired times in milliseconds, range 1~2000.
///
/// # Unified range
/// Position always uses the 0~1000 range; the SDK handles conversion internally.
void stark_set_finger_positions_and_durations(DeviceHandler *handle,
                                              uint8_t slave_id,
                                              const uint16_t *positions,
                                              const uint16_t *millis,
                                              uintptr_t len);

/// Set positions + desired speeds for multiple fingers (Revo2 only).
///
/// # Parameters
/// - `positions`: Array of length 6, positions in the unified range
///   **0~1000** (all protocols).
/// - `speeds`: Array of length 6, speeds in the range 1~1000.
///
/// # Unified range
/// Position always uses the 0~1000 range; the SDK handles conversion internally.
void stark_set_finger_positions_and_speeds(DeviceHandler *handle,
                                           uint8_t slave_id,
                                           const uint16_t *positions,
                                           const uint16_t *speeds,
                                           uintptr_t len);

/// Get finger motor status.
///
/// # Return value
/// Returns a pointer to `MotorStatusData`, which contains:
/// - `positions`: Position array, unified range **0~1000** (all devices,
///   all protocols).
/// - `speeds`: Speed array, unified range **-1000~+1000** (all devices,
///   all protocols).
/// - `currents`: Current array, unified range **-1000~+1000** (all devices,
///   all protocols).
/// - `states`: Motor state array.
///
/// # Unified range
/// For both Revo1 and Revo2, and for Modbus, CAN 2.0, and CANFD, the returned
/// data is always converted to the unified ranges above; the SDK handles
/// conversion internally.
///
/// # Memory management
/// You must call `free_motor_status_data` to free the result.
/// Returns NULL on failure.
CMotorStatusData *stark_get_motor_status(DeviceHandler *handle, uint8_t slave_id);

/// Run an action sequence.
/// action_id: Action sequence ID.
void stark_run_action_sequence(DeviceHandler *handle, uint8_t slave_id, ActionSequenceId action_id);

/// Transfer action sequences to the device.
///
/// This function sends multiple action sequences. Each sequence is represented
/// by a fixed‑length array of `u16` parameters.
///
/// - `slave_id`: Slave device ID.
/// - `uses_revo2_motor_api`: Whether the device uses Revo2 Motor API.
///   (Revo1 Advanced/AdvancedTouch and all Revo2 use Revo2 Motor API)
/// - `action_id`: ID that uniquely identifies the action sequence.
/// - `sequences`: Flattened parameter array containing multiple sequences.
///
/// For Revo1, each action sequence contains 20 `u16` elements:
///   - Sequence index (u16): index of the sequence in the queue.
///   - Duration (u16): execution time in milliseconds.
///   - Finger positions (6 × u16): values in range 0~100.
///   - Finger speeds   (6 × u16): values in range 0~100.
///   - Finger forces   (6 × u16): values in range 0~100.
///
/// Example sequence array:
/// [0, 2000, 0, 0, 100, 100, 100, 100, 10, 20, 30, 40, 50, 60, 5, 10, 15, 20, 25, 30]
///
/// Meaning:
/// - `0`: sequence index.
/// - `2000`: duration in milliseconds.
/// - `0, 0, 100, 100, 100, 100`: 6 finger positions.
/// - `10, 20, 30, 40, 50, 60`: 6 finger speeds.
/// - `5, 10, 15, 20, 25, 30`: 6 finger forces.
///
/// For Revo2, each action sequence contains 27 `u16` elements:
///   - Sequence index (u16): index of the sequence in the queue.
///   - Duration (u16): execution time in milliseconds.
///   - Control mode (u16): 1=position+time, 2=position+speed,
///     3=current, 4=speed.
///   - Finger positions (6 × u16): physical angle in degrees; 65535 (0xFFFF)
///     means keep current angle.
///   - Finger speeds   (6 × u16): physical rotational speed in °/s.
///   - Finger currents (6 × u16): physical current in mA.
///
/// - `len`: Number of sequences, i.e. number of rows in the flattened array.
///
/// # Parameters
/// - `action_id`: Action sequence ID.
/// - `sequences`: Pointer to the flattened sequence array.
/// - `len`: Number of sequences.
///
/// # Error handling
/// - If `handle` or `sequences` is NULL, the function returns immediately.
/// - If `len` exceeds the maximum allowed steps (Revo1: 32, Revo2: 8), a
///   warning is logged and the function returns.
void stark_set_action_sequence(DeviceHandler *handle,
                               uint8_t slave_id,
                               bool uses_revo2_motor_api,
                               ActionSequenceId action_id,
                               const uint16_t *sequences,
                               uintptr_t len);

/// Enable tactile sensors.
/// bits: Bitmask of sensors to enable, range 0~31.
/// For example: 0b00000001 enables only the thumb sensor.
void stark_enable_touch_sensor(DeviceHandler *handle, uint8_t slave_id, uint8_t bits);

/// Get raw channel data from the tactile sensors.
/// Returns a pointer to `TouchRawData`; call `free_touch_raw_data` to free it.
/// Returns NULL on failure.
CTouchRawData *stark_get_touch_raw_data(DeviceHandler *handle, uint8_t slave_id);

/// Read 3D force, proximity and status for a single finger.
/// Returns a pointer to `TouchFingerItem`.
/// index: 0~4 for thumb, index, middle, ring, pinky.
/// Call `free_touch_finger_item` to free the result. Returns NULL on failure.
CTouchFingerItem *stark_get_single_touch_status(DeviceHandler *handle,
                                                uint8_t slave_id,
                                                uint8_t index);

/// Read 3D force, proximity and status for all five fingers.
/// Returns a pointer to `TouchFingerData` containing all fingers.
/// Call `free_touch_finger_data` to free the result. Returns NULL on failure.
CTouchFingerData *stark_get_touch_status(DeviceHandler *handle, uint8_t slave_id);

/// Reset tactile sensor measurement channels.
/// Try to keep the fingers unloaded (no force) when executing this command.
/// bits: Bitmask of sensors to reset, range 0~31.
/// For example: 0b00000001 resets the first sensor.
void stark_reset_touch_sensor(DeviceHandler *handle, uint8_t slave_id, uint8_t bits);

/// Get touch sensor type from hardware type.
/// Returns: 0=None, 1=Capacitive, 2=Pressure
/// This is a pure function based on hardware type classification.
uint8_t stark_get_touch_sensor_type(uint8_t hw_type);

/// Check if hardware type has touch sensor.
/// Returns: true if device has touch sensor, false otherwise.
bool stark_is_touch_device(uint8_t hw_type);

/// Check if hardware type is Revo1 device (motor API perspective).
/// Check if hardware type uses Revo1 Motor API.
/// Revo1 Basic/Touch use Revo1 Motor API.
/// Revo1 Advanced/AdvancedTouch and all Revo2 use Revo2 Motor API.
/// Note: Revo1Protobuf is legacy firmware, also uses Revo1 Motor API.
/// Returns: true if uses Revo1 Motor API, false if uses Revo2 Motor API.
bool stark_uses_revo1_motor_api(uint8_t hw_type);

/// Check if hardware type uses Revo1 Touch API.
/// Revo1 Touch API has different sensor counts per finger (capacitive).
/// Returns: true if uses Revo1 Touch API, false otherwise.
bool stark_uses_revo1_touch_api(uint8_t hw_type);

/// Calibrate tactile sensor parameters.
/// bits: Bitmask of sensors to calibrate, range 0~31.
/// For example: 0b00000001 calibrates the first sensor.
/// Use this when 3D force values are non‑zero in idle state.
/// https://www.brainco-hz.com/docs/revolimb-hand/protocol/stark_protocol_touch.html#_5-3-%E5%8F%82%E6%95%B0%E6%A0%A1%E5%87%864105
void stark_calibrate_touch_sensor(DeviceHandler *handle,
                                  uint8_t slave_id,
                                  uint8_t bits);

/// Check if hardware type uses Revo2 Touch API.
/// Revo2 Touch API has uniform sensor counts per finger (capacitive).
/// Returns: true if uses Revo2 Touch API, false otherwise.
bool stark_uses_revo2_touch_api(uint8_t hw_type);

/// Check if hardware type uses Pressure/Modulus Touch API.
/// Pressure Touch API uses completely different data structure (pressure sensors).
/// Returns: true if uses Pressure Touch API, false otherwise.
bool stark_uses_pressure_touch_api(uint8_t hw_type);

/// Get RS485 serial baud rate.
/// Valid values: 115200, 57600, 19200, 460800.
uint32_t stark_get_rs485_baudrate(DeviceHandler *handle, uint8_t slave_id);

/// Set RS485 serial baud rate.
/// Supported values: 115200, 57600, 19200, 460800.
void stark_set_rs485_baudrate(DeviceHandler *handle, uint8_t slave_id, uint32_t baudrate);

/// Get CANFD baud rate.
/// Supported values: 1M, 2M, 4M, 5M.
uint32_t stark_get_canfd_baudrate(DeviceHandler *handle, uint8_t slave_id);

/// Set CANFD baud rate.
/// Supported values: 1M, 2M, 4M, 5M.
void stark_set_canfd_baudrate(DeviceHandler *handle, uint8_t slave_id, uint32_t baudrate);

/// Set device slave ID.
/// Default is 1, valid range 1~247; 0 is the broadcast address.
/// Revo1 default ID is 1; Revo2 default left/right IDs are 0x7E and 0x7F.
/// When controlling multiple devices on the same bus, assign different IDs,
/// e.g. left=1, right=2.
/// Using broadcast ID 0 controls all devices on the bus; per Modbus spec,
/// broadcast commands do not receive responses.
void stark_set_slave_id(DeviceHandler *handle, uint8_t slave_id, uint8_t new_id);

/// Get Force3D touch summary data (4 fingers, no thumb).
/// Returns a pointer to `CForce3DTouchSummary`; call `free_force3d_touch_summary` to free it.
/// Returns NULL on failure.
CForce3DTouchSummary *stark_get_force3d_touch_summary(DeviceHandler *handle, uint8_t slave_id);

/// Free Force3D touch summary data.
void free_force3d_touch_summary(CForce3DTouchSummary *ptr);

/// Get Force3D finger array data (31 measurement points).
/// finger: 0=index, 1=middle, 2=ring, 3=pinky
/// Returns a pointer to `CForce3DFingerArray`; call `free_force3d_finger_array` to free it.
/// Returns NULL on failure.
CForce3DFingerArray *stark_get_force3d_finger_array(DeviceHandler *handle,
                                                    uint8_t slave_id,
                                                    uint8_t finger);

/// Free Force3D finger array data.
void free_force3d_finger_array(CForce3DFingerArray *ptr);

/// Check if device uses Revo2 Motor API.
///
/// Revo1 Advanced/AdvancedTouch and all Revo2 use Revo2 Motor API.
/// Revo1 Basic/Touch use Revo1 Motor API.
bool device_info_uses_revo2_motor_api(const CDeviceInfo *info);

/// Check if device uses Revo2 Touch API.
///
/// Includes capacitive (Revo2Touch), pressure (Revo2TouchPressure),
/// force3D (Revo2TouchForce3D), and array pressure (Revo2TouchArrayPressure) sensors.
bool device_info_uses_revo2_touch_api(const CDeviceInfo *info);

/// Check if device has any touch sensor
bool device_info_is_touch(const CDeviceInfo *info);

void free_motor_status_data(CMotorStatusData *data);

void free_touch_raw_data(CTouchRawData *data);

void free_touch_finger_data(CTouchFingerData *status);

void free_touch_finger_item(CTouchFingerItem *item);

void free_turbo_config(CTurboConfig *config);

void free_led_info(CLedInfo *info);

void free_button_event(CButtonPressEvent *event);

/// Get ArrayPressure touch data (status + 25 registers).
/// Returns a pointer to `CArrayPressureTouchData`; call `free_array_pressure_touch_data` to free it.
/// Returns NULL on failure.
CArrayPressureTouchData *stark_get_array_pressure_touch_data(DeviceHandler *handle,
                                                             uint8_t slave_id);

/// Free ArrayPressure touch data.
void free_array_pressure_touch_data(CArrayPressureTouchData *ptr);

/// Set ArrayPressure device sleep mode.
/// enable: 1=sleep, 0=wake
void stark_set_array_pressure_sleep(DeviceHandler *handle, uint8_t slave_id, bool enable);

/// Get finger backlash compensation values (6 values, unit °×10).
/// Returns pointer to array of 6 u16 values; caller must free with `free_u16_array`.
/// Returns NULL on failure.
uint16_t *stark_factory_get_backlash(DeviceHandler *handle, uint8_t slave_id, uint16_t *out_len);

/// Set finger backlash compensation values (6 values, unit °×10).
/// Requires factory key to be set first.
void stark_factory_set_backlash(DeviceHandler *handle,
                                uint8_t slave_id,
                                const uint16_t *values,
                                uint16_t len);

/// Trigger finger backlash self-check.
/// finger_id: 0=thumb_tip, 1=thumb_root, 2=index, 3=middle, 4=ring, 5=pinky
/// Requires factory key to be set first.
void stark_factory_backlash_self_check(DeviceHandler *handle, uint8_t slave_id, uint8_t finger_id);

/// Query whether automatic position calibration on power‑up is enabled.
bool stark_get_auto_calibration(DeviceHandler *handle, uint8_t slave_id);

/// Enable or disable automatic position calibration on power‑up.
void stark_set_auto_calibration(DeviceHandler *handle, uint8_t slave_id, bool enabled);

/// Send manual position‑calibration command.
void stark_send_calibrate_position(DeviceHandler *handle, uint8_t slave_id);

/// Reset default gestures to factory settings.
void stark_reset_default_gesture(DeviceHandler *handle, uint8_t slave_id);

/// Reset all settings to factory defaults.
/// ● Default IDs: left hand → 0x7E (126); right hand → 0x7F (127).
/// ● RS485 baud rate: 460800 bps.
/// ● CAN FD baud rate: 5 Mbps.
void stark_reset_default_settings(DeviceHandler *handle, uint8_t slave_id);

/// Check whether Turbo mode is enabled.
/// When enabled, the hand will keep squeezing continuously.
bool stark_get_turbo_mode_enabled(DeviceHandler *handle, uint8_t slave_id);

/// Enable or disable Turbo mode.
void stark_set_turbo_mode_enabled(DeviceHandler *handle, uint8_t slave_id, bool enabled);

/// Get Turbo mode configuration.
/// Returns a pointer to `TurboConfig`; call `free_turbo_config` to free it.
/// Returns NULL on failure.
CTurboConfig *stark_get_turbo_config(DeviceHandler *handle, uint8_t slave_id);

/// Get LED information.
/// Returns a pointer to `LedInfo`; call `free_led_info` to free it.
/// Returns NULL on failure.
CLedInfo *stark_get_led_info(DeviceHandler *handle, uint8_t slave_id);

/// Get button press event.
/// Returns a pointer to `ButtonPressEvent`; call `free_button_event` to free it.
/// Returns NULL on failure.
CButtonPressEvent *stark_get_button_event(DeviceHandler *handle, uint8_t slave_id);

/// Start the DFU (Device Firmware Update) process.
/// - `handle`: Device handler pointer.
/// - `slave_id`: Slave device ID.
/// - `dfu_file_path`: DFU file path, as a C string.
/// - `wait_secs`: Timeout for entering DFU mode, in seconds (default 5).
void start_dfu(DeviceHandler *handle,
               uint8_t slave_id,
               const char *dfu_file_path,
               uintptr_t wait_secs);

/// Stop the DFU process.
///
/// - `slave_id`: Slave device ID.
///
/// This stops an ongoing DFU session.
///
/// Note: If DFU has not started or has already completed, this function has no effect.
void stop_dfu(uint8_t slave_id);

void free_device_config(CDeviceConfig *config);

void free_device_info(CDeviceInfo *info);

/// Check if device uses Revo1 Motor API.
///
/// Revo1 Basic/Touch use Revo1 Motor API.
/// Revo1 Advanced/AdvancedTouch and all Revo2 use Revo2 Motor API.
bool device_info_uses_revo1_motor_api(const CDeviceInfo *info);

/// Check if device uses Revo1 Touch API.
///
/// Revo1 Touch API has different sensor counts per finger (4 sensors per finger).
/// Includes Revo1Touch and Revo1AdvancedTouch.
bool device_info_uses_revo1_touch_api(const CDeviceInfo *info);

}  // extern "C"

#endif  // STARK_SDK_H

API Quick Reference

Initialization and Configuration

APIDescription
init_logging()Initialize SDK logging
APIDescription
stark_auto_detect()Auto-detect devices (all protocols) ⭐
init_from_detected()Initialize from detected device ⭐
close_device_handler()Close device connection (unified) ⭐
list_zqwl_devices()List ZQWL CAN/CANFD devices ⭐
init_zqwl_canfd()Initialize ZQWL CANFD device
init_zqwl_can()Initialize ZQWL CAN 2.0 device
close_zqwl()Close ZQWL device
list_socketcan_devices()List available SocketCAN interfaces (Linux) ⭐
init_socketcan_canfd()Initialize SocketCAN CANFD interface (Linux)
init_socketcan_can()Initialize SocketCAN CAN 2.0 interface (Linux)
close_socketcan()Close SocketCAN interface (Linux)
scan_can_devices()Scan CAN bus for devices

Connection Management (Legacy)

APIDescription
modbus_open()Open Modbus connection
modbus_close()Close Modbus connection
init_device_handler()Create device handler (CAN/CANFD/EtherCAT, manual adapter management)
init_device_handler_with_hw_type()Create device handler with pre-set hardware type (skip get_device_info) ⭐
auto_detect_device()Auto-detect device (Modbus only)
auto_detect_modbus_revo2()Auto-detect Revo 2 device (Modbus only)
list_available_ports()List available serial ports

Device Information

APIDescription
stark_get_device_info()Get complete device information
stark_is_touch_device()Check if touch-enabled ⭐
stark_uses_revo1_motor_api()Check if uses Revo 1 Motor API ⭐
stark_uses_pressure_touch_api()Check if uses Pressure Touch API ⭐
stark_get_touch_vendor()Get touch sensor type ⭐
stark_get_voltage()Get device voltage
stark_get_rs485_baudrate()Get RS485 baud rate
stark_set_rs485_baudrate()Set RS485 baud rate
stark_get_canfd_baudrate()Get CANFD baud rate
stark_set_canfd_baudrate()Set CANFD baud rate
stark_set_slave_id()Set slave ID

Device Configuration

APIDescription
stark_get_force_level()Get force level
stark_set_force_level()Set force level
stark_get_auto_calibration_enabled()Get auto-calibration status
stark_set_auto_calibration()Set auto-calibration
stark_calibrate_position()Manual position calibration
stark_get_turbo_mode_enabled()Get turbo mode status
stark_set_turbo_mode_enabled()Set turbo mode
stark_get_turbo_config()Get turbo configuration
stark_set_turbo_config()Set turbo configuration
stark_reboot()Reboot device

Motor Control - Position (Unified Range 0-1000)

APIDescription
stark_set_finger_position()Set single finger position
stark_set_finger_position_with_millis()Set position with duration ⭐
stark_set_finger_position_with_speed()Set position with speed ⭐
stark_set_finger_positions()Set all finger positions
stark_set_finger_positions_and_durations()Set positions with durations ⭐
stark_set_finger_positions_and_speeds()Set positions with speeds ⭐
stark_get_finger_positions()Get all finger positions

Motor Control - Speed (Unified Range -1000~+1000)

APIDescription
stark_set_finger_speed()Set single finger speed
stark_set_finger_speeds()Set all finger speeds
stark_get_finger_speeds()Get all finger speeds

Motor Control - Current (Unified Range -1000~+1000)

APIDescription
stark_set_finger_current()Set single finger current
stark_set_finger_currents()Set all finger currents
stark_get_finger_currents()Get all finger currents

Motor Control - PWM (Unified Range -1000~+1000) ⭐

APIDescription
stark_set_finger_pwm()Set single finger PWM
stark_set_finger_pwms()Set all finger PWMs

Motor Status

APIDescription
stark_get_motor_status()Get comprehensive motor status
stark_get_motor_state()Get motor running state

Motor Settings ⭐

APIDescription
stark_get_finger_unit_mode()Get unit mode
stark_set_finger_unit_mode()Set unit mode
stark_get_finger_min_position()Get minimum position limit
stark_set_finger_min_position()Set minimum position limit
stark_get_finger_max_position()Get maximum position limit
stark_set_finger_max_position()Set maximum position limit
stark_get_finger_max_speed()Get maximum speed limit
stark_set_finger_max_speed()Set maximum speed limit
stark_get_finger_max_current()Get maximum current limit
stark_set_finger_max_current()Set maximum current limit
stark_get_finger_protected_current()Get protected current
stark_set_finger_protected_current()Set protected current
stark_get_thumb_aux_lock_current()Get thumb aux lock current
stark_set_thumb_aux_lock_current()Set thumb aux lock current

Touch Sensors

APIDescription
stark_get_touch_sensor_enabled()Get touch sensor enabled status
stark_get_touch_sensor_fw_versions()Get touch sensor firmware versions
stark_get_touch_sensor_raw_data()Get touch raw data
stark_get_touch_sensor_status()Get touch sensor status
stark_touch_sensor_setup()Setup touch sensors
stark_touch_sensor_reset()Reset touch sensors
stark_touch_sensor_calibrate()Calibrate touch sensors

Force3D Touch Sensors ⭐

APIDescription
stark_get_force3d_touch_summary()Get 4-finger force summary (FxFyFz)
stark_get_force3d_finger_array()Get full array data for single finger (31 channels)

ArrayPressure Sensors ⭐

APIDescription
stark_get_array_pressure_touch_data()Get full array pressure data
stark_set_array_pressure_sleep()Set array sensor sleep status

High-Performance Data Collection ⭐

APIDescription
motor_buffer_new()Create motor status buffer
motor_buffer_free()Free motor status buffer
motor_buffer_pop_all()Get all motor data
touch_buffer_new()Create touch status buffer
touch_buffer_free()Free touch status buffer
pressure_summary_buffer_new()Create pressure summary buffer
pressure_detailed_buffer_new()Create pressure detailed buffer
data_collector_new_basic()Create basic collector
data_collector_new_capacitive()Create capacitive touch collector
data_collector_new_pressure_summary()Create pressure summary collector
data_collector_new_pressure_detailed()Create pressure detailed collector
data_collector_new_pressure_hybrid()Create hybrid mode collector
data_collector_new_force3d()Create Force3D collector
data_collector_new_array_pressure()Create ArrayPressure collector
data_collector_new_v3_basic()Create Revo3 basic collector
data_collector_new_v3_full()Create Revo3 full collector
data_collector_start()Start data collection
data_collector_stop()Stop data collection
data_collector_wait()Wait for collection thread
data_collector_free()Free data collector

LED, Buzzer, Vibration ⭐

APIDescription
stark_get_led_enabled()Get LED enabled status
stark_set_led_enabled()Set LED enabled status
stark_get_buzzer_enabled()Get buzzer enabled status
stark_set_buzzer_enabled()Set buzzer enabled status
stark_get_vibration_enabled()Get vibration enabled status
stark_set_vibration_enabled()Set vibration enabled status

Action Sequences

APIDescription
stark_get_action_sequence()Get action sequence
stark_transfer_action_sequence()Upload action sequence
stark_run_action_sequence()Execute action sequence

EtherCAT Specific ⭐

APIDescription
stark_ec_setup_sdo()Setup SDO
stark_ec_start_loop()Start cyclic loop
stark_ec_stop_loop()Stop cyclic loop
stark_ec_start_dfu()Start firmware upgrade

Firmware Upgrade

APIDescription
stark_start_dfu()Start firmware upgrade

⭐ Indicates Revo2-specific or enhanced features

Help