Files
Uni-Lab-OS/unilabos/devices/gripper/rmaxis_v4.py
Junhan Chang a62a695812 Ready for open source (#47)
* Create app/main API

* create example device

* create ROS backend and example device SDK Wrapper

* Add ROS host and host starting from app.py

* Add gripper device and mock implementation

* add "status_types" & "action_types" to ROS device decorator

* add ActionServer debug example

* [bugfix] complete mock gripper example

* ROS Backend Host for Device action calling and Resource management

* add conda/mamba ENV file

* add host_node communication with app/main.py

* add action message value mappings and converters

* Update ilabos.yaml

* Update issue templates

* example devices.json and resources.json

* Fix Device wrapper to use async property and actions (#7)

* Fix Device wrapper to use async property and actions

* Resolve #1 : support async get methods and actions. Give new examples.

* add both sync/async GRBL controller SDK

* 2 call device actions from appmainpy api to ros hostpy (#8)

* feature: add job

* fix:node start

* feature:add get job status

* fix:get device

* clean

* Resolve #5 device connection diagram and workflow compilation support (#9)

* add syringe pump device and its compilation using device connection diagram

* add RunzeSyringePump real device with ROS2 example

* Prototype machine with 1 pump and 1 CNC

* add ROS2ProtocolNode and related functions

* add ilabos_msgs (to use PumpTransfer action)

* add example device connection graph

* refactor protocol_node code into separate file

* add ROS2SerialNode

* add SerialCommand srv in ilabos_msgs

* add pump_protocol example, and fix bugs

* [fix] serial service: avoid async service deadlock by directly call serial `send_command`

* use SendCmd instead of SingleJointPosition for valve control

* initialize device connection graph when server starts

* Fix #5: async workflow execution (#16)

* add rclpyx and protocol example for async-native workflow

* use async in ROS2ProtocolNode, and host initialization

* add examples of "ros-async" protocol implementation, and `run_in_event_loop` for using native async functions

* use "ros-async" in protocols and device nodes

* fix pump_protocol: push to 0.0 μL

* Envs, docs, and conda recipes (#19)

* update ENV: use python 3.11 and deprecate ros-humble-gazebo-ros

* add ilabos-msgs conda recipe

* Update ilabos.yaml

* fix recipe and env yaml

* Add sphinx docs

* add aichemeco

* add bioyong

* add bioyong

* Support XDL devices & protocols (#20)

* [Feature] support multiple protocols in a single ProtocolNode

* add Junjie's code

* Support "Clean" protocol

* Update Grignard_flow_batchreact_single_pumpvalve.json

* test_grignard_add

* add stir device node and example

* Update device_node.py

add print_publish flag to control the node's log output

* NH4Cl_add

* add "HeaterStirrer" device and "HeatChill" action

* add wait time after each pump action for equilibration

* fix stir

* add Separate protocol

* Refactor Separator device and Stir action

* add rotavap_node

* fix stir

* add chiller node

* Move rinsings into PumpTransfer

* Fix SeparateProtocol under refactored Separator device and Stir action

* Supports automatically add new protocol action_types

* fix PumpTransfer protocol because of rinsing

* Add Rotavap and Chiller devices

* fix SeparateProtocol

* add EvaporateProtocol

* add rotavap devices config

* fix HeaterStirrer and SeparatorController IO

* Fix automatically add new protocol action_types

* Add HeaterStirrer and SeparatorController device config

* fix pump protocols

* Fix Evaporate action

* Update evaporate_protocol.py

* add temp_sensor node and add function remap

* update docs

---------

Co-authored-by: 王俊杰 <1800011822@pku.edu.cn>
Co-authored-by: q434343 <554662886@qq.com>

* fix aichemeco

* [Bugfix] fix Windows conda packaging

* add file upload api

* update dependencies: force to use 3.11 and remove conflict in WIN64 and OSX64

* update dependencies: force to use 3.11 and remove conflict in WIN64 and OSX64

* Create aichemeco_simple.py

* fix

* update

* add aichemeco file

* MQTT [1/2]: action start (#25)

* add mq

* fix

* clean

* add class

* fix excel

* update bioyong

* add api

* fix

---------

Co-authored-by: caok@dp.tech <xiaoyeqiannian@163.com>

* motor & grasp

* Add Grasp motor support and enhance EleGripper class

- Introduced a new motor configuration for Grasp in sjtu.json.
- Updated EleGripper class to inherit from UniversalDriver and added status property.
- Implemented move_and_rotate method for coordinated movement.
- Adjusted threading logic in EleGripper initialization.
- Registered Grasp motor in ROS2 device node configuration.

This commit enhances the motor control capabilities by integrating the Grasp motor and improving the existing EleGripper functionality.

* fix read data lenth

* Update Grasp.py

* MQTT (2/2): publish Device Status, Action Feedback & Results (#27)

* Add bridges in HostNode for device_status publishing

* Add "bridges" selection (fastapi & mqtt) when app start

* add MQ feedback & result publisher, and fix message converter

* fix UUID converting between ROS and MQ

* lint api model.py

* Continuous controllers: PID, MPC, custom controllers etc. (#23)

* add controller config & wrapper

* add controller setup at app.main

* control loop example

* fix com port

* add agv , ur_arm and raman

* MQTT (3/4): Unified Resources and Sync when starting the server (#28)

* update http upload api

* generate uuid when init device

* example resource json

* fix

* add new example full-content json (device, resource, graph)

* fix full-content json and related reading code

* fix

* add json_schema when initialize resources

* fix

* update schema

* refactor heaterstirrer.dalong

* fix

* fix refactor heaterstirrer.dalong

* refactor syringepump.runze: use ml instead of μL

* Update ilabos/ros/host.py

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: 王俊杰 <1800011822@pku.edu.cn>
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>

* Distributed initialization with self-organizing network (#29)

* add distributed launching option "--without_host"

* fix

---------

Co-authored-by: 王俊杰 <1800011822@pku.edu.cn>

* Refactor Workstation: Add resource service and tracking (#30)

* move ilabos/ros/rpc to ilabos/device_comms/rpc, and merge bioyond/aichemeco files under /devices

* add Resource srv and message_converter

* move graphio to ilabos/resources

* refactor resources type conversion

* add resource clients in device_node

* add mock resources service

* pass Gripper1 resource test

* update http resource services

* add AGV compile function

* add AGV transfer protocol

* update full mock_gripper edit_id example

* update full mock_gripper edit_id example

* get and update resource also in protocol_node

* mock resource update in AichemecoHiwo

* Create HT_hiwo.json

* add children in resources

* bugfixes

* fix rpc

* add Revvity winprep

---------

Co-authored-by: wjjxxx <43375851+wjjxxx@users.noreply.github.com>
Co-authored-by: 3218923350 <105201755+3218923350@users.noreply.github.com>

* Distributed launch (2/2): distributed resource create (#32)

* add resource_add request to host for slave mode

* add AGV

* fix protocol resources

* optimize host callbacks

* bugfixes

* add revvity registry

---------

Co-authored-by: 王俊杰 <1800011822@pku.edu.cn>
Co-authored-by: wjjxxx <43375851+wjjxxx@users.noreply.github.com>

* Refactor Driver Files Structure (#33)

* Integration with pywinauto & recorder
Added execute run and initialize procdure

* 酶标仪状态检测、使用示例,整体流程待测试

* nivo ready version

* Add HPLC driver and example script

- Introduced HPLCDriver class for managing HPLC device status and operations.
- Implemented device status monitoring and command execution via ROS2 actions.
- Added example script (hplc.py) demonstrating how to run commands on the HPLC device.
- Created PlayerUtil and UniversalDriver classes for shared functionality across devices.
- Refactored NivoDriver to utilize the new UniversalDriver structure.
- Enhanced error handling and process management in the NivoDriver.

* 修复start的错误定位

* hplc tested ok

* relative path to build msgs

* template_driver & jiageng devices

* fetch correct status type and action type

* fix mtype fetch

* gpc bus integration

* ilab build

* remove chs

* recipe rename

* modbus update 1

* json available

* hplc & modbus rewrite

* Update AgilentHPLC.py

hplc datafile reader

* move ilabos/ros/rpc to ilabos/device_comms/rpc, and merge bioyond/aichemeco files under /devices

* modbus分设备

* gpc

* gpc 2

* fix address

* default register node

* fix MainScreenGPC

* add Resource srv and message_converter

* move graphio to ilabos/resources

* refactor resources type conversion

* add resource clients in device_node

* add mock resources service

* pass Gripper1 resource test

* update http resource services

* add AGV compile function

* add AGV transfer protocol

* update recipe.yaml

* update full mock_gripper edit_id example

* update full mock_gripper edit_id example

* get and update resource also in protocol_node

* mock resource update in AichemecoHiwo

* feat: add other jiageng PLC device code

* ilabos compile

* correct format

* correct recipe format

* correct setup.py format

* remove unnecessary files

* remove unnecessary files

* Create HT_hiwo.json

* add children in resources

* hplc support sample_id

* correct hplc sample_id

* correct hplc sample_id

* hplc upload

* fix type hint

* oss upload tested ver

* recipe yaml fix for linux

* update installation yaml

* refactor: moved all driver files according to its feat

* merge main to dev

---------

Co-authored-by: 王俊杰 <2201110460@stu.pku.edu.cn>
Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: jiawei <miaojiawei@dp.tech>

* add: NMR LH and RU device control (#34)

* Add Registry for device drivers and Support GraphML (#35)

* read chemputer graphml

* read graphml in app/main

* add devices in ros/devices

* add schema for devices

* read registry directory and initialize when entry from main

* Delete devices.py

* Update add_protocol.md

* delete unecessary files

* feat: 2278 devices registry yaml (#36)

* read chemputer graphml

* read graphml in app/main

* add devices in ros/devices

* add schema for devices

* read registry directory and initialize when entry from main

* Delete devices.py

* add: NMR LH and RU device control

* fix: modify jiageng devices registry

---------

Co-authored-by: Junhan Chang <changjh@pku.edu.cn>

* Device/Resource Registry and GraphML support (#37)

* add resource type conversion to PLR

* add resource registry and test

* add docs

* fix registry

* add solenoid_valve_mock, its registry and test

* fix registry for directly using examples

* add EvacuateAndRefillProtocol and testcases

* allow function sequence call in ACTION

* add read & write & extra_info for hardware_interface

* Update device_node.py

* add solenoid valve

* add doc developer guide yaml

* fixes for starting IK station

* add graphml grouping parser

* fix graphml grouping parser

* add communication edge parser

* fix io solenoid valve

* Update .gitignore

* Update plates.yaml

---------

Co-authored-by: ColumbiaCC <2100011801@stu.pku.edu.cn>

* Uni-Lab Doc v0.2 (#39)

* add Uni-Lab docs

* change doc name

* Dev (#41)

* Integration with pywinauto & recorder
Added execute run and initialize procdure

* 酶标仪状态检测、使用示例,整体流程待测试

* nivo ready version

* Add HPLC driver and example script

- Introduced HPLCDriver class for managing HPLC device status and operations.
- Implemented device status monitoring and command execution via ROS2 actions.
- Added example script (hplc.py) demonstrating how to run commands on the HPLC device.
- Created PlayerUtil and UniversalDriver classes for shared functionality across devices.
- Refactored NivoDriver to utilize the new UniversalDriver structure.
- Enhanced error handling and process management in the NivoDriver.

* 修复start的错误定位

* hplc tested ok

* relative path to build msgs

* template_driver & jiageng devices

* fetch correct status type and action type

* fix mtype fetch

* gpc bus integration

* ilab build

* remove chs

* recipe rename

* modbus update 1

* json available

* hplc & modbus rewrite

* Update AgilentHPLC.py

hplc datafile reader

* move ilabos/ros/rpc to ilabos/device_comms/rpc, and merge bioyond/aichemeco files under /devices

* modbus分设备

* gpc

* gpc 2

* fix address

* default register node

* fix MainScreenGPC

* add Resource srv and message_converter

* move graphio to ilabos/resources

* refactor resources type conversion

* add resource clients in device_node

* add mock resources service

* pass Gripper1 resource test

* update http resource services

* add AGV compile function

* add AGV transfer protocol

* update recipe.yaml

* update full mock_gripper edit_id example

* update full mock_gripper edit_id example

* get and update resource also in protocol_node

* mock resource update in AichemecoHiwo

* feat: add other jiageng PLC device code

* ilabos compile

* correct format

* correct recipe format

* correct setup.py format

* remove unnecessary files

* remove unnecessary files

* Create HT_hiwo.json

* add children in resources

* hplc support sample_id

* correct hplc sample_id

* correct hplc sample_id

* hplc upload

* fix type hint

* oss upload tested ver

* recipe yaml fix for linux

* update installation yaml

* refactor: moved all driver files according to its feat

* merge main to dev

* add HPLC registry and json

* 升级 ros2-distro-mutex 依赖版本至 0.6

* 修改 ros2-distro-mutex 依赖版本为通配符匹配

* 更新 ros-humble-ilabos-msgs 依赖为 robostack-humble 命名空间

* add resource type conversion to PLR

* add resource registry and test

* feat: 更新oss上传

* fix device id

* add docs

* fix registry

* add solenoid_valve_mock, its registry and test

* fix registry for directly using examples

* add EvacuateAndRefillProtocol and testcases

* allow function sequence call in ACTION

* add read & write & extra_info for hardware_interface

* Update device_node.py

* add solenoid valve

* add doc developer guide yaml

* use robostack-staging

* rclpy version test

* lower rclpy

* ensure 0.6* env

* fixes for starting IK station

* add graphml grouping parser

* fix graphml grouping parser

* add communication edge parser

* fix io solenoid valve

* Update .gitignore

* Update plates.yaml

* Feature/device node later init (#40)

* 修改config路径,方便后续打包
增加device_node打印

* 支持plr序列化/init创建

* 统一命名

* import mgr
logger optimize
banner print

* 日志OK

* fix unicorn frame

* banner print

* correct import format

* file path changes

* 取消后补全,在加载设备的时候直接替换

* converter update

* web page update

* 在线device更新,node继承替换

* 修复动作、状态的类型缺失 和 命令提示

* web功能实现结束

* host节点更改完成
新增status时间戳管理
新增每10s动态发现其他node

* ros2类型的节点也应该被包一次

* 修复类型提示

* websocket 动态显示状态

* add workflow & book theme for docs

* add workflow & book theme for docs

* fix workflow build

* fix workflow build

* 理清启动关系

* stm32 example

* mac . name

* device_instance device_cls

* 新增config添加方式
更新mqtt提示

* plr test

* 移动is_host_mode
新增slave_no_host

* 确保config优先修改生效

* fix graph io

* 支持带参数传入

* 支持物料解析

* 支持物料解析

* device为空的时候不进行绑定或初始化

* protocol node new

* protocol node runnable

* protocol node runnable

---------

Co-authored-by: 王俊杰 <2201110460@stu.pku.edu.cn>
Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: jiawei <miaojiawei@dp.tech>
Co-authored-by: ColumbiaCC <2100011801@stu.pku.edu.cn>

* Dev (#45)

* Integration with pywinauto & recorder
Added execute run and initialize procdure

* 酶标仪状态检测、使用示例,整体流程待测试

* nivo ready version

* Add HPLC driver and example script

- Introduced HPLCDriver class for managing HPLC device status and operations.
- Implemented device status monitoring and command execution via ROS2 actions.
- Added example script (hplc.py) demonstrating how to run commands on the HPLC device.
- Created PlayerUtil and UniversalDriver classes for shared functionality across devices.
- Refactored NivoDriver to utilize the new UniversalDriver structure.
- Enhanced error handling and process management in the NivoDriver.

* 修复start的错误定位

* hplc tested ok

* relative path to build msgs

* template_driver & jiageng devices

* fetch correct status type and action type

* fix mtype fetch

* gpc bus integration

* ilab build

* remove chs

* recipe rename

* modbus update 1

* json available

* hplc & modbus rewrite

* Update AgilentHPLC.py

hplc datafile reader

* move ilabos/ros/rpc to ilabos/device_comms/rpc, and merge bioyond/aichemeco files under /devices

* modbus分设备

* gpc

* gpc 2

* fix address

* default register node

* fix MainScreenGPC

* add Resource srv and message_converter

* move graphio to ilabos/resources

* refactor resources type conversion

* add resource clients in device_node

* add mock resources service

* pass Gripper1 resource test

* update http resource services

* add AGV compile function

* add AGV transfer protocol

* update recipe.yaml

* update full mock_gripper edit_id example

* update full mock_gripper edit_id example

* get and update resource also in protocol_node

* mock resource update in AichemecoHiwo

* feat: add other jiageng PLC device code

* ilabos compile

* correct format

* correct recipe format

* correct setup.py format

* remove unnecessary files

* remove unnecessary files

* Create HT_hiwo.json

* add children in resources

* hplc support sample_id

* correct hplc sample_id

* correct hplc sample_id

* hplc upload

* fix type hint

* oss upload tested ver

* recipe yaml fix for linux

* update installation yaml

* refactor: moved all driver files according to its feat

* merge main to dev

* add HPLC registry and json

* 升级 ros2-distro-mutex 依赖版本至 0.6

* 修改 ros2-distro-mutex 依赖版本为通配符匹配

* 更新 ros-humble-ilabos-msgs 依赖为 robostack-humble 命名空间

* add resource type conversion to PLR

* add resource registry and test

* feat: 更新oss上传

* fix device id

* add docs

* fix registry

* add solenoid_valve_mock, its registry and test

* fix registry for directly using examples

* add EvacuateAndRefillProtocol and testcases

* allow function sequence call in ACTION

* add read & write & extra_info for hardware_interface

* Update device_node.py

* add solenoid valve

* add doc developer guide yaml

* use robostack-staging

* rclpy version test

* lower rclpy

* ensure 0.6* env

* fixes for starting IK station

* add graphml grouping parser

* fix graphml grouping parser

* add communication edge parser

* fix io solenoid valve

* Update .gitignore

* Update plates.yaml

* Feature/device node later init (#40)

* 修改config路径,方便后续打包
增加device_node打印

* 支持plr序列化/init创建

* 统一命名

* import mgr
logger optimize
banner print

* 日志OK

* fix unicorn frame

* banner print

* correct import format

* file path changes

* 取消后补全,在加载设备的时候直接替换

* converter update

* web page update

* 在线device更新,node继承替换

* 修复动作、状态的类型缺失 和 命令提示

* web功能实现结束

* host节点更改完成
新增status时间戳管理
新增每10s动态发现其他node

* ros2类型的节点也应该被包一次

* 修复类型提示

* websocket 动态显示状态

* add workflow & book theme for docs

* add workflow & book theme for docs

* fix workflow build

* fix workflow build

* 理清启动关系

* stm32 example

* mac . name

* device_instance device_cls

* 新增config添加方式
更新mqtt提示

* plr test

* 移动is_host_mode
新增slave_no_host

* 确保config优先修改生效

* fix graph io

* 支持带参数传入

* 支持物料解析

* 支持物料解析

* device为空的时候不进行绑定或初始化

* protocol node new

* protocol node runnable

* protocol node runnable

* Feature/device node later init (#42)

* 修改config路径,方便后续打包
增加device_node打印

* 支持plr序列化/init创建

* 统一命名

* import mgr
logger optimize
banner print

* 日志OK

* fix unicorn frame

* banner print

* correct import format

* file path changes

* 取消后补全,在加载设备的时候直接替换

* converter update

* web page update

* 在线device更新,node继承替换

* 修复动作、状态的类型缺失 和 命令提示

* web功能实现结束

* host节点更改完成
新增status时间戳管理
新增每10s动态发现其他node

* ros2类型的节点也应该被包一次

* 修复类型提示

* websocket 动态显示状态

* add workflow & book theme for docs

* add workflow & book theme for docs

* fix workflow build

* fix workflow build

* 理清启动关系

* stm32 example

* mac . name

* device_instance device_cls

* 新增config添加方式
更新mqtt提示

* plr test

* 移动is_host_mode
新增slave_no_host

* 确保config优先修改生效

* fix graph io

* 支持带参数传入

* 支持物料解析

* 支持物料解析

* device为空的时候不进行绑定或初始化

* protocol node new

* protocol node runnable

* protocol node runnable

* action

* plr suc

* plr suc!!

* plr suc!!

* plr suc!!

* plr msgs

* Feature/device node later init (#43)

* 修改config路径,方便后续打包
增加device_node打印

* 支持plr序列化/init创建

* 统一命名

* import mgr
logger optimize
banner print

* 日志OK

* fix unicorn frame

* banner print

* correct import format

* file path changes

* 取消后补全,在加载设备的时候直接替换

* converter update

* web page update

* 在线device更新,node继承替换

* 修复动作、状态的类型缺失 和 命令提示

* web功能实现结束

* host节点更改完成
新增status时间戳管理
新增每10s动态发现其他node

* ros2类型的节点也应该被包一次

* 修复类型提示

* websocket 动态显示状态

* add workflow & book theme for docs

* add workflow & book theme for docs

* fix workflow build

* fix workflow build

* 理清启动关系

* stm32 example

* mac . name

* device_instance device_cls

* 新增config添加方式
更新mqtt提示

* plr test

* 移动is_host_mode
新增slave_no_host

* 确保config优先修改生效

* fix graph io

* 支持带参数传入

* 支持物料解析

* 支持物料解析

* device为空的时候不进行绑定或初始化

* protocol node new

* protocol node runnable

* protocol node runnable

* action

* plr suc

* plr suc!!

* plr suc!!

* plr suc!!

* plr msgs

* plr

* action

* plr reg fix

* Feature/device node later init (#44)

* 修改config路径,方便后续打包
增加device_node打印

* 支持plr序列化/init创建

* 统一命名

* import mgr
logger optimize
banner print

* 日志OK

* fix unicorn frame

* banner print

* correct import format

* file path changes

* 取消后补全,在加载设备的时候直接替换

* converter update

* web page update

* 在线device更新,node继承替换

* 修复动作、状态的类型缺失 和 命令提示

* web功能实现结束

* host节点更改完成
新增status时间戳管理
新增每10s动态发现其他node

* ros2类型的节点也应该被包一次

* 修复类型提示

* websocket 动态显示状态

* add workflow & book theme for docs

* add workflow & book theme for docs

* fix workflow build

* fix workflow build

* 理清启动关系

* stm32 example

* mac . name

* device_instance device_cls

* 新增config添加方式
更新mqtt提示

* plr test

* 移动is_host_mode
新增slave_no_host

* 确保config优先修改生效

* fix graph io

* 支持带参数传入

* 支持物料解析

* 支持物料解析

* device为空的时候不进行绑定或初始化

* protocol node new

* protocol node runnable

* protocol node runnable

* action

* plr suc

* plr suc!!

* plr suc!!

* plr suc!!

* plr msgs

* plr

* fix convert error
fix async logic error
added async error print

* new device test

* test resource add

* test resource add

* test resource add

* test resource add

* local env setup

* node type fix
temp fix root_node error
fix convert res from type error

* resource tracker

* fix bug from qhh

* fix bug from qhh

* fix bug from qhh

* fix bug from qhh

* refactor MQTT client logging and connection handling; update group ID in config

* driver_params allow empty

* allow other init param

* fix driver param and enhance type hint

* refactor MQConfig to use double quotes for string literals

* fix wrong function calling

* fix wrong function calling

* fix log for mac

* fix networkx compatibility

* add mqtt loggers

* add action to jsonschema converter

* random client id

* type converter & registry

* correct conversion

* fix action publish only from discovered devices

* add "Bio" tag for action doc generation

* 改进module提示

* Fix doc

* mqtt不连接也可用
性价样例提示

* add docs

* 更新plr test案例

* Update intro.md

* 更新有机案例

* skip

---------

Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: Junhan Chang <1700011741@pku.edu.cn>

---------

Co-authored-by: 王俊杰 <2201110460@stu.pku.edu.cn>
Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: jiawei <miaojiawei@dp.tech>
Co-authored-by: ColumbiaCC <2100011801@stu.pku.edu.cn>
Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: Junhan Chang <1700011741@pku.edu.cn>

* Canonicalize before Open Source (#46)

* big big refactor try01

* refactor 02

---------

Co-authored-by: ck <xiaoyeqiannian@163.com>
Co-authored-by: 王俊杰 <1800011822@pku.edu.cn>
Co-authored-by: q434343 <554662886@qq.com>
Co-authored-by: Xuwznln <xuwznln@gmail.com>
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Co-authored-by: wjjxxx <43375851+wjjxxx@users.noreply.github.com>
Co-authored-by: 3218923350 <105201755+3218923350@users.noreply.github.com>
Co-authored-by: Xuwznln <1023025701@qq.com>
Co-authored-by: 王俊杰 <2201110460@stu.pku.edu.cn>
Co-authored-by: jiawei <miaojiawei@dp.tech>
Co-authored-by: Jiawei <91898272+jiawei723@users.noreply.github.com>
Co-authored-by: ColumbiaCC <2100011801@stu.pku.edu.cn>
Co-authored-by: Harvey Que <Q-Query@outlook.com>
2025-04-17 15:09:58 +08:00

409 lines
16 KiB
Python

import time
from asyncio import Event
from enum import Enum, auto
from dataclasses import dataclass
from typing import Dict, Tuple
from pymodbus.client import ModbusSerialClient as ModbusClient
from pymodbus.client import ModbusTcpClient as ModbusTcpClient
class CommandType(Enum):
COMMAND_NONE = 0
COMMAND_GO_HOME = 1
COMMAND_DELAY = 2
COMMAND_MOVE_ABSOLUTE = 3
COMMAND_PUSH = 4
COMMAND_MOVE_RELATIVE = 5
COMMAND_PRECISE_PUSH = 6
class ParamType(Enum):
BOOLEAN = 0
INT32 = 1
FLOAT = 2
ENUM = 3
class ParamEdit(Enum):
NORMAL = 0
READONLY = 1
@dataclass
class Param:
type: ParamType
editability: ParamEdit
address: int
# 用于存储参数的字典类型
ParamsDict = Dict[str, Param]
# Constants and other required elements can be defined as needed
IO_GAP_TIME = 0.01
EXECUTE_COMMAND_INDEX = 15 # Example index
COMMAND_REACH_SIGNAL = "reach_15"
# Define other constants or configurations as needed
def REVERSE(x):
return ((x << 16) & 0xFFFF0000) | ((x >> 16) & 0x0000FFFF)
def int32_to_uint16_list(int32_list):
uint16_list = []
for num in int32_list:
lower_half = num & 0xFFFF
upper_half = (num >> 16) & 0xFFFF
uint16_list.extend([upper_half, lower_half])
return uint16_list
def uint16_list_to_int32_list(uint16_list):
if len(uint16_list) % 2 != 0:
raise ValueError("Input list must have even number of uint16 elements.")
int32_list = []
for i in range(0, len(uint16_list), 2):
# Combine two uint16 values into one int32 value
high = uint16_list[i + 1]
low = uint16_list[i]
# Assuming the uint16_list is in big-endian order
int32_value = (high << 16) | low
int32_list.append(int(int32_value))
return int32_list
class RMAxis:
modbus_device = {}
def __init__(self, port, is_modbus_rtu, baudrate: int = 115200, address: str = "", slave_id: int = 1):
self.device = port
self.is_modbus_rtu = is_modbus_rtu
if is_modbus_rtu:
self.client = ModbusClient(port=port, baudrate=baudrate, parity='N', stopbits=1, bytesize=8, timeout=3)
else:
self.client = ModbusTcpClient(address, port)
if not self.client.connect():
raise Exception(f"Modbus Connection failed")
self.slave_id = slave_id
self._error_event = Event()
self.device_params = {} # Assuming some initialization for parameters
self.command_edited = {}
self.init_parameters(self.device_params)
def init_parameters(self, params):
params["current_command_position"] = Param(ParamType.FLOAT, ParamEdit.NORMAL, 4902)
params["current_command_velocity"] = Param(ParamType.FLOAT, ParamEdit.NORMAL, 4904)
params["current_command_acceleration"] = Param(ParamType.FLOAT, ParamEdit.NORMAL, 4906)
params["current_command_deacceleration"] = Param(ParamType.FLOAT, ParamEdit.NORMAL, 4908)
params["current_position"] = Param(ParamType.FLOAT, ParamEdit.READONLY, 0)
params["current_velocity"] = Param(ParamType.FLOAT, ParamEdit.READONLY, 2)
params["control_torque"] = Param(ParamType.FLOAT, ParamEdit.NORMAL, 4)
params["error"] = Param(ParamType.INT32, ParamEdit.READONLY, 6)
params["current_force_sensor"] = Param(ParamType.FLOAT, ParamEdit.READONLY, 18)
params["io_in_go_home"] = Param(ParamType.BOOLEAN, ParamEdit.NORMAL, 1401)
params["io_in_error_reset"] = Param(ParamType.BOOLEAN, ParamEdit.NORMAL, 1402)
params["io_in_start"] = Param(ParamType.BOOLEAN, ParamEdit.NORMAL, 1403)
params["io_in_servo"] = Param(ParamType.BOOLEAN, ParamEdit.NORMAL, 1404)
params["io_in_stop"] = Param(ParamType.BOOLEAN, ParamEdit.NORMAL, 1405)
params["io_in_force_reset"] = Param(ParamType.BOOLEAN, ParamEdit.NORMAL, 1424)
params["io_in_save_parameters"] = Param(ParamType.BOOLEAN, ParamEdit.NORMAL, 1440)
params["io_in_load_parameters"] = Param(ParamType.BOOLEAN, ParamEdit.NORMAL, 1441)
params["io_in_save_positions"] = Param(ParamType.BOOLEAN, ParamEdit.NORMAL, 1442)
params["io_in_load_positions"] = Param(ParamType.BOOLEAN, ParamEdit.NORMAL, 1443)
params["io_out_gone_home"] = Param(ParamType.BOOLEAN, ParamEdit.NORMAL, 1501)
params["command_address"] = Param(ParamType.INT32, ParamEdit.NORMAL, 5000)
params["selected_command_index"] = Param(ParamType.INT32, ParamEdit.NORMAL, 4001)
params["io_out_reach_15"] = Param(ParamType.BOOLEAN, ParamEdit.NORMAL, 1521)
params["io_out_moving"] = Param(ParamType.BOOLEAN, ParamEdit.NORMAL, 1505)
params["limit_pos"] = Param(ParamType.FLOAT, ParamEdit.NORMAL, 74)
params["limit_neg"] = Param(ParamType.FLOAT, ParamEdit.NORMAL, 72)
params["hardware_direct_output"] = Param(ParamType.INT32, ParamEdit.NORMAL, 158)
params["hardware_direct_input"] = Param(ParamType.INT32, ParamEdit.NORMAL, 160)
def get_version(self):
version_major = self.client.read_input_registers(8, 1, unit=self.slave_id).registers[0]
version_minor = self.client.read_input_registers(10, 1, unit=self.slave_id).registers[0]
version_build = self.client.read_input_registers(12, 1, unit=self.slave_id).registers[0]
version_type = self.client.read_input_registers(14, 1, unit=self.slave_id).registers[0]
return (version_major, version_minor, version_build, version_type)
def set_input_signal(self, signal, level):
param_name = f"io_in_{signal}"
self.set_parameter(param_name, level)
def get_output_signal(self, signal):
param_name = f"io_out_{signal}"
return self.get_device_parameter(param_name)
def config_motion(self, velocity, acceleration, deacceleration):
self.set_parameter("current_command_velocity", velocity)
self.set_parameter("current_command_acceleration", acceleration)
self.set_parameter("current_command_deacceleration", deacceleration)
def move_to(self, position):
self.set_parameter("current_command_position", position)
def go_home(self):
self.set_input_signal("go_home", False)
time.sleep(IO_GAP_TIME) # Assuming IO_GAP_TIME is 0.1 seconds
self.set_input_signal("go_home", True)
def move_absolute(self, position, velocity, acceleration, deacceleration, band):
command = {
'type': CommandType.COMMAND_MOVE_ABSOLUTE.value,
'position': position,
'velocity': velocity,
'acceleration': acceleration,
'deacceleration': deacceleration,
'band': band,
'push_force': 0,
'push_distance': 0,
'delay': 0,
'next_command_index': -1
}
self.execute_command(command)
def move_relative(self, position, velocity, acceleration, deacceleration, band):
command = {
'type': CommandType.COMMAND_MOVE_RELATIVE.value,
'position': position,
'velocity': velocity,
'acceleration': acceleration,
'deacceleration': deacceleration,
'band': band,
'push_force': 0,
'push_distance': 0,
'delay': 0,
'next_command_index': -1
}
self.execute_command(command)
def push(self, force, distance, velocity):
command = {
'type': CommandType.COMMAND_PUSH.value,
'position': 0,
'velocity': velocity,
'acceleration': 0,
'deacceleration': 0,
'band': 0,
'push_force': force,
'push_distance': distance,
'delay': 0,
'next_command_index': -1
}
self.execute_command(command)
def precise_push(self, force, distance, velocity, force_band, force_check_time):
command = {
'type': CommandType.COMMAND_PRECISE_PUSH.value,
'position': 0,
'velocity': velocity,
'acceleration': 0,
'deacceleration': 0,
'band': force_band,
'push_force': force,
'push_distance': distance,
'delay': force_check_time,
'next_command_index': -1
}
self.execute_command(command)
def is_moving(self):
return self.get_output_signal("moving")
def is_reached(self):
return self.get_output_signal(COMMAND_REACH_SIGNAL)
def is_push_empty(self):
return not self.is_moving()
def set_command(self, index, command):
print("Setting command", command)
self.command_edited[index] = True
command_buffer = [
command['type'],
int(command['position'] * 1000),
int(command['velocity'] * 1000),
int(command['acceleration'] * 1000),
int(command['deacceleration'] * 1000),
int(command['band'] * 1000),
int(command['push_force'] * 1000),
int(command['push_distance'] * 1000),
int(command['delay']),
int(command['next_command_index'])
]
buffer = int32_to_uint16_list(command_buffer)
response = self.client.write_registers(self.device_params["command_address"].address + index * 20, buffer, self.slave_id)
def get_command(self, index):
response = self.client.read_holding_registers(self.device_params["command_address"].address + index * 20, 20, self.slave_id)
print(response)
buffer = response.registers
command_buffer = uint16_list_to_int32_list(buffer)
command = {
'type': command_buffer[0],
'position': command_buffer[1] / 1000.0,
'velocity': command_buffer[2] / 1000.0,
'acceleration': command_buffer[3] / 1000.0,
'deacceleration': command_buffer[4] / 1000.0,
'band': command_buffer[5] / 1000.0,
'push_force': command_buffer[6] / 1000.0,
'push_distance': command_buffer[7] / 1000.0,
'delay': command_buffer[8],
'next_command_index': command_buffer[9]
}
return command
def execute_command(self, command):
self.set_command(EXECUTE_COMMAND_INDEX, command)
self.save_commands()
self.trig_command(EXECUTE_COMMAND_INDEX)
time.sleep(IO_GAP_TIME) # Assuming IO_GAP_TIME is 0.1 seconds
def trig_command(self, index):
print("Triggering command", index)
self.set_parameter("selected_command_index", index)
self.set_input_signal("start", False)
time.sleep(IO_GAP_TIME) # Assuming IO_GAP_TIME is 0.1 seconds
self.set_input_signal("start", True)
def load_commands(self):
self.set_input_signal("load_positions", False)
time.sleep(IO_GAP_TIME) # Assuming IO_GAP_TIME is 0.1 seconds
self.set_input_signal("load_positions", True)
def save_commands(self):
for index, edited in self.command_edited.items():
if edited:
self.set_parameter("selected_command_index", index)
self.set_input_signal("save_positions", False)
time.sleep(IO_GAP_TIME) # Assuming IO_GAP_TIME is 0.1 seconds
self.set_input_signal("save_positions", True)
time.sleep(IO_GAP_TIME) # Assuming IO_GAP_TIME is 0.1 seconds
self.command_edited[index] = False
@property
def position(self) -> float:
return self.get_device_parameter("current_position")
def get_position(self) -> float:
return self.get_device_parameter("current_position")
@property
def velocity(self) -> float:
return self.get_device_parameter("current_velocity")
@property
def torque(self) -> float:
return self.get_device_parameter("control_torque")
@property
def force_sensor(self) -> float:
return self.get_device_parameter("current_force_sensor")
def error_code(self):
return self.get_device_parameter("error")
def get_device_parameter(self, name):
# Assuming self.device_params is a dictionary with address and type
param = self.device_params.get(name)
if not param:
self._error_event.set()
raise Exception(f"parameter {name} does not exist")
address = param.address
if param.editability == ParamEdit.READONLY:
if param.type == ParamType.BOOLEAN:
return self.client.read_input_discretes(address, 1).bits[0]
elif param.type == ParamType.ENUM:
return self.client.read_input_registers(address, 1).registers[0]
elif param.type == ParamType.INT32:
return self.client.read_input_registers(address, 2).registers[0] # Handle as needed
elif param.type == ParamType.FLOAT:
return self.client.read_input_registers(address, 2).registers[0] # Handle as needed
else:
self._error_event.set()
raise Exception(f"parameter {name} has unknown data type {param.type}")
else:
if param.type == ParamType.BOOLEAN:
return self.client.read_holding_registers(address, 1).registers[0]
elif param.type == ParamType.ENUM:
return self.client.read_holding_registers(address, 1).registers[0]
elif param.type == ParamType.INT32:
return self.client.read_holding_registers(address, 2).registers[0] # Handle as needed
elif param.type == ParamType.FLOAT:
return self.client.read_holding_registers(address, 2).registers[0] # Handle as needed
else:
self._error_event.set()
raise Exception(f"parameter {name} has unknown data type {param['type']}")
def set_parameter(self, name, value):
param = self.device_params.get(name)
if not param:
self._error_event.set()
raise Exception(f"parameter {name} does not exist")
address = param.address
if param.editability == ParamEdit.READONLY:
raise Exception(f"parameter {name} is read only")
else:
if param.type == ParamType.BOOLEAN:
self.client.write_coil(address, bool(value))
elif param.type == ParamType.ENUM:
self.client.write_register(address, value)
elif param.type == ParamType.INT32:
self.client.write_register(address, int(value))
elif param.type == ParamType.FLOAT:
self.client.write_register(address, float(value))
else:
self._error_event.set()
raise Exception(f"parameter {name} has unknown data type {param['type']}")
def load_parameters(self):
self.set_input_signal("load_parameters", False)
time.sleep(IO_GAP_TIME) # Assuming IO_GAP_TIME is 0.1 seconds
self.set_input_signal("load_parameters", True)
def save_parameters(self):
self.set_input_signal("save_parameters", False)
time.sleep(IO_GAP_TIME) # Assuming IO_GAP_TIME is 0.1 seconds
self.set_input_signal("save_parameters", True)
def reset_error(self):
self.set_input_signal("error_reset", False)
time.sleep(IO_GAP_TIME) # Assuming IO_GAP_TIME is 0.1 seconds
self.set_input_signal("error_reset", True)
def set_servo_on_off(self, on_off):
self.set_input_signal("servo", on_off)
def stop(self):
self.set_input_signal("stop", False)
time.sleep(IO_GAP_TIME) # Assuming IO_GAP_TIME is 0.1 seconds
self.set_input_signal("stop", True)
def soft_reset(self):
self.client.write_register(186, 0x22205682)
async def wait_error(self):
await self._error_event.wait()
def close(self):
self.client.close()
del self.client
if __name__ == "__main__":
# gripper = RMAxis.create_rmaxis_modbus_rtu("COM7", 115200, 1)
gripper = RMAxis.create_rmaxis_modbus_rtu('/dev/tty.usbserial-B002YGXY', 115200, 0)
gripper.go_home()
# gripper.move_to(20)
# print("Moving abs...")
# gripper.move_absolute(20, 5, 100, 100, 0.1)
# print(gripper.get_command(EXECUTE_COMMAND_INDEX))
# gripper.go_home()
# print("Pushing...")
# gripper.push(0.7, 10, 20)