一种动态联动的实现方法

安防领域中的联动规则

有安防领域相关的开发经历的人知道,IPCamera可以配置使能"侦测"功能,并且指定仅针对图像传感器的某个区载进行侦测。除了基本的"移动侦测"外,侦测的功能点还有细化的类别,如人员侦测、车辆侦测、烟雾侦测等。这些侦测配置后若被触发,会在IPCamera内部产生一些事件;这事件发生时,IPC内部会有相应的动作,例如:邮件报告、图片上传、视频上传等;还可以配置IPC蜂鸣器报警、外置LED灯闪烁等(若有相应的硬件外设)。在IPC的Web界面,用户可以方便地配置这些选项:

如何实现这一功能呢?笔者咨询过相关在安防产业工作的朋友,得到的方案有两种:

  • IPC内部产生的事件种类是固定的,能够做出动作也是有限的,那么直接使用C/C++编码实现这些逻辑;
  • 将事件与动作分别独立为应用中的子模块,使用"胶水"语言Lua将二者联动起来;

朋友也坦言,第一种方案在代码库中存在很久了,代码逻辑错踪复杂;尤其是一些定制类的IPCamera项目,需求不同会造成开发人员花费不少的时间在动态联动的代码修改上。而第二种方案,虽然在项目开发上可以缩短时间,但只有少部分项目中有,接口也不一样,帮助不大:第一种方案仍是团队内部项目开发的主要方法。

动态联动的问题抽象

本文中,笔者将此类动态联动的问题抽象化,并提供上面提到的第二种方法的演示实现。假设IPCamera内部的图像处理会产生五个维度的状态量,名称分别为:
ex_var1/ex_var2/ex_var3/ex_var4/ex_var5

这五个状态量,与三个联动规则相关(即事件,当判定条件为真时会触发);也可能会产生另外三个联动规则不为真时的动作。也就是说,三个联动规则需要这五个变量来决定是否为真。笔者将这些信息写入到配置文件中由演示应用读取:

json 复制代码
{
  "linked-vars": {
    "ex_var1": 1,
    "ex_var2": 2,
    "ex_var3": 3,
    "ex_var4": 4.2,
    "ex_var5": 5
    },
    "linked-action": [
    {
      "rule": "(ex_var1 + ex_var2 * 2 + ex_var3 * 3) > 14",
      "true": "echo 'The planet Earth is freaking too hot'",
      "false": "echo 'The planet Earth is freaking too cold'"
    },
    {
      "rule": "math.floor(ex_var4 * 5 / 4) > ex_var5",
      "true": "echo 'Note that floor(ex_var4 * 5 / 4) > ex_var5'",
      "false": "echo 'Note that floor(ex_var4 * 5 / 4) <= ex_var5'"
    },
    {
      "rule": "(ex_var1 + ex_var2 + ex_var3 + ex_var4 + ex_var4) >= 16",
      "true": "echo 'All example values summed up greater or equal to 16.'",
      "false": "echo 'All example values summed up less than 16'"
    }
  ]
}

注意,上面的rule即一个动态联动规则是否判定为真的条件。它是一个Lua语句;这里只一行语句,在具体的实践层面上,可以是多行的计算(这里只支持一个语句)。上面还给出当事件联动为真时要执行的动作,例如,当全球平均温度大于14(度)时,就会触发事件:

复制代码
echo 'The planet Earth is freaking too hot'

也就是简单地调用/bin/sh输出一行信息。当该条件为假时,则会触发另一个动作(可选)。

动态联动规则的实现

上面的JSON配置文件描述了我们抽象化的动态联动问题。笔者编写的演示例子,会根据上面的信息构造一个Lua脚本,用于计算三个联动规则是否成立:

lua 复制代码
local ex_var1 = 1
local ex_var3 = 3
local ex_var5 = 5
local ex_var2 = 2
local ex_var4 = 4.2
function rulefunc_1()
        return (ex_var1 + ex_var2 * 2 + ex_var3 * 3) > 14
end
function rulefunc_2()
        return math.floor(ex_var4 * 5 / 4) > ex_var5
end
function rulefunc_3()
        return (ex_var1 + ex_var2 + ex_var3 + ex_var4 + ex_var4) >= 16
end
function rulefunc_all()
        local link_tval_ = 0
        link_tval_ = link_tval_ + ex_var1
        link_tval_ = link_tval_ + ex_var3
        link_tval_ = link_tval_ + ex_var5
        link_tval_ = link_tval_ + ex_var2
        link_tval_ = link_tval_ + ex_var4
        return link_tval_
end

根据Lua引用规则说明,五个演示变量都是上面构造的四个函数的上级变量(upvalue)。注意,上面的Lua函数rulefunc_2只引用了两个变量;也就是说,一些联动规则并不需要全部的状态量。当上面的代码编译为Lua字节码后,如何通一个函数的上级变量索引来更新所有的上级变量呢?这正是rulefunc_all函数的用处:它引用了全部五个变量,它的名称是固定的,虽然它不会被调用,但我们可以通过它来更新该构造脚本的全部上级变量。之后,这个脚本会使用luaL_dostring API预编译为一个Lua状态机:

c 复制代码
rext_any_t rext_lua_new(const struct rext_var * lvar, int * errp) {
    ...
    luaL_openlibs(L); /* load standard library */
    if (script != NULL && script[0] != '\0') {
        int ret;
        ret = luaL_dostring(L, script);
        if (ret != 0) {
            *errp = EINVAL;
            lua_close(L);
            L = NULL;
        }
    }

注意到,上面的配置信息给出了五个演示变量的初始值。这些值在判定一个联动规则是否被触发前,需要被更新。笔者采用的方法,正是上面提到的,通过函数rulefunc_all来更新:

c 复制代码
int rext_upval_set(rext_any_t anyt, const struct rext_var * rfunc,
    int upindex, const struct rext_var * rval)
{
  ...
      ret = rext_pushval(anyt, rval);
    if (ret < 0) {
        lua_settop(L, oldtop);
        return -7;
    }

    valp = lua_setupvalue(L, ntop, upindex);
    if (valp == NULL) {
        lua_settop(L, oldtop);
        return -8;
    }

之后,笔者简单编了5个变量的值,来测试演示动态联动:

rust 复制代码
    let t_map: HashMap<&str, f64> = HashMap::from([
        ("ex_var1", 1f64),
        ("ex_var2", 2f64),
        ("ex_var3", 3f64),
        ("ex_var4", 4f64),
        ("ex_var5", 5f64),
    ]);
    linked.act(&t_map);

    let t_map: HashMap<&str, f64> = HashMap::from([
        ("ex_var1", 2f64),
        ("ex_var2", 3f64),
        ("ex_var3", 4f64),
        ("ex_var4", 5f64),
        ("ex_var5", 6f64),
    ]);
    linked.act(&t_map);

动态联动的演示代码与结果

笔者提供了联动规则的演示代码,感兴趣的可以从此处获取。它的正常运行需要Ubuntu系统下安装Lua5.1开发库及Rust编译器:

sh 复制代码
sudo apt install lua5.1 liblua5.1-0-dev rust-full bindgen

笔者运行的结果如下:

复制代码
Created Lua-state machine: 0x562e74aecfc0
Inserting ex_var5 => 1
Inserting ex_var4 => 2
Inserting ex_var3 => 3
Inserting ex_var1 => 4
Inserting ex_var2 => 5
Lua upvalues found: 5
The planet Earth is freaking too cold
false: 0 => (ex_var1 + ex_var2 * 2 + ex_var3 * 3) > 14
Note that floor(ex_var4 * 5 / 4) <= ex_var5
false: 0 => math.floor(ex_var4 * 5 / 4) > ex_var5
All example values summed up less than 16
false: 0 => (ex_var1 + ex_var2 + ex_var3 + ex_var4 + ex_var4) >= 16

The planet Earth is freaking too hot
true : 0 => (ex_var1 + ex_var2 * 2 + ex_var3 * 3) > 14
Note that floor(ex_var4 * 5 / 4) <= ex_var5
false: 0 => math.floor(ex_var4 * 5 / 4) > ex_var5
All example values summed up greater or equal to 16.
true : 0 => (ex_var1 + ex_var2 + ex_var3 + ex_var4 + ex_var4) >= 16

至此,我们就可以避免使用C/C++实现复杂的联动规则是否为真的判断逻辑了。这不仅仅是提高了开发效率,而且还避免过多的编码可能会引入的缺陷。

相关推荐
deeper_wind28 分钟前
MySQL数据库基础(小白的“升级打怪”成长之路)
linux·数据库·mysql
Raners_33 分钟前
【Linux】文件权限以及特殊权限(SUID、SGID)
linux·安全
egoist202336 分钟前
【Linux仓库】进程优先级及进程调度【进程·肆】
linux·运维·服务器·进程切换·进程调度·进程优先级·大o1调度
2301_1472583692 小时前
7月2日作业
java·linux·服务器
xuanzdhc6 小时前
Linux 基础IO
linux·运维·服务器
愚润求学6 小时前
【Linux】网络基础
linux·运维·网络
bantinghy7 小时前
Linux进程单例模式运行
linux·服务器·单例模式
小和尚同志8 小时前
29.4k!使用 1Panel 来管理你的服务器吧
linux·运维
帽儿山的枪手8 小时前
为什么Linux需要3种NAT地址转换?一探究竟
linux·网络协议·安全
shadon1789 天前
回答 如何通过inode client的SSLVPN登录之后,访问需要通过域名才能打开的服务
linux