@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<CPoint3D>& 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<int>					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<bool>                	m_laser_scan_flag;          //雷达扫描的使能标志位
	//雷达状态和任务状态同步，m_scan_flag停止之后，还要等任务执行完成,才会切回 ready。
	std::atomic<Laser_statu>			m_laser_statu;             	//雷达工作状态，基类三线程的状态
	//注：m_laser_statu是基类的三线程的状态，和livox sdk后台线程状态没有任何关系。
	//子类重载 is_ready()，里面增加livox sdk后台线程状态的判断。
..
	std::atomic<int>            		m_points_count;         	//雷达采集点的计数
	Thread_safe_queue<Binary_buf*>		m_queue_laser_data;         //二进制缓存的队列容器
	Binary_buf							m_last_data;			    //上一个二进制缓存，用作数据拼接
..
	std::atomic<bool> 					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<pcl::PointXYZ>::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<pcl::PointXYZ>::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<pcl::PointXYZ>::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<bool> 		m_kill_flag;			//是否杀死线程，让线程强制退出，
	std::atomic<bool> 		m_pass_ever;			//线程能否直接通过等待，对后面的线程也生效。
	std::atomic<bool> 		m_pass_once;			//线程能否直接通过等待，一次（通过一次之后，wait里面自动改为false）
	std::mutex 				m_mutex;				//线程的锁
	std::condition_variable m_condition_variable;	//线程的条件变量
}


class Thread_safe_queue <<    template<class T>    >>
{
	//等待并弹出数据，成功弹出则返回true
	bool wait_and_pop(T& value);
	//尝试弹出数据，成功弹出则返回true
	bool try_pop(T& value);
	//等待并弹出数据，成功弹出则返回true
	std::shared_ptr<T> wait_and_pop();
	//尝试弹出数据，成功弹出则返回true
	std::shared_ptr<T> 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<std::shared_ptr<T>> 	m_data_queue;			//队列数据，使用智能指针shared_ptr
	std::condition_variable 		m_data_cond;			//条件变量
	std::atomic<bool> 				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