配置(preferences)

  • 配置是“<key>=<value>”格式,它以着文本存储在一个叫preferences的文件,存放在小程序的userdata目录根下。
  • 在C代码,是用着trose_prefs封装配置。如果要在非主线程读取配置,为和主线程可能的写同步,须要先申请prefs.mutex锁。
C:/Users/ancientcc/我的文档/RoseApp/launcher/aplt_leagor_basic__documents/preferences
------
base_product="wheeltec"
base_serial="COM5"
base_serial_baudrate=115200
laser_product="N10"
laser_serial="COM3"
laser_serial_baudrate=230400
moveit_serial="COM5"
moveit_serial_baudrate=115200
[signature]
[/signature]

以上是一个配置文件示例。signature是个必须存在的空块,trose_prefs要用它判断此个preferences文件是否有效。

 

一、lua代码

1.1 在main.lua定义preferences_def变量

aplt_leagor_basic = {
	...
	preferences_def = {
		laser_product = "",
		laser_serial = "",
		laser_serial_baudrate = 115200,
		...
	};
};

启动小程序时,首先会从aplt_leagor_basic__documents/preferences文件读出配置,然后加载main.lua,解析当中的preferences_def,来同步配置。这同步表现在几个方面。

  1. 字段名(key)一样。告知存在哪些配置字段,新版本可以用这功能添加新字段,或删除不用了的字段。如果preferences_def有,preferences文件没有,那要加上,加上时,用的值就是这里指定的值。相应地,preferences_def没有,preferences文件有,那要删除。换句话说,在析解preferences_def后,对已放到内存中的配置,它存储的key和preferences_def一模一样。
  2. 值(value)类型一样。新版本可以用这功能更改值类型。解析preferences_def时,如果该key两处都存在,除非值类型发生变化,否则不替换。举个例子,laser_serial字段之前存的是string类型,现在改为integer,解析preferences_def时,发现前、后类型发生变化,会用此刻的integer值替换掉原值。注意,这里不区分float和integer,原因么,preferences文件是以文本存储值,文本“1239”,你无法确定它是float还是integer。

preferences_def中“def”是默认(default)的缩写。默认含义体现在两处,1)该字段在preferences_def存在,在preferences文件不存在,用这默认值作为值。2)值类型发生变化时,也将用这默认值。

preferences_def必须写全小程序会用到的所有字段。不论读取(_value)还是设置(_set_value),要是用到一个这里没出现字段,那都会报错。

 

1.2 读

local laser_product, laser_serial = aplt.preferences:_value("laser_product", "laser_serial");

aplt指的是小程序主表,像aplt_leagor_basic。preferences是C代码为lua环境创建的一个表,lua代码可用这个表中提供的_value方法读取配置值。参数是要读出的key,一次调用可以读多个key。

 

1.3 写

aplt.preferences:_set_value("laser_product", "N10", "laser_serial_baudrate", 115200);

_set_value用于写配置。参数是要写的[key, value]对,一次调用可以写多个key。

对value,不能是nil。如果想从配置删除该key,用preferences_def机制。

 

二、C代码

在C代码,是用着trose_prefs封装配置。小程序在运行时,全局变量aplt::curr_aplt指向一个class tapplet,当中就有个类型是trose_prefs字段prefs。

const trose_prefs& aplt_prefs = aplt::curr_aplt->prefs;
std::string path = aplt_prefs.get_str("base_serial");
int baudrate aplt_prefs.get_int("base_serial_baudrate", nposm);

上面是一个典型的在C代码读取配置逻辑。get_str用于读取一个string类型的值。get_int读取int类型,第二个参数是当“base_serial_baudrate”这个key不存在时,要返回的值。当然,由于preferences_def机制,理论上不会出现key不存在情况,不过rose处理app配置用的也是trose_prefs,那里就须要第二个参数。

 

三、多线程同步

  • 主线程。读不需要加锁,写需要。
  • 非主线程。读需要加锁,不允许写。

不论lua,还是C,归根用的都是class trose_prefs。trose_prefs采用的同步机制就决定了配置的同步要求。

只能在主线程更改配置。在lua,窗口肯定运行在主线程。所以对要更改配置,推荐方式是给用户提供个窗口界面。如果必须要在C代码,那确保在主线程。

若想在非主线程读取配置,为和主线程可能的写同步,须要先申请prefs.mutex锁。至于在主线程中读,因为写肯定在主线程,天然同步,不须要锁。

 

四、lua修改了配置后,如何尽快让c++代码知道配置改变了

lua修改了配置,意味着已把新配置写到preferences文件。这里说的是,让c++尽快知道哪些配置的值可能变了,以便c++知道后,去preferences文件读这几个配置,用到本地变量。

这里涉及到的两情况,都只是通知到正运行的小程序任务,即使这样任务,还要考虑它此刻没在运行。因而,任何小程序任务在开始运行时,都得有段读“全部”配置到本地变量的代码。

这里分两种情况,一是不须要通知,c++代码会在某个阶段去读取。二是lua调用send_cpp_id,告知c++,配置变了。

不建议c++以着时间片方法去轮询配置值。考虑到主线程(lua)修改配置不是个常用操作,用时间片轮询,太耗资源。

 

4.1 不须要通知,c++代码会在某个阶段去读取

目前符合的就这几个配置:底盘驱动、雷达驱动、机械臂驱动的型号、串口路径和波特率。

以底盘驱动为例。底盘驱动所在小程序在前台运行,修改了或型号,或串口路径,或波特率。退出,这时launcher会判断型号、串口路径和波特率是否有发生改变。一旦有改变,它就会重启底盘驱动节点。

 

4.2 lua调用send_cpp_id,告知c++,配置变了

以kHome小程序改变了“正坐(正面)”相关配置为例,说下这过程。

 

1、(lua)posture.lua以参数cpp_id填aplt.cpp_id_save_sf_6fields,调用send_cpp_id

<aplt_leagor_khome>/lua/posture.lua
------
function aplt_leagor_khome__posture.save_pref_fields(current_layer)
	local aplt = aplt_leagor_khome;
	local this = aplt_leagor_khome__posture;
	local var = this.var;

	if (current_layer == this.SIT_FRONT_LAYER) then
		var.window_:send_cpp_id(aplt.cpp_id_save_sf_6fields);

	elseif (current_layer == this.SIT_SIDE_LAYER) then
		var.window_:send_cpp_id(aplt.cpp_id_save_ss_4fields);

	elseif (current_layer == this.SIT_SHARE_LAYER) then
		var.window_:send_cpp_id(aplt.cpp_id_save_sshare_3fields);
	end

	return true;
end

小程序对可能参数进行分类,用一个int值标识每一种类。和“正坐(正面)”相关的配置归为一类,数值是aplt.cpp_id_save_sf_6fields(100)。

send_cpp_id是window表的一个操作。

 

2、(c++)向可能正运行着小程序任务,调用它的fg_aplt_send_cpp_id方法

C++处理send_cpp_id,会向正运行着小程序任务发fg_aplt_send_cpp_id。因为有个规则,当运行前台小程序时,不会运行后台小程序。于是会发向的任务只可能是7个:6个驱动和底盘子任务。

fg_aplt_send_cpp_id是任务的方法,而不是小程序。如果小程序A实现了底盘、雷达驱动,并且都正在运行,那此个send_cpp_id,小程序A就会收到两次fg_aplt_send_cpp_id,当然,这是发向在不同任务。这里建议把每个任务归为一套或多套配置,然后用cpp_id进行区分。尽量不要让某个配置涉及多个任务。

 

3、(c++)特定任务读配置,应用到本地变量

void tkpose::fg_aplt_send_cpp_id(const std::string& window_id, int cpp_id)
{
	if (cpp_id == cpp_id_save_sf_6fields || cpp_id == cpp_id_save_ss_4fields || 
		cpp_id == cpp_id_save_sshare_3fields) {
		threading::lock lock(variable_mutex_);
		reload_pref_fields(cpp_id);
	}
}

这是在主线程读配置,不须要加配置锁。上面这锁是同步tkpose内部变量。当这些配置赋值给内部变量,非主线程可能正要用这些变量,所以须要加锁。

window_id是lua代码发出send_cpp_id的window_,指示发出的是哪个窗口。示例:aplt_leagor_khome__posture。

 

4.3 在send_cpp_id机制下,小程序要做的几件事

1、对配置进行分类,用一个int值标识某类别。

小程序可使用的最小值是rosek.cpp_id_aplt_min(100)。对小于100的,保留给系统使用。

 

2、在lua代码,修改了配置后,在适当时机调用send_cpp_id

时机一般分在两个。

时机一:是个多tab窗口,要切到另外tab

function aplt_leagor_khome__posture.did_navigation_pre_change(report, from, to)
	local this = aplt_leagor_khome__posture;
	local var = this.var;

	this.save_pref_fields(from:at());

	return true;
end

 

时机二:所在窗口要被关闭

function aplt_leagor_khome__posture.post_show()
	local this = aplt_leagor_khome__posture;
	local var = this.var;

	this.save_pref_fields(var.current_layer_);
	var = {};
end

两种时机时,都要调用send_cpp_id。

 

3、在c代码,重载fg_aplt_send_cpp_id,保存配置到本地变量

要注意,send_cpp_id只是发向了此时正运行着的小程序任务。对该任务来说,还要考虑到它没在运行时,配置被lua改了。因而任务在开始运行,依旧是得有段读配置到本地变量的代码。

tkpose::tkpose(const aplt::tapplet::ttask& cfg_task, ttask_vars& task_vars)
{
        ...
	reload_pref_fields(cpp_id_save_sf_6fields);
	reload_pref_fields(cpp_id_save_ss_4fields);
	reload_pref_fields(cpp_id_save_sshare_3fields);
        ...
}

在构造kpose时,读配置到本地变量的代码。

 

全部评论: 0

    写评论: