启用基于使用量计费
Tạo vào: 13 tháng 2, 2025
Tạo vào: 13 tháng 2, 2025
Due to low rate limits during the preview period, usage-based pricing is required to use o1. Please enable usage-based pricing.什么意思?
这句话的意思是:由于在预览期内速率限制较低,使用 O1 需要启用基于使用量的计费模式。
也就是说,在 O1 的预览期(可能是 OpenAI 或某个 API 服务的试用阶段),默认的免费或固定费率使用受到了限制。如果你想要继续使用 O1 并突破这些限制,你需要启用按使用量计费的模式(usage-based pricing),即根据实际使用量支付费用。
如果你不希望额外付费,可以检查该服务是否有其他免费额度或替代方案。
cursor如何看版本,以及关闭自动更新。
在 Cursor 代码编辑器中,你可以通过以下方法查看版本和关闭自动更新:
在 Cursor 内部查看:
Cmd + Shift + P
(Mac) 或 Ctrl + Shift + P
(Windows) 调出命令面板About
或 Version
About Cursor
,即可查看当前版本信息。在终端查看:
bashcursor --version
Cursor 目前是基于 VS Code 的修改版本,和 VS Code 一样默认会自动更新。你可以用以下方法关闭自动更新:
Settings
(按 Cmd + ,
(Mac) 或 Ctrl + ,
(Windows))update.mode
update.mode
选项设置为 manual
(手动更新)Cmd + Shift + P
(Mac) 或 Ctrl + Shift + P
(Windows) 调出命令面板Preferences: Open Settings (JSON)
,然后回车settings.json
文件中添加以下内容:
json{ "update.mode": "manual" }
这样 Cursor 就不会自动更新了,你可以在需要时手动更新。
#pragma once
// #include "mmw_msgs/ContiRadarArray.h"
#include "mmw_msgs/msg/conti_radar_array.hpp"
#include "perception_utils/config/config.h"
#include "perception_utils/visualizer.h"
// #include <autoware_perception_msgs/DynamicObjectWithFeatureArray.h>
#include "autoware_perception_msgs/msg/dynamic_object_with_feature_array.hpp"
// #include <ros/ros.h>
#include <rclcpp/rclcpp.hpp>
// #include <visualization_msgs/MarkerArray.h>
#include <visualization_msgs/msg/marker_array.hpp>
#include <memory>
namespace mmw_object_detection
{
namespace coordinate_transform
{
class Debugger
{
public:
using Ptr = std::shared_ptr<Debugger>;
using ConstPtr = std::shared_ptr<const Debugger>;
text// inline static Ptr makeShared(ros::NodeHandle &nh, // inline static Ptr makeShared(rclcpp::Node::SharedPtr nh, inline static Ptr makeShared(rclcpp::Node& nh, perception_utils::Config::Ptr config, int id) { return std::make_shared<Debugger>(nh, config, id); } // Debugger(ros::NodeHandle &nh, perception_utils::Config::Ptr config, int id); // Debugger(rclcpp::Node::SharedPtr nh, perception_utils::Config::Ptr config, int id); Debugger(rclcpp::Node& nh, perception_utils::Config::Ptr config, int id); ~Debugger() = default; void publish(const mmw_msgs::msg::ContiRadarArray &objects);
private:
perception_utils::Config::Ptr config_;
perception_utils::Visualizer<mmw_msgs::msg::ContiRadarArray> viz_;
// ros::Publisher pub_;
rclcpp::Publisher<visualization_msgs::msg::MarkerArray>::SharedPtr pub_;
const int id_;
bool enabled_;
};
} // namespace coordinate_transform
} // namespace mmw_object_detection
上面是我改动的代码,从ros1到ros2,然后又从指针改为引用,调用如下:
debuggers_.insert(std::make_pair(
radar->id(), Debugger::makeShared(*this, config_, radar->id())));
你知道我为什么从ros2的智能指针改为引用吗?
在 ROS2 的典型用法中,我们经常会见到 rclcpp::Node::SharedPtr
这种形式,因为它可以方便地管理 Node 的生命周期。但在你的代码中,你把 Debugger(rclcpp::Node::SharedPtr nh, ...)
改成了 Debugger(rclcpp::Node& nh, ...)
,再从调用端传入 *this
,实际上就是将智能指针改成了引用。常见的原因可能有以下几点:
避免循环引用(cyclic reference)或不必要的引用计数
Debugger
这个类本身保留了一个 rclcpp::Node::SharedPtr
,而它又是该节点内创建并持有的,那么就可能出现循环引用的问题:
Debugger
Debugger
又持有 Node::SharedPtr
Node&
)后,Debugger
不会对 Node 的生命周期产生所有权,也就避免了循环引用。使用引用更能体现 “只使用而不管理生命周期”
Debugger
并不需要“拥有”或“管理”这个 Node 的生命周期,就可以选择使用引用或原生指针(甚至是 std::weak_ptr
)。代码语义和场景需求
rclcpp::Node
的类)在构造时本来就确保了 Node 的存在周期,那么向内部的组件传引用即可,符合直觉且不需要额外的智能指针开销。简化调用方式
这里的cppdebuggers_.insert(std::make_pair( radar->id(), Debugger::makeShared(*this, config_, radar->id())));
*this
很明显就是把当前对象(大概率是继承自 rclcpp::Node
的类)当作实参传给 Debugger
。这样从使用者角度看很清晰:Debugger
只需要用到这个节点的部分功能(日志输出、发布话题等),并不需要独立管理它。std::weak_ptr<rclcpp::Node>
来保存。但通常在一个已经继承 Node 的类内部,直接用引用或者裸指针指向自己(或者 *this
)是比较常见也很直观的做法。所以,你从智能指针改为引用,大概率就是为了避免 Debugger
与 Node 之间形成共享所有权,导致循环引用或管理成本过高的问题。
给你个提示,我的那个调用是在另外一个构造函数中。
从你给出的使用方式和提示推测,你之所以从 rclcpp::Node::SharedPtr
改成传递 rclcpp::Node&
(引用),更直接的原因很可能是:
rclcpp::Node
)的 构造函数 中,去调用 Debugger::makeShared(*this, ...)
。std::shared_ptr<rclcpp::Node>
指向 this
,往往需要该类也继承自 std::enable_shared_from_this
并且“正确地”用 std::shared_ptr
来管理它。但在构造函数内部,无法直接对 this
调用 shared_from_this()
(因为对象本身还没被 std::shared_ptr
完整包裹起来)。shared_ptr
。除了在构造函数里不方便用 shared_ptr
指向自身之外,传引用还有其他常见优点,比如:
shared_ptr<Node>
,而该 Node 本身又持有 Debugger,容易造成循环引用)。Debugger
,就不会有悬空引用的问题。因此,你会看到很多 ROS2 或者基于节点管理的库中,如果一个类继承自 rclcpp::Node
且在其构造函数中实例化各种「工具类」时,通常会把 *this
直接以引用或裸指针的形式传给对方,而不是用 std::shared_ptr<rclcpp::Node>
。这是最常见、最直接的做法。
你说得对,如果使用智能指针传递我的代码编译就会报错,这是否也说明了并不是都得用智能指针?
是的,这确实说明了 并不是所有场景都一定要用智能指针。
在 C++ 中,是否使用智能指针要看实际的 “对象所有权(ownership)” 需求:
如果你的场景不需要共享所有权,或者像你提到的那样,在构造函数里传递自身 时不便于使用智能指针(会报错/不易管理),那么用引用或裸指针就很合理,而且在性能和逻辑上都更符合需求。
简单来说,选择合适的指针类型是为了表达对象的“所有权”语义,而不是一律用 shared_ptr。如果某个类只是依赖使用一个对象,而不需要负责该对象的生命周期,那么用 “引用”或“裸指针” 都是更直接、清晰的做法。
这里为何使用std::move?
if (lidar_type == "ch64w") {
sensor_msgs::msg::LaserScan::UniquePtr scan_msg(new sensor_msgs::msg::LaserScan());
scan_msg->header.frame_id = frame_id;
scan_msg->angle_min = DEG2RAD(0);
scan_msg->angle_max = DEG2RAD(180);
scan_msg->range_min = min_range;
scan_msg->range_max = max_range;
scan_msg->angle_increment = horizontal_angle_resolution * DEG_TO_RAD;
uint point_size = ceil((scan_msg->angle_max - scan_msg->angle_min) / scan_msg->angle_increment) * 2;
scan_msg->ranges.assign(point_size, std::numeric_limits<float>::quiet_NaN());
scan_msg->intensities.assign(point_size, std::numeric_limits<float>::quiet_NaN());
text//scan_msg->header.stamp = pcl_conversions::fromPCL(point_cloud_timestamp); scan_msg->header.stamp = rclcpp::Time(point_cloud_timestamp); if (channel_num / 4 % 2 == 0) { channel_num1 = channel_num; channel_num2 = channel_num + 4; } else { channel_num1 = channel_num; channel_num2 = channel_num - 4; } if (sweep_data_bak->points.size() > 0) { for (size_t j = 0; j < sweep_data_bak->points.size() - 1; ++j) { if (channel_num1 == sweep_data_bak->points[j].line || channel_num2 == sweep_data_bak->points[j].line) { float horizontal_angle = sweep_data_bak->points[j].azimuth; uint point_idx = (int) ((horizontal_angle - scan_msg->angle_min) / scan_msg->angle_increment); point_idx = (point_idx < point_size) ? point_idx : (point_idx % point_size); scan_msg->ranges[point_idx] = sweep_data_bak->points[j].distance; scan_msg->intensities[point_idx] = sweep_data_bak->points[j].intensity; } } laserscan_pub->publish(std::move(scan_msg)); }
在这段代码中:
cpplaserscan_pub->publish(std::move(scan_msg));
之所以需要使用 std::move
,主要有以下原因:
publish
接口需要接受右值引用或 unique_ptr
在 ROS2 中,常见的发布接口签名类似于:
cppvoid publish(std::unique_ptr<T> msg);
或者
cppvoid publish(T && msg);
当函数需要一个 unique_ptr<T>
参数时,就意味着你需要“转移(move)”这份数据的所有权。std::move(scan_msg)
会将局部的 scan_msg
从左值变为右值,从而满足函数的参数签名,让编译器知道你要把这份内存“交给” publish
。
转移所有权,避免拷贝
std::unique_ptr
不可拷贝,只能移动。std::move(scan_msg)
,你把 scan_msg
所指向的对象所有权交给了 publish
方法。发布完成后,本地的 scan_msg
将不再拥有那块资源,也就不能再使用它。性能与内存管理
std::move
可以避免对消息的额外深拷贝,减少内存分配与拷贝的开销,在实时性和效率要求较高的系统(如机器人、自动驾驶)中非常重要。简而言之,std::move
用于将 scan_msg
转换成右值引用,以符合发布接口的签名需求,并完成消息所有权转移,避免不必要的拷贝。
操作系统分配资源的基本单位
在操作系统(OS)中,分配资源的基本单位通常可以从进程和线程两个角度来看:
进程(Process)——资源分配的基本单位
线程(Thread)——调度的基本单位
我有看到虚拟内存 虚拟CPU的概念,和这个相关吗?
是的,虚拟内存(Virtual Memory)和虚拟 CPU(vCPU)的概念确实与操作系统的资源分配有关,特别是在进程管理、资源分配和调度方面。我们可以分别来看它们的作用,以及它们如何与进程和线程的资源管理相关。
概念:
与进程的关系:
作用:
概念:
与进程/线程的关系:
作用:
资源 | 基本作用 | 谁使用它? |
---|---|---|
虚拟内存 | 提供进程独立的地址空间,支持分页、交换,提高内存管理效率 | 进程管理虚拟内存,多个线程共享同一进程的虚拟内存 |
虚拟 CPU | 把物理 CPU 资源抽象为多个 vCPU,多个进程或 VM 共享 CPU | 进程/线程 在 vCPU 上运行,由 OS 调度 vCPU |
在现代计算机系统(包括操作系统、多线程编程、云计算、虚拟化)中,虚拟化技术(虚拟内存、vCPU)是实现高效资源管理的关键。
我有看到虚拟内存和虚拟CPU,这个和上面的概念相关吗?
是的,虚拟内存(Virtual Memory)和虚拟 CPU(vCPU)与进程、线程、资源分配等概念是紧密相关的,它们都是操作系统为了高效管理和调度计算资源而引入的虚拟化技术。让我们从操作系统的角度来梳理它们之间的关系。
概念 | 作用 | 与进程/线程的关系 |
---|---|---|
进程(Process) | 资源分配的基本单位 | 每个进程有独立的虚拟内存,但共享 CPU 资源 |
线程(Thread) | CPU 调度的基本单位 | 线程共享进程的虚拟内存,但可分配到不同的 vCPU 运行 |
虚拟内存(Virtual Memory) | 让进程看到连续的地址空间,支持进程隔离和内存扩展 | 进程的虚拟内存是独立的,多个线程共享同一进程的虚拟内存 |
虚拟 CPU(vCPU) | 让多个任务共享 CPU 资源,提高计算能力 | 进程/线程 运行在 vCPU 上,多个进程可以共享一个 vCPU |
虚拟内存和 vCPU 是现代操作系统的关键机制,它们让计算资源更加高效、灵活、可扩展,特别是在多任务操作系统、云计算和高性能计算环境中起到了重要作用。
如果你能结合linux中的c/c++函数说明就更好了。
好的!下面我会结合 Linux 中的 C/C++ 相关函数,详细说明 进程、线程、虚拟内存和虚拟 CPU(vCPU) 的概念和具体应用。
Linux 相关函数:fork()
、exec()
、wait()
、getpid()
在 Linux 中,fork()
是创建进程的基本函数,它会复制当前进程(包括代码、数据、栈等),生成一个新的子进程。
c#include <unistd.h> #include <stdio.h> int main() { pid_t pid = fork(); // 创建一个子进程 if (pid == 0) { printf("我是子进程,进程 ID:%d\n", getpid()); } else { printf("我是父进程,子进程 ID:%d\n", pid); } return 0; }
fork()
会创建一个新进程,但父子进程各自有独立的虚拟内存空间(地址空间是独立的)。exec()
可以让子进程执行另一个程序:
cexeclp("/bin/ls", "ls", "-l", NULL);
exec()
执行后,当前进程会被替换成新程序,不再继续原来的代码。如果父进程需要等待子进程结束,可以使用 wait()
:
c#include <sys/types.h> #include <sys/wait.h> #include <stdio.h> #include <unistd.h> int main() { pid_t pid = fork(); if (pid > 0) { wait(NULL); // 等待子进程结束 printf("子进程已退出\n"); } else { printf("我是子进程,进程 ID:%d\n", getpid()); } return 0; }
Linux 相关函数:pthread_create()
、pthread_join()
Linux 提供 pthread
库用于创建线程,一个进程可以有多个线程,多个线程共享同一进程的虚拟内存,但可以被不同的 vCPU 执行。
c#include <pthread.h> #include <stdio.h> void *thread_function(void *arg) { printf("我是线程,线程 ID:%ld\n", pthread_self()); return NULL; } int main() { pthread_t thread; pthread_create(&thread, NULL, thread_function, NULL); pthread_join(thread, NULL); // 等待线程结束 return 0; }
cint shared_variable = 10;
特性 | 多进程 | 多线程 |
---|---|---|
地址空间 | 各自独立 | 共享进程地址空间 |
通信 | 需要 IPC(管道、共享内存等) | 直接访问共享变量 |
开销 | 创建/切换代价大 | 开销小,切换快 |
安全性 | 进程隔离,安全性高 | 线程共享数据,易导致竞态 |
Linux 相关函数:mmap()
、brk()
、sbrk()
、malloc()
每个进程在 Linux 下拥有独立的虚拟地址空间,包括:
c#include <stdio.h> #include <stdlib.h> int main() { int *arr = (int *)malloc(10 * sizeof(int)); // 在堆上分配 printf("堆上的地址: %p\n", arr); free(arr); return 0; }
malloc()
在堆上分配内存,本质上调用 sbrk()
或 mmap()
来扩展进程的虚拟内存。mmap()
直接映射物理内存/文件到进程空间:
cvoid *ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); printf("映射的地址: %p\n", ptr);
每个进程有自己的 page table(页表),操作系统会将虚拟地址映射到不同的物理地址。
Linux 相关命令:top
、htop
、taskset
、sched_setaffinity()
在 Linux 服务器或云计算环境中,htop
命令可以查看 vCPU:
shhtop
或者:
shcat /proc/cpuinfo | grep processor
如果你在 虚拟机(如 AWS、Azure)上运行,它显示的 processor
数可能是vCPU 数量,并不代表物理核心数。
在多核系统中,可以用 taskset
或 sched_setaffinity()
让进程或线程只在特定的 vCPU 上运行。
c#include <sched.h> #include <stdio.h> int main() { cpu_set_t mask; CPU_ZERO(&mask); CPU_SET(1, &mask); // 绑定到 CPU 1 if (sched_setaffinity(0, sizeof(mask), &mask) == 0) { printf("进程绑定到 CPU 1\n"); } return 0; }
CPU_ZERO()
清空 CPU 集合CPU_SET(1, &mask)
让进程只运行在 CPU 1sched_setaffinity()
设置 CPU 亲和性概念 | C/C++ 相关函数 | 作用 |
---|---|---|
进程 | fork() , exec() , wait() | 资源分配单位,独立地址空间 |
线程 | pthread_create() , pthread_join() | 共享进程资源,调度单位 |
虚拟内存 | malloc() , mmap() , sbrk() | 每个进程有独立的地址空间 |
虚拟 CPU(vCPU) | sched_setaffinity() , taskset | 让多个任务共享物理 CPU |
mmap()
、pthread_create()
等函数来管理虚拟内存和 CPU 调度。希望这些 C/C++ 代码示例能帮你更直观地理解 Linux 操作系统如何管理进程、线程、虚拟内存和 vCPU!
再聊聊进程间通讯和线程之间的同步互斥
进程间通信(IPC)和线程同步互斥是多任务编程中必须解决的重要问题。进程间通信用于在不同进程之间交换数据,而线程同步互斥用于控制同一进程内的多个线程的执行顺序,避免竞态条件(race condition)。
进程间通信的主要方法包括:
管道是一种单向的通信机制,一个进程写入数据,另一个进程读取数据。通常用于父子进程之间的通信。
pipe()
进行进程间通信c#include <stdio.h> #include <unistd.h> int main() { int fd[2]; // fd[0]: 读端, fd[1]: 写端 pipe(fd); // 创建管道 pid_t pid = fork(); if (pid == 0) { // 子进程 close(fd[0]); // 关闭读端 write(fd[1], "Hello, parent!", 14); close(fd[1]); } else { // 父进程 close(fd[1]); // 关闭写端 char buffer[20]; read(fd[0], buffer, sizeof(buffer)); printf("父进程收到: %s\n", buffer); close(fd[0]); } return 0; }
特点
消息队列是基于内核的队列,允许进程间发送和接收结构化的消息。
msgget()
进行进程间通信c#include <stdio.h> #include <sys/ipc.h> #include <sys/msg.h> #include <string.h> struct msg_buffer { long msg_type; char msg_text[100]; }; int main() { key_t key = ftok("progfile", 65); int msgid = msgget(key, 0666 | IPC_CREAT); struct msg_buffer msg; msg.msg_type = 1; strcpy(msg.msg_text, "Hello, process!"); msgsnd(msgid, &msg, sizeof(msg), 0); printf("消息已发送: %s\n", msg.msg_text); return 0; }
特点
共享内存是最快的进程间通信方式,多个进程可以直接读写相同的内存区域,但需要使用**同步机制(如信号量)**来避免竞争。
shmget()
创建共享内存c#include <stdio.h> #include <sys/ipc.h> #include <sys/shm.h> #include <string.h> int main() { key_t key = ftok("shmfile", 65); int shmid = shmget(key, 1024, 0666 | IPC_CREAT); char *str = (char*) shmat(shmid, NULL, 0); strcpy(str, "Shared memory communication"); printf("写入共享内存: %s\n", str); shmdt(str); return 0; }
特点
信号是一种异步的进程间通信方式,用于通知进程发生了某种事件(如 SIGKILL
终止进程)。
kill()
发送信号c#include <stdio.h> #include <signal.h> #include <unistd.h> void signal_handler(int signum) { printf("收到信号: %d\n", signum); } int main() { signal(SIGUSR1, signal_handler); pid_t pid = fork(); if (pid == 0) { sleep(1); kill(getppid(), SIGUSR1); } else { pause(); // 等待信号 } return 0; }
特点
由于线程共享同一进程的资源,因此需要同步与互斥机制来确保多个线程正确访问共享资源,避免竞态条件(race condition)。
互斥锁用于防止多个线程同时访问共享资源。
pthread_mutex_t
c#include <pthread.h> #include <stdio.h> pthread_mutex_t lock; int counter = 0; void* thread_function(void* arg) { pthread_mutex_lock(&lock); // 加锁 counter++; printf("线程 %ld, counter: %d\n", pthread_self(), counter); pthread_mutex_unlock(&lock); // 解锁 return NULL; } int main() { pthread_t thread1, thread2; pthread_mutex_init(&lock, NULL); pthread_create(&thread1, NULL, thread_function, NULL); pthread_create(&thread2, NULL, thread_function, NULL); pthread_join(thread1, NULL); pthread_join(thread2, NULL); pthread_mutex_destroy(&lock); return 0; }
特点
读写锁允许多个线程同时读,但写时独占,适用于读多写少的情况。
pthread_rwlock_t
c#include <pthread.h> #include <stdio.h> pthread_rwlock_t rwlock; int data = 0; void* reader(void* arg) { pthread_rwlock_rdlock(&rwlock); printf("读线程 %ld 读取数据: %d\n", pthread_self(), data); pthread_rwlock_unlock(&rwlock); return NULL; } void* writer(void* arg) { pthread_rwlock_wrlock(&rwlock); data++; printf("写线程 %ld 修改数据: %d\n", pthread_self(), data); pthread_rwlock_unlock(&rwlock); return NULL; } int main() { pthread_t r1, r2, w1; pthread_rwlock_init(&rwlock, NULL); pthread_create(&r1, NULL, reader, NULL); pthread_create(&r2, NULL, reader, NULL); pthread_create(&w1, NULL, writer, NULL); pthread_join(r1, NULL); pthread_join(r2, NULL); pthread_join(w1, NULL); pthread_rwlock_destroy(&rwlock); return 0; }
特点
方法 | 适用于 | 主要特点 |
---|---|---|
进程间通信 | 独立进程数据共享 | 共享内存最快,管道/消息队列适用于父子进程 |
互斥锁 | 线程间数据保护 | 只能有一个线程访问共享资源 |
读写锁 | 读多写少 | 允许多个线程同时读,但写时互斥 |
进程间通信适用于多个进程共享数据,而线程同步用于避免数据竞争。在实际应用中,两者通常结合使用。
if(PLATFORM_X86_64)
set(CMAKE_AUTOMOC ON)
endif()
为何这个cmake文件只打开了第一个
在 CMake 代码中,set(CMAKE_AUTOMOC ON)
这一行被保留,而 CMAKE_AUTOUIC
和 CMAKE_AUTORCC
被注释掉了。这可能的原因包括以下几种:
AUTOMOC
,而 AUTOUIC
和 AUTORCC
可能不适用CMAKE_AUTOMOC
负责自动处理 Qt MOC(Meta-Object Compiler),主要用于Qt 的信号与槽机制,所以如果项目中使用了 Q_OBJECT
宏(比如 Qt 类派生的对象),那么 AUTOMOC
需要开启。CMAKE_AUTOUIC
负责自动处理 Qt Designer 生成的 .ui
文件,如果项目没有使用 Qt Designer 界面文件(.ui 文件),就不需要开启 AUTOUIC
。CMAKE_AUTORCC
负责自动处理 Qt 的资源文件(.qrc),如果项目没有使用 Qt 资源系统,也就不需要 AUTORCC
。可能情况:
Q_OBJECT
),但没有 UI 文件或 Qt 资源文件,所以只开启 AUTOMOC
。AUTOUIC
和 AUTORCC
可能引发问题某些情况下,CMAKE_AUTOUIC
和 CMAKE_AUTORCC
可能会导致编译问题,例如:
uic
或 rcc
工具(尤其是在 Qt 版本兼容性问题上)。CMAKE_AUTOUIC
自动生成的 ui_*.h
文件与手动编写的 UI 代码重复定义,会导致编译失败。AUTOUIC
仍然尝试生成代码,可能会造成多余的处理。可能情况:
AUTOUIC
或 AUTORCC
相关的构建问题,所以它们被注释掉以避免错误。PLATFORM_X86_64
仅需要 AUTOMOC
if(PLATFORM_X86_64) ... endif()
说明这些 CMake 选项可能是针对 X86_64 平台的。PLATFORM_X86_64
这个分支里,作者可能发现只需要 AUTOMOC
,不需要 AUTOUIC
和 AUTORCC
,所以把后两个注释掉了。可能情况:
AUTOUIC
和 AUTORCC
仅在某些平台上有用。AUTOUIC
和 AUTORCC
在 CMake 开发过程中,开发者可能会尝试关闭某些自动生成机制以进行调试,之后可能会重新开启。
可能情况:
AUTOUIC
和 AUTORCC
,但还未完全移除它们。这段 CMake 代码仅开启 CMAKE_AUTOMOC
的原因可能是:
AUTOUIC
)和 Qt 资源文件(不需要 AUTORCC
)。AUTOUIC
或 AUTORCC
的编译问题,导致开发者注释掉它们。AUTOUIC
和 AUTORCC
在这个平台上不需要。如果你发现项目确实需要 .ui
或 .qrc
文件的自动处理,可以尝试取消 CMAKE_AUTOUIC
和 CMAKE_AUTORCC
的注释,并检查是否会产生编译错误。
详细解读一、以下rapid的.mod文件,我是一个对相关语法零基础的人,从逻辑和语法两方面讲解:
MODULE SERVER
!//Robot configuration
PERS tooldata currentTool := [TRUE,[[0.5,0,595],[1,0,0,0]],[5,[0,0,595],[1,0,0,0],0,0,0]];
PERS tooldata sideKnifeTool := [TRUE,[[319.163,-3.89064,267.5],[1,0,0,0]],[100,[319,0,267],[1,0,0,0],0,0,0]];
PERS tooldata brushKnifeTool := [TRUE,[[0,328,376],[1,0,0,0]],[100,[0,450,273],[1,0,0,0],0,0,0]];
PERS wobjdata currentWobj := [FALSE,TRUE,"",[[0,0,0],[1,0,0,0]],[[0,0,0],[1,0,0,0]]];
PERS speeddata currentSpeed;
PERS zonedata currentZone;
VAR robtarget cartesianPose;
!//PC communication
VAR socketdev clientSocket;
VAR socketdev serverSocket;
VAR num instructionCode;
VAR num idCode;
VAR num params{20};
VAR num nParams;
PERS string ipController:= "127.0.0.1"; !robot default IP
!PERS string ipController:= "192.168.5.150"; !local IP for testing in simulation
PERS num serverPort:= 5000;
!//Motion of the robot
VAR robtarget cartesianTarget;
VAR jointtarget jointsTarget;
!//External axis position variables
VAR extjoint externalAxis;
!//Circular move buffer
VAR robtarget circPoint;
!//Correct Instruction Execution and possible return values
VAR num ok;
CONST num SERVER_BAD_MSG := 0;
CONST num SERVER_OK := 1;
CONST num CLLIOSION_ERROR := 100;
VAR bool CLLIOSION_SATUS := FALSE;
! ===================egm=========================
VAR egmident egmID1;
VAR egmstate egmSt1;
PERS egm_minmax egm_minmax_lin1:=[-2,2];
PERS egm_minmax egm_minmax_rot1:=[-2,2];
VAR pose corr_frame_offs:=[[0,0,0],[1,0,0,0]];
PERS egm_minmax egm_minmax_joint1:=[-0.1,0.1];
! ===================egm=========================
VAR robtarget p10:=[[1257.67,2112.50,2260.00],[0.707107,3.71468E-05,0.707107,-3.71468E-05],[0,0,0,0],[1000.67,9E+09,9E+09,9E+09,9E+09,9E+09]];
VAR robtarget p20:=[[400.33,2112.50,2260.00],[0.707107,3.71468E-05,0.707107,-3.71468E-05],[0,0,0,0],[400.33,9E+09,9E+09,9E+09,9E+09,9E+09]];
VAR robtarget p30:=[[3238.15,-0.22,2260.00],[0.707107,3.71468E-05,0.707107,-3.71468E-05],[-2,0,0,0],[125.652,9E+09,9E+09,9E+09,9E+09,9E+09]];
!////////////////
!LOCAL METHODS
!////////////////
!//Method to parse the message received from a PC
!// If correct message, loads values on:
!// - instructionCode.
!// - nParams: Number of received parameters.
!// - params{nParams}: Vector of received params.
PROC ParseMsg(string msg)
!//Local variables
VAR bool auxOk;
VAR num ind:=1;
VAR num newInd;
VAR num idcodeInd;
VAR num length;
VAR num indParam:=1;
VAR string subString;
VAR bool end := FALSE;
text!//Find the end character length := StrMatch(msg,1,"#"); IF length > StrLen(msg) THEN TPWrite "Corrupt message"; nParams := -1; ELSE !//Read Instruction code !// msg 03 238 ** ** # newInd := StrMatch(msg,ind," ") + 1; subString := StrPart(msg,ind,newInd - ind - 1); auxOk:= StrToVal(subString, instructionCode); ! as instruction format: [instruction_code] [id_code] [other params] ind := newInd; newInd := StrMatch(msg,ind," ") + 1; subString := StrPart(msg,ind,newInd - ind - 1); auxOk := StrToVal(subString, idCode); IF auxOk = FALSE THEN !//Impossible to read instruction code TPWrite "auxOk = FALSE"; nParams := -1; ELSE ind := newInd; !//Read all instruction parameters (maximum of 8) WHILE end = FALSE DO newInd := StrMatch(msg,ind," ") + 1; IF newInd > length THEN end := TRUE; ELSE subString := StrPart(msg,ind,newInd - ind - 1); auxOk := StrToVal(subString, params{indParam}); indParam := indParam + 1; ind := newInd; ENDIF ENDWHILE nParams:= indParam - 1; ENDIF ENDIF
ENDPROC
PROC intoegm()
EGMReset egmID1;
waittime 1;
EGMGetId egmID1;
egmSt1:=EGMGetState(egmID1);
TPWrite "EGM state: "\Num:=egmSt1;
IF egmSt1<=EGM_STATE_CONNECTED THEN
EGMSetupUC ROB_1,egmID1,"default","UCdevice"\pose;
ENDIF
cartesianPose := CRobT(\Tool:=tool0 \WObj:=wobj0);
MoveJ cartesianPose,v100,fine,tool0\WObj:=wobj0;
EGMActPose egmID1\Tool:=tool0,corr_frame_offs,EGM_FRAME_WORLD,corr_frame_offs,EGM_FRAME_WORLD\x:=egm_minmax_lin1\y:=egm_minmax_lin1\z:=egm_minmax_lin1\rx:=egm_minmax_rot1\ry:=egm_minmax_rot1\rz:=egm_minmax_rot1\LpFilter:=1\MaxPosDeviation:=10000\MaxSpeedDeviation:=100;
!EGMRunPose egmID1, EGM_STOP_HOLD\NoWaitCond\X\Y\Z\Rx\Ry\Rz\CondTime:=WAIT_MAX\RampInTime:=0.05\PosCorrGain:=1;
EGMRunPose egmID1, EGM_STOP_HOLD\X\Y\Z\Rx\Ry\Rz\CondTime:=WAIT_MAX\RampInTime:=0.05\PosCorrGain:=1;
TPWrite "finish";
ENDPROC
!//Handshake between server and client:
!// - Creates socket.
!// - Waits for incoming TCP connection.
PROC ServerCreateAndConnect(string ip, num port)
VAR string clientIP;
SocketCreate serverSocket;
SocketBind serverSocket, ip, port;
SocketListen serverSocket;
TPWrite "SERVER: Server waiting for incoming connections ...";
WHILE SocketGetStatus(clientSocket) <> SOCKET_CONNECTED DO
SocketAccept serverSocket,clientSocket \ClientAddress:=clientIP \Time:=WAIT_MAX;
IF SocketGetStatus(clientSocket) <> SOCKET_CONNECTED THEN
TPWrite "SERVER: Problem serving an incoming connection.";
TPWrite "SERVER: Try reconnecting.";
ENDIF
!//Wait 0.5 seconds for the next reconnection
WaitTime 0.5;
ENDWHILE
TPWrite "SERVER: Connected to IP " + clientIP;
ENDPROC
!//Parameter initialization
!// Loads default values for
!// - Tool.
!// - WorkObject.
!// - Zone.
!// - Speed.
PROC Initialize()
currentZone := [FALSE, 3, 4.5,4.5,0.45,4.5,0.45]; !z0
!currentZone := [FALSE, 0, 0, 0, 0, 0, 0]; !z0
!currentZone := [FALSE, 1, 1, 1, 0.1, 1, 0.1]; !z0
jointsTarget := CJointT();
externalAxis := jointsTarget.extax;
EGMReset egmID1;
ENDPROC
!////////////////////////
!//SERVER: Main procedure
!////////////////////////
PROC main()
!//Local variables
VAR string receivedString; !//Received string
VAR string sendString; !//Reply string
VAR string addString; !//String to add to the reply.
VAR bool connected; !//Client connected
VAR bool reconnected; !//Drop and reconnection happened during serving a command
VAR jointtarget jointsPose;
VAR bool conf_changed; !//is cartesianTarget conf changed
VAR confdata conf_data; !//
currentSpeed.v_tcp:=200;
currentSpeed.v_ori:=30;
!//Motion configuration
ConfL \On;
SingArea \Wrist;
TPWrite "pose now-----------------: " + NumToStr(p10.extax.eax_a,2);
!SingArea \LockAxis4;
!SingArea \Off;
!//Initialization of WorkObject, Tool, Speed and Zone
Initialize;
textTPWrite "Current version 20220511_00"; cartesianPose := CRobT(\Tool:=currentTool \WObj:=currentWObj); addString := NumToStr(cartesianPose.trans.x,2) + " "; addString := addString + NumToStr(cartesianPose.trans.y,2) + " "; addString := addString + NumToStr(cartesianPose.trans.z,2) + " "; addString := addString + NumToStr(cartesianPose.rot.q1,3) + " "; addString := addString + NumToStr(cartesianPose.rot.q2,3) + " "; addString := addString + NumToStr(cartesianPose.rot.q3,3) + " "; addString := addString + NumToStr(cartesianPose.rot.q4,3); !End of string TPWrite "pose now: " + addString; !//Socket connection connected:=FALSE; ServerCreateAndConnect ipController,serverPort; connected:=TRUE; conf_changed:=FALSE; !//Server Loop WHILE TRUE DO !//Initialization of program flow variables ok:=SERVER_OK; !//Correctness of executed instruction. reconnected:=FALSE; !//Has communication dropped after receiving a command? addString := ""; !//Wait for a command SocketReceive clientSocket \Str:=receivedString \Time:=WAIT_MAX; ParseMsg receivedString; !//Execution of the command TEST instructionCode CASE 0: !Ping IF nParams = 0 THEN ok := SERVER_OK; ELSE ok := SERVER_BAD_MSG; ENDIF CASE 1: !Cartesian Move IF nParams = 7 THEN cartesianTarget := CRobT(\Tool:=currentTool \WObj:=currentWObj); cartesianTarget.trans.x := params{1}; cartesianTarget.trans.y := params{2}; cartesianTarget.trans.z := params{3}; cartesianTarget.rot.q1 := params{4}; cartesianTarget.rot.q2 := params{5}; cartesianTarget.rot.q3 := params{6}; cartesianTarget.rot.q4 := params{7}; cartesianTarget.extax := externalAxis; IF conf_changed = TRUE THEN cartesianTarget.robconf.cf1:=conf_data.cf1; cartesianTarget.robconf.cf4:=conf_data.cf4; cartesianTarget.robconf.cf6:=conf_data.cf6; conf_changed:=FALSE; ENDIF ok := SERVER_OK; MoveL cartesianTarget, currentSpeed, currentZone, currentTool \WObj:=currentWobj ; ELSE ok := SERVER_BAD_MSG; ENDIF CASE 2: !Joint Move IF nParams = 6 THEN jointsTarget:=[[params{1},params{2},params{3},params{4},params{5},params{6}], externalAxis]; ok := SERVER_OK; ! TPWrite "start MoveAbsJ:" + NumToStr(params{1},2) ! + "," + NumToStr(params{2},2) ! + "," + NumToStr(params{3},2) ! + "," + NumToStr(params{4},2) ! + "," + NumToStr(params{5},2) ! + "," + NumToStr(params{6},2); MoveAbsJ jointsTarget, currentSpeed, currentZone, currentTool \Wobj:=currentWobj; ! TPWrite "finish"; ELSE ok :=SERVER_BAD_MSG; ENDIF CASE 3: !Get Cartesian Coordinates (with current tool and workobject) IF nParams = 0 THEN cartesianPose := CRobT(\Tool:=currentTool \WObj:=currentWObj); addString := NumToStr(cartesianPose.trans.x,2) + " "; addString := addString + NumToStr(cartesianPose.trans.y,2) + " "; addString := addString + NumToStr(cartesianPose.trans.z,2) + " "; addString := addString + NumToStr(cartesianPose.rot.q1,3) + " "; addString := addString + NumToStr(cartesianPose.rot.q2,3) + " "; addString := addString + NumToStr(cartesianPose.rot.q3,3) + " "; addString := addString + NumToStr(cartesianPose.rot.q4,3); ! TPWrite "MIDDLE add string = " + addString; ok := SERVER_OK; ELSE ok :=SERVER_BAD_MSG; ENDIF CASE 4: !Get Joint Coordinates IF nParams = 0 THEN jointsPose := CJointT(); addString := NumToStr(jointsPose.robax.rax_1,2) + " "; addString := addString + NumToStr(jointsPose.robax.rax_2,2) + " "; addString := addString + NumToStr(jointsPose.robax.rax_3,2) + " "; addString := addString + NumToStr(jointsPose.robax.rax_4,2) + " "; addString := addString + NumToStr(jointsPose.robax.rax_5,2) + " "; addString := addString + NumToStr(jointsPose.robax.rax_6,2); ok := SERVER_OK; ELSE ok:=SERVER_BAD_MSG; ENDIF CASE 5: !Get external axis positions IF nParams = 0 THEN jointsPose := CJointT(); addString := StrPart(NumToStr(jointsTarget.extax.eax_a, 2),1,8) + " "; addString := addString + StrPart(NumToStr(jointsTarget.extax.eax_b,2),1,8) + " "; addString := addString + StrPart(NumToStr(jointsTarget.extax.eax_c,2),1,8) + " "; addString := addString + StrPart(NumToStr(jointsTarget.extax.eax_d,2),1,8) + " "; addString := addString + StrPart(NumToStr(jointsTarget.extax.eax_e,2),1,8) + " "; addString := addString + StrPart(NumToStr(jointsTarget.extax.eax_f,2),1,8); !End of string ok := SERVER_OK; ELSE ok:=SERVER_BAD_MSG; ENDIF CASE 6: !Set Tool IF nParams = 7 THEN currentTool.tframe.trans.x:=params{1}; currentTool.tframe.trans.y:=params{2}; currentTool.tframe.trans.z:=params{3}; currentTool.tframe.rot.q1:=params{4}; currentTool.tframe.rot.q2:=params{5}; currentTool.tframe.rot.q3:=params{6}; currentTool.tframe.rot.q4:=params{7}; ok := SERVER_OK; TPWrite "set tool finish"; ELSE ok:=SERVER_BAD_MSG; ENDIF CASE 7: !Set Work Object IF nParams = 7 THEN currentWobj.oframe.trans.x:=params{1}; currentWobj.oframe.trans.y:=params{2}; currentWobj.oframe.trans.z:=params{3}; currentWobj.oframe.rot.q1:=params{4}; currentWobj.oframe.rot.q2:=params{5}; currentWobj.oframe.rot.q3:=params{6}; currentWobj.oframe.rot.q4:=params{7}; ok := SERVER_OK; ELSE ok:=SERVER_BAD_MSG; ENDIF CASE 8: !Set Speed of the Robot IF params{1} > 800 THEN TPWrite "speed over 800mm/s, set speed reject!!!"; ok:=SERVER_BAD_MSG; ELSE IF nParams = 4 THEN currentSpeed.v_tcp:=params{1}; currentSpeed.v_ori:=params{2}; currentSpeed.v_leax:=params{3}; currentSpeed.v_reax:=params{4}; ok := SERVER_OK; ELSEIF nParams = 2 THEN currentSpeed.v_tcp:=params{1}; currentSpeed.v_ori:=params{2}; ok := SERVER_OK; ELSE ok:=SERVER_BAD_MSG; ENDIF ENDIF CASE 9: !Set zone data IF nParams = 4 THEN IF params{1}=1 THEN currentZone.finep := TRUE; currentZone.pzone_tcp := 0.0; currentZone.pzone_ori := 0.0; currentZone.zone_ori := 0.0; ELSE currentZone.finep := FALSE; currentZone.pzone_tcp := params{2}; currentZone.pzone_ori := params{3}; currentZone.zone_ori := params{4}; ENDIF ok := SERVER_OK; ELSE ok:=SERVER_BAD_MSG; ENDIF CASE 10: !Set soft act IF nParams = 2 THEN IF params{1} = 0 THEN SoftDeact; ELSE SoftAct params{1},params{2}; ENDIF ok := SERVER_OK; ELSE ok:=SERVER_BAD_MSG; ENDIF CASE 13: !into egm IF nParams = 1 THEN IF params{1} = 1 THEN intoegm; ELSEIF params{1} = 2 THEN EGMStop egmID1,EGM_STOP_HOLD; ENDIF ok := SERVER_OK; ELSE ok :=SERVER_BAD_MSG; ENDIF CASE 34: !External Axis move IF nParams = 6 THEN externalAxis :=[params{1},params{2},params{3},params{4},params{5},params{6}]; ! jointsTarget := CJointT(); ! jointsTarget.extax := externalAxis; ok := SERVER_OK; ! MoveAbsJ jointsTarget, currentSpeed, currentZone, currentTool \Wobj:=currentWobj; ELSE ok := SERVER_BAD_MSG; ENDIF CASE 35: !Specify circPoint for circular move, and then wait on toPoint IF nParams = 7 THEN circPoint :=[[params{1},params{2},params{3}], [params{4},params{5},params{6},params{7}], [0,0,0,0], externalAxis]; ok := SERVER_OK; ELSE ok:=SERVER_BAD_MSG; ENDIF CASE 36: !specify toPoint, and use circPoint specified previously IF nParams = 7 THEN cartesianTarget :=[[params{1},params{2},params{3}], [params{4},params{5},params{6},params{7}], [0,0,0,0], externalAxis]; MoveC circPoint, cartesianTarget, currentSpeed, currentZone, currentTool \WObj:=currentWobj ; ok := SERVER_OK; ELSE ok:=SERVER_BAD_MSG; ENDIF CASE 98: !returns current robot info: serial number, robotware version, and robot type IF nParams = 0 THEN addString := GetSysInfo(\SerialNo) + "*"; addString := addString + GetSysInfo(\SWVersion) + "*"; addString := addString + GetSysInfo(\RobotType); ok := SERVER_OK; ELSE ok :=SERVER_BAD_MSG; ENDIF CASE 99: !Close Connection IF nParams = 0 THEN TPWrite "SERVER: Client has closed connection."; connected := FALSE; !//Closing the server SocketClose clientSocket; SocketClose serverSocket; !Reinitiate the server ServerCreateAndConnect ipController,serverPort; connected := TRUE; reconnected := TRUE; ok := SERVER_OK; ELSE ok := SERVER_BAD_MSG; ENDIF CASE 100: !Set axis conf IF nParams = 4 THEN conf_data.cf1:=params{1}; conf_data.cf4:=params{2}; conf_data.cf6:=params{3}; conf_data.cfx:=params{4}; conf_changed:=TRUE; TPWrite "SERVER: conf_changed!!!"; ok := SERVER_OK; ELSE ok:=SERVER_BAD_MSG; ENDIF DEFAULT: TPWrite "SERVER: Illegal instruction code"; ok := SERVER_BAD_MSG; ENDTEST !Compose the acknowledge string to send back to the client IF connected = TRUE THEN IF reconnected = FALSE THEN IF SocketGetStatus(clientSocket) = SOCKET_CONNECTED THEN sendString := NumToStr(instructionCode,0); sendString := sendString + " " + NumToStr(idCode,0); sendString := sendString + " " + NumToStr(ok,0); sendString := sendString + " " + addString; SocketSend clientSocket \Str:=sendString; ENDIF ENDIF ENDIF ENDWHILE
ERROR (LONG_JMP_ALL_ERR)
TPWrite "SERVER: ------";
TPWrite "SERVER: Error Handler:" + NumtoStr(ERRNO,0);
TEST ERRNO
CASE ERR_SOCK_CLOSED:
TPWrite "SERVER: Lost connection to the client.";
TPWrite "SERVER: Closing socket and restarting.";
TPWrite "SERVER: ------";
connected:=FALSE;
SocketClose clientSocket;
SocketClose serverSocket;
ServerCreateAndConnect ipController,serverPort;
reconnected:= FALSE;
connected:= TRUE;
RETRY;
CASE ERR_COLL_STOP:
WaitTime 0.1;
StopMove;
ClearPath;
StorePath;
CLLIOSION_SATUS := TRUE;
RestoPath;
StartMove;
EGMGetId egmID1;
egmSt1:=EGMGetState(egmID1);
TPWrite "EGM state: "\Num:=egmSt1;
IF egmSt1>EGM_STATE_CONNECTED THEN
EGMStop egmID1,EGM_STOP_HOLD;
ENDIF
textTPWrite "try next"; TRYNEXT; DEFAULT: TPWrite "SERVER: Unknown error."; TPWrite "SERVER: Closing socket and restarting."; TPWrite "SERVER: ------"; connected:=FALSE; SocketClose clientSocket; SocketClose serverSocket; ServerCreateAndConnect ipController,serverPort; reconnected:= FALSE; connected:= TRUE; RETRY; ENDTEST
ENDPROC
ENDMODULE
非常棒的问题,下面我将用逻辑 + 语法的方式,从零基础角度详细讲解这段 RAPID .mod
模块文件的结构、含义和执行流程。
RAPID 是 ABB 机器人控制器使用的编程语言,类似于 Pascal。它结构清晰,支持模块化、运动控制、TCP/IP通信、条件判断、错误处理等。
这段代码的顶层是一个 MODULE SERVER,表示这是一个名为 SERVER
的 RAPID 模块。
rapidMODULE SERVER ... ENDMODULE
**模块(MODULE)**是 RAPID 的逻辑单元,包含常量(CONST)、变量(VAR)、过程(PROC)、持久数据(PERS)、方法等。
rapidPERS tooldata currentTool := [...]; PERS wobjdata currentWobj := [...];
tooldata
:工具坐标系(比如机器人末端的刀、笔刷等)wobjdata
:工件坐标系PERS
表示该变量是 持久变量,保存在控制器中,不随程序结束而消失。rapidVAR socketdev clientSocket; PERS num serverPort := 5000;
用于 TCP 通信的 Socket 定义,服务端端口为 5000。
rapidVAR robtarget cartesianTarget; VAR jointtarget jointsTarget; VAR extjoint externalAxis;
robtarget
表示笛卡尔坐标的目标点(位置 + 姿态)jointtarget
表示关节角度位置extjoint
是外部轴的数据结构(如外部轨道)PROC main()
— 主程序主控制流程:负责初始化、接受客户端指令、解析并执行。
textInitialize() ↓ 建立 Socket 服务器 ↓ 进入主循环: 接收消息 → ParseMsg → 根据 instructionCode 调用对应 CASE → 执行运动、设置、查询等操作 → 生成回复 → 发送给客户端
PROC ParseMsg(string msg)
— 指令解析器从 PC 接收类似 "1 123 500 100 200 1 0 0 0 #"
的消息字符串:
1
是指令代码123
是 ID#
是结束符#
结束符instructionCode
、idCode
、params{}
数组中ServerCreateAndConnect
— 建立 Socket 服务使用 RAPID 的 Socket 接口:
SocketCreate
创建服务器SocketBind
绑定 IP/端口SocketListen
开始监听SocketAccept
等待客户端连接CASE
分发系统(指令码对应逻辑)这是核心的指令控制逻辑:
指令码 | 功能 | 说明 |
---|---|---|
0 | Ping | 检查连接 |
1 | MoveL(笛卡尔直线移动) | 传入目标位置 + 姿态 |
2 | MoveAbsJ(关节移动) | 传入6个关节角 |
3 | 获取当前笛卡尔位置 | 返回末端姿态 |
4 | 获取当前关节角度 | 返回6轴角度 |
5 | 获取外部轴位置 | 返回扩展轴 |
6 | 设置工具 | 设定 Tool |
7 | 设置工件坐标系 | 设定 WObj |
8 | 设置速度 | TCP、方向等速度 |
9 | 设置运动精度 zone | 精度控制(过渡、急停) |
13 | 进入 EGM 模式 | 实时控制接口 |
34-36 | 圆弧运动点设定 | 圆弧段前、中、后点 |
98 | 返回机器人信息 | 序列号、软件版本 |
99 | 关闭 socket 重启服务 | 通信重启 |
100 | 设置配置角 | robot configuration 角度(cf1, cf4, cf6) |
PROC intoegm()
— 启动 EGM 实时控制EGM(External Guided Motion)用于机器人高速实时控制(如视觉反馈系统),此处设置了坐标补偿、速度约束、姿态增益等。
rapidERROR (LONG_JMP_ALL_ERR) ... CASE ERR_SOCK_CLOSED: 重建 socket CASE ERR_COLL_STOP: 停止路径、清除、恢复 DEFAULT: 通用错误处理 + socket 重连 ENDPROC
使用 ERROR
块拦截异常,如通信断开、碰撞中止、未知错误等,并做出恢复动作,如重新连接 socket 或停止机器人。
元素 | 说明 |
---|---|
VAR | 变量(生命周期短,仅在程序运行时) |
PERS | 持久变量(生命周期长,断电不丢失) |
CONST | 常量 |
PROC | 过程/函数(函数没有返回值) |
IF/ELSE/WHILE | 条件和循环语句 |
CASE/TEST | 多分支选择语句 |
TPWrite | 向示教器终端输出信息(调试用) |
NumToStr() | 数字转字符串,常用于拼接和输出 |
CRobT() | 读取当前的笛卡尔位姿 |
MoveL/MoveAbsJ | 运动指令 |
SocketReceive | 接收 TCP 消息 |
StrPart/StrMatch/StrToVal | 字符串解析函数 |
本模块可以理解为一个机器人 TCP 服务端控制程序,用于接收来自 PC 的运动或配置指令,并实时反馈状态,适用于:
若你想进一步深入,比如添加自定义指令、扩展支持外部传感器、或调试 Socket 通信等,可以在此基础上逐步拓展模块功能。
如需我对特定函数、流程再进一步细讲,可以随时说!
你通过这个mod文件,能知道包含这个代码的工程是放在类似工控机的上面吗?又是如何控制ABB机械臂的呢?
通过分析这个 RAPID .mod
文件,我们可以推测出它是一个用于控制 ABB 机械臂的机器人程序,并且与外部设备(例如工控机)通过网络进行通信。下面我将详细分析该代码,以推测它如何控制机械臂,并如何与外部设备进行交互。
该 .mod
文件包含了控制ABB 机械臂的运动和配置的核心逻辑,具体的控制方式包括:
笛卡尔和关节运动:例如通过 MoveL
和 MoveAbsJ
控制机械臂在笛卡尔空间和关节空间的移动。
MoveL cartesianTarget, currentSpeed, currentZone, currentTool \WObj:=currentWobj;
cartesianTarget
进行直线运动。工具和工件坐标系的设置:工具和工件坐标系(Tool 和 Work Object)可以根据指令进行动态配置,允许机器人根据不同的工艺需求切换末端执行器(如刀具、焊枪等)。
速度和精度设置:使用 currentSpeed
和 currentZone
来设置机器人的运动速度和精度。例如,速度限制 (v_tcp
, v_ori
) 控制机器人末端的线速度和角速度,而 zone
用于控制运动的精度。
状态反馈:机器人的状态(如当前坐标、关节角度)可以通过 TCP 接口发送给外部设备,支持命令查询和实时反馈。例如,指令 CASE 3
和 CASE 4
用于获取当前的笛卡尔坐标和关节坐标。
这些操作表明,这段代码通过RAPID语言直接控制机械臂的动作,涉及到工具的设置、工作物坐标系、机器人位姿的实时反馈等。
从代码中可以看到 Socket
相关的命令:
rapidVAR socketdev clientSocket; VAR socketdev serverSocket; VAR string ipController := "127.0.0.1"; !机器人默认的 IP PERS num serverPort := 5000;
clientSocket
和 serverSocket
用于建立与外部设备(如工控机或PC)之间的通信。ipController
定义了与机器人通信的目标 IP 地址,而 serverPort
是监听端口(5000)。ServerCreateAndConnect
是一个方法,用于等待和接受来自工控机的 TCP 连接请求。一旦连接建立,机器人就开始接收外部设备发来的控制指令。ParseMsg
方法用于解析从外部设备收到的字符串消息,提取指令码、参数等内容,指令码对应具体的运动或配置命令(例如移动指令、设置工具等)。instructionCode
,主控制程序 main
会执行相应的操作(如笛卡尔/关节运动、设置速度、获取坐标等)。例如,如果接收到的指令是 关节运动,机器人就会执行 MoveAbsJ
指令,控制机械臂的关节角度。ERROR (LONG_JMP_ALL_ERR)
处理了连接丢失的情况,并在丢失连接后重新初始化 socket。params
数组进行处理。从代码来看,外部设备(例如工控机)扮演了控制和监视的角色,主要通过 TCP/IP 协议与 ABB 机器人进行通信:
这种通信模式非常适合需要远程控制机器人、监控机器人状态或动态调整控制参数的场景。
MoveL
, MoveAbsJ
)来实现位姿的变化。SocketSend
向外部设备反馈执行结果或当前状态,例如当前的笛卡尔坐标、关节角度等。从代码中分析,控制机器人时,**机器人控制器(例如 IRC5)**与工控机(可能是 PC 或工业计算机)通过 TCP/IP 网络进行通信。这个通信架构说明了以下几点:
因此,这段代码很可能属于一个 机器人控制系统,运行在一个类似于工控机的计算设备上,并通过网络与 ABB 机器人进行交互和控制。这类架构常见于自动化生产线、物流系统等领域。
如果有其他具体细节需要探讨或进一步深入分析,请随时告知!