mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-06 15:05:13 +00:00
feat(devices): add mettler_toledo xpr balance
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -246,3 +246,5 @@ local_test2.py
|
|||||||
ros-humble-unilabos-msgs-0.9.13-h6403a04_5.tar.bz2
|
ros-humble-unilabos-msgs-0.9.13-h6403a04_5.tar.bz2
|
||||||
*.bz2
|
*.bz2
|
||||||
test_config.py
|
test_config.py
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,9 @@ dependencies:
|
|||||||
# ros-humble-gazebo-ros // ignored because of the conflict with ign-gazebo
|
# ros-humble-gazebo-ros // ignored because of the conflict with ign-gazebo
|
||||||
# ilab equipments
|
# ilab equipments
|
||||||
- uni-lab::ros-humble-unilabos-msgs
|
- uni-lab::ros-humble-unilabos-msgs
|
||||||
|
- zeep
|
||||||
|
- jinja2
|
||||||
|
- pprp
|
||||||
- pip:
|
- pip:
|
||||||
- paho-mqtt
|
- paho-mqtt
|
||||||
- opentrons_shared_data
|
- opentrons_shared_data
|
||||||
|
|||||||
@@ -62,6 +62,9 @@ dependencies:
|
|||||||
# ros-humble-gazebo-ros // ignored because of the conflict with ign-gazebo
|
# ros-humble-gazebo-ros // ignored because of the conflict with ign-gazebo
|
||||||
# ilab equipments
|
# ilab equipments
|
||||||
- uni-lab::ros-humble-unilabos-msgs
|
- uni-lab::ros-humble-unilabos-msgs
|
||||||
|
- zeep
|
||||||
|
- jinja2
|
||||||
|
- pprp
|
||||||
- pip:
|
- pip:
|
||||||
- paho-mqtt
|
- paho-mqtt
|
||||||
- opentrons_shared_data
|
- opentrons_shared_data
|
||||||
|
|||||||
@@ -65,6 +65,9 @@ dependencies:
|
|||||||
# ros-humble-gazebo-ros // ignored because of the conflict with ign-gazebo
|
# ros-humble-gazebo-ros // ignored because of the conflict with ign-gazebo
|
||||||
# ilab equipments
|
# ilab equipments
|
||||||
- uni-lab::ros-humble-unilabos-msgs
|
- uni-lab::ros-humble-unilabos-msgs
|
||||||
|
- zeep
|
||||||
|
- jinja2
|
||||||
|
- pprp
|
||||||
- pip:
|
- pip:
|
||||||
- paho-mqtt
|
- paho-mqtt
|
||||||
- opentrons_shared_data
|
- opentrons_shared_data
|
||||||
|
|||||||
@@ -65,6 +65,9 @@ dependencies:
|
|||||||
- uni-lab::ros-humble-unilabos-msgs
|
- uni-lab::ros-humble-unilabos-msgs
|
||||||
# driver
|
# driver
|
||||||
#- crcmod
|
#- crcmod
|
||||||
|
- zeep
|
||||||
|
- jinja2
|
||||||
|
- pprp
|
||||||
- pip:
|
- pip:
|
||||||
- paho-mqtt
|
- paho-mqtt
|
||||||
- opentrons_shared_data
|
- opentrons_shared_data
|
||||||
|
|||||||
6
unilabos/devices/balance/__init__.py
Normal file
6
unilabos/devices/balance/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Balance devices module
|
||||||
|
|
||||||
|
# Import balance device modules
|
||||||
|
from . import mettler_toledo_xpr
|
||||||
|
|
||||||
|
__all__ = ['mettler_toledo_xpr']
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
WSDL Template for Mettler Toledo XPR/XSR Balance
|
||||||
|
|
||||||
|
IMPORTANT: This is a template file. You need to obtain the actual WSDL file
|
||||||
|
from Mettler Toledo for your specific balance model.
|
||||||
|
|
||||||
|
To use this driver:
|
||||||
|
1. Contact Mettler Toledo support to obtain the official WSDL file
|
||||||
|
2. Replace this template with the actual WSDL file
|
||||||
|
3. Rename it to: MT.Laboratory.Balance.XprXsr.V03.wsdl
|
||||||
|
|
||||||
|
The WSDL file contains proprietary information and cannot be distributed
|
||||||
|
with this open-source project.
|
||||||
|
-->
|
||||||
|
<wsdl:definitions xmlns:wsx="http://schemas.xmlsoap.org/ws/2004/09/mex"
|
||||||
|
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
|
||||||
|
xmlns:wsa10="http://www.w3.org/2005/08/addressing"
|
||||||
|
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
|
||||||
|
xmlns:wsap="http://schemas.xmlsoap.org/ws/2004/08/addressing/policy"
|
||||||
|
xmlns:msc="http://schemas.microsoft.com/ws/2005/12/wsdl/contract"
|
||||||
|
xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/"
|
||||||
|
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"
|
||||||
|
xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata"
|
||||||
|
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||||
|
xmlns:tns="http://MT/Laboratory/Balance/XprXsr/V03"
|
||||||
|
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
|
||||||
|
xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl"
|
||||||
|
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
|
||||||
|
targetNamespace="http://MT/Laboratory/Balance/XprXsr/V03"
|
||||||
|
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
PLACEHOLDER CONTENT
|
||||||
|
|
||||||
|
This template contains only the basic structure.
|
||||||
|
The actual WSDL file should contain:
|
||||||
|
- Service definitions
|
||||||
|
- Port types
|
||||||
|
- Message definitions
|
||||||
|
- Binding information
|
||||||
|
- Endpoint addresses with template variables: {{host}}, {{port}}, {{api_path}}
|
||||||
|
-->
|
||||||
|
|
||||||
|
<wsdl:types>
|
||||||
|
<!-- Schema definitions will be here in the actual WSDL -->
|
||||||
|
</wsdl:types>
|
||||||
|
|
||||||
|
<!-- Service definitions will be here in the actual WSDL -->
|
||||||
|
|
||||||
|
</wsdl:definitions>
|
||||||
@@ -0,0 +1,268 @@
|
|||||||
|
# 梅特勒天平 ROS2 使用指南 / Mettler Toledo Balance ROS2 User Guide
|
||||||
|
|
||||||
|
## 概述 / Overview
|
||||||
|
|
||||||
|
梅特勒托利多XPR/XSR天平驱动支持通过ROS2动作进行操作,包括去皮、清零、读取重量等功能。
|
||||||
|
|
||||||
|
The Mettler Toledo XPR/XSR balance driver supports operations through ROS2 actions, including tare, zero, weight reading, and other functions.
|
||||||
|
|
||||||
|
## 主要功能 / Main Features
|
||||||
|
|
||||||
|
### 1. 去皮操作 / Tare Operation (`tare`)
|
||||||
|
|
||||||
|
- **功能 / Function**: 执行天平去皮操作 / Perform balance tare operation
|
||||||
|
- **输入 / Input**: `{"immediate": bool}` - 是否立即去皮 / Whether to tare immediately
|
||||||
|
- **输出 / Output**: `{"return_info": str, "success": bool}`
|
||||||
|
|
||||||
|
### 2. 清零操作 / Zero Operation (`zero`)
|
||||||
|
|
||||||
|
- **功能 / Function**: 执行天平清零操作 / Perform balance zero operation
|
||||||
|
- **输入 / Input**: `{"immediate": bool}` - 是否立即清零 / Whether to zero immediately
|
||||||
|
- **输出 / Output**: `{"return_info": str, "success": bool}`
|
||||||
|
|
||||||
|
### 3. 读取重量 / Read Weight (`read` / `get_weight`)
|
||||||
|
|
||||||
|
- **功能 / Function**: 读取当前天平重量 / Read current balance weight
|
||||||
|
- **输入 / Input**: 无参数 / No parameters
|
||||||
|
- **输出 / Output**: `{"return_info": str, "success": bool}` - 包含重量信息 / Contains weight information
|
||||||
|
|
||||||
|
### 4. 带去皮读取重量 / Read with Tare (`read_with_tare`)
|
||||||
|
|
||||||
|
- **功能 / Function**: 先去皮再读取重量 / Tare first then read weight
|
||||||
|
- **输入 / Input**: 无参数 / No parameters
|
||||||
|
- **输出 / Output**: `{"return_info": str, "success": bool}` - 包含去皮后的重量信息 / Contains weight information after taring
|
||||||
|
|
||||||
|
### 5. 断开连接 / Disconnect (`disconnect`)
|
||||||
|
|
||||||
|
- **功能 / Function**: 断开与天平的连接 / Disconnect from the balance
|
||||||
|
- **输入 / Input**: 无参数 / No parameters
|
||||||
|
- **输出 / Output**: `{"return_info": str, "success": bool}` - 断开连接结果 / Disconnection result
|
||||||
|
|
||||||
|
## 使用方法 / Usage Methods
|
||||||
|
|
||||||
|
### ROS2命令行使用 / ROS2 Command Line Usage
|
||||||
|
|
||||||
|
### 1. 去皮操作 / Tare Operation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ros2 action send_goal /devices/BALANCE_STATION/send_cmd unilabos_msgs/action/SendCmd "{
|
||||||
|
command: '{\"command\": \"tare\", \"params\": {\"immediate\": false}}'
|
||||||
|
}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 清零操作 / Zero Operation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ros2 action send_goal /devices/BALANCE_STATION/send_cmd unilabos_msgs/action/SendCmd "{
|
||||||
|
command: '{\"command\": \"zero\", \"params\": {\"immediate\": false}}'
|
||||||
|
}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 读取重量 / Read Weight
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ros2 action send_goal /devices/BALANCE_STATION/send_cmd unilabos_msgs/action/SendCmd "{
|
||||||
|
command: '{\"command\": \"read\"}'
|
||||||
|
}"
|
||||||
|
```
|
||||||
|
|
||||||
|
或者使用别名 / Or use alias:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ros2 action send_goal /devices/BALANCE_STATION/send_cmd unilabos_msgs/action/SendCmd "{
|
||||||
|
command: '{\"command\": \"get_weight\"}'
|
||||||
|
}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 带去皮读取重量 / Read with Tare
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ros2 action send_goal /devices/BALANCE_STATION/send_cmd unilabos_msgs/action/SendCmd "{
|
||||||
|
command: '{\"command\": \"read_with_tare\"}'
|
||||||
|
}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 断开连接 / Disconnect
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ros2 action send_goal /devices/BALANCE_STATION/send_cmd unilabos_msgs/action/SendCmd "{
|
||||||
|
command: '{\"command\": \"disconnect\"}'
|
||||||
|
}"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 命令格式说明 / Command Format Description
|
||||||
|
|
||||||
|
所有命令都使用JSON格式,包含以下字段 / All commands use JSON format with the following fields:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"command": "命令名称 / Command name",
|
||||||
|
"params": {
|
||||||
|
"参数名 / Parameter name": "参数值 / Parameter value"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意事项 / Notes:**
|
||||||
|
1. JSON字符串需要正确转义引号 / JSON strings need proper quote escaping
|
||||||
|
2. 布尔值使用小写(true/false)/ Boolean values use lowercase (true/false)
|
||||||
|
3. 如果命令不需要参数,可以省略`params`字段 / If command doesn't need parameters, `params` field can be omitted
|
||||||
|
|
||||||
|
## 返回结果 / Return Results
|
||||||
|
|
||||||
|
所有命令都会返回包含以下字段的结果 / All commands return results with the following fields:
|
||||||
|
|
||||||
|
- `success`: 布尔值,表示操作是否成功 / Boolean value indicating operation success
|
||||||
|
- `return_info`: 字符串,包含操作结果的详细信息 / String containing detailed operation result information
|
||||||
|
|
||||||
|
## 成功执行示例 / Successful Execution Example
|
||||||
|
|
||||||
|
以下是一个成功执行读取重量命令的示例 / Here is an example of successfully executing a weight reading command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ros2 action send_goal /devices/BALANCE_STATION/send_cmd unilabos_msgs/action/SendCmd "{
|
||||||
|
command: '{\"command\": \"read\"}'
|
||||||
|
}"
|
||||||
|
```
|
||||||
|
|
||||||
|
**成功返回结果 / Successful Return Result:**
|
||||||
|
```
|
||||||
|
Waiting for an action server to become available...
|
||||||
|
Sending goal:
|
||||||
|
command: '{"command": "read"}'
|
||||||
|
|
||||||
|
Goal accepted :)
|
||||||
|
|
||||||
|
Result:
|
||||||
|
success: True
|
||||||
|
return_info: Weight: 0.24866 Milligram
|
||||||
|
|
||||||
|
Goal finished with status: SUCCEEDED
|
||||||
|
```
|
||||||
|
|
||||||
|
### Python代码使用 / Python Code Usage
|
||||||
|
|
||||||
|
```python
|
||||||
|
import rclpy
|
||||||
|
from rclpy.node import Node
|
||||||
|
from rclpy.action import ActionClient
|
||||||
|
from unilabos_msgs.action import SendCmd
|
||||||
|
import json
|
||||||
|
|
||||||
|
class BalanceController(Node):
|
||||||
|
"""梅特勒天平控制器 / Mettler Balance Controller"""
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__('balance_controller')
|
||||||
|
self._action_client = ActionClient(self, SendCmd, '/devices/BALANCE_STATION/send_cmd')
|
||||||
|
|
||||||
|
def send_command(self, command, params=None):
|
||||||
|
"""发送命令到天平 / Send command to balance"""
|
||||||
|
goal_msg = SendCmd.Goal()
|
||||||
|
|
||||||
|
cmd_data = {'command': command}
|
||||||
|
if params:
|
||||||
|
cmd_data['params'] = params
|
||||||
|
|
||||||
|
goal_msg.command = json.dumps(cmd_data)
|
||||||
|
|
||||||
|
self._action_client.wait_for_server()
|
||||||
|
future = self._action_client.send_goal_async(goal_msg)
|
||||||
|
|
||||||
|
return future
|
||||||
|
|
||||||
|
def tare_balance(self, immediate=False):
|
||||||
|
"""去皮操作 / Tare operation"""
|
||||||
|
return self.send_command('tare', {'immediate': immediate})
|
||||||
|
|
||||||
|
def zero_balance(self, immediate=False):
|
||||||
|
"""清零操作 / Zero operation"""
|
||||||
|
return self.send_command('zero', {'immediate': immediate})
|
||||||
|
|
||||||
|
def read_weight(self):
|
||||||
|
"""读取重量 / Read weight"""
|
||||||
|
return self.send_command('read')
|
||||||
|
|
||||||
|
def read_with_tare(self):
|
||||||
|
"""带去皮读取重量 / Read weight with tare"""
|
||||||
|
return self.send_command('read_with_tare')
|
||||||
|
|
||||||
|
def disconnect_balance(self):
|
||||||
|
"""断开连接 / Disconnect"""
|
||||||
|
return self.send_command('disconnect')
|
||||||
|
|
||||||
|
# 使用示例 / Usage Example
|
||||||
|
def main():
|
||||||
|
rclpy.init()
|
||||||
|
controller = BalanceController()
|
||||||
|
|
||||||
|
# 去皮操作 / Tare operation
|
||||||
|
future = controller.tare_balance(immediate=False)
|
||||||
|
rclpy.spin_until_future_complete(controller, future)
|
||||||
|
result = future.result().result
|
||||||
|
print(f"去皮结果 / Tare result: {result.success}, 信息 / Info: {result.return_info}")
|
||||||
|
|
||||||
|
# 读取重量 / Read weight
|
||||||
|
future = controller.read_weight()
|
||||||
|
rclpy.spin_until_future_complete(controller, future)
|
||||||
|
result = future.result().result
|
||||||
|
print(f"读取结果 / Read result: {result.success}, 信息 / Info: {result.return_info}")
|
||||||
|
|
||||||
|
controller.destroy_node()
|
||||||
|
rclpy.shutdown()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用注意事项 / Usage Notes
|
||||||
|
|
||||||
|
1. **设备连接 / Device Connection**: 确保梅特勒天平设备已连接并可访问 / Ensure Mettler balance device is connected and accessible
|
||||||
|
2. **命令格式 / Command Format**: JSON字符串需要正确转义引号 / JSON strings need proper quote escaping
|
||||||
|
3. **参数类型 / Parameter Types**: 布尔值使用小写(true/false)/ Boolean values use lowercase (true/false)
|
||||||
|
4. **权限 / Permissions**: 确保有操作天平的权限 / Ensure you have permission to operate the balance
|
||||||
|
|
||||||
|
## 故障排除 / Troubleshooting
|
||||||
|
|
||||||
|
### 常见问题 / Common Issues
|
||||||
|
|
||||||
|
1. **JSON格式错误 / JSON Format Error**: 确保JSON字符串格式正确且引号已转义 / Ensure JSON string format is correct and quotes are escaped
|
||||||
|
2. **未知命令名称 / Unknown Command Name**: 检查命令名称是否正确 / Check if command name is correct
|
||||||
|
3. **设备连接失败 / Device Connection Failed**: 检查网络连接和设备状态 / Check network connection and device status
|
||||||
|
4. **操作超时 / Operation Timeout**: 检查设备是否响应正常 / Check if device is responding normally
|
||||||
|
|
||||||
|
### 错误处理 / Error Handling
|
||||||
|
|
||||||
|
如果命令执行失败,返回结果中的`success`字段将为`false`,`return_info`字段将包含错误信息。
|
||||||
|
|
||||||
|
If command execution fails, the `success` field in the return result will be `false`, and the `return_info` field will contain error information.
|
||||||
|
|
||||||
|
### 调试技巧 / Debugging Tips
|
||||||
|
|
||||||
|
1. 检查设备节点是否正在运行 / Check if device node is running:
|
||||||
|
```bash
|
||||||
|
ros2 node list | grep BALANCE
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 查看可用的action / View available actions:
|
||||||
|
```bash
|
||||||
|
ros2 action list | grep BALANCE
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 检查action接口 / Check action interface:
|
||||||
|
```bash
|
||||||
|
ros2 action info /devices/BALANCE_STATION/send_cmd
|
||||||
|
```
|
||||||
|
|
||||||
|
4. 查看节点日志 / View node logs:
|
||||||
|
```bash
|
||||||
|
ros2 topic echo /rosout
|
||||||
|
```
|
||||||
|
|
||||||
|
## 总结 / Summary
|
||||||
|
|
||||||
|
梅特勒托利多天平设备现在支持 / Mettler Toledo balance device now supports:
|
||||||
|
|
||||||
|
1. 通过ROS2 SendCmd动作进行统一操作 / Unified operations through ROS2 SendCmd actions
|
||||||
|
2. 完整的天平功能支持(去皮、清零、读重等)/ Complete balance function support (tare, zero, weight reading, etc.)
|
||||||
|
3. 完善的错误处理和日志记录 / Comprehensive error handling and logging
|
||||||
|
4. 简化的操作流程和调试方法 / Simplified operation workflow and debugging methods
|
||||||
123
unilabos/devices/balance/mettler_toledo_xpr/README.md
Normal file
123
unilabos/devices/balance/mettler_toledo_xpr/README.md
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
# Mettler Toledo XPR/XSR Balance Driver
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
本驱动程序为梅特勒托利多XPR/XSR系列天平提供标准接口,支持去皮、清零和重量读取等操作。
|
||||||
|
|
||||||
|
## ⚠️ 重要说明 - WSDL文件配置
|
||||||
|
|
||||||
|
### 问题说明
|
||||||
|
|
||||||
|
本驱动程序需要使用梅特勒托利多官方提供的WSDL文件来与天平通信。由于该WSDL文件包含专有信息,不能随开源项目一起分发。
|
||||||
|
|
||||||
|
### 配置步骤
|
||||||
|
|
||||||
|
1. **获取WSDL文件**
|
||||||
|
- 联系梅特勒托利多技术支持
|
||||||
|
- 或从您的天平设备Web界面下载
|
||||||
|
- 或从梅特勒托利多官方SDK获取
|
||||||
|
|
||||||
|
2. **安装WSDL文件**
|
||||||
|
```bash
|
||||||
|
# 将获取的WSDL文件复制到驱动目录
|
||||||
|
cp /path/to/your/MT.Laboratory.Balance.XprXsr.V03.wsdl \
|
||||||
|
unilabos/devices/balance/mettler_toledo_xpr/
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **验证安装**
|
||||||
|
- 确保文件名为:`MT.Laboratory.Balance.XprXsr.V03.wsdl`
|
||||||
|
- 确保文件包含Jinja2模板变量:`{{host}}`、`{{port}}`、`{{api_path}}`
|
||||||
|
|
||||||
|
### WSDL文件要求
|
||||||
|
|
||||||
|
- 文件必须是有效的WSDL格式
|
||||||
|
- 必须包含SessionService和WeighingService的定义
|
||||||
|
- 端点地址应使用模板变量以支持动态IP配置:
|
||||||
|
```xml
|
||||||
|
<soap:address location="http://{{host}}:{{port}}/{{api_path}}/SessionService" />
|
||||||
|
<soap:address location="http://{{host}}:{{port}}/{{api_path}}/WeighingService" />
|
||||||
|
```
|
||||||
|
|
||||||
|
### 文件结构
|
||||||
|
|
||||||
|
```
|
||||||
|
mettler_toledo_xpr/
|
||||||
|
├── MT.Laboratory.Balance.XprXsr.V03.wsdl # 实际WSDL文件(用户提供)
|
||||||
|
├── MT.Laboratory.Balance.XprXsr.V03.wsdl.template # 模板文件(仅供参考)
|
||||||
|
├── mettler_toledo_xpr.py # 驱动程序
|
||||||
|
├── balance.yaml # 设备配置
|
||||||
|
├── SendCmd_Usage_Guide.md # 使用指南
|
||||||
|
└── README.md # 本文件
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### 基本配置
|
||||||
|
|
||||||
|
```python
|
||||||
|
from unilabos.devices.balance.mettler_toledo_xpr import MettlerToledoXPR
|
||||||
|
|
||||||
|
# 创建天平实例
|
||||||
|
balance = MettlerToledoXPR(
|
||||||
|
ip="192.168.1.10", # 天平IP地址
|
||||||
|
port=81, # 天平端口
|
||||||
|
password="123456", # 天平密码
|
||||||
|
timeout=10 # 连接超时时间
|
||||||
|
)
|
||||||
|
|
||||||
|
# 执行操作
|
||||||
|
balance.tare() # 去皮
|
||||||
|
balance.zero() # 清零
|
||||||
|
weight = balance.get_weight() # 读取重量
|
||||||
|
```
|
||||||
|
|
||||||
|
### ROS2 SendCmd Action
|
||||||
|
|
||||||
|
详细的ROS2使用方法请参考 [SendCmd_Usage_Guide.md](SendCmd_Usage_Guide.md)
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
|
||||||
|
### 常见错误
|
||||||
|
|
||||||
|
1. **FileNotFoundError: WSDL template not found**
|
||||||
|
- 确保WSDL文件已正确放置在驱动目录中
|
||||||
|
- 检查文件名是否正确
|
||||||
|
|
||||||
|
2. **连接失败**
|
||||||
|
- 检查天平IP地址和端口配置
|
||||||
|
- 确保天平Web服务已启用
|
||||||
|
- 验证网络连接
|
||||||
|
|
||||||
|
3. **认证失败**
|
||||||
|
- 检查天平密码是否正确
|
||||||
|
- 确保天平允许Web服务访问
|
||||||
|
|
||||||
|
### 调试模式
|
||||||
|
|
||||||
|
```python
|
||||||
|
import logging
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
|
# 创建天平实例,将显示详细日志
|
||||||
|
balance = MettlerToledoXPR(ip="192.168.1.10")
|
||||||
|
```
|
||||||
|
|
||||||
|
## 支持的操作
|
||||||
|
|
||||||
|
- **去皮 (Tare)**: 将当前重量设为零点
|
||||||
|
- **清零 (Zero)**: 重新校准零点
|
||||||
|
- **读取重量 (Get Weight)**: 获取当前重量值
|
||||||
|
- **带去皮读取**: 先去皮再读取重量
|
||||||
|
- **连接管理**: 自动连接和断开
|
||||||
|
|
||||||
|
## 技术支持
|
||||||
|
|
||||||
|
如果您在配置WSDL文件时遇到问题,请:
|
||||||
|
|
||||||
|
1. 查看梅特勒托利多官方文档
|
||||||
|
2. 联系梅特勒托利多技术支持
|
||||||
|
3. 在项目GitHub页面提交Issue
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
本驱动程序遵循项目主许可证。WSDL文件的使用需遵循梅特勒托利多的许可条款。
|
||||||
5
unilabos/devices/balance/mettler_toledo_xpr/__init__.py
Normal file
5
unilabos/devices/balance/mettler_toledo_xpr/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Mettler Toledo XPR Balance Driver Module
|
||||||
|
|
||||||
|
from .mettler_toledo_xpr import MettlerToledoXPR
|
||||||
|
|
||||||
|
__all__ = ['MettlerToledoXPR']
|
||||||
256
unilabos/devices/balance/mettler_toledo_xpr/balance.yaml
Normal file
256
unilabos/devices/balance/mettler_toledo_xpr/balance.yaml
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
balance.mettler_toledo_xpr:
|
||||||
|
category:
|
||||||
|
- balance
|
||||||
|
class:
|
||||||
|
action_value_mappings:
|
||||||
|
disconnect:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result:
|
||||||
|
success: success
|
||||||
|
schema:
|
||||||
|
description: Disconnect from balance
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
success:
|
||||||
|
description: Whether disconnect was successful
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- success
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
get_weight:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result:
|
||||||
|
unit: unit
|
||||||
|
weight: weight
|
||||||
|
schema:
|
||||||
|
description: Get current weight reading
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
unit:
|
||||||
|
description: Weight unit (e.g., g, kg)
|
||||||
|
type: string
|
||||||
|
weight:
|
||||||
|
description: Weight value
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- weight
|
||||||
|
- unit
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
read_with_tare:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
immediate_tare: immediate_tare
|
||||||
|
goal_default:
|
||||||
|
immediate_tare: true
|
||||||
|
handles: []
|
||||||
|
result:
|
||||||
|
unit: unit
|
||||||
|
weight: weight
|
||||||
|
schema:
|
||||||
|
description: Perform tare then read weight (standard read operation)
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
immediate_tare:
|
||||||
|
default: true
|
||||||
|
description: Whether to use immediate tare
|
||||||
|
type: boolean
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
unit:
|
||||||
|
description: Weight unit (e.g., g, kg)
|
||||||
|
type: string
|
||||||
|
weight:
|
||||||
|
description: Weight value after tare
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- weight
|
||||||
|
- unit
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
send_cmd:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
command: command
|
||||||
|
goal_default:
|
||||||
|
command: ''
|
||||||
|
handles: []
|
||||||
|
result:
|
||||||
|
return_info: return_info
|
||||||
|
success: success
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
title: SendCmd_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
title: SendCmd_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: SendCmd_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: SendCmd
|
||||||
|
type: object
|
||||||
|
type: SendCmd
|
||||||
|
tare:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
immediate: immediate
|
||||||
|
goal_default:
|
||||||
|
immediate: false
|
||||||
|
handles: []
|
||||||
|
result:
|
||||||
|
success: success
|
||||||
|
schema:
|
||||||
|
description: Tare operation for balance
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
immediate:
|
||||||
|
default: false
|
||||||
|
description: Whether to perform immediate tare
|
||||||
|
type: boolean
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
success:
|
||||||
|
description: Whether tare operation was successful
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- success
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
zero:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
immediate: immediate
|
||||||
|
goal_default:
|
||||||
|
immediate: false
|
||||||
|
handles: []
|
||||||
|
result:
|
||||||
|
success: success
|
||||||
|
schema:
|
||||||
|
description: Zero operation for balance
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
immediate:
|
||||||
|
default: false
|
||||||
|
description: Whether to perform immediate zero
|
||||||
|
type: boolean
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
success:
|
||||||
|
description: Whether zero operation was successful
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- success
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
module: unilabos.devices.balance.mettler_toledo_xpr.mettler_toledo_xpr:MettlerToledoXPR
|
||||||
|
status_types:
|
||||||
|
error_message: str
|
||||||
|
is_stable: bool
|
||||||
|
status: str
|
||||||
|
unit: str
|
||||||
|
weight: float
|
||||||
|
type: python
|
||||||
|
config_info: []
|
||||||
|
description: Mettler Toledo XPR/XSR Balance Driver
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
description: MettlerToledoXPR __init__ parameters
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
description: Initialization parameters for Mettler Toledo XPR balance
|
||||||
|
properties:
|
||||||
|
ip:
|
||||||
|
default: 192.168.1.10
|
||||||
|
description: Balance IP address
|
||||||
|
type: string
|
||||||
|
password:
|
||||||
|
default: '123456'
|
||||||
|
description: Balance password
|
||||||
|
type: string
|
||||||
|
port:
|
||||||
|
default: 81
|
||||||
|
description: Balance port number
|
||||||
|
type: integer
|
||||||
|
timeout:
|
||||||
|
default: 10
|
||||||
|
description: Connection timeout in seconds
|
||||||
|
type: integer
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: __init__ command parameters
|
||||||
|
type: object
|
||||||
|
version: 1.0.0
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "BALANCE_STATION",
|
||||||
|
"name": "METTLER_TOLEDO_XPR",
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "balance.mettler_toledo_xpr",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"ip": "192.168.1.10",
|
||||||
|
"port": 81,
|
||||||
|
"password": "123456",
|
||||||
|
"timeout": 10
|
||||||
|
},
|
||||||
|
"data": {},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,654 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Mettler Toledo XPR/XSR Balance Driver for Uni-Lab OS
|
||||||
|
|
||||||
|
This driver provides standard interface for Mettler Toledo XPR/XSR balance operations
|
||||||
|
including tare, zero, and weight reading functions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import enum
|
||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
from decimal import Decimal
|
||||||
|
from typing import Tuple, Optional
|
||||||
|
|
||||||
|
from jinja2 import Template
|
||||||
|
from requests import Session
|
||||||
|
from zeep import Client
|
||||||
|
from zeep.transports import Transport
|
||||||
|
import pprp
|
||||||
|
|
||||||
|
# Import UniversalDriver - handle import error gracefully
|
||||||
|
try:
|
||||||
|
from unilabos.device_comms.universal_driver import UniversalDriver
|
||||||
|
except ImportError:
|
||||||
|
# Fallback for standalone testing
|
||||||
|
class UniversalDriver:
|
||||||
|
"""Fallback UniversalDriver for standalone testing"""
|
||||||
|
def __init__(self):
|
||||||
|
self.success = False
|
||||||
|
|
||||||
|
|
||||||
|
class Outcome(enum.Enum):
|
||||||
|
"""Balance operation outcome enumeration"""
|
||||||
|
SUCCESS = "Success"
|
||||||
|
ERROR = "Error"
|
||||||
|
|
||||||
|
|
||||||
|
class MettlerToledoXPR(UniversalDriver):
|
||||||
|
"""Mettler Toledo XPR/XSR Balance Driver
|
||||||
|
|
||||||
|
Provides standard interface for balance operations including:
|
||||||
|
- Tare (去皮)
|
||||||
|
- Zero (清零)
|
||||||
|
- Weight reading (读数)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, ip: str = "192.168.1.10", port: int = 81,
|
||||||
|
password: str = "123456", timeout: int = 10):
|
||||||
|
"""Initialize the balance driver
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ip: Balance IP address
|
||||||
|
port: Balance port number
|
||||||
|
password: Balance password
|
||||||
|
timeout: Connection timeout in seconds
|
||||||
|
"""
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.ip = ip
|
||||||
|
self.port = port
|
||||||
|
self.password = password
|
||||||
|
self.timeout = timeout
|
||||||
|
self.api_path = "MT/Laboratory/Balance/XprXsr/V03"
|
||||||
|
|
||||||
|
# Status properties
|
||||||
|
self._status = "Disconnected"
|
||||||
|
self._last_weight = 0.0
|
||||||
|
self._last_unit = "g"
|
||||||
|
self._is_stable = False
|
||||||
|
self._error_message = ""
|
||||||
|
|
||||||
|
# ROS2 action result properties
|
||||||
|
self.success = False
|
||||||
|
self.return_info = ""
|
||||||
|
|
||||||
|
# Service objects
|
||||||
|
self.client = None
|
||||||
|
self.session_svc = None
|
||||||
|
self.weighing_svc = None
|
||||||
|
self.session_id = None
|
||||||
|
|
||||||
|
# WSDL template path
|
||||||
|
self.wsdl_template = Path(__file__).parent / "MT.Laboratory.Balance.XprXsr.V03.wsdl"
|
||||||
|
|
||||||
|
# Bindings
|
||||||
|
self.bindings = {
|
||||||
|
"session": "{http://MT/Laboratory/Balance/XprXsr/V03}BasicHttpBinding_ISessionService",
|
||||||
|
"weigh": "{http://MT/Laboratory/Balance/XprXsr/V03}BasicHttpBinding_IWeighingService",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Setup logging
|
||||||
|
self.logger = logging.getLogger(f"MettlerToledoXPR-{ip}")
|
||||||
|
|
||||||
|
# Initialize connection
|
||||||
|
self._connect()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status(self) -> str:
|
||||||
|
"""Current device status"""
|
||||||
|
return self._status
|
||||||
|
|
||||||
|
@property
|
||||||
|
def weight(self) -> float:
|
||||||
|
"""Last measured weight value"""
|
||||||
|
return self._last_weight
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit(self) -> str:
|
||||||
|
"""Weight unit (e.g., 'g', 'kg')"""
|
||||||
|
return self._last_unit
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_stable(self) -> bool:
|
||||||
|
"""Whether the weight reading is stable"""
|
||||||
|
return self._is_stable
|
||||||
|
|
||||||
|
@property
|
||||||
|
def error_message(self) -> str:
|
||||||
|
"""Last error message"""
|
||||||
|
return self._error_message
|
||||||
|
|
||||||
|
def _decrypt_session_id(self, pw: str, enc_sid: str, salt: str) -> str:
|
||||||
|
"""Decrypt session ID using password and salt"""
|
||||||
|
key = hashlib.pbkdf2_hmac("sha1", pw.encode(),
|
||||||
|
base64.b64decode(salt), 1000, dklen=32)
|
||||||
|
plain = pprp.decrypt_sink(
|
||||||
|
pprp.rijndael_decrypt_gen(
|
||||||
|
key, pprp.data_source_gen(base64.b64decode(enc_sid))))
|
||||||
|
return plain.decode()
|
||||||
|
|
||||||
|
def _render_wsdl(self) -> Path:
|
||||||
|
"""Render WSDL template with current connection parameters"""
|
||||||
|
if not self.wsdl_template.exists():
|
||||||
|
error_msg = (
|
||||||
|
f"WSDL file not found: {self.wsdl_template}\n\n"
|
||||||
|
"IMPORTANT: You need to obtain the official WSDL file from Mettler Toledo.\n"
|
||||||
|
"Please follow these steps:\n"
|
||||||
|
"1. Contact Mettler Toledo support to get the WSDL file\n"
|
||||||
|
"2. Place it in the driver directory as 'MT.Laboratory.Balance.XprXsr.V03.wsdl'\n"
|
||||||
|
"3. Ensure it contains Jinja2 template variables: {{host}}, {{port}}, {{api_path}}\n\n"
|
||||||
|
"For detailed instructions, see the README.md file in the driver directory."
|
||||||
|
)
|
||||||
|
raise FileNotFoundError(error_msg)
|
||||||
|
|
||||||
|
try:
|
||||||
|
text = Template(self.wsdl_template.read_text(encoding="utf-8")).render(
|
||||||
|
host=self.ip, port=self.port, api_path=self.api_path)
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = (
|
||||||
|
f"Failed to render WSDL template: {e}\n\n"
|
||||||
|
"This usually means the WSDL file doesn't contain the required template variables.\n"
|
||||||
|
"Please ensure your WSDL file contains: {{host}}, {{port}}, {{api_path}}\n"
|
||||||
|
"See README.md for detailed configuration instructions."
|
||||||
|
)
|
||||||
|
raise RuntimeError(error_msg) from e
|
||||||
|
|
||||||
|
wsdl_path = self.wsdl_template.parent / f"rendered_{self.ip}_{self.port}.wsdl"
|
||||||
|
wsdl_path.write_text(text, encoding="utf-8")
|
||||||
|
|
||||||
|
return wsdl_path
|
||||||
|
|
||||||
|
def _connect(self):
|
||||||
|
"""Establish connection to the balance"""
|
||||||
|
try:
|
||||||
|
self._status = "Connecting"
|
||||||
|
|
||||||
|
# Render WSDL
|
||||||
|
wsdl_path = self._render_wsdl()
|
||||||
|
self.logger.info(f"WSDL rendered to {wsdl_path}")
|
||||||
|
|
||||||
|
# Create SOAP client
|
||||||
|
transport = Transport(session=Session(), timeout=self.timeout)
|
||||||
|
self.client = Client(wsdl=str(wsdl_path), transport=transport)
|
||||||
|
|
||||||
|
# Create service proxies
|
||||||
|
base_url = f"http://{self.ip}:{self.port}/{self.api_path}"
|
||||||
|
self.session_svc = self.client.create_service(
|
||||||
|
self.bindings["session"], f"{base_url}/SessionService")
|
||||||
|
self.weighing_svc = self.client.create_service(
|
||||||
|
self.bindings["weigh"], f"{base_url}/WeighingService")
|
||||||
|
|
||||||
|
self.logger.info("Zeep service proxies created")
|
||||||
|
|
||||||
|
# Open session
|
||||||
|
self.logger.info("Opening session...")
|
||||||
|
reply = self.session_svc.OpenSession()
|
||||||
|
if reply.Outcome != Outcome.SUCCESS.value:
|
||||||
|
raise RuntimeError(f"OpenSession failed: {getattr(reply, 'ErrorMessage', '')}")
|
||||||
|
|
||||||
|
self.session_id = self._decrypt_session_id(
|
||||||
|
self.password, reply.SessionId, reply.Salt)
|
||||||
|
|
||||||
|
self.logger.info(f"Session established successfully, SessionId={self.session_id}")
|
||||||
|
self._status = "Connected"
|
||||||
|
self._error_message = ""
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self._status = "Error"
|
||||||
|
self._error_message = str(e)
|
||||||
|
self.logger.error(f"Connection failed: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def _ensure_connected(self):
|
||||||
|
"""Ensure the device is connected"""
|
||||||
|
if self._status != "Connected" or self.session_id is None:
|
||||||
|
self._connect()
|
||||||
|
|
||||||
|
def tare(self, immediate: bool = False) -> bool:
|
||||||
|
"""Perform tare operation (去皮)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
immediate: Whether to perform immediate tare
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if successful, False otherwise
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self._ensure_connected()
|
||||||
|
self._status = "Taring"
|
||||||
|
|
||||||
|
self.logger.info(f"Performing tare (immediate={immediate})...")
|
||||||
|
reply = self.weighing_svc.Tare(self.session_id, immediate)
|
||||||
|
|
||||||
|
if reply.Outcome != Outcome.SUCCESS.value:
|
||||||
|
error_msg = getattr(reply, 'ErrorMessage', 'Unknown error')
|
||||||
|
self.logger.error(f"Tare failed: {error_msg}")
|
||||||
|
self._error_message = f"Tare failed: {error_msg}"
|
||||||
|
self._status = "Error"
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.logger.info("Tare completed successfully")
|
||||||
|
self._status = "Connected"
|
||||||
|
self._error_message = ""
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Tare operation failed: {e}")
|
||||||
|
self._error_message = str(e)
|
||||||
|
self._status = "Error"
|
||||||
|
return False
|
||||||
|
|
||||||
|
def zero(self, immediate: bool = False) -> bool:
|
||||||
|
"""Perform zero operation (清零)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
immediate: Whether to perform immediate zero
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if successful, False otherwise
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self._ensure_connected()
|
||||||
|
self._status = "Zeroing"
|
||||||
|
|
||||||
|
self.logger.info(f"Performing zero (immediate={immediate})...")
|
||||||
|
reply = self.weighing_svc.Zero(self.session_id, immediate)
|
||||||
|
|
||||||
|
if reply.Outcome != Outcome.SUCCESS.value:
|
||||||
|
error_msg = getattr(reply, 'ErrorMessage', 'Unknown error')
|
||||||
|
self.logger.error(f"Zero failed: {error_msg}")
|
||||||
|
self._error_message = f"Zero failed: {error_msg}"
|
||||||
|
self._status = "Error"
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.logger.info("Zero completed successfully")
|
||||||
|
self._status = "Connected"
|
||||||
|
self._error_message = ""
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Zero operation failed: {e}")
|
||||||
|
self._error_message = str(e)
|
||||||
|
self._status = "Error"
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_weight(self) -> float:
|
||||||
|
"""Get current weight reading (读数)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
float: Weight value
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self._ensure_connected()
|
||||||
|
self._status = "Reading"
|
||||||
|
|
||||||
|
self.logger.info("Getting weight...")
|
||||||
|
reply = self.weighing_svc.GetWeight(self.session_id)
|
||||||
|
|
||||||
|
if reply.Outcome != Outcome.SUCCESS.value:
|
||||||
|
error_msg = getattr(reply, 'ErrorMessage', 'Unknown error')
|
||||||
|
self.logger.error(f"GetWeight failed: {error_msg}")
|
||||||
|
self._error_message = f"GetWeight failed: {error_msg}"
|
||||||
|
self._status = "Error"
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
# Handle different response structures
|
||||||
|
if hasattr(reply, 'WeightSample'):
|
||||||
|
# Handle WeightSample structure (most common for XPR)
|
||||||
|
weight_sample = reply.WeightSample
|
||||||
|
if hasattr(weight_sample, 'NetWeight'):
|
||||||
|
weight_val = float(Decimal(weight_sample.NetWeight.Value))
|
||||||
|
weight_unit = weight_sample.NetWeight.Unit
|
||||||
|
elif hasattr(weight_sample, 'GrossWeight'):
|
||||||
|
weight_val = float(Decimal(weight_sample.GrossWeight.Value))
|
||||||
|
weight_unit = weight_sample.GrossWeight.Unit
|
||||||
|
else:
|
||||||
|
weight_val = 0.0
|
||||||
|
weight_unit = 'g'
|
||||||
|
is_stable = getattr(weight_sample, 'Stable', True)
|
||||||
|
elif hasattr(reply, 'Weight'):
|
||||||
|
weight_val = float(Decimal(reply.Weight.Value))
|
||||||
|
weight_unit = reply.Weight.Unit
|
||||||
|
is_stable = getattr(reply.Weight, 'IsStable', True)
|
||||||
|
elif hasattr(reply, 'Value'):
|
||||||
|
weight_val = float(Decimal(reply.Value))
|
||||||
|
weight_unit = getattr(reply, 'Unit', 'g')
|
||||||
|
is_stable = getattr(reply, 'IsStable', True)
|
||||||
|
else:
|
||||||
|
# Try to extract from reply attributes
|
||||||
|
weight_val = float(Decimal(getattr(reply, 'WeightValue', getattr(reply, 'Value', '0'))))
|
||||||
|
weight_unit = getattr(reply, 'WeightUnit', getattr(reply, 'Unit', 'g'))
|
||||||
|
is_stable = getattr(reply, 'IsStable', True)
|
||||||
|
|
||||||
|
# Convert to grams for consistent output (ROS2 requirement)
|
||||||
|
if weight_unit.lower() in ['milligram', 'mg']:
|
||||||
|
weight_val_grams = weight_val / 1000.0
|
||||||
|
elif weight_unit.lower() in ['kilogram', 'kg']:
|
||||||
|
weight_val_grams = weight_val * 1000.0
|
||||||
|
elif weight_unit.lower() in ['gram', 'g']:
|
||||||
|
weight_val_grams = weight_val
|
||||||
|
else:
|
||||||
|
# Default to assuming grams if unit is unknown
|
||||||
|
weight_val_grams = weight_val
|
||||||
|
self.logger.warning(f"Unknown weight unit: {weight_unit}, assuming grams")
|
||||||
|
|
||||||
|
# Update internal state (keep original values for reference)
|
||||||
|
self._last_weight = weight_val
|
||||||
|
self._last_unit = weight_unit
|
||||||
|
self._is_stable = is_stable
|
||||||
|
|
||||||
|
self.logger.info(f"Weight: {weight_val_grams} g (original: {weight_val} {weight_unit})")
|
||||||
|
self._status = "Connected"
|
||||||
|
self._error_message = ""
|
||||||
|
|
||||||
|
return weight_val_grams
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Get weight failed: {e}")
|
||||||
|
self._error_message = str(e)
|
||||||
|
self._status = "Error"
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
def get_weight_with_unit(self) -> Tuple[float, str]:
|
||||||
|
"""Get current weight reading with unit (读数含单位)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple[float, str]: Weight value and unit
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self._ensure_connected()
|
||||||
|
self._status = "Reading"
|
||||||
|
|
||||||
|
self.logger.info("Getting weight with unit...")
|
||||||
|
reply = self.weighing_svc.GetWeight(self.session_id)
|
||||||
|
|
||||||
|
if reply.Outcome != Outcome.SUCCESS.value:
|
||||||
|
error_msg = getattr(reply, 'ErrorMessage', 'Unknown error')
|
||||||
|
self.logger.error(f"GetWeight failed: {error_msg}")
|
||||||
|
self._error_message = f"GetWeight failed: {error_msg}"
|
||||||
|
self._status = "Error"
|
||||||
|
return 0.0, ""
|
||||||
|
|
||||||
|
# Handle different response structures
|
||||||
|
if hasattr(reply, 'WeightSample'):
|
||||||
|
# Handle WeightSample structure (most common for XPR)
|
||||||
|
weight_sample = reply.WeightSample
|
||||||
|
if hasattr(weight_sample, 'NetWeight'):
|
||||||
|
weight_val = float(Decimal(weight_sample.NetWeight.Value))
|
||||||
|
weight_unit = weight_sample.NetWeight.Unit
|
||||||
|
elif hasattr(weight_sample, 'GrossWeight'):
|
||||||
|
weight_val = float(Decimal(weight_sample.GrossWeight.Value))
|
||||||
|
weight_unit = weight_sample.GrossWeight.Unit
|
||||||
|
else:
|
||||||
|
weight_val = 0.0
|
||||||
|
weight_unit = 'g'
|
||||||
|
is_stable = getattr(weight_sample, 'Stable', True)
|
||||||
|
elif hasattr(reply, 'Weight'):
|
||||||
|
weight_val = float(Decimal(reply.Weight.Value))
|
||||||
|
weight_unit = reply.Weight.Unit
|
||||||
|
is_stable = getattr(reply.Weight, 'IsStable', True)
|
||||||
|
elif hasattr(reply, 'Value'):
|
||||||
|
weight_val = float(Decimal(reply.Value))
|
||||||
|
weight_unit = getattr(reply, 'Unit', 'g')
|
||||||
|
is_stable = getattr(reply, 'IsStable', True)
|
||||||
|
else:
|
||||||
|
# Try to extract from reply attributes
|
||||||
|
weight_val = float(Decimal(getattr(reply, 'WeightValue', getattr(reply, 'Value', '0'))))
|
||||||
|
weight_unit = getattr(reply, 'WeightUnit', getattr(reply, 'Unit', 'g'))
|
||||||
|
is_stable = getattr(reply, 'IsStable', True)
|
||||||
|
|
||||||
|
# Update internal state
|
||||||
|
self._last_weight = weight_val
|
||||||
|
self._last_unit = weight_unit
|
||||||
|
self._is_stable = is_stable
|
||||||
|
|
||||||
|
self.logger.info(f"Weight: {weight_val} {weight_unit}")
|
||||||
|
self._status = "Connected"
|
||||||
|
self._error_message = ""
|
||||||
|
|
||||||
|
return weight_val, weight_unit
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Get weight with unit failed: {e}")
|
||||||
|
self._error_message = str(e)
|
||||||
|
self._status = "Error"
|
||||||
|
return 0.0, ""
|
||||||
|
|
||||||
|
def read_with_tare(self, immediate_tare: bool = True) -> Tuple[float, str]:
|
||||||
|
"""Perform tare then read weight (standard read operation)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
immediate_tare: Whether to use immediate tare
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple[float, str]: Weight value and unit
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Try immediate tare first
|
||||||
|
if not self.tare(immediate_tare):
|
||||||
|
# If immediate tare fails and it's an LFT balance, try normal tare
|
||||||
|
if immediate_tare and "Tare immediate cannot be executed" in self._error_message:
|
||||||
|
self.logger.warning("LFT balance doesn't support immediate tare, using normal tare")
|
||||||
|
if not self.tare(False):
|
||||||
|
return 0.0, ""
|
||||||
|
else:
|
||||||
|
return 0.0, ""
|
||||||
|
|
||||||
|
# Small delay to ensure tare is complete
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
# Get weight
|
||||||
|
return self.get_weight_with_unit()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Read with tare failed: {e}")
|
||||||
|
self._error_message = str(e)
|
||||||
|
self._status = "Error"
|
||||||
|
return 0.0, ""
|
||||||
|
|
||||||
|
def disconnect(self):
|
||||||
|
"""Disconnect from the balance"""
|
||||||
|
try:
|
||||||
|
if self.session_svc and self.session_id:
|
||||||
|
self.session_svc.CloseSession(self.session_id)
|
||||||
|
self.logger.info("Session closed")
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.warning(f"Error closing session: {e}")
|
||||||
|
finally:
|
||||||
|
self.session_id = None
|
||||||
|
self.session_svc = None
|
||||||
|
self.weighing_svc = None
|
||||||
|
self.client = None
|
||||||
|
self._status = "Disconnected"
|
||||||
|
|
||||||
|
def send_cmd(self, command: str) -> dict:
|
||||||
|
"""ROS2 SendCmd action handler
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command: JSON string containing command and parameters
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Result containing success status and return_info
|
||||||
|
"""
|
||||||
|
return self.execute_command_from_outer(command)
|
||||||
|
|
||||||
|
def execute_command_from_outer(self, command: str) -> dict:
|
||||||
|
"""Execute command from ROS2 SendCmd action
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command: JSON string containing command and parameters
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Result containing success status and return_info
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
# Parse JSON command
|
||||||
|
cmd_data = json.loads(command.replace("'", '"').replace("False", "false").replace("True", "true"))
|
||||||
|
|
||||||
|
# Extract command name and parameters
|
||||||
|
cmd_name = cmd_data.get('command', '')
|
||||||
|
params = cmd_data.get('params', {})
|
||||||
|
|
||||||
|
self.logger.info(f"Executing command: {cmd_name} with params: {params}")
|
||||||
|
|
||||||
|
# Execute different commands
|
||||||
|
if cmd_name == 'tare':
|
||||||
|
immediate = params.get('immediate', False)
|
||||||
|
success = self.tare(immediate)
|
||||||
|
result = {
|
||||||
|
'success': success,
|
||||||
|
'return_info': f"Tare operation {'successful' if success else 'failed'}"
|
||||||
|
}
|
||||||
|
# Update instance attributes for ROS2 action system
|
||||||
|
self.success = result['success']
|
||||||
|
self.return_info = result['return_info']
|
||||||
|
return result
|
||||||
|
|
||||||
|
elif cmd_name == 'zero':
|
||||||
|
immediate = params.get('immediate', False)
|
||||||
|
success = self.zero(immediate)
|
||||||
|
result = {
|
||||||
|
'success': success,
|
||||||
|
'return_info': f"Zero operation {'successful' if success else 'failed'}"
|
||||||
|
}
|
||||||
|
# Update instance attributes for ROS2 action system
|
||||||
|
self.success = result['success']
|
||||||
|
self.return_info = result['return_info']
|
||||||
|
return result
|
||||||
|
|
||||||
|
elif cmd_name == 'read' or cmd_name == 'get_weight':
|
||||||
|
try:
|
||||||
|
self.logger.info(f"Executing {cmd_name} command via ROS2...")
|
||||||
|
self.logger.info(f"Current status: {self._status}")
|
||||||
|
|
||||||
|
# Use get_weight to get weight value (returns float in grams)
|
||||||
|
weight_grams = self.get_weight()
|
||||||
|
self.logger.info(f"get_weight() returned: {weight_grams} g")
|
||||||
|
|
||||||
|
# Get the original weight and unit for display
|
||||||
|
original_weight = getattr(self, '_last_weight', weight_grams)
|
||||||
|
original_unit = getattr(self, '_last_unit', 'g')
|
||||||
|
self.logger.info(f"Original reading: {original_weight} {original_unit}")
|
||||||
|
|
||||||
|
result = {
|
||||||
|
'success': True,
|
||||||
|
'return_info': f"Weight: {original_weight} {original_unit}"
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Exception in {cmd_name}: {str(e)}")
|
||||||
|
self.logger.error(f"Exception type: {type(e).__name__}")
|
||||||
|
import traceback
|
||||||
|
self.logger.error(f"Traceback: {traceback.format_exc()}")
|
||||||
|
result = {
|
||||||
|
'success': False,
|
||||||
|
'return_info': f"Failed to read weight: {str(e)}"
|
||||||
|
}
|
||||||
|
# Update instance attributes for ROS2 action system
|
||||||
|
self.success = result['success']
|
||||||
|
self.return_info = result['return_info']
|
||||||
|
return result
|
||||||
|
|
||||||
|
elif cmd_name == 'read_with_tare':
|
||||||
|
try:
|
||||||
|
weight, unit = self.read_with_tare()
|
||||||
|
result = {
|
||||||
|
'success': True,
|
||||||
|
'return_info': f"Weight with tare: {weight} {unit}"
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
result = {
|
||||||
|
'success': False,
|
||||||
|
'return_info': f"Failed to read weight with tare: {str(e)}"
|
||||||
|
}
|
||||||
|
# Update instance attributes for ROS2 action system
|
||||||
|
self.success = result['success']
|
||||||
|
self.return_info = result['return_info']
|
||||||
|
return result
|
||||||
|
|
||||||
|
elif cmd_name == 'disconnect':
|
||||||
|
self.disconnect()
|
||||||
|
result = {
|
||||||
|
'success': True,
|
||||||
|
'return_info': "Disconnect successful"
|
||||||
|
}
|
||||||
|
# Update instance attributes for ROS2 action system
|
||||||
|
self.success = result['success']
|
||||||
|
self.return_info = result['return_info']
|
||||||
|
return result
|
||||||
|
|
||||||
|
else:
|
||||||
|
result = {
|
||||||
|
'success': False,
|
||||||
|
'return_info': f"Unknown command: {cmd_name}. Available commands: tare, zero, read, read_with_tare, disconnect"
|
||||||
|
}
|
||||||
|
# Update instance attributes for ROS2 action system
|
||||||
|
self.success = result['success']
|
||||||
|
self.return_info = result['return_info']
|
||||||
|
return result
|
||||||
|
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
self.logger.error(f"JSON parsing failed: {e}")
|
||||||
|
result = {
|
||||||
|
'success': False,
|
||||||
|
'return_info': f"JSON parsing failed: {str(e)}"
|
||||||
|
}
|
||||||
|
# Update instance attributes for ROS2 action system
|
||||||
|
self.success = result['success']
|
||||||
|
self.return_info = result['return_info']
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Command execution failed: {e}")
|
||||||
|
result = {
|
||||||
|
'success': False,
|
||||||
|
'return_info': f"Command execution failed: {str(e)}"
|
||||||
|
}
|
||||||
|
# Update instance attributes for ROS2 action system
|
||||||
|
self.success = result['success']
|
||||||
|
self.return_info = result['return_info']
|
||||||
|
return result
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
"""Cleanup when object is destroyed"""
|
||||||
|
self.disconnect()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Test the driver
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="Mettler Toledo XPR Balance Driver Test")
|
||||||
|
parser.add_argument("--ip", default="192.168.1.10", help="Balance IP address")
|
||||||
|
parser.add_argument("--port", type=int, default=81, help="Balance port")
|
||||||
|
parser.add_argument("--password", default="123456", help="Balance password")
|
||||||
|
parser.add_argument("action", choices=["tare", "zero", "read"],
|
||||||
|
nargs="?", default="read", help="Action to perform")
|
||||||
|
parser.add_argument("--immediate", action="store_true", help="Use immediate mode")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Setup logging
|
||||||
|
logging.basicConfig(level=logging.INFO,
|
||||||
|
format="%(asctime)s %(levelname)s: %(message)s")
|
||||||
|
|
||||||
|
# Create driver instance
|
||||||
|
balance = MettlerToledoXPR(ip=args.ip, port=args.port, password=args.password)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if args.action == "tare":
|
||||||
|
success = balance.tare(args.immediate)
|
||||||
|
print(f"Tare {'successful' if success else 'failed'}")
|
||||||
|
elif args.action == "zero":
|
||||||
|
success = balance.zero(args.immediate)
|
||||||
|
print(f"Zero {'successful' if success else 'failed'}")
|
||||||
|
else: # read
|
||||||
|
weight, unit = balance.read_with_tare()
|
||||||
|
print(f"Weight: {weight} {unit}")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
balance.disconnect()
|
||||||
0
unilabos/devices/zhida_gcms/__init__.py
Normal file
0
unilabos/devices/zhida_gcms/__init__.py
Normal file
256
unilabos/registry/devices/balance.yaml
Normal file
256
unilabos/registry/devices/balance.yaml
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
balance.mettler_toledo_xpr:
|
||||||
|
category:
|
||||||
|
- balance
|
||||||
|
class:
|
||||||
|
action_value_mappings:
|
||||||
|
disconnect:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result:
|
||||||
|
success: success
|
||||||
|
schema:
|
||||||
|
description: Disconnect from balance
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
success:
|
||||||
|
description: Whether disconnect was successful
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- success
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
get_weight:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result:
|
||||||
|
unit: unit
|
||||||
|
weight: weight
|
||||||
|
schema:
|
||||||
|
description: Get current weight reading
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
unit:
|
||||||
|
description: Weight unit (e.g., g, kg)
|
||||||
|
type: string
|
||||||
|
weight:
|
||||||
|
description: Weight value
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- weight
|
||||||
|
- unit
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
read_with_tare:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
immediate_tare: immediate_tare
|
||||||
|
goal_default:
|
||||||
|
immediate_tare: true
|
||||||
|
handles: []
|
||||||
|
result:
|
||||||
|
unit: unit
|
||||||
|
weight: weight
|
||||||
|
schema:
|
||||||
|
description: Perform tare then read weight (standard read operation)
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
immediate_tare:
|
||||||
|
default: true
|
||||||
|
description: Whether to use immediate tare
|
||||||
|
type: boolean
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
unit:
|
||||||
|
description: Weight unit (e.g., g, kg)
|
||||||
|
type: string
|
||||||
|
weight:
|
||||||
|
description: Weight value after tare
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- weight
|
||||||
|
- unit
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
send_cmd:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
command: command
|
||||||
|
goal_default:
|
||||||
|
command: ''
|
||||||
|
handles: []
|
||||||
|
result:
|
||||||
|
return_info: return_info
|
||||||
|
success: success
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
title: SendCmd_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
title: SendCmd_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: SendCmd_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: SendCmd
|
||||||
|
type: object
|
||||||
|
type: SendCmd
|
||||||
|
tare:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
immediate: immediate
|
||||||
|
goal_default:
|
||||||
|
immediate: false
|
||||||
|
handles: []
|
||||||
|
result:
|
||||||
|
success: success
|
||||||
|
schema:
|
||||||
|
description: Tare operation for balance
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
immediate:
|
||||||
|
default: false
|
||||||
|
description: Whether to perform immediate tare
|
||||||
|
type: boolean
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
success:
|
||||||
|
description: Whether tare operation was successful
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- success
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
zero:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
immediate: immediate
|
||||||
|
goal_default:
|
||||||
|
immediate: false
|
||||||
|
handles: []
|
||||||
|
result:
|
||||||
|
success: success
|
||||||
|
schema:
|
||||||
|
description: Zero operation for balance
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
immediate:
|
||||||
|
default: false
|
||||||
|
description: Whether to perform immediate zero
|
||||||
|
type: boolean
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
success:
|
||||||
|
description: Whether zero operation was successful
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- success
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
module: unilabos.devices.balance.mettler_toledo_xpr.mettler_toledo_xpr:MettlerToledoXPR
|
||||||
|
status_types:
|
||||||
|
error_message: str
|
||||||
|
is_stable: bool
|
||||||
|
status: str
|
||||||
|
unit: str
|
||||||
|
weight: float
|
||||||
|
type: python
|
||||||
|
config_info: []
|
||||||
|
description: Mettler Toledo XPR/XSR Balance Driver
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
description: MettlerToledoXPR __init__ parameters
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
description: Initialization parameters for Mettler Toledo XPR balance
|
||||||
|
properties:
|
||||||
|
ip:
|
||||||
|
default: 192.168.1.10
|
||||||
|
description: Balance IP address
|
||||||
|
type: string
|
||||||
|
password:
|
||||||
|
default: '123456'
|
||||||
|
description: Balance password
|
||||||
|
type: string
|
||||||
|
port:
|
||||||
|
default: 81
|
||||||
|
description: Balance port number
|
||||||
|
type: integer
|
||||||
|
timeout:
|
||||||
|
default: 10
|
||||||
|
description: Connection timeout in seconds
|
||||||
|
type: integer
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: __init__ command parameters
|
||||||
|
type: object
|
||||||
|
version: 1.0.0
|
||||||
Reference in New Issue
Block a user