Rule engine design

Posted by Nunners on Sun, 20 Feb 2022 10:19:12 +0100

summary

The so-called rule engine refers to the mechanism of if some condition match then trigger some thing. Condition is a series of expression s, such as when the equipment status is changed to offline (attribute), and someone passes through the gate (event); Trigger a series of actions, such as storing in the database and sending alarm information. Even trigger the action of other equipment. For example, if the temperature is too high, judge the fire and trigger the spray linkage.

Abstracting rules so that users can define them freely is design rules. Associating rules and actions is essentially a callback registration mechanism. When the condition s match, the code will query the actions defined by the user and trigger them in turn (of course, some actions written by the server).

Conditions that can match and actions that can trigger are templates defined by our implementation. Users can fill in custom parameters in rule/action. Therefore, the design rule engine is to design a template language. The front end can render this language for users to fill in parameters, and the back end can parse this language to realize the corresponding logic. Because this is equivalent to designing a language. For example, if you write python, you can write condition directly in Python and then eval the calculation result. For Java, open source drools is essentially a scripting language, which requires too much work for the client to parse and is unlikely to be adopted; The open source version of urule is too weak and can only be used for reference. However, if you use a workflow engine, such as flowable, which integrates DMN decision tree internally, it is essentially a rule engine and can be used directly. However, we simplify it here and use our own implementation.

The design of condition and action depends on the functions provided by the device itself, so it is necessary to associate the specific model of the device with the supported condition/action. For example, if the attendance machine supports temperature reporting, it can support the definition of temperature alarm. If it is not supported, the user cannot set the temperature alarm.

Rule engine core data

Because we use the gateway as the middle layer communication, the communication of mqtt protocol is asynchronous. Therefore, we define the rule as the event triggered by the device, and the event is associated with the specific field (i.e. attribute, attr). The type of attr (such as bool, enumeration value, date, time, integer, floating point and string) determines the associated front-end controls (generally switches and inputbox) and operators (greater than or less than, string inclusion, regular matching, etc.). The user splices attr and values to form a complex expression, that is, the rule is established Event minimization disassembly has nothing to do with the equipment itself, that is, the manufacturer's model either supports an event or does not support it. There is no possibility of supporting only part of it.

The actions that can be triggered are supported by the device and our service. Like event, we need to define actions (and parameters) that allow users to trigger freely, and then associate these actions with the device model. After the user creates a rule, select the actions supported by the service / device and fill in the necessary parameters, which can be combined freely.

This is different from Alibaba cloud's design, because all our devices need our own access, so we know the boundary between event and cmd. As a PAAS platform, Alibaba cloud allows all devices to access, so we can't predefine these things.

Access control equipment is still used here for example. event support includes:

  1. Attendance data report (EVENT_UPLOAD_ATTEND), attr includes visitor identity (personId, personName), time (timestamp, type time), face image (image, type) and direction of entry and exit;
  2. Event_upload_temp, attr includes visitor ID, personname, time and temperature;

In many cases, 1 and 2 are reported at the same time, but we still divide them into two events according to the principle of minimization, so that we can distinguish the equipment models that support body temperature measurement from those that do not. Similarly, the data of the environment detector may be reported in many pieces at a time, but we still separate them into different events to freely combine condition s.

Here, trigger action s include:

  1. System services. For example, several alarms can be predefined for users. Including: in station message, offline push, SMS push, wechat push and telephone alarm, which are combined to users as parameters.
  2. Actions supported by the access control device itself, such as remote door opening;
  3. Actions supported by other IOT devices, such as site broadcasting;

Users can make the following combinations:

For EVENT_UPLOAD_ATTEND, if timestamp > = 21:00 and direction = 2 then trigger alarm (param = in station signal), that is, if someone leaves from the access control after 21:00, the in station signal alarm will be used.

For EVENT_UPLOAD_TEMP, if temperature > = 37.3 then trigger broadcaster 125 action 1 template 5. If someone's temperature is greater than 37.0 degrees, the broadcast with id 125 in the construction site will be used to execute action 1 (assuming language broadcast), and the broadcast content is template 5 (predefined temperature alarm). Obviously, template 5 here may use the user's name in event, so you need to pass event as the parameter of action.

Storage design

First define iot_rule_base, that is, the predefined of event/action (the contents of this table are written directly to the library and are not edited through the interface), for example:

{
  	id: int,
    rule_code:str, //Rule unique descriptor, such as EVENT_UPLOAD_BODY_TEMP temperature report, or ACTION_SYS_NOTIFY system notification
    device_type: int, //Device type, 0 indicates global support
    rule_type: int, //Event or action
    name: str, //Event name, brief description
    params:{  //json schema
      {
        "type": "object",
        "properties": {
          "time": {
            "type": "string",
            "title": "Time of occurrence"
          },
          "direction": {
            "type": "integer",
            "title": "direction",
            "enum": [
              1,
              2
            ],
            "enumDesc": "enter;Out"
          },
          "personId": {
            "type": "string",
            "title": "user ID"
          }
        }
      }
    }
}

Then we define iot_device_model table, which associates the device type with event/action:

{
    id: long
    type: int,
    name: str, //Description of the model, such as face recognition (without temperature measurement), face recognition (including temperature measurement)
    events:[1,2,3], //Supported events, json array
    actions:[1,2,3] //Supported actions, json array
}

The predefined events and actions associated with the current version of the equipment model have not been displayed on the interface, and V2 should be added (in the equipment model editing interface). The events and actions supported by the device model are indicated here.

The following defines the rule, namely iot_rule_trigger

{
    id: long,
    group_code: str, //Define the organization of the rule
    device_type: int, //Equipment type
    device_model: int, //Equipment model ID; If 0, all models of this type are indicated
    device_ids: [], //List of equipment triggering the rule; For all devices of this model, enter [0]
    event_code: str, //Unique identifier of the triggered event
    condition: str, //Regular expression (for backend parsing)
    dom: str, //dom description corresponding to the front end (for front-end rendering)
    memo: str, //Rule comments
}

v2 version adds a layer of abstraction to actions, that is, execution library. Execute = Action + parameter, that is, the preset parameterized action. Execution is associated to the rule. Defined as iot_rule_exec:

{
	id: long,
  group_code: str, //Define the organization of execution
  name: str, //Name of execution
  params: {
  	"actOn":[{
        code:"ACTION_SYS_NOTIFY", 
        delay: 0, //Delay execution time in seconds
        pushType:1, 
        cycle: 30, //Minimum trigger period in seconds
        level:1, //0-ordinary message, level 1-4:x
        targets:[int] //User id
    	},{
      	code: "ACTION_SPRAY_SWITCH",
        delay: 300,
        children:[{ //Sub events, in strict order
        	code:"ACTION_SYS_NOTIFY",
          delay: 0,
          pushType:1,
          level: 0,
          targets:[]
        }]
      }],
    "actOff":[]
  }, //json, action when a rule is triggered
  rules: [], //List of associated rules
}

params is fixed with the parameter act_on and act_off indicates the action list triggered when entering / leaving the rule; The following is a tree structure: the data in the array at the same level can run in parallel; The child event specified by children must run after the parent event.

There is a parallel relationship between multiple executions, and there is no order.

Design of action/event data structure

Use json schema syntax and make the following extensions:

  1. enum can use enumCode to indicate the featCode of the background dictionary; If it is a simple enumeration, use the standard syntax (refer to the above direction). At this time, the Chinese description divided by English semicolon is in enumDesc; In addition, if there is a parentCode field, it indicates the corresponding dictionary item and filters the parent node (refer to the API related to the dictionary); Take direction as an example. Suppose the dictionary code is ATTEND_DIR, then the definition form is as follows:
{
  "type": "object",
  "properties": {
    "direction": {
      "type": "integer",
      "title": "direction",
      "enumCode": "ATTEND_DIR"
    }
  }
}

If you don't need enumCode, you can write out the entry and exit options directly, that is:

{
  "type": "object",
  "properties": {
    "direction": {
      "type": "integer",
      "title": "direction",
      "enum": [	//Here, the optional values and the corresponding desc are given directly
        1,
        2
      ],
      "enumDesc": "enter;Out"
    }
  }
}
  1. There is a fixed field time to indicate the time point of the event. You can configure the date, week and time for it. For example, configure the trigger rule between 8-18 o'clock on a working day only; However, some special configurations different from the general format should be used here. This version is not required for the time being;
  2. If you want to alarm the attendance of some special users, you may need to add a person selector when configuring rules for personId. Not for the time being;
  3. Some fields will have the unit attribute, which indicates the Chinese description of the unit, such as decibels, for sending messages or front-end display;
  4. The first layer of event has an eventType attribute, which is 0 by default, indicating that it can be automatically released; 1 indicates that it cannot be automatically released; Like AI alarm / helmet alarm, it is a non cancellable alarm. At this time, a new alarm record will be created every time an event is triggered, which must be manually cancelled by the user (in other words, the "homogeneous message frequency" option set in the execution is invalid.)
    condition syntax

condition: mongo query expression is used here, which is similar to the initial design of q parameter. The expression of the above example is:

{
    "key1":{"$lt": 8}, //key1 is less than 8$ lt(<), $gt(>), $le(<=), $ge(>=), $ne(!=), $eq(=)
    "key2":{"$ge": "37.3"}, //key2 ≥ 37.3
    "$or":[{"key3":{"$regex": "\d{3}"}},{"key4": "3"}], //key3 satisfies the regularity, or key4=3, and $like is used to indicate inclusion
    "key4":{"$in":[1, 2, 3]}, //key4=1 or 2 or 3
    "key6.0": 10, //The first element of the array is 10
    "key7.subKey1":{"$lt": 10}, //The child element of object is less than 10

}

In order to facilitate self parsing, the front end can use another set of syntax, that is, the dom field mentioned earlier, which will not be parsed by the server.

Existing Action

Alarm message

rule_code: ACTION_SYS_NOTIFY 
{
  delay: 0, //Delay time in seconds
  pushType:1, //Push type, bit operation, 1: in station message, 2: offline push, 4: SMS, 8: phone, 16: smart broadcast, 32: wechat
  broaderIds: [1], //The user selects the associated voice column id. note that there is no voice column available at the government and enterprise end, so this field must be empty or empty
  cycle:60, //The minimum trigger period, in seconds, during which the same type of alarm can send one notification at most
  level:1, //0-general message, 1-4: risk level
  targets:[],//Push the target id. note that if it is through the rules set by the government and enterprise end, it will be pushed to the government and enterprise end; Otherwise, it is pushed to the project end
  targetRoles: [], //Push role id, if the user selects all, then roleId=-1
  muteTimes:[["19:00", "07:00"]],//Do not disturb the time period. Note that if the left is larger, it indicates that this time period is across days
  muteType: 1, //The silent push type is also a bit operation
}

Spray linkage

rule_code: ACTION_SPRAY_SWITCH

{
  turnOn: bool, //On or off
  delay: 0, //Delayed opening
  deviceIds: [], //Select spraying equipment
}

Theoretically, the actions that users can set need to be determined according to the current equipment model of the project: the back end obtains all the equipment of the project, and then takes all the actions and reassembles them and returns them to the front end.

The current version only needs to support alarm notification.

reference

  1. From 0 to 1: build a powerful and easy-to-use rule engine

Topics: Java