@startuml skinparam classAttributeIconSize 0 title Laser_base 雷达的基类 enum Laser_statu { //雷达工作状态,基类三线程的状态 //connect_laser 和 disconnect_laser 中要切换状态 LASER_DISCONNECT <===> LASER_READY //start_scan 和 stop_scan 和 end_task 中要切换状态 LASER_READY <===> LASER_BUSY //默认值 LASER_DISCONNECT =0 LASER_DISCONNECT =0, //雷达断连 LASER_READY =1, //雷达正常待机,空闲 LASER_BUSY =2, //雷达正在工作,正忙 LASER_FAULT =3, //雷达错误 } class Laser_base { //雷达的基类,不能直接使用,必须子类继承 ==public:== Laser_base() = delete; Laser_base(const Laser_base& other) = delete; .. //唯一的构造函数,按照设备名称和雷达参数来创建实例。 //input:id: 雷达设备的id,(唯一索引) //input:laser_param:雷达的参数, //注:利用protobuf创建 laser_parameter 类,然后从文件读取参数 Laser_base(int laser_id,Laser_proto::laser_parameter laser_param); //析构函数 ~Laser_base(); .. //雷达链接设备,为3个线程添加线程执行函数。 virtual Error_manager connect_laser(); //雷达断开链接,释放3个线程 virtual Error_manager disconnect_laser(); //对外的接口函数,负责接受并处理任务单, //input:p_laser_task 雷达任务单,基类的指针,指向子类的实例,(多态) //注:这个函数为虚函数,实际的处理任务的代码由子类重载并实现。 virtual Error_manager execute_task(Task_Base* p_laser_task); //检查雷达状态,是否正常运行 virtual Error_manager check_laser(); //雷达的启动接口函数, 让雷达进行扫描,一般需要子类重载,不同的雷达开始方式不同。 virtual Error_manager start_scan(); //雷达的停止接口函数, 让雷达停止扫描,一般需要子类重载,不同的雷达结束方式不同。 virtual Error_manager stop_scan(); //结束任务单,stop之后,要检查线程状态和数据结果,然后才能 end_task virtual Error_manager end_task(); ==public:== //设置保存文件的路径,并打开文件, Error_manager set_open_save_path(std::string save_path,bool is_save=true); //关闭保存文件,推出前一定要执行 Error_manager close_save_path(); //判断雷达状态是否为待机,如果已经准备好,则可以执行任务。 //子类重载 is_ready(),里面增加livox sdk后台线程状态的判断。 virtual bool is_ready(); //获取雷达id int get_laser_id(); ==protected:== //接受二进制消息的功能函数,每次只接受一个CBinaryData // 纯虚函数,必须由子类重载, virtual bool receive_buf_to_queue(Binary_buf& binary_buf) = 0; //线程执行函数,将二进制消息存入队列缓存, void thread_receive(); .. //将二进制消息转化为三维点云的功能函数,每次只转化一个CBinaryData, // 纯虚函数,必须由子类重载, virtual Buf_type transform_buf_to_points(Binary_buf* p_binary_buf, std::vector& point3D_cloud)=0; //线程执行函数,转化并处理三维点云。 void thread_transform(); .. //公开发布雷达信息的功能函数, Error_manager publish_laser_to_message(); //线程执行函数,公开发布雷达的相关信息,用作上位机的监视。 static void thread_publish(Laser_base* p_laser); .. //获取雷达状态 Laser_statu get_laser_statu(); ==protected:== //初始化变换矩阵,设置默认值 Error_manager init_laser_matrix(); //设置变换矩阵,用作三维点的坐标变换, Error_manager set_laser_matrix(double* p_matrix, int size); .. //三维点的坐标变换的功能函数,从雷达自己的坐标系,转化到公共坐标系,(目前以plc为公共坐标系) virtual CPoint3D transform_by_matrix(CPoint3D point); ==protected:== //为了保证多线程的数据安全,修改共享数据必须加锁。 atomic 和 安全队列 可以不加锁进行读写 //建议:判断标志位使用 atomic, 容器要封装并使用 安全容器, std::mutex m_laser_lock; //雷达数据锁 .. std::atomic m_laser_id; //雷达设备id Laser_proto::laser_parameter m_laser_param; //雷达的配置参数 //雷达变换矩阵,三维点的坐标变换的矩阵,从雷达自己的坐标系,转化到公共坐标系,(目前以plc为公共坐标系) //必须在set_laser_matrix之后,才能使用。(connect时,从雷达的配置参数导入) double mp_laser_matrix[LASER_MATRIX_ARRAY_SIZE]; //雷达变换矩阵 //雷达扫描事件的标志位,受start和stop控制,然后其他线程判断m_scan_flag标准位,来进行启停。 std::atomic m_laser_scan_flag; //雷达扫描的使能标志位 //雷达状态和任务状态同步,m_scan_flag停止之后,还要等任务执行完成,才会切回 ready。 std::atomic m_laser_statu; //雷达工作状态,基类三线程的状态 //注:m_laser_statu是基类的三线程的状态,和livox sdk后台线程状态没有任何关系。 //子类重载 is_ready(),里面增加livox sdk后台线程状态的判断。 .. std::atomic m_points_count; //雷达采集点的计数 Thread_safe_queue m_queue_laser_data; //二进制缓存的队列容器 Binary_buf m_last_data; //上一个二进制缓存,用作数据拼接 .. std::atomic m_save_flag; //雷达保存文件的使能标志位 std::string m_save_path; //雷达保存文件的保存路径 CLogFile m_binary_log_tool; //二进制缓存的日志工具 CLogFile m_points_log_tool; //三维点云的日志工具 .. //线程指针的内存管理,由Connect和Disconnect进行分配和释放。 std::thread* mp_thread_receive; //接受缓存的线程指针 Thread_condition m_condition_receive; //接受缓存的条件变量 std::thread* mp_thread_transform; //转化数据的线程指针 Thread_condition m_condition_transform; //转化数据的条件变量 std::thread* mp_thread_publish; //发布信息的线程指针 Thread_condition m_condition_publish; //发布信息的条件变量 .. //任务单的指针,实际内存由应用层管理, //接受任务后,指向新的任务单 Laser_task * mp_laser_task; //任务单的指针 } class CPoint3D { //三维点 double x, y, z; } class CLogFile { //日志文件 void open(const char *_Filename, bool binary = false); void write(const char *_Str, streamsize _Count); void close(); fstream m_file; std::mutex m_lock; } class laser_parameter { //雷达参数 } class Laser_task { //雷达模块的任务指令,从Task_Base继承, //补充了雷达专属的数据输入和输出 ==public:== //初始化任务单,必须初始化之后才可以使用,(必选参数) Error_manager task_init(Task_statu task_statu, pcl::PointCloud::Ptr p_task_point_cloud, std::mutex* p_task_cloud_lock); .. //初始化任务单,必须初始化之后才可以使用,(可选参数) Error_manager task_init(Task_statu task_statu, std::string & task_statu_information, unsigned int task_frame_maxnum, pcl::PointCloud::Ptr p_task_point_cloud, std::mutex* p_task_cloud_lock); .. //push点云,把 point_xyz 添加到 p_task_point_cloud 里面。 Error_manager task_push_point(pcl::PointXYZ point_xyz); ==protected:== //点云的采集帧数最大值,任务输入 unsigned int m_task_frame_maxnum; //点云保存文件的路径,任务输入 std::string m_task_save_path; //三维点云的数据保护锁,任务输入 std::mutex* mp_task_cloud_lock; //采集结果,三维点云容器的智能指针,任务输出 //这里只是指针,实际内存由应用层管理,初始化时必须保证内存有效。 pcl::PointCloud::Ptr mp_task_point_cloud; } class Task_Base { //任务单基类 ==public:== //初始化任务单,初始任务单类型为 UNKONW_TASK virtual Error_manager init(); //更新任务单 Error_manager update_statu(Task_statu task_statu,std::string statu_information=""); //获取任务类型 Task_type get_task_type(); ==protected:== Task_type m_task_type; //任务类型 Task_statu m_task_statu; //任务状态 std::string m_task_statu_information; //任务状态说明 Error_manager m_task_error_manager;//错误码,任务故障信息,任务输出 } Laser_task <-- Task_Base : inherit class Error_manager { //错误码管理 } class Binary_buf { //二进制缓存, //比较前面部分的buf是否相等,使用 other.m_length 为标准 bool is_equal_front(const Binary_buf& other); //比较前面部分的buf是否相等,len为0时,使用strlen(buf)为标准,不比较结束符'\0' bool is_equal_front(const char* p_buf, int len = 0); .. char* get_buf()const; int get_length()const; -- char* mp_buf; //二进制缓存指针 int m_length; //二进制缓存长度 } class Thread_condition { bool wait(); //等待一定的时间(默认时间单位:毫秒ms),由 is_pass_wait 决定是否阻塞。 bool wait_for_millisecond(unsigned int millisecond); //唤醒已经阻塞的线程,唤醒一个线程 void notify_one(bool pass_ever, bool pass_once = false); //唤醒已经阻塞的线程,唤醒全部线程 void notify_all(bool pass_ever, bool pass_once = false); //杀死所有的线程,强制退出线程函数,只是操作受当前Thread_condition影响的所有线程 void kill_all(); //判断是否或者,return !m_kill_flag bool is_alive(); ==protected:== static bool is_pass_wait(Thread_condition * other); .. std::atomic m_kill_flag; //是否杀死线程,让线程强制退出, std::atomic m_pass_ever; //线程能否直接通过等待,对后面的线程也生效。 std::atomic m_pass_once; //线程能否直接通过等待,一次(通过一次之后,wait里面自动改为false) std::mutex m_mutex; //线程的锁 std::condition_variable m_condition_variable; //线程的条件变量 } class Thread_safe_queue << template >> { //等待并弹出数据,成功弹出则返回true bool wait_and_pop(T& value); //尝试弹出数据,成功弹出则返回true bool try_pop(T& value); //等待并弹出数据,成功弹出则返回true std::shared_ptr wait_and_pop(); //尝试弹出数据,成功弹出则返回true std::shared_ptr try_pop(); //插入一项,并唤醒一个线程, bool push(T new_value); //清除队列,只是将队列的实例抛出。T是实例内存,系统自动回收的。 bool clear(); //清除队列,抛出之后还要delete指针。T是动态内存,需要手动回收的。 bool clear_and_delete(); ==public:== //判空 bool empty(); //获取队列大小 size_t size(); //设置队列为退出状态。并唤醒所有的线程,使其通过wait void termination_queue(); //唤醒队列,恢复所有的功能函数。wait_and_pop会继续阻塞。 void wake_queue(); //获取退出状态 bool get_termination_flag(); //判断是否可以直接通过wait, m_data_queue不为空或者m_termination终止时都可以通过等待。 bool is_pass(); ==protected:== std::mutex m_mutex; //队列的锁 std::queue> m_data_queue; //队列数据,使用智能指针shared_ptr std::condition_variable m_data_cond; //条件变量 std::atomic m_termination_flag; //终止标志位 } Laser_base <-- Laser_statu : include Laser_base <-- CPoint3D : include Laser_base <-- CLogFile : include Laser_base <-- laser_parameter : include Laser_base <-- Laser_task : include Laser_base <-- Error_manager : include Laser_base <-- Binary_buf : include Laser_base <-- Thread_condition : include Laser_base <-- Thread_safe_queue : include @enduml