User Manuel -Hand R1

Robotic Hand R1 Python Start Guide

- OTA

Communication

- Details
- Sample Code
- Preparation

C++ SDK

- Details
- Sample Code Modbus

Python SDK

Start Preparation

Recommended use :

  • Ubuntu 20.04/22.04 LTS
  • Mac 10.15 or later
  • Windows 10/11
  • Conda Python 3.8 ~ 3.12
# Install Python dependencies, it is recommended to use conda to manage the Python environment.
➜  stark-serialport-example git:(main) cd python 
➜  python git:(main) ls
__init__.py      README.md        requirements.txt    revo2
➜  python git:(main) conda activate py310
(py310) ➜  python git:(main) which python
/path/to/miniconda/base/envs/py310/bin/python
(py310) ➜  python git:(main) python -V
Python 3.10.16
(py310) ➜  python git:(main) pip install -r requirements.txt --index-url https://pypi.org/simple/ # PyPI source
Looking in indexes: https://pypi.org/simple/
Collecting bc-stark-sdk==0.8.6 (from -r requirements.txt (line 1))
  Downloading bc_stark_sdk-0.8.6-cp38-abi3-manylinux_2_31_x86_64.whl.metadata (283 bytes)
Requirement already satisfied: other-xxx1
Requirement already satisfied: other-xxx2
Downloading bc_stark_sdk-0.8.6-cp38-abi3-manylinux_2_31_x86_64.whl (1.7 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.7/1.7 MB 1.7 MB/s eta 0:00:00
Installing collected packages: bc-stark-sdk
Successfully installed bc-stark-sdk-0.8.6

# If you cannot install using the above method, you can try manually downloading and then installing the .whl file using pip.
# https://pypi.org/project/bc-stark-sdk/0.8.6/#files
(py310) ➜  python git:(main) pip install --force-reinstall '/path/to/bc_stark_sdk-0.8.6-cp38-abi3-manylinux_2_31_x86_64.whl'
Processing /path/to/bc_stark_sdk-0.8.6-cp38-abi3-manylinux_2_31_x86_64.whl
Installing collected packages: bc-stark-sdk
Successfully installed bc-stark-sdk-0.8.6

# Device Permissions and User Groups
# In a Linux system, device files (such as serial devices) are located in the /dev directory. These device files typically belong to specific user groups (for example, dialout or tty), and only members of these groups can read or write to the device.
# For example, the serial device /dev/ttyUSB0 may have the following permissions:
crw-rw---- 1 root dialout 188, 0 Aug 14 12:00 /dev/ttyUSB0
# This means that only the root user and members of the dialout group can access this device.

# Adding users to the relevant user group
# In order to allow regular users to access serial devices, you can add users to the appropriate user group (usually the dialout group). This way, users can access and operate these devices without needing to elevate to root privileges.
# The command to add a user to the dialout group is as follows:
sudo usermod -aG dialout $USER

# run example
(py310) ➜  python git:(main) ✗ cd revo2
# Control/Read information - Single hand
(py310) ➜  revo2 git:(main) ✗ python revo2_ctrl.py
# Control/Read Information - Single Hand (Tactile Version)
(py310) ➜  revo2 git:(main) ✗ python revo2_touch.py
# Control/Read information - multiple hands
(py310) ➜  revo2 git:(main) ✗ python revo2_ctrl_multi.py
# Action sequence
(py310) ➜  revo2 git:(main) ✗ python revo2_action_seq.py
# Update the configuration, modify the device ID, baud rate, Turbo mode, etc.
(py310) ➜  revo2 git:(main) ✗ python revo2_cfg.py
# Firmware OTA
(py310) ➜  revo2 git:(main) ✗ python revo2_dfu.py

## CANFD Communication protocol
(py310) ➜  python git:(main) ✗ cd revo2_canfd
# ZQWL CANFD equipment currently only provides Windows dependencies.
# For CAN FD devices from other manufacturers, please implement the read and write callbacks by yourself. The SDK itself supports Windows and Linux.
(py310) ➜  revo2_canfd git:(main) ✗ python zqwl_canfd.py # Read device information and control the device.
(py310) ➜  revo2_canfd git:(main) ✗ python zqwl_canfd_dfu.py # Firmware OTA

## EtherCAT Communication protocol
(py310) ➜  python git:(main) ✗ cd revo2_ethercat
(py310) ➜  revo2_ethercat git:(main) ✗ python ec_sdo.py # Sdo read/configure
(py310) ➜  revo2_ethercat git:(main) ✗ python ec_pdo.py # Pdo reads joint status and controls the device.
(py310) ➜  revo2_ethercat git:(main) ✗ python ec_dfu.py # Firmware OTA 

Example Codes

Modbus-RTU

Single hand control

"""
Revo1 Dexterous Hand Automatic Control Example

This example demonstrates how to implement automatic control for the Revo1 dexterous hand, including:
- Initialization and connection of the hand
- Periodic motor state monitoring
- Automatic grip/open actions based on current states
- Precise control of individual fingers
- Creation and management of asynchronous tasks
"""

import asyncio
import sys
import time
from utils import setup_shutdown_event
from revo1_utils import *


async def get_motor_status_periodically(client, slave_id):
    """
    Periodically retrieve motor status and perform automatic control.

    This function continuously monitors the motor status and automatically triggers
    grip/open actions based on finger positions:
    - When the fingers are open, perform a grip action.
    - When the fingers are closed, perform an open action.
    - Record the time cost and status information for each operation.

    Args:
        client: Modbus client instance
        slave_id: Device ID
    """
    logger.info("Motor status monitoring started")
    index = 0

    while True:
        try:
            # Retrieve motor status
            logger.debug("get_motor_status")
            start = time.perf_counter()
            status: libstark.MotorStatusData = await client.get_motor_status(slave_id)
            cost_ms = (time.perf_counter() - start) * 1000

            # Log status information
            logger.info(
                f"[{index}] Motor status - cost: {cost_ms:.2f}ms, "
                f"is_idle: {status.is_idle()}, "
                f"is_closed: {status.is_closed()}, "
                f"is_opened: {status.is_opened()}"
            )
            logger.info(f"[{index}] Motor status: {status.description}")
            index += 1

            # Perform automatic control based on current state
            if status.is_idle():
                if status.is_opened():
                    # When fingers are open, perform a grip action
                    # Position values: [Thumb, ThumbAux, Index, Middle, Ring, Pinky]
                    await client.set_finger_positions(
                        slave_id, [60, 60, 100, 100, 100, 100]
                    )
                elif status.is_closed():
                    # When fingers are closed, perform an open action
                    # Set all fingers to the minimum position value (0)
                    await client.set_finger_positions(slave_id, [0] * 6)

        except Exception as e:
            logger.error(f"Error in motor status monitoring: {e}")
            # Wait 1 second before retrying after an error
            await asyncio.sleep(1)


async def main():
    """
    Main function: Initialize the connection to the dexterous hand and start the automatic control task.
    """
    # Set up shutdown event listener
    shutdown_event = setup_shutdown_event(logger)

    # Detect the baud rate and device ID of the dexterous hand, and initialize the client
    (client, slave_id) = await open_modbus_revo1(port_name=None, quick=True)  # Replace with the actual serial port name; passing None will try automatic detection

    # Perform initialization actions
    # Control a single finger: close the pinky to 100% position
    await client.set_finger_position(slave_id, libstark.FingerId.Pinky, 100)
    await asyncio.sleep(1)  # Wait 1 second

    # Perform a grip action: set all fingers to the preset positions
    # Position parameters: [Thumb 60%, ThumbAux 60%, Index 100%, Middle 100%, Ring 100%, Pinky 100%]
    await client.set_finger_positions(slave_id, [60, 60, 100, 100, 100, 100])
    await asyncio.sleep(1)  # Wait 1 second

    # Perform an OPEN action: set all fingers to the minimum position (0%)
    await client.set_finger_positions(slave_id, [0] * 6)
    await asyncio.sleep(1)  # Wait 1 second

    # Create and start the motor status monitoring task
    asyncio.create_task(get_motor_status_periodically(client, slave_id))
    logger.info("Motor status monitoring task started")

    # Wait for the shutdown event (Ctrl+C or another termination signal)
    await shutdown_event.wait()

    # Close resources
    libstark.modbus_close(client)
    logger.info("Modbus client closed")
    sys.exit(0)


if __name__ == "__main__":
    asyncio.run(main())
 

Double hand control - Use two serial ports

"""
Revo1 Dual-Hand Control Example

Connection Instructions:
- Use two serial ports to connect the left and right hands separately.
- Both devices can share the same device ID (default ID is 1).
"""

import asyncio
import sys
import time
from revo1_utils import *
from utils import setup_shutdown_event

LEFT_PORT = "/dev/ttyUSB0"  # Replace with the actual left-hand device port
RIGHT_PORT = "/dev/ttyUSB1"  # Replace with the actual right-hand device port
LEFT_ID = 1  # Replace with the actual left-hand device ID
RIGHT_ID = 1  # Replace with the actual right-hand device ID
BAUDRATE = libstark.Baudrate.Baud115200


async def main():
    """
    Main function: initialize device connections and control tasks.
    """
    libstark.init_config(libstark.StarkProtocolType.Modbus)
    shutdown_event = setup_shutdown_event(logger)

    # Open both devices
    client_left = await libstark.modbus_open(LEFT_PORT, BAUDRATE)
    client_right = await libstark.modbus_open(RIGHT_PORT, BAUDRATE)
    if not client_left or not client_right:
        logger.critical("Failed to open one or both serial ports.")
        sys.exit(1)

    # Retrieve device information
    left_info = await client_left.get_device_info(LEFT_ID)
    right_info = await client_right.get_device_info(RIGHT_ID)
    if not left_info or not right_info:
        logger.critical("Failed to get device info for one or both devices.")
        sys.exit(1)
    logger.info(f"Left: {left_info.description}")
    logger.info(f"Right: {right_info.description}")

    # Start motor status monitoring task
    task = asyncio.create_task(
        monitor_and_control(client_left, client_right, LEFT_ID, RIGHT_ID)
    )

    # Wait for shutdown event
    await shutdown_event.wait()

    # Clean up resources
    task.cancel()
    await asyncio.gather(task, return_exceptions=True)
    await client_left.modbus_close()
    await client_right.modbus_close()
    logger.info("Modbus clients closed")
    sys.exit(0)


async def monitor_and_control(client_left, client_right, left_id, right_id):
    """
    Periodically retrieve motor status and perform automatic control.
    """
    logger.info(f"Motor status monitoring started for devices {left_id:02x} and {right_id:02x}")
    index = 0
    positions = [0] * 6
    period = 2.0

    while True:
        try:
            index += 1
            now_ms = int(time.time() * 1000)
            t = (now_ms / 1000.0) % period
            if t < period / 2:
                positions = [0, 0, 0, 0, 0, 0]
            else:
                positions = [0, 0, 100, 100, 100, 100]

            # Retrieve motor status
            for client, dev_id, hand in [
                (client_left, left_id, "Left"),
                (client_right, right_id, "Right"),
            ]:
                start = time.perf_counter()
                try:
                    status = await client.get_motor_status(dev_id)
                    cost_ms = (time.perf_counter() - start) * 1000
                    logger.debug(
                        f"{hand} [{dev_id:02x}] [{index}] Motor status - cost: {cost_ms:.2f}ms, "
                        f"is_idle: {status.is_idle()}, is_closed: {status.is_closed()}, is_opened: {status.is_opened()}"
                    )
                    logger.debug(f"{hand} [{dev_id:02x}] [{index}] Motor status: {status.description}")
                except Exception as e:
                    logger.error(f"Error getting motor status for {hand} [{dev_id:02x}]: {e}")

            logger.info(f"Publishing positions: {positions}")
            await asyncio.gather(
                client_left.set_finger_positions(left_id, positions),
                client_right.set_finger_positions(right_id, positions),
            )
            await asyncio.sleep(0.01)
        except Exception as e:
            logger.error(f"Error in monitor_and_control loop: {e}")
            await asyncio.sleep(1)


if __name__ == "__main__":
    asyncio.run(main())
 

Dual-hand Control - Connect the left hand and the right hand via a single serial port. The left hand and the right hand need to be configured with different IDs, such as 1 and 2.

"""
Revo1 Smart Hand Multi-device Control example

This example demonstrates how to control multiple Revo1 Dextrous Hand devices simultaneously, including:
 - Multi-device connection methods (single serial port for multiple devices vs multiple serial ports for multiple devices)
 - Tactile sensor configuration and data acquisition (tactile version)
 - Motor status monitoring and automatic control
 - Finger position and current control
 - Concurrent task management and exception handling

Connection Method Description:
 - Method 1: Connect multiple devices via a single serial port (requires configuring different device IDs)
 - Method 2: Connect different devices via multiple serial ports (can use the same device ID)

Applicable Scenarios:
 - Two-handed control systems
 - Multi-device collaborative operations
 - Device performance comparison testing
 """

import asyncio
import sys
import time
from revo1_utils import *
from utils import setup_shutdown_event

# Multi-device Control Configuration
# Method 1: Connecting multiple devices to a single serial port (requires configuring different device IDs)
# Method 2: Multiple serial ports are connected to different devices (the same device ID can be used)
# For example:
# client_left_hand = await libstark.modbus_open(port_name_left, baudrate)
# client_right_hand = await libstark.modbus_open(port_name_right, baudrate)

# Device ID List Configuration
slave_ids = [1]        # A single serial port connects to one device
# slave_ids = [1, 2]   # A single serial port connects two devices, with device IDs 1 and 2 respectively.
# Device Tactile Version Marking Dictionary
# Used to record whether each device is a tactile version, so that they can be distinguished during subsequent processing
slave_is_touch = {1: False, 2: False}


async def main():
    """
    Main function: Initialize device connection and control tasks
    """
    # Set up a close event listener
    shutdown_event = setup_shutdown_event(logger)

    # Open ModbusConnection
    (client, detected_slave_id) = await open_modbus_revo1()

    # Configure a tactile sensor for each device and start the status monitoring task
    for slave_id in slave_ids:
        await setup_touch_sensor(client, slave_id)
        asyncio.create_task(get_motor_status_periodically(client, slave_id))
    logger.info("Status monitoring tasks started")

    # An example of multiple serial ports connected to different devices (optional)
    # Suitable for scenarios where complete independent control of two devices is required
    # client_left_hand = await libstark.modbus_open("/dev/ttyUSB0", libstark.Baudrate.Baud115200)
    # client_right_hand = await libstark.modbus_open("/dev/ttyUSB1", libstark.Baudrate.Baud115200)
    # slave_id = 1  # Both devices use the default ID=1
    # await setup_touch_sensor(client_left_hand, slave_id) # If it is the tactile version
    # await setup_touch_sensor(client_right_hand, slave_id) # If it is the tactile version
    # asyncio.create_task(get_motor_status_periodically(client_left_hand, slave_id))
    # asyncio.create_task(get_motor_status_periodically(client_right_hand, slave_id))

    # Wait for the close event
    await shutdown_event.wait()

    # Clean up resources
    libstark.modbus_close(client)
    logger.info("Modbus client closed")
    sys.exit(0)


async def setup_touch_sensor(client, slave_id):
    """
    Configure the tactile sensor

    Configure accordingly based on the device type (Tactile Edition/Basic Edition).
    The tactile version of the device will enable the tactile sensor function, while the basic version will skip this step.

    Args:
        client: Modbus Client instance
        slave_id: Device ID
    """
    # Verify device type
    device_info = await client.get_device_info(slave_id)
    logger.info(f"Device {slave_id} info: {device_info.description}")
    is_revo1_touch = device_info.is_revo1_touch()
    slave_is_touch[slave_id] = is_revo1_touch

    if not is_revo1_touch:
        logger.warning(f"Device {slave_id} is not Revo1 Touch hardware, skipping touch sensor setup")
        return

    # Enable the tactile sensor function (default disabled on startup)
    bits = 0x1F  # 0x1f: Enable all tactile sensors on 5 fingers
    await client.touch_sensor_setup(slave_id, bits)
    await asyncio.sleep(1)  # Wait for the tactile sensor to be ready

    # Verify sensor enable status
    bits = await client.get_touch_sensor_enabled(slave_id)
    logger.info(f"Device {slave_id} Touch Sensor Enabled: {(bits & 0x1F):05b}")

    # Get the firmware version of the tactile sensor (can only be obtained after setup)
    touch_fw_versions = await client.get_touch_sensor_fw_versions(slave_id)
    logger.info(f"Device {slave_id} Touch Fw Versions: {touch_fw_versions}")


async def get_touch_status(client, slave_id):
    """
    Get tactile sensor status and data

  Tactile Sensor Data Description:
    - Normal force: The force (pressure) perpendicular to the contact surface
    - Tangential force: A force acting in the tangential direction of the contact surface (such as frictional force, inertial force, etc.)
    - Self-approach: Self-capacitance change value, reflecting the degree of proximity of an object
    - Mutual Proximity: Mutual Capacitance Variation, Reflecting the Degree of Object Proximity
    - Sensor status: 0=Normal, 1=Data anomaly, 2=Communication anomaly

    Args:
        client: Modbus Client instance
        slave_id: Device ID
    """
    # Get the tactile sensor status of all fingers
    touch_status: list[libstark.TouchFingerItem] = await client.get_touch_sensor_status(slave_id)
    thumb = touch_status[0]   # Thumb
    index = touch_status[1]   # Index finger
    middle = touch_status[2]  # Middle finger
    ring = touch_status[3]    # Ring finger
    pinky = touch_status[4]   # Little finger

    # Record detailed data of the index finger sensor
    logger.debug(f"Device {slave_id} Index Sensor Desc: {index.description}")
    logger.info(f"Device {slave_id} Index Sensor status: {index.status}")
    logger.info(f"Device {slave_id} Index normal_force1: {index.normal_force1}")      # Normal force 1    logger.info(f"Device {slave_id} Index normal_force2: {index.normal_force2}")      # Normal Force 2    logger.info(f"Device {slave_id} Index normal_force3: {index.normal_force3}")      # Normal force 3
    logger.info(f"Device {slave_id} Index tangential_force1: {index.tangential_force1}")         # Tangential Force 1
    logger.info(f"Device {slave_id} Index tangential_force2: {index.tangential_force2}")         # Tangential Force 2
    logger.info(f"Device {slave_id} Index tangential_force3: {index.tangential_force3}")         # Tangential force 3
    logger.info(f"Device {slave_id} Index tangential_direction1: {index.tangential_direction1}") # Tangential Force Direction 1
    logger.info(f"Device {slave_id} Index tangential_direction2: {index.tangential_direction2}") # Tangential Force Direction 2
    logger.info(f"Device {slave_id} Index tangential_direction3: {index.tangential_direction3}") # Tangential force direction 3
    logger.info(f"Device {slave_id} Index self_proximity1: {index.self_proximity1}")             # Since approaching 1
    logger.info(f"Device {slave_id} Index self_proximity2: {index.self_proximity2}")             # Since approaching 2
    logger.info(f"Device {slave_id} Index mutual_proximity: {index.mutual_proximity}")           # Mutual approach

    # Get the tactile sensor status of a single finger (another way to obtain it)
    thumb = await client.get_single_touch_sensor_status(slave_id, 0)
    index = await client.get_single_touch_sensor_status(slave_id, 1)
    middle = await client.get_single_touch_sensor_status(slave_id, 2)
    ring = await client.get_single_touch_sensor_status(slave_id, 3)
    pinky = await client.get_single_touch_sensor_status(slave_id, 4)

    # Record thumb sensor data
    logger.debug(f"Device {slave_id} Thumb Sensor Desc: {thumb.description}")
    logger.info(f"Device {slave_id} Thumb Sensor status: {thumb.status}")
    logger.info(f"Device {slave_id} Thumb normal_force1: {thumb.normal_force1}")           # Normal force 1
    logger.info(f"Device {slave_id} Thumb normal_force2: {thumb.normal_force2}")           # Normal force 2
    logger.info(f"Device {slave_id} Thumb tangential_force1: {thumb.tangential_force1}")   # Tangential force 1
    logger.info(f"Device {slave_id} Thumb tangential_force2: {thumb.tangential_force2}")   # Tangential Force 2
    logger.info(f"Device {slave_id} Thumb tangential_direction1: {thumb.tangential_direction1}") # Tangential Force Direction 1
    logger.info(f"Device {slave_id} Thumb tangential_direction2: {thumb.tangential_direction2}") # Tangential Force Direction 2
    logger.info(f"Device {slave_id} Thumb self_proximity1: {thumb.self_proximity1}")       # Since approaching 1

    # Get sensor raw channel values (optional, for debugging and advanced analysis)
    # touch_raw_data = await client.get_touch_sensor_raw_data(slave_id)
    # logger.info(f"Device {slave_id} Touch Sensor Raw Data: {touch_raw_data.description}")
    # logger.debug(f"Device {slave_id} Touch Sensor Raw Data Thumb: {touch_raw_data.thumb}")
    # logger.debug(f"Device {slave_id} Touch Sensor Raw Data Index: {touch_raw_data.index}")
    # logger.debug(f"Device {slave_id} Touch Sensor Raw Data Middle: {touch_raw_data.middle}")
    # logger.debug(f"Device {slave_id} Touch Sensor Raw Data Ring: {touch_raw_data.ring}")
    # logger.debug(f"Device {slave_id} Touch Sensor Raw Data Pinky: {touch_raw_data.pinky}")


async def get_motor_status_periodically(client, slave_id):
    """
    Regularly obtain the motor status and perform automatic control

    This function will continuously monitor the motor status and perform simple opening and closing actions based on the finger position:
    - When the fingers are spread, perform a handshake action
    - When the fingers close, perform the opening action
    - For tactile version devices, acquire tactile sensor data simultaneously

    Args:
        client: Modbus Client instance
        slave_id: Device ID
    """
    logger.info(f"Motor status monitoring started for device {slave_id}")
    index = 0

    while True:
        try:
            # Check if it is the tactile version, and if so, get the status of the tactile sensor
            if slave_is_touch.get(slave_id, False):
                logger.debug(f"Getting touch sensor status for device {slave_id}...")
                await get_touch_status(client, slave_id)

            # Get motor status
            logger.debug(f"Getting motor status for device {slave_id}...")
            start = time.perf_counter()
            status: libstark.MotorStatusData = await client.get_motor_status(slave_id)
            cost_ms = (time.perf_counter() - start) * 1000

            # Record status information
            logger.info(
                f"Device {slave_id} [{index}] Motor status - cost: {cost_ms:.2f}ms, "
                f"is_idle: {status.is_idle()}, "
                f"is_closed: {status.is_closed()}, "
                f"is_opened: {status.is_opened()}"
            )
            logger.info(f"Device {slave_id} [{index}] Motor status: {status.description}")
            index += 1

            # Execute automatic control based on the current status
            if status.is_idle():
                if status.is_opened():
                    # When fingers are spread, perform a handshake action
                    # Position values: [Thumb, Thumb assist, Index finger, Middle finger, Ring finger, Little finger]
                    await client.set_finger_positions(
                        slave_id, [60, 60, 100, 100, 100, 100]
                    )
                    logger.debug(f"Device {slave_id} executing grip action")
                elif status.is_closed():
                    # When the fingers close, perform an opening action
                    await client.set_finger_positions(slave_id, [0] * 6)
                    logger.debug(f"Device {slave_id} executing open action")

            # Add a delay to avoid overly frequent queries
            await asyncio.sleep(0.001)

        except Exception as e:
            logger.error(f"Error in motor status monitoring for device {slave_id}: {e}")
            await asyncio.sleep(1)  # Wait longer before retrying when an error occurs


if __name__ == "__main__":
    asyncio.run(main())
 

Obtain tactile sensor status and data

Acquiring tactile information

"""
Revo1 Tactile Version Dextrous Hand Control Example

This example demonstrates how to control the Revo1 Tactile Edition Dextrous Hand device, including:
- Configuration and Enablement of Tactile Sensors
- Acquisition and processing of tactile sensor data
- Motor status monitoring and automatic control
- Finger Position Control and Current Control
"""

import asyncio
import sys
import time
from revo1_utils import *
from utils import setup_shutdown_event

async def main():
    """
    Main function: Initialize tactile version dexterous hand connection and control tasks
    """
    # Set up a close event listener
    shutdown_event = setup_shutdown_event(logger)

    # Detect the baud rate and device ID of the dexterous hand, and initialize the client
    (client, slave_id) = await open_modbus_revo1()

    # Verify that the device type is the tactile version
    device_info = await client.get_device_info(slave_id)
    logger.info(f"Device info: {device_info.description}")
    if not device_info.is_revo1_touch():
        logger.error("This example is only for Revo1 Touch hardware")
        sys.exit(1)

    # Configure the tactile sensor
    await setup_touch_sensor(client, slave_id)

    # Start the motor status monitoring task
    asyncio.create_task(get_motor_status_periodically(client, slave_id))
    logger.info("Status task started")

    # Wait for the close event
    await shutdown_event.wait()

    # Close the resource
    libstark.modbus_close(client)
    logger.info("Modbus client closed")
    sys.exit(0)

async def setup_touch_sensor(client, slave_id):
    """
    Configure and enable the tactile sensor

    Args:
        client: Modbus Client instance
        slave_id: Device ID
    """
    # Enable the tactile sensor function (default disabled on startup)
    bits = 0x1F  # 0x1f: Enable all tactile sensors on 5 fingers
    await client.touch_sensor_setup(slave_id, bits)
    await asyncio.sleep(1)  # Wait for the tactile sensor to be ready

    # Verify sensor enable status
    bits = await client.get_touch_sensor_enabled(slave_id)
    logger.info(f"Touch Sensor Enabled: {(bits & 0x1F):05b}")

    # Get the firmware version of the tactile sensor (can only be obtained after setup)
    touch_fw_versions = await client.get_touch_sensor_fw_versions(slave_id)
    logger.info(f"Touch Fw Versions: {touch_fw_versions}")

    # Tactile Sensor Maintenance Command (Optional)
    # Reset: Send the sensor acquisition channel reset command. When executing, try to avoid applying force to the finger sensor.
    # await client.touch_sensor_reset(slave_id, 0x1f)

    # Calibration: Used when the three-dimensional force value in idle state is not zero, and the execution time is long.
    # It is recommended to ignore data within ten seconds after calibration. When in operation, the finger sensor should not be subjected to force.
    # await client.touch_sensor_calibrate(slave_id, 0x1f)

async def get_touch_status(client, slave_id):
    """
    Get the status and data of the tactile sensor

    Tactile Sensor Data Description:
    - Normal Force: The force perpendicular to the contact surface (pressure)
    - Tangential force: A force acting along the tangent direction of the contact surface (such as frictional force, inertial force, etc.)
    - Self-approach: Self-capacitance change value
    - Mutual Proximity: Mutual Capacitance Change Value
    - Sensor status: 0=Normal, 1=Data anomaly, 2=Communication anomaly

    Args:
        client: Modbus Client instance
        slave_id: Device ID
    """
    # Get the tactile sensor status of all fingers
    touch_status: list[libstark.TouchFingerItem] = await client.get_touch_sensor_status(slave_id)
    thumb = touch_status[0]   # Thumb
    index = touch_status[1]   # Index finger
    middle = touch_status[2]  # Middle finger
    ring = touch_status[3]    # Ring finger
    pinky = touch_status[4]   # Little finger

    # Record detailed data of the index finger sensor
    logger.debug(f"Index Sensor Desc: {index.description}")
    logger.info(f"Index Sensor status: {index.status}")  # Tactile sensor status
    logger.info(f"Index normal_force1: {index.normal_force1}")  # Normal force 1
    logger.info(f"Index normal_force2: {index.normal_force2}")  # Normal Force 2
    logger.info(f"Index normal_force3: {index.normal_force3}")  # Normal force 3
    logger.info(f"Index tangential_force1: {index.tangential_force1}")  # Tangential force 1
    logger.info(f"Index tangential_force2: {index.tangential_force2}")  # Tangential Force 2
    logger.info(f"Index tangential_force3: {index.tangential_force3}")  # Tangential force 3
    logger.info(f"Index tangential_direction1: {index.tangential_direction1}")  # Tangential force direction 1    logger.info(f"Index tangential_direction2: {index.tangential_direction2}")  # Tangential Force Direction 2
    logger.info(f"Index tangential_direction3: {index.tangential_direction3}")  # Tangential force direction 3
    logger.info(f"Index self_proximity1: {index.self_proximity1}")  # Since approaching 1
    logger.info(f"Index self_proximity2: {index.self_proximity2}")  # Since approaching 2
    logger.info(f"Index mutual_proximity: {index.mutual_proximity}")  # Mutual proximity

    # Get the tactile sensor status of a single finger (another way to obtain it)
    thumb = await client.get_single_touch_sensor_status(slave_id, 0)
    index = await client.get_single_touch_sensor_status(slave_id, 1)
    middle = await client.get_single_touch_sensor_status(slave_id, 2)
    ring = await client.get_single_touch_sensor_status(slave_id, 3)
    pinky = await client.get_single_touch_sensor_status(slave_id, 4)

    # Record thumb sensor data
    logger.debug(f"Thumb Sensor Desc: {thumb.description}")
    logger.info(f"Thumb Sensor status: {thumb.status}")
    logger.info(f"Thumb normal_force1: {thumb.normal_force1}")  # Normal Force 1
    logger.info(f"Thumb normal_force2: {thumb.normal_force2}")  # Normal Force 2
    logger.info(f"Thumb tangential_force1: {thumb.tangential_force1}")  # Tangential Force 1
    logger.info(f"Thumb tangential_force2: {thumb.tangential_force2}")  # Tangential force 2
    logger.info(f"Thumb tangential_direction1: {thumb.tangential_direction1}")  # Tangential Force Direction 1
    logger.info(f"Thumb tangential_direction2: {thumb.tangential_direction2}")  # Tangential Force Direction 2    logger.info(f"Thumb self_proximity1: {thumb.self_proximity1}")  # As it approaches 1

    # Get sensor raw channel values (optional, for debugging)
    # touch_raw_data = await client.get_touch_sensor_raw_data(slave_id)
    # logger.info(f"Touch Sensor Raw Data: {touch_raw_data.description}")
    # logger.debug(f"Touch Sensor Raw Data Thumb: {touch_raw_data.thumb}")
    # logger.debug(f"Touch Sensor Raw Data Index: {touch_raw_data.index}")
    # logger.debug(f"Touch Sensor Raw Data Middle: {touch_raw_data.middle}")
    # logger.debug(f"Touch Sensor Raw Data Ring: {touch_raw_data.ring}")
    # logger.debug(f"Touch Sensor Raw Data Pinky: {touch_raw_data.pinky}")

async def get_motor_status_periodically(client, slave_id):
    """
    Regularly obtain the motor status and perform automatic control

    This function will continuously monitor the motor status and perform simple opening and closing actions based on the finger position:
    - When the fingers are spread, perform a handshake action
    - When the fingers close, perform the opening action

    Args:
        client: Modbus Client instance
        slave_id: Device ID
    """
    logger.info("Motor status monitoring started")
    index = 0

    while True:
        try:
            # Get the status of the tactile sensor
            logger.debug("Getting touch sensor status...")
            await get_touch_status(client, slave_id)

            # Get motor status
            logger.debug("Getting motor status...")
            start = time.perf_counter()
            status: libstark.MotorStatusData = await client.get_motor_status(slave_id)
            cost_ms = (time.perf_counter() - start) * 1000

            # Record status information
            logger.info(
                f"[{index}] Motor status - cost: {cost_ms:.2f}ms, "
                f"is_idle: {status.is_idle()}, "
                f"is_closed: {status.is_closed()}, "
                f"is_opened: {status.is_opened()}"
            )
            logger.info(f"[{index}] Motor status: {status.description}")
            index += 1

            # Perform actions based on the current status
            if status.is_idle():
                if status.is_opened():
                    # When fingers are spread, perform a handshake action
                    await client.set_finger_positions(
                        slave_id, [60, 60, 100, 100, 100, 100]
                    )
                elif status.is_closed():
                    # When the fingers close, perform an opening action
                    await client.set_finger_positions(slave_id, [0] * 6)

            # Add a delay to avoid overly frequent queries
            await asyncio.sleep(0.001)

        except Exception as e:
            logger.error(f"Error in motor status monitoring: {e}")
            await asyncio.sleep(1)  # Wait longer when an error occurs

async def set_finger_current(client, slave_id: int):
    """
    Set finger current control

    Current control parameter range:-100~-20, 20~100
    Positive numbers indicate the closing direction, and negative numbers indicate the opening direction.

    Args:
        client: Modbus Client instance
        slave_id: Device ID
    """
    # Select the finger to control
    # finger_id = libstark.FingerId.Thumb     # Thumb
    # finger_id = libstark.FingerId.ThumbAux  # Thumb assist
    # finger_id = libstark.FingerId.Index     # Index finger
    # finger_id = libstark.FingerId.Middle    # Middle finger
    finger_id = libstark.FingerId.Ring       # Ring finger
    # finger_id = libstark.FingerId.Pinky     # Little finger

    # Set single finger current
    await client.set_finger_current(slave_id, finger_id, -20)

    # Or set all finger currents
    # await client.set_finger_currents(slave_id, [-100] * 6)

if __name__ == "__main__":
    asyncio.run(main())
 
"""
Revo1 Tactile Version Dextrous Hand Control Example

This example demonstrates how to control the Revo1 Tactile Edition Dextrous Hand device, including:
- Configuration and Enablement of Tactile Sensors
- Acquisition and processing of tactile sensor data
- Motor status monitoring and automatic control
- Finger Position Control and Current Control
"""

import asyncio
import sys
import time
from revo1_utils import *
from utils import setup_shutdown_event

async def main():
    """
    Main function: Initialize tactile version dexterous hand connection and control tasks
    """
    # Set up a close event listener
    shutdown_event = setup_shutdown_event(logger)

    # Detect the baud rate and device ID of the dexterous hand, and initialize the client
    (client, slave_id) = await open_modbus_revo1()

    # Verify that the device type is the tactile version
    device_info = await client.get_device_info(slave_id)
    logger.info(f"Device info: {device_info.description}")
    if not device_info.is_revo1_touch():
        logger.error("This example is only for Revo1 Touch hardware")
        sys.exit(1)

    # Configure the tactile sensor
    await setup_touch_sensor(client, slave_id)

    # Start the motor status monitoring task
    asyncio.create_task(get_motor_status_periodically(client, slave_id))
    logger.info("Status task started")

    # Wait for the close event
    await shutdown_event.wait()

    # Close the resource
    libstark.modbus_close(client)
    logger.info("Modbus client closed")
    sys.exit(0)

async def setup_touch_sensor(client, slave_id):
    """
    Configure and enable the tactile sensor

    Args:
        client: Modbus Client instance
        slave_id: Device ID
    """
    # Enable the tactile sensor function (default disabled on startup)
    bits = 0x1F  # 0x1f: Enable all tactile sensors on 5 fingers
    await client.touch_sensor_setup(slave_id, bits)
    await asyncio.sleep(1)  # Wait for the tactile sensor to be ready

    # Verify sensor enable status
    bits = await client.get_touch_sensor_enabled(slave_id)
    logger.info(f"Touch Sensor Enabled: {(bits & 0x1F):05b}")

    # Get the firmware version of the tactile sensor (can only be obtained after setup)
    touch_fw_versions = await client.get_touch_sensor_fw_versions(slave_id)
    logger.info(f"Touch Fw Versions: {touch_fw_versions}")

    # Tactile Sensor Maintenance Command (Optional)
    # Reset: Send the sensor acquisition channel reset command. When executing, try to avoid applying force to the finger sensor.
    # await client.touch_sensor_reset(slave_id, 0x1f)

    # Calibration: Used when the three-dimensional force value in idle state is not zero, and the execution time is long.
    # It is recommended to ignore data within ten seconds after calibration. When in operation, the finger sensor should not be subjected to force.
    # await client.touch_sensor_calibrate(slave_id, 0x1f)

async def get_touch_status(client, slave_id):
    """
    Get the status and data of the tactile sensor

    Tactile Sensor Data Description:
    - Normal Force: The force perpendicular to the contact surface (pressure)
    - Tangential force: A force acting along the tangent direction of the contact surface (such as frictional force, inertial force, etc.)
    - Self-approach: Self-capacitance change value
    - Mutual Proximity: Mutual Capacitance Change Value
    - Sensor status: 0=Normal, 1=Data anomaly, 2=Communication anomaly

    Args:
        client: Modbus Client instance
        slave_id: Device ID
    """
    # Get the tactile sensor status of all fingers
    touch_status: list[libstark.TouchFingerItem] = await client.get_touch_sensor_status(slave_id)
    thumb = touch_status[0]   # Thumb
    index = touch_status[1]   # Index finger
    middle = touch_status[2]  # Middle finger
    ring = touch_status[3]    # Ring finger
    pinky = touch_status[4]   # Little finger

    # Record detailed data of the index finger sensor
    logger.debug(f"Index Sensor Desc: {index.description}")
    logger.info(f"Index Sensor status: {index.status}")  # Tactile sensor status
    logger.info(f"Index normal_force1: {index.normal_force1}")  # Normal force 1
    logger.info(f"Index normal_force2: {index.normal_force2}")  # Normal Force 2
    logger.info(f"Index normal_force3: {index.normal_force3}")  # Normal force 3
    logger.info(f"Index tangential_force1: {index.tangential_force1}")  # Tangential force 1
    logger.info(f"Index tangential_force2: {index.tangential_force2}")  # Tangential Force 2
    logger.info(f"Index tangential_force3: {index.tangential_force3}")  # Tangential force 3
    logger.info(f"Index tangential_direction1: {index.tangential_direction1}")  # Tangential force direction 1    logger.info(f"Index tangential_direction2: {index.tangential_direction2}")  # Tangential Force Direction 2
    logger.info(f"Index tangential_direction3: {index.tangential_direction3}")  # Tangential force direction 3
    logger.info(f"Index self_proximity1: {index.self_proximity1}")  # Since approaching 1
    logger.info(f"Index self_proximity2: {index.self_proximity2}")  # Since approaching 2
    logger.info(f"Index mutual_proximity: {index.mutual_proximity}")  # Mutual proximity

    # Get the tactile sensor status of a single finger (another way to obtain it)
    thumb = await client.get_single_touch_sensor_status(slave_id, 0)
    index = await client.get_single_touch_sensor_status(slave_id, 1)
    middle = await client.get_single_touch_sensor_status(slave_id, 2)
    ring = await client.get_single_touch_sensor_status(slave_id, 3)
    pinky = await client.get_single_touch_sensor_status(slave_id, 4)

    # Record thumb sensor data
    logger.debug(f"Thumb Sensor Desc: {thumb.description}")
    logger.info(f"Thumb Sensor status: {thumb.status}")
    logger.info(f"Thumb normal_force1: {thumb.normal_force1}")  # Normal Force 1
    logger.info(f"Thumb normal_force2: {thumb.normal_force2}")  # Normal Force 2
    logger.info(f"Thumb tangential_force1: {thumb.tangential_force1}")  # Tangential Force 1
    logger.info(f"Thumb tangential_force2: {thumb.tangential_force2}")  # Tangential force 2
    logger.info(f"Thumb tangential_direction1: {thumb.tangential_direction1}")  # Tangential Force Direction 1
    logger.info(f"Thumb tangential_direction2: {thumb.tangential_direction2}")  # Tangential Force Direction 2    logger.info(f"Thumb self_proximity1: {thumb.self_proximity1}")  # As it approaches 1

    # Get sensor raw channel values (optional, for debugging)
    # touch_raw_data = await client.get_touch_sensor_raw_data(slave_id)
    # logger.info(f"Touch Sensor Raw Data: {touch_raw_data.description}")
    # logger.debug(f"Touch Sensor Raw Data Thumb: {touch_raw_data.thumb}")
    # logger.debug(f"Touch Sensor Raw Data Index: {touch_raw_data.index}")
    # logger.debug(f"Touch Sensor Raw Data Middle: {touch_raw_data.middle}")
    # logger.debug(f"Touch Sensor Raw Data Ring: {touch_raw_data.ring}")
    # logger.debug(f"Touch Sensor Raw Data Pinky: {touch_raw_data.pinky}")

async def get_motor_status_periodically(client, slave_id):
    """
    Regularly obtain the motor status and perform automatic control

    This function will continuously monitor the motor status and perform simple opening and closing actions based on the finger position:
    - When the fingers are spread, perform a handshake action
    - When the fingers close, perform the opening action

    Args:
        client: Modbus Client instance
        slave_id: Device ID
    """
    logger.info("Motor status monitoring started")
    index = 0

    while True:
        try:
            # Get the status of the tactile sensor
            logger.debug("Getting touch sensor status...")
            await get_touch_status(client, slave_id)

            # Get motor status
            logger.debug("Getting motor status...")
            start = time.perf_counter()
            status: libstark.MotorStatusData = await client.get_motor_status(slave_id)
            cost_ms = (time.perf_counter() - start) * 1000

            # Record status information
            logger.info(
                f"[{index}] Motor status - cost: {cost_ms:.2f}ms, "
                f"is_idle: {status.is_idle()}, "
                f"is_closed: {status.is_closed()}, "
                f"is_opened: {status.is_opened()}"
            )
            logger.info(f"[{index}] Motor status: {status.description}")
            index += 1

            # Perform actions based on the current status
            if status.is_idle():
                if status.is_opened():
                    # When fingers are spread, perform a handshake action
                    await client.set_finger_positions(
                        slave_id, [60, 60, 100, 100, 100, 100]
                    )
                elif status.is_closed():
                    # When the fingers close, perform an opening action
                    await client.set_finger_positions(slave_id, [0] * 6)

            # Add a delay to avoid overly frequent queries
            await asyncio.sleep(0.001)

        except Exception as e:
            logger.error(f"Error in motor status monitoring: {e}")
            await asyncio.sleep(1)  # Wait longer when an error occurs

async def set_finger_current(client, slave_id: int):
    """
    Set finger current control

    Current control parameter range:-100~-20, 20~100
    Positive numbers indicate the closing direction, and negative numbers indicate the opening direction.

    Args:
        client: Modbus Client instance
        slave_id: Device ID
    """
    # Select the finger to control
    # finger_id = libstark.FingerId.Thumb     # Thumb
    # finger_id = libstark.FingerId.ThumbAux  # Thumb assist
    # finger_id = libstark.FingerId.Index     # Index finger
    # finger_id = libstark.FingerId.Middle    # Middle finger
    finger_id = libstark.FingerId.Ring       # Ring finger
    # finger_id = libstark.FingerId.Pinky     # Little finger

    # Set single finger current
    await client.set_finger_current(slave_id, finger_id, -20)

    # Or set all finger currents
    # await client.set_finger_currents(slave_id, [-100] * 6)

if __name__ == "__main__":
    asyncio.run(main())
 

Get tactile sensor status and data

async def get_touch_status(client, slave_id):
  """
  Get the status and data of the tactile sensor

  Tactile Sensor Data Description:
  - Normal Force: The force perpendicular to the contact surface (pressure)
  - Tangential force: A force acting in the tangential direction of the contact surface (such as frictional force, inertial force, etc.)
  - Self-approach: Self-capacitance change value
  - Mutual Proximity: Mutual Capacitance Change Value
  - Sensor status: 0=Normal, 1=Data anomaly, 2=Communication anomaly

  Args:
      client: Modbus Client instance
      slave_id: Device ID
  """
  # Get the status of tactile sensors on all fingers
  touch_status = await client.get_touch_sensor_status(slave_id)
  thumb = touch_status[0]   # Thumb
  index = touch_status[1]   # Index finger
  middle = touch_status[2]  # Middle finger
  ring = touch_status[3]    # Ring finger
  pinky = touch_status[4]   # Little finger

  # Record detailed data of the index finger sensor
  logger.debug(f"Index Sensor Desc: {index.description}")
  logger.info(f"Index Sensor status: {index.status}")  # Tactile sensor status
  logger.info(f"Index normal_force1: {index.normal_force1}")  # Normal Force 1
  logger.info(f"Index normal_force2: {index.normal_force2}")  # Normal Force 2
  logger.info(f"Index normal_force3: {index.normal_force3}")  # Normal force 3
  logger.info(f"Index tangential_force1: {index.tangential_force1}")  # Tangential Force 1
  logger.info(f"Index tangential_force2: {index.tangential_force2}")  # Tangential force 2
  logger.info(f"Index tangential_force3: {index.tangential_force3}")  # Tangential force 3
  logger.info(f"Index tangential_direction1: {index.tangential_direction1}")  # Tangential Force Direction 1
  logger.info(f"Index tangential_direction2: {index.tangential_direction2}")  # Tangential Force Direction 2
  logger.info(f"Index tangential_direction3: {index.tangential_direction3}")  # Tangential force direction 3
  logger.info(f"Index self_proximity1: {index.self_proximity1}")  # Since approaching 1
  logger.info(f"Index self_proximity2: {index.self_proximity2}")  # Since approaching 2
  logger.info(f"Index mutual_proximity: {index.mutual_proximity}")  # Mutual approach

  # Get the tactile sensor status of a single finger (another way to obtain it)
  thumb = await client.get_single_touch_sensor_status(slave_id, 0)
  index = await client.get_single_touch_sensor_status(slave_id, 1)
  middle = await client.get_single_touch_sensor_status(slave_id, 2)
  ring = await client.get_single_touch_sensor_status(slave_id, 3)
  pinky = await client.get_single_touch_sensor_status(slave_id, 4)

  # Record thumb sensor data
  logger.debug(f"Thumb Sensor Desc: {thumb.description}")
  logger.info(f"Thumb Sensor status: {thumb.status}")
  logger.info(f"Thumb normal_force1: {thumb.normal_force1}")  # Normal force 1
  logger.info(f"Thumb normal_force2: {thumb.normal_force2}")  # Normal Force 2
  logger.info(f"Thumb tangential_force1: {thumb.tangential_force1}")  # Tangential force 1
  logger.info(f"Thumb tangential_force2: {thumb.tangential_force2}")  # Tangential Force 2
  logger.info(f"Thumb tangential_direction1: {thumb.tangential_direction1}")  # Tangential Force Direction 1
  logger.info(f"Thumb tangential_direction2: {thumb.tangential_direction2}")  # Tangential Force Direction 2
  logger.info(f"Thumb self_proximity1: {thumb.self_proximity1}")  # Since approaching 1

  # Get sensor raw channel values (optional, for debugging)
  # touch_raw_data = await client.get_touch_sensor_raw_data(slave_id)
  # logger.info(f"Touch Sensor Raw Data: {touch_raw_data.description}")
  # logger.debug(f"Touch Sensor Raw Data Thumb: {touch_raw_data.thumb}")
  # logger.debug(f"Touch Sensor Raw Data Index: {touch_raw_data.index}")
  # logger.debug(f"Touch Sensor Raw Data Middle: {touch_raw_data.middle}")
  # logger.debug(f"Touch Sensor Raw Data Ring: {touch_raw_data.ring}")
  # logger.debug(f"Touch Sensor Raw Data Pinky: {touch_raw_data.pinky}")
 

Set device ID

# Set up the device ID
# Parameter Description:
# client: client Object,Device Connection handle
# slave_id: Device ID,The scope is 1~247, 0 As a broadcast address,The first generation assumes 1
# Set the device asynchronously ID
await client.set_slave_id(slave_id, new_slave_id) # After setting it up, the device will automatically restart (soft reboot)
 

Read/Set Serial Port Baud Rate

# Read/Set Serial Port Baud Rate
# Parameter Description:
# client: client Object,Device Connection handle
# baudrate, Baud rate,115200, 57600, 19200, 460800,First-generation handsets are considered 115200
# Read/Configure Serial Port Baud Rate in Asynchronous Mode
baudrate = await client.get_serialport_baudrate(slave_id)
logger.info(f"Baudrate: {baudrate}")
await client.set_serialport_baudrate(slave_id, new_baudrate)
logger.info(f"write success, wait 3s for device reboot")
await asyncio.sleep(3.0)  # Wait for the device to restart
 

Enable/disable Turbo mode

# Set Turbo mode on/off
# Parameter Description:
# client: client Object,Device Connection handle
# slave_id: Device ID
# enabled: Whether to enable Turbo mode,True means enabled,False means disabled
# Return value: None
# Set Turbo mode asynchronously
await client.set_turbo_mode_enabled(slave_id, True)  # Enable Turbo mode
await client.set_turbo_mode_enabled(slave_id, False) # Turn off Turbo mode
turbo_mode = await client.get_turbo_mode_enabled(slave_id) # Check if Turbo mode is enabled
# Print Turbo mode status
if turbo_mode:
    logger.info("Turbo mode: enabled")
else:
    logger.info("Turbo mode: disabled")
 

Set Turbo mode parameters

# Set Turbo mode parameters
# Parameter Description:
# client: client object,Device connection handle
# slave_id: Device ID
# turbo_interval: Turbo mode interval time, unit in milliseconds
# turbo_duration: Turbo mode duration, in milliseconds
# Return value: None
# Set Turbo mode parameters asynchronously
await client.set_turbo_config(slave_id, libstark.TurboConfig(turbo_interval, turbo_duration))
 

Sequence of actions

"""
Revo1 Dextrous Hand Motion Sequence Control Example

This example demonstrates how to use the motion sequence function of Revo1 Dextrous Hand, including:
- Creation and configuration of custom action sequences
- Upload and storage of action sequences
- Execution of built-in action sequences
- Execution and management of custom action sequences
- Reading and validation of action sequence data
"""

import asyncio
import sys
from utils import setup_shutdown_event
from revo1_utils import *

# A generation of dexterous hand motion sequence configuration
# Note:Only the positions parameter is actually effective in the current version,The speeds and forces parameters are reserved fields
sample_action_sequences = [
    {
        "index": 0,                          # Action Sequence Index
        "duration_ms": 2000,                 # Action Duration (milliseconds)
        "positions": [0, 0, 100, 100, 100, 100],  # Finger position: [thumb, thumb assist, index finger, middle finger, ring finger, pinky finger]
        "speeds": [10, 20, 30, 40, 50, 60],      # Finger speed (reserved parameter)
        "forces": [5, 10, 15, 20, 25, 30],       # Finger Pressure (Reserved Parameter)
    },
    {
        "index": 1,
        "duration_ms": 2000,
        "positions": [100, 100, 0, 0, 0, 0],     # Close the thumb and spread the other fingers
        "speeds": [15, 25, 35, 45, 55, 65],
        "forces": [6, 11, 16, 21, 26, 31],
    },
    {
        "index": 2,
        "duration_ms": 2000,
        "positions": [0, 0, 100, 100, 100, 100], # Spread the thumb and close the other fingers
        "speeds": [10, 20, 30, 40, 50, 60],
        "forces": [5, 10, 15, 20, 25, 30],
    },
    {
        "index": 3,
        "duration_ms": 2000,
        "positions": [100, 100, 0, 0, 0, 0],
        "speeds": [15, 25, 35, 45, 55, 65],
        "forces": [6, 11, 16, 21, 26, 31],
    },
    {
        "index": 4,
        "duration_ms": 2000,
        "positions": [0, 0, 0, 0, 0, 0],         # All fingers fully spread
        "speeds": [15, 25, 35, 45, 55, 65],
        "forces": [6, 11, 16, 21, 26, 31],
    },
    {
        "index": 5,
        "duration_ms": 2000,
        "positions": [0, 0, 0, 0, 0, 0],         # Keep in an open state
        "speeds": [15, 25, 35, 45, 55, 65],
        "forces": [6, 11, 16, 21, 26, 31],
    },
    {
        "index": 6,
        "duration_ms": 2000,
        "positions": [0, 0, 0, 0, 0, 0],
        "speeds": [15, 25, 35, 45, 55, 65],
        "forces": [6, 11, 16, 21, 26, 31],
    },
    {
        "index": 7,
        "duration_ms": 2000,
        "positions": [0, 0, 0, 0, 0, 0],
        "speeds": [15, 25, 35, 45, 55, 65],
        "forces": [6, 11, 16, 21, 26, 31],
    },
    {
        "index": 8,
        "duration_ms": 2000,
        "positions": [0, 0, 0, 0, 0, 0],
        "speeds": [15, 25, 35, 45, 55, 65],
        "forces": [6, 11, 16, 21, 26, 31],
    },
    {
        "index": 9,
        "duration_ms": 2000,
        "positions": [0, 0, 0, 0, 0, 0],
        "speeds": [15, 25, 35, 45, 55, 65],
        "forces": [6, 11, 16, 21, 26, 31],
    },
]


def action_sequence_info_to_list(action):
    """
    Convert the action sequence dictionary to list format

    Convert the configuration information of the action sequence into a list format required by the SDK for uploading to the device.

    Args:
        action (dict): Action Sequence Configuration Dictionary,contains index、duration_ms、positions、speeds、forces

    Returns:
        list: Converted list format [index, duration_ms, positions..., speeds..., forces...]
    """
    return (
        [action["index"]]
        + [action["duration_ms"]]
        + action["positions"]
        + action["speeds"]
        + action["forces"]
    )


async def main():
    """
    Main function: Initialize the dexterous hand and execute the action sequence control
    """
    # Set up a close event listener
    shutdown_event = setup_shutdown_event(logger)

    # Detect the baud rate and device ID of the dexterous hand, and initialize the client
    (client, slave_id) = await open_modbus_revo1(port_name=None)  # Replace with the actual serial port name. Passing None will attempt automatic detection.

    # Convert action sequence format
    # sample_action_sequences = random_action_sequences()  # Optional: Use a random action sequence
    mapped_sequences = map(lambda action: action_sequence_info_to_list(action), sample_action_sequences)
    action_sequences = list(mapped_sequences)

    # Upload custom action sequences to the device
    # The device supports a maximum of 6 custom action sequences(CustomGesture1 ~ CustomGesture6)
    custom_action_id = libstark.ActionSequenceId.CustomGesture1
    await client.transfer_action_sequence(slave_id, custom_action_id, action_sequences)
    logger.info(f"Custom action sequence uploaded to {custom_action_id}")

    # Read and verify the uploaded action sequence
    action_result: libstark.ActionSequenceItem = await client.get_action_sequence(slave_id, custom_action_id)
    logger.info(f"Action sequence: {action_result.description}")

    # Execute the sequence of built-in actions: Fist action
    logger.info("Executing built-in fist gesture...")
    await client.run_action_sequence(slave_id, libstark.ActionSequenceId.DefaultGestureFist)
    await asyncio.sleep(3)  # Wait for the built-in action sequence to complete execution

    # Execute a custom action sequence
    logger.info("Executing custom action sequence...")
    await client.run_action_sequence(slave_id, custom_action_id)

    # Wait for the close event(Ctrl+C or other closing signals)
    await shutdown_event.wait()

    # Close the resource
    libstark.modbus_close(client)
    logger.info("Modbus client closed")
    sys.exit(0)


if __name__ == "__main__":
    asyncio.run(main())
 

Action Sequence - Gesture

Action sequence

# Detect the baud rate and device ID of the dexterous hand,Initialize client
(client, slave_id) = await open_modbus_revo1()

# Upload custom action sequence to device
# The device supports a maximum of 6 custom action sequences(CustomGesture1 ~ CustomGesture6)
custom_action_id = libstark.ActionSequenceId.CustomGesture1
await client.transfer_action_sequence(slave_id, custom_action_id, action_sequences)
logger.info(f"Custom action sequence uploaded to {custom_action_id}")

# Read and verify the uploaded action sequence
action_result = await client.get_action_sequence(slave_id, custom_action_id)
logger.info(f"Action sequence: {action_result.description}")

# Execute the built-in action sequence: Fist action
logger.info("Executing built-in fist gesture...")
await client.run_action_sequence(slave_id, libstark.ActionSequenceId.DefaultGestureFist)
await asyncio.sleep(3)  # Wait for the built-in action sequence to complete

# Execute custom action sequence
logger.info("Executing custom action sequence...")
await client.run_action_sequence(slave_id, custom_action_id)
 

Firmware OTA

# Establish Modbus connection
client = await libstark.modbus_open(port_name, baudrate)
await client.start_dfu(
    slave_id,
    ota_bin_path,
    wait_seconds,
    on_dfu_state,      # State change callback
    on_dfu_progress,   # Progress Update Callback
)

def on_dfu_state(_slave_id, state):
    """
    DFU Status Change Callback Function

    Called during the firmware upgrade process when the status changes。

    Args:
        _slave_id: Device ID (Unused)
        state: DFU Status enumeration value
    """
    logger.info(f"DFU STATE: {libstark.DfuState(state)}")
    dfu_state = libstark.DfuState(state)


def on_dfu_progress(_slave_id, progress):
    """
    DFU Progress Update Callback Function

    Report the upgrade progress regularly during the firmware upgrade process。

    Args:
        _slave_id: Device ID (Unused)
        progress: Upgrade progress,Scope 0.0-1.0
    """
    logger.info(f"progress: {progress * 100.0 :.2f}%")
 

CAN 2.0

Single-handed/Two-handed Control

One-Handed Control - ZLG USBCAN Device - C++ Example

// This example is a simple control example combining Revo1 with ZLG USB-CAN devices.
// USBCANFD-200U
// USBCANFD-100U
// USBCANFD-100U-mini
// Need to download the .so file provided by the vendor
// https://manual.zlg.cn/web/#/146
#include <stdio.h>
#include <cstring>
#include <unistd.h>
#include "stark-sdk.h"
#include "zlgcan/zcan.h"

// Constant Definition
#define ZCAN_TYPE_USBCANFD 33 // Device Type
#define ZCAN_CARD_INDEX 0     // Card index
#define ZCAN_CHANNEL_INDEX 0  // Channel index
#define MAX_CHANNELS 2        // Maximum number of channels
#define RX_WAIT_TIME 100      // Reception waiting time
#define RX_BUFF_SIZE 1000     // Receive buffer size

// Function declaration
bool init_can_device();
bool start_can_channel();
void cleanup_resources();
void setup_can_callbacks();
void get_device_info(DeviceHandler *handle, uint8_t slave_id);

int main(int argc, char const *argv[])
{
  // Initialize CAN device
  if (!init_can_device())
  {
    return -1;
  }

  // Start the CAN channel
  if (!start_can_channel())
  {
    cleanup_resources();
    return -1;
  }

  setup_can_callbacks(); // Set read and write callbacks

  init_cfg(STARK_PROTOCOL_TYPE_CAN, LOG_LEVEL_DEBUG);
  auto handle = create_device_handler();
  uint8_t slave_id = 1; // The default ID for a first-generation device is 1
  get_device_info(handle, slave_id);

  uint16_t positions_fist[] = {50, 50, 100, 100, 100, 100}; // Make a fist
  uint16_t positions_open[] = {0, 0, 0, 0, 0, 0};           // Open
  useconds_t delay = 1000 * 1000;                           // 1000ms
  stark_set_finger_positions(handle, slave_id, positions_fist, 6);
  usleep(delay);
  stark_set_finger_positions(handle, slave_id, positions_open, 6);
  usleep(delay);

  auto finger_status = stark_get_motor_status(handle, slave_id);
  if (finger_status != NULL)
  {
    printf("Positions: %hu, %hu, %hu, %hu, %hu, %hu\n", finger_status->positions[0], finger_status->positions[1], finger_status->positions[2], finger_status->positions[3], finger_status->positions[4], finger_status->positions[5]);
    printf("Speeds: %hd, %hd, %hd, %hd, %hd, %hd\n", finger_status->speeds[0], finger_status->speeds[1], finger_status->speeds[2], finger_status->speeds[3], finger_status->speeds[4], finger_status->speeds[5]);
    printf("Currents: %hd, %hd, %hd, %hd, %hd, %hd\n", finger_status->currents[0], finger_status->currents[1], finger_status->currents[2], finger_status->currents[3], finger_status->currents[4], finger_status->currents[5]);
    printf("States: %hhu, %hhu, %hhu, %hhu, %hhu, %hhu\n", finger_status->states[0], finger_status->states[1], finger_status->states[2], finger_status->states[3], finger_status->states[4], finger_status->states[5]);
    free_motor_status_data(finger_status);
  }

  // Clean up resources
  cleanup_resources();
  return 0;
}

bool init_can_device()
{
  // Turn on the device
  if (!VCI_OpenDevice(ZCAN_TYPE_USBCANFD, ZCAN_CARD_INDEX, ZCAN_CHANNEL_INDEX))
  {
    printf("Failed to open device\n");
    return false;
  }
  return true;
}

bool start_can_channel()
{
  ZCAN_INIT init;      // Baud rate structure, data is derived from the baud rate calculator of zcanpro
  init.mode = 0;       // 0- Normal
  init.clk = 60000000; // clock: 60M(V1.01) 80M(V1.03 That is the above)

  // Arbitration Domain 1Mbps
  init.aset.sjw = 1;
  init.aset.brp = 5;
  init.aset.tseg1 = 6;
  init.aset.tseg2 = 1;
  init.aset.smp = 0;

  // Data domain 5Mbps
  init.dset.sjw = 1;
  init.dset.brp = 0;
  init.dset.tseg1 = 7;
  init.dset.tseg2 = 2;
  init.dset.smp = 0;

  // Initialize CAN channel
  if (!VCI_InitCAN(ZCAN_TYPE_USBCANFD, ZCAN_CARD_INDEX, ZCAN_CHANNEL_INDEX, &init))
  {
    printf("Failed to initialize CAN channel\n");
    return false;
  }

  // Start the CAN channel
  if (!VCI_StartCAN(ZCAN_TYPE_USBCANFD, ZCAN_CARD_INDEX, ZCAN_CHANNEL_INDEX))
  {
    printf("Failed to start CAN channel\n");
    return false;
  }

  return true;
}

void setup_can_callbacks()
{
  // CAN Send callback
  set_can_tx_callback([](uint8_t slave_id,
                         uint32_t can_id,
                         const uint8_t *data,
                         uintptr_t data_len) -> int
                      {
                        printf("CAN Send: Slave ID: %d, CAN ID: 0x%X, Data Length: %zu\n", slave_id, can_id, data_len);
                        printf("Data: ");
                        for (uintptr_t i = 0; i < data_len; ++i)
                        {
                          printf("%02x ", data[i]);
                        }
                        printf("\n");

                        // Construct CAN send data structure
                        ZCAN_20_MSG can_msg;
                        memset(&can_msg, 0, sizeof(ZCAN_20_MSG));

                        can_msg.hdr.inf.txm = 0; // 0- Send normally
                        can_msg.hdr.inf.fmt = 0; // 0-CAN
                        can_msg.hdr.inf.sdf = 0; // 0-Data Frame, 1-Remote Frame
                        can_msg.hdr.inf.sef = 0; // 0 - Standard Frame, 1-Extended Frame
                        // can_msg.hdr.inf.echo = 1;    // Send echo back

                        can_msg.hdr.id = can_id;              // ID
                        can_msg.hdr.chn = ZCAN_CHANNEL_INDEX; // Channel
                        can_msg.hdr.len = data_len;           // Data length

                        // Fill in the data
                        for (uintptr_t i = 0; i < data_len && i < 8; ++i)
                        {
                          can_msg.dat[i] = data[i];
                        }

                        // Send CAN frame
                        printf("Transmitting CAN frame...\n");
                        int result = VCI_Transmit(ZCAN_TYPE_USBCANFD, ZCAN_CARD_INDEX, ZCAN_CHANNEL_INDEX, &can_msg, 1);
                        printf("Transmit result: %d\n", result);
                        return result == 1 ? 0 : -1; // 0 Indicates success
                      });

  // CAN Read callback
  set_can_rx_callback([](uint8_t slave_id,
                         uint32_t *can_id_out,
                         uint8_t *data_out,
                         uintptr_t *data_len_out) -> int
                      {
                        printf("CAN Read: Slave ID: %d\n", slave_id);

                        // Read data
                        ZCAN_20_MSG can_data[RX_BUFF_SIZE];
                        int len = VCI_Receive(ZCAN_TYPE_USBCANFD, ZCAN_CARD_INDEX, ZCAN_CHANNEL_INDEX, can_data, RX_BUFF_SIZE, RX_WAIT_TIME); // CAN
                        printf("ZCAN Receive, len: %d\n", len);
                        if (len < 1)
                        {
                          return -1;
                        }

                        // Concatenate multiple CAN frames, and the CAN IDs of multiple CAN frames should be consistent
                        *can_id_out = can_data[0].hdr.id & 0x1FFFFFFF;

                        int idx = 0;
                        int total_dlc = 0;

                        for (int i = 0; i < len; i++)
                        {
                          ZCAN_20_MSG recv_data = can_data[i];
                          int can_dlc = recv_data.hdr.len;
                          for (int j = 0; j < can_dlc; j++)
                          {
                            data_out[idx++] = recv_data.dat[j];
                          }
                          total_dlc += can_dlc;
                        }

                        *data_len_out = total_dlc;
                        return 0; // Successful return 0
                      });
}

void get_device_info(DeviceHandler *handle, uint8_t slave_id)
{
  DeviceInfo *info = stark_get_device_info(handle, slave_id);
  if (info != NULL)
  {
    printf("Slave[%hhu] Serial Number: %s\n", slave_id, info->serial_number);
    free_device_info(info);
  }
}

void cleanup_resources()
{
  VCI_ResetCAN(ZCAN_TYPE_USBCANFD, ZCAN_CARD_INDEX, ZCAN_CHANNEL_INDEX); // Reset CAN Channel
  VCI_CloseDevice(ZCAN_TYPE_USBCANFD, ZCAN_CARD_INDEX);
  printf("Resources cleaned up.\n");
}
 

One-Handed Control - ZQWL USBCAN Device - C++ Example

// This example is a simple control example combining Revo1 with the ZQWL USB-CAN device.
// USBCANFD-200U
// USBCANFD-100U
// USBCANFD-100U-mini
// Need to download the .dll provided by the vendor
// http://39.108.220.80/download/user/ZQWL/UCANFD/
#include <stdio.h>
#include <windows.h>
#include "stark-sdk.h"
#include "zlgcan/zlgcan.h"

// Global variable declaration
static long device_handle_ = INVALID_DEVICE_HANDLE;
static Handle_chl channel_handle_ = {INVALID_CHANNEL_HANDLE};

// Function declaration
bool init_can_device(int device_type, int channel_index);
bool setup_can_baudrate(int channel_index, int baudrate);
bool start_can_channel(int channel_index);
void setup_can_callbacks();
void get_device_info(DeviceHandler *handle, uint8_t slave_id);
void cleanup_resources();

int main(int argc, char const *argv[])
{
  const int device_type = ZCAN_USBCANFD_100U;
  const int channel_index = 0;
  const int baudrate = 1000000; // 1Mbps

  // Initialize CAN device
  if (!init_can_device(device_type, channel_index))
  {
    return -1;
  }

  // Start the CAN channel
  if (!start_can_channel(channel_index))
  {
    cleanup_resources();
    return -1;
  }

  // Set baud rate and timing parameters
  if (!setup_can_baudrate(channel_index, baudrate))
  {
    cleanup_resources();
    return -1;
  }

  setup_can_callbacks(); // Set read and write callbacks

  init_cfg(STARK_PROTOCOL_TYPE_CAN, LOG_LEVEL_DEBUG);
  auto handle = create_device_handler();
  get_device_info(handle, slave_id);

  uint16_t positions_fist[] = {50, 50, 100, 100, 100, 100}; // Make a fist
  uint16_t positions_open[] = {0, 0, 0, 0, 0, 0};           // Open
  int delay = 1000;                                         // 1000ms
  // stark_set_finger_position(handle, slave_id, STARK_FINGER_ID_PINKY, 100);
  // Sleep(delay);
  stark_set_finger_positions(handle, slave_id, positions_fist, 6);
  Sleep(delay);
  stark_set_finger_positions(handle, slave_id, positions_open, 6);
  Sleep(delay);

  // Clean up resources
  cleanup_resources();
  return 0;
}

bool init_can_device(int device_type, int channel_index)
{
  device_handle_ = ZCAN_OpenDevice(device_type, channel_index, 0);
  if (device_handle_ == INVALID_DEVICE_HANDLE)
  {
    printf("Failed to open device\n");
    return false;
  }
  return true;
}

bool setup_can_baudrate(int channel_index, int baudrate)
{
  // Set baud rate
  char path[50] = {0};
  char value[10] = {0};
  sprintf_s(path, "%d/baud_rate", channel_index);
  sprintf_s(value, "%d", baudrate);
  ZCAN_SetValue(device_handle_, path, value);

  // Set CAN timing parameters(timing0, timing1)
  // 1Mbps Recommendation timing0=0x00, timing1=0x14
  char timing_path0[50] = {0};
  char timing_value0[10] = {0};
  char timing_path1[50] = {0};
  char timing_value1[10] = {0};
  sprintf_s(timing_path0, "%d/abit_timing0", channel_index);
  sprintf_s(timing_value0, "0x00");
  sprintf_s(timing_path1, "%d/abit_timing1", channel_index);
  sprintf_s(timing_value1, "0x14");
  ZCAN_SetValue(device_handle_, timing_path0, timing_value0);
  ZCAN_SetValue(device_handle_, timing_path1, timing_value1);

  return true;
}

bool start_can_channel(int channel_index)
{
  ZCAN_CHANNEL_INIT_CONFIG config;
  memset(&config, 0, sizeof(config));
  config.can_type = TYPE_CAN;
  config.can.mode = 0; // 0: Normal mode, 1: Listen-only mode

  channel_handle_ = ZCAN_InitCAN(device_handle_, channel_index, config)[0];
  if (INVALID_CHANNEL_HANDLE == channel_handle_.handle)
  {
    printf("Failed to initialize CAN channel\n");
    return false;
  }

  if (ZCAN_StartCAN(channel_handle_) != STATUS_OK)
  {
    printf("Failed to start CAN channel\n");
    return false;
  }

  return true;
}

void setup_can_callbacks()
{
  // CAN Send callback
  set_can_tx_callback([](uint8_t slave_id,
                         uint32_t can_id,
                         const uint8_t *data,
                         uintptr_t data_len) -> int
                      {
                        printf("CAN Send: Slave ID: %d, CAN ID: 0x%X, Data Length: %zu\n", slave_id, can_id, data_len);
                        printf("Data: ");
                        for (uintptr_t i = 0; i < data_len; ++i)
                        {
                          printf("%02x ", data[i]);
                        }
                        printf("\n");

                        // Construct CAN send data structure
                        ZCAN_Transmit_Data can_data;
                        memset(&can_data, 0, sizeof(can_data));

                        // Settings CAN ID,Standard frame eff=0,Extended frame eff=1
                        // rtr=0: Data frame,rtr=1: Remote Frame;err=0: Normal frame,err=1: Error frame
                        can_data.frame.can_id = MAKE_CAN_ID(can_id, 0, 0, 0); // Standard frame
                        can_data.frame.can_dlc = data_len;
                        can_data.transmit_type = 0; // Send normally

                        // Fill in the data
                        for (uintptr_t i = 0; i < data_len && i < 8; ++i)
                        {
                          can_data.frame.data[i] = data[i];
                        }

                        // Send CAN frame
                        int result = ZCAN_Transmit(channel_handle_, &can_data, 1);
                        return result == 1 ? 0 : -1; // 0 Indicates success
                      });

  // CAN Read callback
  set_can_rx_callback([](uint8_t slave_id,
                         uint32_t *can_id_out,
                         uint8_t *data_out,
                         uintptr_t *data_len_out) -> int
                      {
                        printf("CAN Read: Slave ID: %d\n", slave_id);

                        // Read data
                        ZCAN_Receive_Data can_data[1000];
                        int len = ZCAN_GetReceiveNum(channel_handle_, TYPE_CAN);
                        len = ZCAN_Receive(channel_handle_, can_data, 1000, 50);
                        if (len < 1)
                        {
                          return -1;
                        }

                        // Concatenate multiple CAN frames,The CAN IDs of multiple CAN frames should be consistent.                        *can_id_out = can_data[0].frame.can_id;

                        int idx = 0;
                        int total_dlc = 0;

                        for (int i = 0; i < len; i++)
                        {
                          ZCAN_Receive_Data recv_data = can_data[i];
                          int can_dlc = recv_data.frame.can_dlc;
                          for (int j = 0; j < can_dlc; j++)
                          {
                            data_out[idx++] = recv_data.frame.data[j];
                          }
                          total_dlc += can_dlc;
                        }

                        *data_len_out = total_dlc;
                        return 0; // Successful return 0
                      });
}

void get_device_info(DeviceHandler *handle, uint8_t slave_id)
{
  DeviceInfo *info = stark_get_device_info(handle, slave_id);
  if (info != NULL)
  {
    printf("Slave[%hhu] Serial Number: %s\n", slave_id, info->serial_number);
    free_device_info(info);
  }
}

void cleanup_resources()
{
  if (channel_handle_.handle != INVALID_CHANNEL_HANDLE)
  {
    ZCAN_ResetCAN(channel_handle_);
  }
  if (device_handle_ != INVALID_DEVICE_HANDLE)
  {
    ZCAN_CloseDevice(device_handle_);
  }
}
 

One-Handed Control - ZQWL USBCAN Device - Python Example

import asyncio
import sys

from zqwl_win import zcan_open, zcan_close, zcan_send_message, zqwl_can_receive_message
from can_utils import libstark, logger, setup_shutdown_event


class Revo1CanController:
    """Revo1 CAN Communication Controller"""

    def __init__(self, master_id: int = 1, slave_id: int = 1):
        self.master_id = master_id
        self.slave_id = slave_id
        self.client = libstark.PyDeviceContext()

    async def initialize(
        self, device_type: int = 42, channel: int = 0, baudrate: int = 1000000
    ):
        """ Initialize CAN connection """
        try:
            # Initialize ZCAN device
            zcan_open(device_type=device_type, channel=channel, baudrate=baudrate)

            # Set up a callback function
            libstark.set_can_tx_callback(self._can_send)
            libstark.set_can_rx_callback(self._can_read)

            logger.info(
                f"CAN Connection initialization succeeded - Master ID: {self.master_id}, Slave ID: {self.slave_id}"
            )
            return True

        except Exception as e:
            logger.error(f"CAN Connection initialization failed: {e}")
            return False

    def _can_send(self, slave_id: int, can_id: int, data: list) -> bool:
        """CAN Message sending"""
        try:
            if not zcan_send_message(slave_id, can_id, bytes(data)):
                logger.error("Failed to send CAN message ")
                return False
            return True
        except Exception as e:
            logger.error(f"CAN Sending exception: {e}")
            return False

    def _can_read(self, slave_id: int) -> tuple:
        """CAN Message reception"""
        try:
            recv_msg = zqwl_can_receive_message()
            if recv_msg is None:
                return 0, bytes([])

            can_id, data = recv_msg
            # Optional: Enable detailed debug logs
            # logger.debug(f" ReceiveCAN - ID: {can_id:029b}, Data: {bytes(data).hex()}")
            return can_id, data

        except Exception as e:
            logger.error(f"CAN Receiving an exception: {e}")
            return 0, bytes([])

    async def get_device_info(self):
        """ Get device information """
        try:
            device_info = await self.client.get_device_info(self.slave_id)
            logger.info(f" Device Information: {device_info.description}")
            return device_info
        except Exception as e:
            logger.error(f" Failed to get device information: {e}")
            return None

    async def change_slave_id(self, new_slave_id: int):
        """Modify device slave ID(Use with caution, the device will restart)"""
        try:
            await self.client.set_slave_id(self.slave_id, new_slave_id)
            logger.info(f" The slave station ID has been modified to {new_slave_id}, and the device will restart...")
            return True
        except Exception as e:
            logger.error(f" Failed to modify the substation ID: {e}")
            return False

    async def configure_device(self):
        """ Configure device parameters """
        try:
            # Options: Modify the slave station ID (if needed, uncomment and set a new ID)
            # WARNING: The device will restart, and the program will exit.
            # if await self.change_slave_id(new_slave_id=2):
            #     sys.exit(0)

            # Disable auto-calibration and perform manual calibration
            # await self.client.set_auto_calibration(self.slave_id, False) # Disable automatic calibration after startup            # await self.client.calibrate_position(self.slave_id)
            # await self.client.set_auto_calibration(self.slave_id, True)  # Enable automatic calibration after startup
            # auto_calibration_enabled = await self.client.get_auto_calibration_enabled(self.slave_id)
            # logger.info(f" Automatic calibration on startup: {auto_calibration_enabled}")

            # Configuration: Turbo mode (optional, uncomment to enable)
            # await self.configure_turbo_mode()

            return True
        except Exception as e:
            logger.error(f" Failed to configure the device: {e}")
            return False

    async def configure_turbo_mode(self):
        """ Configure Turbo mode """
        try:
            # Enable Turbo mode
            await self.client.set_turbo_mode_enabled(self.slave_id, True)

            # Set the Turbo parameter
            turbo_interval = 200  # Grip interval time (milliseconds)
            turbo_duration = 300  # Grip duration (milliseconds)
            turbo_conf = libstark.TurboConfig(turbo_interval, turbo_duration)
            await self.client.set_turbo_config(self.slave_id, turbo_conf)

            # Verify configuration
            turbo_mode_enabled = await self.client.get_turbo_mode_enabled(self.slave_id)
            turbo_config = await self.client.get_turbo_config(self.slave_id)

            logger.info(f" Turbo Mode: {turbo_mode_enabled}")
            logger.info(
                f"Turbo Configuration - Interval: {turbo_config.interval}ms, Continuous: {turbo_config.duration}ms"
            )

        except Exception as e:
            logger.error(f" Failed to configure Turbo mode: {e}")

    async def finger_position_examples(self):
        """ Finger Position Control Example """
        logger.info("===Finger Position Control Example ===")

        # Example 1: Make a fist action one by one
        logger.info("Make a fist with each finger one by one ")
        positions = [
            [20, 0, 0, 0, 0, 0],  # Thumb
            [20, 30, 0, 0, 0, 0],  # Thumb + Index finger
            [20, 30, 50, 0, 0, 0],  # + Middle finger
            [20, 30, 50, 70, 0, 0],  # + Ring finger
            [20, 30, 50, 70, 80, 0],  # + Pinky
            [20, 30, 50, 70, 80, 90],  # + wrist
        ]

        for i, pos in enumerate(positions):
            logger.info(f" Steps {i+1}: {pos}")
            await self.client.set_finger_positions(self.slave_id, pos)
            await asyncio.sleep(0.8)

        await asyncio.sleep(1.0)

        # Example 2: Preset Gestures
        logger.info("Example 2: Preset Gestures ")
        gestures = {
            " Open your hands ": [0, 0, 0, 0, 0, 0],
            " Point to ": [0, 30, 0, 0, 0, 0],
            " Victory gesture ": [0, 30, 80, 0, 0, 0],
            " OK gesture ": [80, 30, 80, 0, 0, 0],
            " Make a fist ": [80, 30, 100, 100, 100, 100],
        }

        for gesture_name, positions in gestures.items():
            logger.info(f" Perform the gesture: {gesture_name} - {positions}")
            await self.client.set_finger_positions(self.slave_id, positions)
            await asyncio.sleep(1.5)

        # Example 3: Simulating the Grabbing Action
        logger.info("Example 3: Simulating the Grabbing Action ")
        grab_sequence = [
            ([0, 0, 0, 0, 0, 0], " Initial position "),
            ([30, 40, 60, 80, 80, 0], " Crawling Preparation "),
            ([60, 70, 90, 100, 100, 0], " Capture completed "),
        ]

        for positions, description in grab_sequence:
            logger.info(f"{description}: {positions}")
            await self.client.set_finger_positions(self.slave_id, positions)
            await asyncio.sleep(1.0)

    async def finger_speed_examples(self):
        """ Finger Speed Control Example """
        logger.info("===Finger Speed Control Example ===")

        # Stop all movements
        await self.client.set_finger_speeds(self.slave_id, [0] * 6)
        logger.info("All fingers stop moving ")

    async def single_finger_control_example(self, finger_id: libstark.FingerId):
        """ Single Finger Control Example """
        logger.info(f"=== Single Finger Control Example - {finger_id} ===")

        # Position Control
        logger.info("Position Control Test ")
        await self.client.set_finger_position(self.slave_id, finger_id, 100)  # Maximum position
        await asyncio.sleep(1.0)
        await self.client.set_finger_position(self.slave_id, finger_id, 0)  # Initial position
        await asyncio.sleep(1.0)

        # Speed control
        logger.info("Speed Control Test ")
        await self.client.set_finger_speed(
            self.slave_id, finger_id, 100
        )  # Forward maximum speed
        await asyncio.sleep(1.0)
        await self.client.set_finger_speed(
            self.slave_id, finger_id, -100
        )  # Reverse maximum speed
        await asyncio.sleep(1.0)
        await self.client.set_finger_speed(self.slave_id, finger_id, 0)  # Stop

    async def get_motor_status(self) -> libstark.MotorStatusData:
        """ Get motor status """
        try:
            status = await self.client.get_motor_status(self.slave_id)
            # Optional: Enable detailed status logging
            # logger.info(f" Motor status: {status.description}")
            return status
        except Exception as e:
            logger.error(f" Failed to get motor status: {e}")
            return None  # type: ignore

    async def monitor_motor_status(self, interval: float = 0.001):
        """ Continuously monitor the motor status """
        logger.info(f" Start monitoring the device {self.slave_id:02x} the motor status ")

        while True:
            try:
                await self.get_motor_status()
                await asyncio.sleep(interval)
            except asyncio.CancelledError:
                logger.info("Motor status monitoring has been canceled ")
                break
            except Exception as e:
                logger.error(f" Abnormal motor status monitoring: {e}")
                await asyncio.sleep(1)

    async def demo_task(self):
        """ Demonstration task """
        # Get device information
        await self.get_device_info()

        # Configure device parameters and enable as needed
        # await self.configure_device()

        # Enable the following demo as needed
        await self.finger_position_examples()
        await asyncio.sleep(1.0)

        await self.finger_speed_examples()
        await asyncio.sleep(1.0)

        await self.single_finger_control_example(libstark.FingerId.Pinky)

        # Set the final gesture (boxing gesture)
        # await self.client.set_finger_positions(
        #     self.slave_id, [80, 30, 100, 100, 100, 100]
        # )

    def cleanup(self):
        """ Clean up resources """
        try:
            zcan_close()
            logger.info("CAN connection has been closed ")
        except Exception as e:
            logger.error(f" Error occurred while cleaning up resources: {e}")


async def main():
    """ Main function """
    controller = Revo1CanController(master_id=1, slave_id=1)

    try:
        # Initialize connection
        if not await controller.initialize():
            logger.error("Initialization failed, program exited ")
            return

        # Set up a close event listener
        shutdown_event = setup_shutdown_event(logger)

        # Create a task
        tasks = []

        # Start the demo task
        demo_task = asyncio.create_task(controller.demo_task())
        tasks.append(demo_task)

        # Optional: Start motor status monitoring
        monitor_task = asyncio.create_task(controller.monitor_motor_status())
        tasks.append(monitor_task)

        # Wait for the close signal
        await shutdown_event.wait()
        logger.info("Received shutdown signal, stop all tasks...")

        # Cancel all tasks
        for task in tasks:
            task.cancel()
            try:
                await task
            except asyncio.CancelledError:
                pass

    except Exception as e:
        logger.error(f" Program execution exception: {e}")

    finally:
        controller.cleanup()
        sys.exit(0)


if __name__ == "__main__":
    asyncio.run(main())
 

Python SDK API Details

Modbus-RTU Connection

import libstark
from libstark import Baudrate, PyDeviceContext

# ================================
# Modbus Serial Port Connection (for Revo1/Revo2 Basic Edition)
# ================================

async def connect_modbus(port_name: str = "/dev/ttyUSB0", baudrate: Baudrate = Baudrate.Baud115200) -> PyDeviceContext:
    """
    Connect the device via Modbus protocol

    Args:
        port_name: Serial port name
            - Linux: "/dev/ttyUSB0", "/dev/ttyACM0"
            - Windows: "COM3", "COM4"
            - macOS: "/dev/tty.usbserial-xxx"
        baudrate: Serial Port Baud Rate
            - It is recommended to use Baudrate.Baud115200 (default)
            - Other optional options: Baud57600, Baud460800, Baud1Mbps, etc.

    Returns:
        PyDeviceContext: Device Connection Object

    Example:
        device = await connect_modbus("/dev/ttyUSB0", Baudrate.Baud115200)
        device_info = await device.get_device_info(slave_id=1)
        print(f" Connected devices: {device_info.sku_type}")
    """
    try:
        libstark.init_config(libstark.StarkProtocolType.Modbus)
        device = await libstark.modbus_open(port_name, baudrate)
        print(f"Modbus Connection successful: {port_name} @ {baudrate.name}")
        return device
    except Exception as e:
        print(f"Modbus Connection failed: {e}")
        raise

# ================================
# Modbus Device Connection Utility Function
# ================================

def list_available_ports():
    """
    List the available serial ports in the system

    Returns:
        list: List of available serial ports

    Using example:
        ports = list_available_ports()
        print("Can be used with serial port:")
        for port in ports:
            print(f"  {port}")
    """
    try:
        ports = libstark.list_available_ports()
        print(f" Found {len(ports)} available serial ports ")
        return ports
    except Exception as e:
        print(f" Failed to get serial port list: {e}")
        return []

async def auto_detect_device(port_name: str = None, quick: bool = True):
    """
    Automatically detect device protocols and connection parameters

    Args:
        port_name: Specify port name,None For automatic search
        quick: Is quick detection (skip some optional detection steps)

    Returns:
        PyDeviceContext: Device connection object; returns None if the device is not found

    Note:
        - It will sequentially attempt different ports and baud rates
        - Fast detection mode skips some time-consuming detection steps
        - Suitable for rapid prototyping and testing
    """
    try:
        (protocol, port_name, baudrate, slave_id)  = await libstark.auto_detect_device(port_name, quick)
        logger.info(f"Detected protocol: {protocol}, port: {port_name}, baudrate: {baudrate}, slave_id: {slave_id}")
    except Exception as e:
        print(f" Device automatic detection failed: {e}")
        return None

async def auto_detect_modbus_revo1(port_name: str = None, quick: bool = True):
    """
      Automatically detect Modbus connection parameters for Revo1 device

    Returns:
        tuple: (protocol, port_name, baudrate, slave_id) or None
    """
    try:
        (protocol, port_name, baudrate, slave_id) = await libstark.auto_detect_modbus_revo1(port_name, quick)
        logger.info(f"Detected Revo1 Modbus parameters: {protocol}, {port_name}, {baudrate}, {slave_id}")
        return (protocol, port_name, baudrate, slave_id)
    except Exception as e:
        print(f"Revo1 Device automatic detection failed: {e}")
        return None

async def auto_detect_modbus_revo2(port_name: str = None, quick: bool = True):
    """
      Automatically detect Modbus connection parameters for Revo2 device

    Returns:
        tuple: (protocol, port_name, baudrate, slave_id) or None
    """
    try:
        (protocol, port_name, baudrate, slave_id) = await libstark.auto_detect_modbus_revo2(port_name, quick)
        logger.info(f"Detected Revo2 Modbus parameters: {protocol}, {port_name}, {baudrate}, {slave_id}")
        return (protocol, port_name, baudrate, slave_id)
    except Exception as e:
        print(f"Revo2 Device automatic detection failed: {e}")
        return None
 

CAN 2.0

import libstark
from libstark import PyDeviceContext

# ================================
# CAN Bus Connection (for Revo1)
# ================================

def connect_can():
    """
    Connect devices via CAN bus

    Returns:
        PyDeviceContext: Device Connection Object

    Note:
        - Only applicable to Revo1 devices
        - CAN 2.0 adapter hardware support is required

    Preparation before use:
        1. Connect the CAN 2.0 adapter to the computer
        2. Implement CAN device connection
        3. Implement the _can_send and _can_read callback functions
    """
    try:
        libstark.init_config(libstark.StarkProtocolType.Can)
        client = libstark.PyDeviceContext()

        # CAN Device Connection (needs adjustment according to actual hardware)
        libuser.open_can_device()

        # Set CAN communication callback function
        # Note: The user needs to implement these two callback functions
        libstark.set_can_tx_callback(_can_send)  # Set up send callback
        libstark.set_can_rx_callback(_can_read)  # Set up the receive callback

        return client
    except Exception as e:
        print(f"CAN Connection failed: {e}")
        raise

# ================================
# CAN Communication Callback Function Example
# ================================

def _can_send(slave_id: int, can_id: int, data: list):
    """
    CAN Example of data sending callback function

    Args:
        slave_id: Substation ID
        can_id: CAN ID
        data: CAN data to be sent

    Note:
        - The user needs to implement this function based on the actual CAN adapter.
        - The function is responsible for sending data through the CAN adapter to the bus.
        - When implementing, hardware-related sending logic needs to be handled.
    """
    # Example implementation (needs to be adjusted according to actual hardware)
    # can_interface.send(data)
    print(f" Send CAN data: {data}")

def _can_read(slave_id: int):
    """
    CAN Example of Data Reception Callback Function

    Args:
        slave_id: Substation ID

    Note:
        - This function is called when data is received from the CAN bus
        - The SDK will automatically parse the received data
        - Users usually do not need to perform additional processing in this function
    """
    # Example implementation (needs to be adjusted according to actual hardware)
    # can_id = data = can_interface.recv()
    print(f" Received CAN ID, CAN data,{can_id}, {data}")
    return can_id, data
 

Device Information

from enum import Enum

# Device SKU Type
class SkuType(Enum):
    MediumRight = ...
    MediumLeft = ...

    int_value: int

# Hardware type
class StarkHardwareType(Enum):
    Revo1Basic = ... # First Generation Basic Edition
    Revo1Touch = ... # First-Generation Tactile Version
    Revo2Basic = ... # Second-Generation Basic Version
    Revo2Touch = ... # Second-Generation Tactile Edition

    int_value: int

# Basic Device Information
class DeviceInfo:
    sku_type: SkuType # Device SKU Type
    r"""
    Hand SKU
    """
    serial_number: str # Serial number
    r"""
    Serial number
    """
    firmware_version: str # Firmware version
    r"""
    Firmware version
    """
    description: str
    def is_revo1(self) -> bool: ...
    def is_revo1_touch(self) -> bool: ...
    def is_revo2(self) -> bool: ...
    def is_revo2_touch(self) -> bool: ...
    def is_touch(self) -> bool: ...
    def get_hardware_type(self) -> StarkHardwareType: ...
    def desc(self) -> str: ...

# RS-485 Baud rate
class Baudrate(Enum):
    Baud115200 = ...
    Baud57600 = ...
    Baud19200 = ...
    Baud460800 = ...
    Baud1Mbps = ...
    Baud2Mbps = ...
    Baud5Mbps = ...

    int_value: int

# RS-485 Serial Port Configuration
class SerialPortCfg:
    slave_id: int # From Machine ID
    r"""
    Slave ID for the protocol
    - For Modbus protocol: range 0~254, default is 1, 0 is the broadcast address (broadcast is only for control commands)
    """
    baudrate: Baudrate # Baud rate
    r"""
    Baud rate for the serial communication
    """
    description: str
    def desc(self) -> str: ...

# Get device information
def get_device_info(self, slave_id: int) -> DeviceInfo:
    """
    Get device basic information

    Args:
        slave_id: Device Slave ID

    Returns:
        DeviceInfo: Device information object, which includes the following properties:
            - sku_type: SkuType - Device SKU Type (Left/Right Hand Differentiation)
            - serial_number: str - Device Serial Number
            - firmware_version: str - Firmware version number
            - description: str - Device description information

    DeviceInfo The provided method:
        - is_revo1() -> bool: Determine if it is a Revo1 device
        - is_revo1_touch() -> bool: Determine if it is the Revo1 Tactile version
        - is_revo2() -> bool: Determine if it is a Revo2 device
        - is_revo2_touch() -> bool: Determine if it is the Revo2 Tactile version
        - is_touch() -> bool: Determine if tactile function is supported
        - get_hardware_type() -> StarkHardwareType: Get hardware type
        - desc() -> str: Get description information

    Example:
        device_info = device.get_device_info(slave_id=1)
        print(f" Device Type: {device_info.sku_type}")
        print(f" Serial number: {device_info.serial_number}")
        print(f" Firmware version: {device_info.firmware_version}")

        if device_info.is_revo2_touch():
            print("This is a Revo2 device with tactile support ")
    """
    pass

# Get device serial number
def get_device_sn(self, slave_id: int) -> str:
    """
    Get device firmware serial number

    Args:
        slave_id: Device Slave ID

    Returns:
        str: Device serial number string

    Example:
        serial_number = device.get_device_sn(slave_id=1)
        print(f" Device Serial Number: {serial_number}")
    """
    pass

# Get the device firmware version number
def get_device_fw_version(self, slave_id: int) -> str:
    """
    Get the device firmware version number

    Args:
        slave_id: Device Slave ID

    Returns:
        str: Firmware version number string

    Example:
        fw_version = device.get_device_fw_version(slave_id=1)
        print(f" Firmware version: {fw_version}")
    """
    pass

# Get device SKU type
def get_sku_type(self, slave_id: int) -> SkuType:
    """
    Get the device SKU type to distinguish between left-handed and right-handed

    Args:
        slave_id: Device Slave ID

    Returns:
        SkuType: Device Type Enumeration
            - MediumRight: Medium right hand
            - MediumLeft: Medium left hand
            - SmallRight: Small horn right hand
            - SmallLeft: Small horn left hand

    Example:
        sku_type = device.get_sku_type(slave_id=1)
        if sku_type == SkuType.MediumRight:
            print("This is a medium-sized right-handed device ")
    """
    pass

# Get RS-485 device configuration
def get_serialport_cfg(self, slave_id: int) -> SerialPortCfg:
    """
    Get RS-485 device configuration

    Args:
        slave_id: Device Slave ID

    Returns:
        SerialPortCfg: Serial port configuration object, which includes the following properties:
            - slave_id: int - Protocol Slave ID
              * Protocol Buffers protocol: Range 10~254, default is 10, 254 is the broadcast address
              * Modbus Protocol: Range 0~254, default is 1, 0 is the broadcast address (only used for control commands)
            - baudrate: Baudrate - Serial Communication Baud Rate
            - description: str - Configuration description information

        SerialPortCfg The provided method:
            - desc() -> str: Get configuration description

    Example:
        config = device.get_serialport_cfg(slave_id=1)
        print(f" Substation ID: {config.slave_id}")
        print(f" Baud rate: {config.baudrate}")
    """
    pass

# Get RS-485 baud rate
def get_serialport_baudrate(self, slave_id: int) -> int:
    """
    Get RS-485 Serial Port Baud Rate

    Args:
        slave_id: Device Slave ID

    Returns:
        int: Current configured baud rate value

    Example:
        baudrate = device.get_serialport_baudrate(slave_id=1)
        print(f" Current baud rate: {baudrate}")
    """
    pass

# Set RS-485 Baud Rate
def set_serialport_baudrate(self, slave_id: int, baudrate: "Baudrate"):
    """
    Set serial port baud rate

    Args:
        slave_id: Device Slave ID
        baudrate: Baud rate enumeration values
            - Baud115200: 115200 bps
            - Baud57600: 57600 bps
            - Baud19200: 19200 bps
            - Baud460800: 460800 bps
            - Baud1Mbps: 1 Mbps
            - Baud2Mbps: 2 Mbps
            - Baud5Mbps: 5 Mbps

    Example:
        device.set_serialport_baudrate(slave_id=1, baudrate=Baudrate.Baud460800)
    """
    pass

# Set RS-485 Slave ID
def set_slave_id(self, slave_id: int, new_id: int):
    """
    Set device slave ID

    Args:
        slave_id: Current Device Slave ID
        new_id: New slave station ID

    Example:
        device.set_slave_id(slave_id=1, new_id=2)
    """
    pass
 

Joint Control - Position/Velocity/Current

from typing import Sequence
from enum import Enum

# Joint ID
class FingerId(Enum):
    Thumb = ...
    ThumbAux = ...
    Index = ...
    Middle = ...
    Ring = ...
    Pinky = ...

# ================================
# Joint Control Interface
# ================================

def set_finger_position(self, slave_id: int, finger_id: FingerId, position: int):
    """
    Set a single joint position

    Args:
        slave_id: Device Slave ID
        finger_id: FingerId enumeration value
            - Thumb: Thumb
            - ThumbAux: Thumb assist
            - Index: Index finger
            - Middle: Middle finger
            - Ring: Ring finger
            - Pinky: Little finger
        position: Target position value

    Example:
        # Set thumb position
        device.set_finger_position(slave_id=1, finger_id=FingerId.Thumb, position=500)

        # Set the position of the index finger
        device.set_finger_position(slave_id=1, finger_id=FingerId.Index, position=1000)
    """
    pass

def set_finger_position_with_millis(self, slave_id: int, finger_id: FingerId,
                                  position: int, milliseconds: int):
    """
    Set single joint position (specify run time)

    Args:
        slave_id: Device Slave ID
        finger_id: FingerId enumeration value
        position: Target position value
        milliseconds: Running time (milliseconds)

    Example:
        # Thumb moves to position 500 within 2 seconds
        device.set_finger_position_with_millis(
            slave_id=1,
            finger_id=FingerId.Thumb,
            position=500,
            milliseconds=2000
        )
    """
    pass

def set_finger_position_with_speed(self, slave_id: int, finger_id: FingerId,
                                 position: int, speed: int):
    """
    Set single joint position (specify running speed)

    Args:
        slave_id: Device Slave ID
        finger_id: FingerId enumeration value
        position: Target position value
        speed: Running speed value

    Example:
        # Thumb moves to position 500 at a speed of 300
        device.set_finger_position_with_speed(
            slave_id=1,
            finger_id=FingerId.Thumb,
            position=500,
            speed=300
        )
    """
    pass

def set_finger_positions(self, slave_id: int, positions: Sequence[int]):
    """
    Set all joint positions

    Args:
        slave_id: Device Slave ID
        positions: List of position values, in the order [thumb, thumb auxiliary, index finger, middle finger, ring finger, little finger]

    Example:
        # Set all joints to the specified position
        positions = [500, 500, 1000, 1000, 1000, 1000]  # The thumb is half-open, and the other joints are closed
        device.set_finger_positions(slave_id=1, positions=positions)
    """
    pass

def set_finger_positions_and_durations(self, slave_id: int, positions: Sequence[int],
                                     durations: Sequence[int]):
    """
    Set all joint positions and run time

    Args:
        slave_id: Device Slave ID
        positions: List of position values
        durations: List of running times (milliseconds)

    Example:
        positions = [1000, 1000, 1000, 1000, 1000, 1000]
        durations = [2000, 2000, 1500, 1500, 1500, 1500]  # Different joints have different operating times
        device.set_finger_positions_and_durations(slave_id=1, positions=positions, durations=durations)
    """
    pass

def set_finger_positions_and_speeds(self, slave_id: int, positions: Sequence[int],
                                   speeds: Sequence[int]):
    """
    Set all joint positions and running speed

    Args:
        slave_id: Device Slave ID
        positions: List of position values
        speeds: List of operating speeds

    Example:
        positions = [1000, 1000, 1000, 1000, 1000, 1000]
        speeds = [300, 300, 400, 400, 400, 400]  # Different joints at different speeds
        device.set_finger_positions_and_speeds(slave_id=1, positions=positions, speeds=speeds)
    """
    pass

def set_finger_speed(self, slave_id: int, finger_id: FingerId, speed: int):
    """
    Set the operating speed of a single joint

    Args:
        slave_id: Device Slave ID
        finger_id: FingerId enumeration value
        speed: Target speed value

    Example:
        device.set_finger_speed(slave_id=1, finger_id=FingerId.Thumb, speed=400)
    """
    pass

def set_finger_speeds(self, slave_id: int, speeds: Sequence[int]):
    """
    Set the operating speed of all joints

    Args:
        slave_id: Device Slave ID
        speeds: List of speed values

    Example:
        speeds = [300, 300, 400, 400, 400, 400]
        device.set_finger_speeds(slave_id=1, speeds=speeds)
    """
    pass

def set_finger_current(self, slave_id: int, finger_id: FingerId, current: int):
    """
    Set single joint drive current

    Args:
        slave_id: Device Slave ID
        finger_id: FingerId enumeration value
        current: Target current value

    Example:
        device.set_finger_current(slave_id=1, finger_id=FingerId.Thumb, current=200)
    """
    pass

def set_finger_currents(self, slave_id: int, currents: Sequence[int]):
    """
    Set all joint drive currents

    Args:
        slave_id: Device Slave ID
        currents: List of current values

    Example:
        currents = [200, 200, 250, 250, 250, 250]
        device.set_finger_currents(slave_id=1, currents=currents)
    """
    pass

def set_finger_pwm(self, slave_id: int, finger_id: FingerId, pwm: int):
    """
    Set the PWM control value of a single joint

    Args:
        slave_id: Device Slave ID
        finger_id: FingerId Enumeration value
        pwm: PWM Control value

    Example:
        device.set_finger_pwm(slave_id=1, finger_id=FingerId.Thumb, pwm=50)
    """
    pass

def set_finger_pwms(self, slave_id: int, pwms: Sequence[int]):
    """
    Set all joint PWM control values

    Args:
        slave_id: Device Slave ID
        pwms: PWM Control value list

    Example:
        pwms = [50, 50, 60, 60, 60, 60]
        device.set_finger_pwms(slave_id=1, pwms=pwms)
    """
    pass
 

Joint information

from typing import List
from enum import Enum

# Joint ID
class FingerId(Enum):
    Thumb = ... # Thumb
    ThumbAux = ... # Thumb assist
    Index = ... # Index finger
    Middle = ... # Middle finger
    Ring = ... # Ring finger
    Pinky = ... # Little finger

# Joint condition
class MotorState(Enum):
    Unknown = ...
    Idle = ... # Idle
    Running = ... # In operation
    Stall = ... # Locked rotor
    Turbo = ... # Turbo Pattern

# All joint information
class MotorStatusData:
    positions: list[int] # Joint position
    speeds: list[int] # Speed of movement
    currents: list[int] # Drive current
    states: list[MotorState] # Status
    description: str

def get_motor_status(self, slave_id: int) -> MotorStatusData:
    """
    Get comprehensive joint status data

    Args:
        slave_id: Device Slave ID

    Returns:
        MotorStatusData: Joint status data object, which includes the following properties:
            - positions: List[int] - Current position values of all joints
            - speeds: List[int] - Current movement speed of all joints
            - currents: List[int] - Current movement speed of all joints
            - states: List[MotorState] - All joint operation status
            - description: str - Status description information

        MotorStatusData The provided method:
            - is_idle() -> bool: Determine if all joints are in an idle state
            - is_opened() -> bool: Determine if it is in an open state
            - is_closed() -> bool: Determine if it is in a gripping state
            - desc() -> str: Get status description

    Example:
        motor_status = device.get_motor_status(slave_id=1)
        print(f" Joint position: {motor_status.positions}")
        print(f" Speed of movement: {motor_status.speeds}")
        print(f" Drive current: {motor_status.currents}")

        if motor_status.is_idle():
            print("All joints are in a free state ")
    """
    pass

def get_finger_positions(self, slave_id: int) -> List[int]:
    """
    Get the current position values of all joints

    Args:
        slave_id: Device Slave ID

    Returns:
        List[int]: List of joint position values, arranged in the order [thumb, thumb auxiliary, index finger, middle finger, ring finger, little finger]

    Example:
        positions = device.get_finger_positions(slave_id=1)
        print(f" Thumb position: {positions[0]}")
        print(f" Index finger position: {positions[2]}")
    """
    pass

def get_finger_speeds(self, slave_id: int) -> List[int]:
    """
    Get the current motion speed of all joints

    Args:
        slave_id: Device Slave ID

    Returns:
        List[int]: List of joint velocity values, arranged in the order [thumb, thumb auxiliary, index finger, middle finger, ring finger, little finger]

    Example:
        speeds = device.get_finger_speeds(slave_id=1)
        print(f" All joint velocities: {speeds}")
    """
    pass

def get_finger_currents(self, slave_id: int) -> List[int]:
    """
    Get the current drive current of all joints

    Args:
        slave_id: Device Slave ID

    Returns:
        List[int]: List of joint current values, arranged in the order [thumb, thumb auxiliary, index finger, middle finger, ring finger, little finger]

    Example:
        currents = device.get_finger_currents(slave_id=1)
        print(f" All joint currents: {currents}")
    """
    pass

def get_motor_state(self, slave_id: int) -> MotorState:
    """
    Get joint operation status

    Args:
        slave_id: Device Slave ID

    Returns:
        MotorState: Joint Status Enumeration
            - Unknown: Unknown status
            - Idle: Joint clearance
            - Running: Joint movement
            - Stall: Joint jamming
            - Turbo: Turbo High-speed mode

    Example:
        motor_state = device.get_motor_state(slave_id=1)
        if motor_state == MotorState.Running:
            print("The joint is operating ")
        elif motor_state == MotorState.Stall:
            print("Joint jamming ")
    """
    pass
 

Tactile information

from typing import List

# ================================
# Tactile Sensor Interface
# ================================

# Tactile sensor data
# Three-dimensional force values, self-approach capacitance, mutual-approach capacitance, and sensor status
class TouchFingerItem:
    normal_force1: int # Normal force 1,Unit 0.01N,For example, 1000 represents 1000 * 0.01, which is 10 N。The measurement range of the normal force is 0 ~ 25 N
    normal_force2: int # Normal force 2,Unit 0.01N
    normal_force3: int # Normal force 3,Unit 0.01N
    tangential_force1: int # Tangential Force 1,Unit 0.01N,For example, 1000 represents 1000 * 0.01, which is 10 N。The measurement range of the tangential force is 0 ~ 25 N
    tangential_force2: int # Tangential Force 2,Unit 0.01N
    tangential_force3: int # Tangential Force 3,Unit 0.01N
    tangential_direction1: int # Tangential force direction 1, numerical range is 0 ~ 359 degrees。The direction towards the fingertips is 0 degrees. It can be rotated clockwise up to a maximum of 359 degrees. When the value is 65535 (0xFFFF), it indicates that the tangential force direction is invalid.
    tangential_direction2: int # Tangential Force Direction 2
    tangential_direction3: int # Tangential Force Direction 3
    self_proximity1: int # Self-capacitance 1
    self_proximity2: int # Self-capacitance 2
    mutual_proximity: int # Mutual capacitance
    status: int # Status
    description: str # Describe
    def is_normal(self) -> bool: ...
    def is_abnormal(self) -> bool: ...
    def desc(self) -> str: ...

class TouchFingerData:
    items: list[TouchFingerItem]

# Tactile raw data
class TouchRawData:
    thumb: list[int]
    index: list[int]
    middle: list[int]
    ring: list[int]
    pinky: list[int]
    description: str
    def desc(self) -> str: ...

"""
This document demonstrates the dedicated interface for tactile sensors, applicable to the Tactile Edition of the first and second generation dexterous hands。

Tactile Sensor Function:
- Multidimensional Force Sensing: Normal Force, Tangential Force, Tangential Force Direction
- Proximity Detection: Self-Capacitance and Mutual-Capacitance Detection
- Contact status recognition: No contact, static contact, sliding contact
- Real-time data stream: supports high-frequency data collection and callback

Please confirm the device type before use:
    device_info = device.get_device_info(slave_id=1)
    if device_info.is_touch():
        print("The device supports haptic feedback ")
        if device_info.is_revo1_touch():
            print("Revo1 Touch - Generation One Dexterous Hand Tactile Version ")
        elif device_info.is_revo2_touch():
            print("Revo2 Touch - Second-Generation Dextrous Hand Tactile Version ")

Tactile Sensor Usage Process:
1. Check if the device supports haptic feedback
2. Enable and initialize the tactile sensor
3. Acquire tactile data
4. Process tactile data and implement corresponding control logic
5. Calibrate zero drift and reset the tactile sensor
"""

def get_touch_sensor_enabled(self, slave_id: int) -> bool:
    """
    Get whether the tactile sensor is enabled

    Args:
        slave_id: Device Slave ID

    Returns:
        bool: True means enabled, False means disabled

    Note:
        - The tactile sensor is defaultly turned off to save power consumption
        - Tactile data can only be obtained after enabling it.
        - Suitable for all touch-enabled devices

    Example:
        if device.get_device_info(slave_id=1).is_touch():
            enabled = device.get_touch_sensor_enabled(slave_id=1)
            if not enabled:
                print("The tactile sensor is not enabled. Please enable it first. ")
            else:
                print("Tactile sensor has been enabled ")
    """
    pass

def get_touch_sensor_fw_versions(self, slave_id: int) -> List[str]:
    """
    Get the list of firmware version numbers for the tactile sensor

    Args:
        slave_id: Device Slave ID

    Returns:
        List[str]: List of firmware version numbers for each finger touch sensor

    Note:
        - The tactile sensor needs to be enabled first
        - The sensors on each finger may have different firmware versions
        - Version information helps with problem diagnosis and compatibility confirmation

    Example:
        if device.get_touch_sensor_enabled(slave_id=1):
            versions = device.get_touch_sensor_fw_versions(slave_id=1)
            finger_names = ["Thumb ", " Index finger ", " Middle finger ", " Ring finger ", " Little finger "]

            for i, version in enumerate(versions):
                if i < len(finger_names):
                    print(f"{finger_names[i]} Tactile Sensor Firmware Version: {version}")
        else:
            print("Please enable the tactile sensor first ")
    """
    pass

def touch_sensor_setup(self, slave_id: int, bits: int):
    """
    Enable and initialize the tactile sensor system

    Args:
        slave_id: Device Slave ID
        bits: Sensor bit mask, specifying the sensors to be enabled
              - Use bitwise operations to specify which fingers to enable
              - For example: 0x1F (0b11111) represents enabling all 5 fingers
              - For example: 0x03 (0b00011) indicates that only the thumb and index finger are enabled.

    Sensor Bit Mask Description:
        - Bit 0: Thumb
        - Bit 1: Index finger
        - Bit 2: Middle finger
        - Bit 3: Ring finger
        - Bit 4: Little finger

    Note:
        - This is the first step in using a tactile sensor
        - After enabling, the sensor will perform initialization and self-check.
        - The initialization process

    Example:
        # Enable the tactile sensors of all fingers
        device.touch_sensor_setup(slave_id=1, bits=0x1F)
        print("All tactile sensors have been enabled ")

        # Only enable the tactile sensors of the thumb and index finger
        device.touch_sensor_setup(slave_id=1, bits=0x03)
        print("The tactile sensor on the thumb and index finger has been enabled ")

        # Only enable the tactile sensor of the index finger
        device.touch_sensor_setup(slave_id=1, bits=0x02)
        print("The index finger tactile sensor has been enabled ")
    """
    pass

def touch_sensor_reset(self, slave_id: int, bits: int):
    """
    Reset the specified tactile sensor

    Args:
        slave_id: Device Slave ID
        bits: Sensor bit mask, specifying the sensors to be reset

    Note:
        - During reset, the sensor status register will continuously return an abnormal state.
        - After reset, the sensor will resume normal operation
        - Used to resolve sensor anomalies or reinitialize
        - Resetting will not change the enabled status of the sensor

    Example:
        # Reset all tactile sensors
        device.touch_sensor_reset(slave_id=1, bits=0x1F)
        print("All tactile sensors have been reset ")

        # Reset only the thumb sensor (if an anomaly is detected)
        device.touch_sensor_reset(slave_id=1, bits=0x01)
        print("The thumb tactile sensor has been reset ")
    """
    pass

def touch_sensor_calibrate(self, slave_id: int, bits: int):
    """
    Perform tactile sensor zero-drift calibration

    Args:
        slave_id: Device Slave ID
        bits: Sensor bit mask, specifying the sensors to be calibrated

    Calibration Notes:
        - When calibrating, make sure your fingers do not touch any objects
        - The calibration process will eliminate the zero-drift error of the sensor
        - It is recommended to perform calibration after the ambient temperature stabilizes
        - After calibration is completed, the sensor accuracy will be significantly improved

    Recommended calibration timing:
        - When using the device for the first time
        - When the ambient temperature changes significantly
        - When sensor readings show a significant offset
        - During regular maintenance

    Example:
        # Calibrate all tactile sensors
        print("Start calibrating the tactile sensor, please make sure your fingers are not in contact with any objects...")
        device.touch_sensor_calibrate(slave_id=1, bits=0x1F)
        print("Tactile sensor calibration completed ")

        # Calibrate the thumb sensor only
        device.touch_sensor_calibrate(slave_id=1, bits=0x01)
        print("Thumb tactile sensor calibration completed ")
    """
    pass

def get_touch_raw_data(self, slave_id: int) -> TouchRawData:
    """
    Get raw data from the tactile sensor(Mainly used for Revo1 Touch)

    Args:
        slave_id: Device Slave ID

    Returns:
        : Tactile raw data object, which contains the following properties:
            - thumb: List[int] - Thumb original sensor data
            - index: List[int] - Index finger raw sensor data
            - middle: List[int] - Middle finger original sensor data
            - ring: List[int] - Raw sensor data of the ring finger
            - pinky: List[int] - Little finger original sensor data
            - description: str - Data Description

        TouchRawData The provided method:
            - desc() -> str: Get data description

    Note:
        - Only applicable to touch-enabled devices, primarily Revo1 Touch
        - The tactile sensor needs to be enabled first
        - The original data consists of unprocessed sensor readings that need to be parsed manually.
        - Data format and meaning may vary depending on the sensor version

    Example:
        if device.get_device_info(slave_id=1).is_revo1_touch():
            if device.get_touch_sensor_enabled(slave_id=1):
                raw_data = device.get_touch_raw_data(slave_id=1)

                print(f" Thumb raw data: {raw_data.thumb}")
                print(f" Index finger raw data: {raw_data.index}")
                print(f" Data Description: {raw_data.desc()}")

                # Example of raw data processing
                if len(raw_data.thumb) > 0:
                    avg_thumb = sum(raw_data.thumb) / len(raw_data.thumb)
                    print(f" Thumb average: {avg_thumb}")
    """
    pass


def get_single_touch_sensor_status(self, slave_id: int, index: int) -> TouchFingerItem:
    """
    Get tactile sensor data for the specified finger

    Args:
        slave_id: Device Slave ID
        index: Finger index (0-4, corresponding to thumb to little finger)

    Note:
        - The tactile sensor of the corresponding finger needs to be enabled first

    Example:
        if device.get_device_info(slave_id=1).is_revo2_touch():
            # Get thumb tactile status
            thumb_status = device.get_single_touch_sensor_status(slave_id=1, index=0)
    """
    pass

def get_touch_sensor_status(self, slave_id: int) -> TouchFingerData:
    """
    Get tactile sensor data for all fingers

    Args:
        slave_id: Device Slave ID

    Returns:
        TouchFingerData

    Note:
        - A single call retrieves the tactile status of all fingers, resulting in higher efficiency
        - Suitable for applications that require the integrated processing of tactile information from multiple fingers

    Example:
        if device.get_device_info(slave_id=1).is_revo2_touch():
            touch_data = device.get_touch_sensor_status(slave_id=1)
            finger_names = ["Thumb ", " Index finger ", " Middle finger ", " Ring finger ", " Little finger "]
    """
    pass


def check_touch_sensor_health(self, slave_id: int) -> dict:
    """
    Check the health status of the tactile sensor (utility function)

    Args:
        slave_id: Device Slave ID

    Returns:
        dict: Sensor Health Status Report

    Example:
        health_report = self.check_touch_sensor_health(slave_id=1)
        print(f" Health Status Report: {health_report}")
    """
    health_report = {
        "enabled": False,
        "fw_versions": [],
        "sensor_status": [],
        "errors": []
    }

    try:
        # Check if tactile support is available
        device_info = self.get_device_info(slave_id)
        if not device_info.is_touch():
            health_report["errors"].append("The device does not support haptic feedback ")
            return health_report

        # Check if enabled
        touch_enabled = self.get_touch_sensor_enabled(slave_id)
        if not touch_enabled:
            health_report["errors"].append("Tactile sensor is not enabled ")
            return health_report

        # Get firmware version
        health_report["fw_versions"] = self.get_touch_sensor_fw_versions(slave_id)

        # Check the status of each sensor
        touch_data = self.get_modulus_touch_data(slave_id)
        finger_names = ["Thumb ", " Index finger ", " Middle finger ", " Ring finger ", " Little finger "]

        for i, item in enumerate(touch_data.items):
            finger_name = finger_names[i] if i < len(finger_names) else f" Finger {i}"
            status = {
                "finger": finger_name,
                "normal": item.is_normal(),
                "description": item.desc()
            }
            health_report["sensor_status"].append(status)

            if not item.is_normal():
                health_report["errors"].append(f"{finger_name} Sensor anomaly: {item.desc()}")

    except Exception as e:
        health_report["errors"].append(f" An error occurred during the check: {str(e)}")

    return health_report

# ================================
# Using example
# ================================

def touch_sensor_basic_example():
    """
    Basic Usage Example of Tactile Sensor
    """
    slave_id = 1

    device = modbus_open("/dev/ttyUSB0", Baudrate.Baud115200)

    # Check if the device supports haptics
    device_info = device.get_device_info(slave_id)
    if not device_info.is_touch():
        print("The device does not support haptic feedback ")
        return

    print(f" The device supports haptic features: {device_info.sku_type}")

    # Enable the tactile sensor
    print("Enable the tactile sensor...")
    device.touch_sensor_setup(slave_id, bits=0x1F)  # Enable all fingers

    # Wait for initialization to complete
    import time
    time.sleep(2)

    # Check if enabled successfully
    if device.get_touch_sensor_enabled(slave_id):
        print("Tactile sensor enabled successfully ")

        # Get firmware version
        versions = device.get_touch_sensor_fw_versions(slave_id)
        print(f" Sensor Firmware Version: {versions}")

        # Perform calibration
        print("Perform calibration(Please ensure that your fingers are not touching any objects)...")
        device.touch_sensor_calibrate(slave_id, bits=0x1F)
        time.sleep(3)
        print("Calibration completed ")

    else:
        print("Tactile sensor activation failed ")
        return

    # Data Acquisition Loop
    print("Start acquiring tactile data...")
    try:
        while True:
            finger_names = ["Thumb ", " Index finger ", " Middle finger ", " Ring finger ", " Little finger "]
            # Obtain three-dimensional force data
            touch_data = device.get_touch_sensor_status(slave_id)
            # Get the original data
            raw_data = device.get_touch_raw_data(slave_id)


    except KeyboardInterrupt:
        print("\\nStop acquiring tactile data")
 

Action Sequence - Gesture

# ================================
# Gesture Control Interface
# ================================

from typing import List, Sequence
from enum import Enum

# Action Sequence ID
class ActionSequenceId(Enum):
    DefaultGestureOpen = ...
    DefaultGestureFist = ...
    DefaultGesturePinchTwo = ...
    DefaultGesturePinchThree = ...
    DefaultGesturePinchSide = ...
    DefaultGesturePoint = ...
    CustomGesture1 = ...
    CustomGesture2 = ...
    CustomGesture3 = ...
    CustomGesture4 = ...
    CustomGesture5 = ...
    CustomGesture6 = ...

# Action sequence sub-item
class ActionSequenceItem:
    index: int
    duration: int
    mode: int
    positions: list[int]
    durations: list[int]
    speeds: list[int]
    currents: list[int]
    description: str
    def desc(self) -> str: ...

# Action sequence
class ActionSequence:
    action_id: ActionSequenceId
    data: list[ActionSequenceItem]
    description: str
    def desc(self) -> str: ...

def get_action_sequence(self, slave_id: int, action_id: ActionSequenceId) -> ActionSequence:
    """
    Get the gesture action sequence of the specified ID

    Args:
        slave_id: Device Slave ID
        action_id: ActionSequenceId Enumeration value
            - DefaultGestureOpen: Default open hand gesture
            - DefaultGestureFist: Default fist gesture
            - DefaultGesturePinchTwo: Default pinch-to-zoom
            - DefaultGesturePinchThree: Default three-finger pinch
            - DefaultGesturePinchSide: Default lateral pinch
            - DefaultGesturePoint: Default pointing gesture
            - CustomGesture1~6: Custom Gestures 1-6

    Returns:
        ActionSequence: Action Sequence Object,Contains action data and description
            - action_id: ActionSequenceId - Action ID
            - data: List[ActionSequenceItem] - Action sequence data
            - description: str - Action Description

    Example:
        action = device.get_action_sequence(slave_id=1, action_id=ActionSequenceId.DefaultGestureFist)
        print(f" Gesture description: {action.description}")
        print(f" Number of action steps: {len(action.data)}")
    """
    pass

def transfer_action_sequence(self, slave_id: int, action_id: ActionSequenceId,
                           sequences: Sequence[Sequence[int]]):
    """
    Transfer and set the gesture action sequence to the device

    Args:
        slave_id: Device Slave ID
        action_id: ActionSequenceId Enumeration value
        sequences: Action sequence data, where each sequence includes parameters such as joint position, velocity, and current

    Example:
        # Define a simple fist-clenching action sequence
        {
            "index": 0,                          # Action Sequence Index
            "duration_ms": 2000,                 # Action Duration (milliseconds)
            "positions": [0, 0, 100, 100, 100, 100],  # Joint position:[Thumb, Thumb assist, Index finger, Middle finger, Ring finger, Little finger]
        },
        device.transfer_action_sequence(
            slave_id=1,
            action_id=ActionSequenceId.CustomGesture1,
            sequences=sequences
        )
    """
    pass

def run_action_sequence(self, slave_id: int, action_id: ActionSequenceId):
    """
    Execute the specified sequence of gesture actions

    Args:
        slave_id: Device Slave ID
        action_id: ActionSequenceId Enumeration value

    Example:
        # Execute the default fist gesture
        device.run_action_sequence(slave_id=1, action_id=ActionSequenceId.DefaultGestureFist)

        # Execute custom gesture
        device.run_action_sequence(slave_id=1, action_id=ActionSequenceId.CustomGesture1)
    """
    pass
 

Turbo Mode

# Turbo Pattern Configuration
class TurboConfig:
    interval: int
    duration: int
    description: str
    def desc(self) -> str: ...

def get_turbo_mode_enabled(self, slave_id: int) -> bool:
    """
    Get whether the Turbo continuous grip mode is enabled

    Args:
        slave_id: Device Slave ID

    Returns:
        bool: True means Turbo mode is enabled, and False means Turbo mode is disabled.

    Example:
        turbo_enabled = device.get_turbo_mode_enabled(slave_id=1)
        if turbo_enabled:
            print("Turbo mode has been enabled ")
    """
    pass

def set_turbo_mode_enabled(self, slave_id: int, enabled: bool):
    """
    Set Turbo high-speed mode switch status

    Args:
        slave_id: Device Slave ID
        enabled: Whether to enable Turbo mode

    Example:
        device.set_turbo_mode_enabled(slave_id=1, enabled=True)
    """
    pass

def get_turbo_config(self, slave_id: int) -> TurboConfig:
    """
    Get detailed configuration for Turbo mode

    Args:
        slave_id: Device Slave ID

    Returns:
        TurboConfig: Turbo configuration object, which includes the following properties:
            - interval: int - Interval time (milliseconds)
            - duration: int - Continuous grip time (milliseconds)
            - description: str - Configuration description information

        TurboConfig The provided method:
            - desc() -> str: Get configuration description

    Example:
        turbo_config = device.get_turbo_config(slave_id=1)
        print(f" Interval time: {turbo_config.interval}ms")
        print(f" Hold on to time tightly: {turbo_config.duration}ms")
    """
    pass

def set_turbo_config(self, slave_id: int, turbo_config: TurboConfig):
    """
    Set Turbo mode configuration

    Args:
        slave_id: Device Slave ID
        turbo_config: TurboConfig configuration object

    Example:
        config = TurboConfig(interval=100, duration=50)
        device.set_turbo_config(slave_id=1, turbo_config=config)
    """
    pass
 

More

# ================================
# Revo1 Dedicated Interface Document
# ================================

"""
This document demonstrates the unique interfaces of the Revo1 device, which are only applicable to Revo1 series devices。

Please confirm the device type before use:
    device_info = device.get_device_info(slave_id=1)
    if device_info.is_revo1():
        print("This is the Revo1 device, which can use the interfaces in this document ")
    elif device_info.is_revo1_touch():
        print("This is the Revo1 Touch device, which supports haptic functionality ")
"""

import builtins
from typing import List, Sequence, Any, Optional
from enum import Enum

# ================================
# Get the voltage value
# ================================

def get_voltage(self, slave_id: int) -> float:
    """
    Get the current battery voltage of the device

    Args:
        slave_id: Device Slave ID

    Returns:
        float: Voltage value (unit: mV)


    示例:
        voltage = device.get_voltage(slave_id=1)
        print(f" Current battery voltage: {voltage:.2f}V")
    """
    pass

# ================================
# LED Indicator Light Control Interface
# ================================

class LedColor(Enum):
    Unchanged = ...
    R = ...
    G = ...
    RG = ...
    B = ...
    RB = ...
    GB = ...
    RGB = ...

class LedMode(Enum):
    Unknown = ...
    Shutdown = ...
    Keep = ...
    Blink = ...
    OneShot = ...
    Blink0_5Hz = ...
    Blink2Hz = ...

class LedInfo:
    color: LedColor
    mode: LedMode
    description: builtins.str
    def desc(self) -> builtins.str: ...

def get_led_info(self, slave_id: int) -> LedInfo:
    """
    Get LED light status

    Args:
        slave_id: Device Slave ID

    Returns:
        LedInfo: LED status object, which includes the following properties:
            - color: LedColor - LED Color
            - mode: LedMode - LED flashing mode
            - description: str - Status description

        LedColor Enumeration value:
            - Unchanged: Keep the original color
            - R: Red
            - G: Green
            - RG: Red and green (yellow)
            - B: Blue
            - RB: Red and blue (purple)
            - GB: Green-blue (cyan)
            - RGB: Red, green, blue (white)

        LedMode Enumeration value:
            - Unknown: Unknown pattern
            - Shutdown: Close
            - Keep: Constantly lit
            - Blink: Flashing
            - OneShot: Single flash
            - Blink0_5Hz: Flashing at a frequency of 0.5Hz
            - Blink2Hz: 2Hz frequency flicker

        LedInfo The provided method:
            - desc() -> str: Get LED status description

    Example:
        led_info = device.get_led_info(slave_id=1)
        print(f"LED Color: {led_info.color}")
        print(f"LED Pattern: {led_info.mode}")
        print(f" Status Description: {led_info.desc()}")
    """
    pass

def set_led_info(self, slave_id: int, led_info: "LedInfo"):
    """
    Set LED light status

    Args:
        slave_id: Device Slave ID
        led_info: LedInfo Object,Contains color and pattern settings

    Example:
        # Set red constant on
        led_info = LedInfo(color=LedColor.R, mode=LedMode.Keep)
        device.set_led_info(slave_id=1, led_info=led_info)

        # Set green flashing
        led_info = LedInfo(color=LedColor.G, mode=LedMode.Blink)
        device.set_led_info(slave_id=1, led_info=led_info)

        # Set blue 2Hz frequency flashing
        led_info = LedInfo(color=LedColor.B, mode=LedMode.Blink2Hz)
        device.set_led_info(slave_id=1, led_info=led_info)

        # Close LED
        led_info = LedInfo(color=LedColor.Unchanged, mode=LedMode.Shutdown)
        device.set_led_info(slave_id=1, led_info=led_info)
    """
    pass

# ================================
# Button Event Interface
# ================================

class PressState(Enum):
    Unknown = ...
    Down = ...
    Up = ...

class ButtonPressEvent:
    timestamp: builtins.int
    button_id: builtins.int
    press_state: PressState
    description: builtins.str

def get_button_event(self, slave_id: int) -> ButtonPressEvent:
    """
    Get the status of the back-of-hand button event

    Args:
        slave_id: Device Slave ID

    Returns:
        ButtonPressEvent: Button event object, which contains the following properties:
            - timestamp: int - Event timestamp (milliseconds)
            - button_id: int - Button ID (usually 0)
            - press_state: PressState - Press state
            - description: str - Event Description

        PressState Enumeration value:
            - Unknown: Unknown status
            - Down: Button pressed
            - Up: Button release

        ButtonPressEvent The provided method:
            - desc() -> str: Get event description

    Note:
        - This interface needs to be polled to detect button events
        - The event status will be cleared after reading
        - Timestamps can be used to determine the newness or age of events

    Example:
        # Detect button event
        button_event = device.get_button_event(slave_id=1)

        if button_event.press_state == PressState.Down:
            print(f" The button was pressed at {button_event.timestamp}ms ")
            # Handle button press events here

        elif button_event.press_state == PressState.Up:
            print(f" The button was released at {button_event.timestamp}ms ")
            # Handle the button release event here

        # Button Event Handling Example
        def handle_button_events():
            while True:
                event = device.get_button_event(slave_id=1)

                if event.press_state == PressState.Down:
                    # Execute a fist gesture when the button is pressed
                    device.run_action_sequence(slave_id=1, action_id=ActionSequenceId.DefaultGestureFist)

                elif event.press_state == PressState.Up:
                    # Execute the spread gesture when the button is released
                    device.run_action_sequence(slave_id=1, action_id=ActionSequenceId.DefaultGestureOpen)

                time.sleep(0.01)  # 10ms Polling interval
    """
    pass

# ================================
# Status Indicator LED Quick Settings
# ================================

def set_status_led_normal(self, slave_id: int):
    """
    Set the LED to normal status (green constant on)

    Args:
        slave_id: Device Slave ID

    Example:
        device.set_status_led_normal(slave_id=1)
    """
    led_info = LedInfo(color=LedColor.G, mode=LedMode.Keep)
    self.set_led_info(slave_id, led_info)

def set_status_led_warning(self, slave_id: int):
    """
    Set the LED to warning status (flashing yellow)

    Args:
        slave_id: Device Slave ID

    Example:
        device.set_status_led_warning(slave_id=1)
    """
    led_info = LedInfo(color=LedColor.RG, mode=LedMode.Blink)
    self.set_led_info(slave_id, led_info)

def set_status_led_error(self, slave_id: int):
    """
    Set the LED to error status (red fast flashing)

    Args:
        slave_id: Device Slave ID

    Example:
        device.set_status_led_error(slave_id=1)
    """
    led_info = LedInfo(color=LedColor.R, mode=LedMode.Blink2Hz)
    self.set_led_info(slave_id, led_info)

def set_status_led_off(self, slave_id: int):
    """
    Turn off the LED

    Args:
        slave_id: Device Slave ID

    Example:
        device.set_status_led_off(slave_id=1)
    """
    led_info = LedInfo(color=LedColor.Unchanged, mode=LedMode.Shutdown)
    self.set_led_info(slave_id, led_info)

# ================================
# Using example
# ================================

def revo1_example_usage():
    """
    Revo1 Device Usage Example
    """
    # Connect the device
    device = modbus_open("/dev/ttyUSB0", Baudrate.Baud115200)
    slave_id = 1

    # Check device type
    device_info = device.get_device_info(slave_id)
    if not device_info.is_revo1():
        print("Error:This is not a Revo1 device ")
        return

    print(f" Connect to Revo1 device,Serial number: {device_info.serial_number}")

    # Check battery voltage
    voltage = device.get_voltage(slave_id)
    print(f" Battery voltage: {voltage:.2f}V")

    if voltage < 6.5:
        # Set the red LED to warn when the voltage is low
        device.set_status_led_error(slave_id)
        print("Warning: Battery voltage is too low ")
    else:
        # Set the green LED when the voltage is normal
        device.set_status_led_normal(slave_id)

    # Button event handling loop
    print("Start listening for button events...")
    try:
        while True:
            # Check button event
            button_event = device.get_button_event(slave_id)

            if button_event.press_state == PressState.Down:
                print("Button pressed - Make a fist gesture ")
                device.run_action_sequence(slave_id, ActionSequenceId.DefaultGestureFist)
                # Set the blue LED to indicate action execution
                device.set_status_led_info(slave_id, LedInfo(LedColor.B, LedMode.Keep))

            elif button_event.press_state == PressState.Up:
                print("Button release - Perform the open hand gesture ")
                device.run_action_sequence(slave_id, ActionSequenceId.DefaultGestureOpen)
                # Restore green LED
                device.set_status_led_normal(slave_id)

            # If it is the Touch version, it can also process tactile data
            if device_info.is_revo1_touch():
                if device.get_touch_sensor_enabled(slave_id):
                    raw_data = device.get_touch_raw_data(slave_id)
                    # Processing tactile data...

            time.sleep(0.01)  # 10ms Polling interval

    except KeyboardInterrupt:
        print("\\nProgram ends")
        device.set_status_led_off(slave_id)