Skip to content

Latest commit

 

History

History
666 lines (577 loc) · 25.5 KB

plain_rules.md

File metadata and controls

666 lines (577 loc) · 25.5 KB

Plain rules

Plain rules allow a full customization of a rule with specific needs by means of setting the final EPL statement used by the Esper engine inside perseo-core. In order to work with perseo (front-end) properly, the EPL statement must fulfill several conventions for the rule to be able to operate on the incoming events and trigger adequate actions.

The “anatomy” of a rule is as follows

{
   "name":"blood_rule_update",
   "text":"select *,\"blood_rule_update\" as ruleName, *, ev.BloodPressure? as Pressure, ev.id? as Meter from pattern [every ev=iotEvent(cast(cast(BloodPressure?,String),float)>1.5 and type=\"BloodMeter\")]",
   "action":{
      "type":"update",
      "parameters":{
         "name":"abnormal",
         "value":"true",
         "type":"boolean"
      }
   }
}

The fields (all must be present) are

  • name: name of the rule, used as identifier
  • text: EPL statment for the rule engine in perseo-core
  • action: action to be performed by perseo if the rule is fired from the core

The rule name must consist of the ASCII characters from A to Z, from a to z, digits (0-9), underscore (_) and dash (-). It can have a maximum length of 50 characters.

The field action can be also an array of "actions", objects with the same structure than the single action described in the rest of the documentation. Each of those actions will be executed when the rule is fired, avoiding to duplicate a rule only for getting several actions executed. For practical purposes, it is the same result that would be obtained with multiple rules with the same condition.

EPL text

The field text of the rule must be a valid EPL statement and additionally must honor several restrictions to match expectations of perseo and perseo-core.

EPL is documented in [Esper web] (http://www.espertech.com/esper/documentation.php)

A EPL statement to use with perseo could be:

select *, "blood_rule_update" as ruleName,
	 ev.BloodPressure? as Pressure, ev.id? as Meter
from pattern
 [every ev=iotEvent(cast(cast(BloodPressure?,String),float)>1.5 and type="BloodMeter")]
  • The rule name must be present with ruleName alias. It must be equal to the ‘name’ field of the rule object
  • The from pattern must name the event as ev and the event stream from which take events must be iotEvent
  • A type= condition must be concatenated for avoiding mixing different kinds of entities

The used entity's attributes must be cast to float in case of being numeric (like in the example). Alphanumeric values must be cast to String. Nested cast to string and to float is something we are analyzing, and could be unnecessary in a future version. Use it by now. All the attributes in the notification from Orion are available in the event object, ev, like ev.BlodPressure? and ev.id?. A question mark is necessary for EPL referring ‘dynamic’ values. Metadata is also available as explained in Metadata and object values.

Actions

When a rule is fired, the "complex event" generated by the select clause is sent to perseo (front-end) which executes the action using the generated event as parameter to the action.

There are a predefined set of actions: sending a SMS, sending a email, updating an attribute of the entity that fired the rule and making a HTTP POST to a provided URL.

The action must be provided in the action field of rule. An example:

   "action":{
      "type":"update",
      "parameters":{
         "name":"abnormal",
         "value":"true",
         "type":"boolean"
      },
      "interval" : "30e3"
   }

The type field is mandatory and must be one of

  • update: update an entity's attribute
  • sms: send a SMS
  • email: send an email
  • post: make an HTTP POST
  • twitter: send a twitter

An action can optionally have a field interval for limiting the frequency of the action execution (for the rule and entity which fired it). The value is expressed in milliseconds and is the minimum period between executions. Once the action is executed successfully, it won't be executed again until that period has elapsed. All the request from core to execute it are silently discarded. This way, the rate of executions for an entity and rule can be limited. (Strictly, the minimum time between executions)

String substitution syntax

Some of the fields of an action (see detailed list below) can include a reference to one of the fields of the notification/event. This allows include information as the received "pressure" value, the id of the device, etc. For example, the actions sms, email, post include a field template used to build the body of message/request. This text can include placeholders for attributes of the generated event. That placeholder has the form ${X} where X may be:

  • id for the id of the entity that triggers the rule.
  • type for the type of the entity that triggers the rule
  • Any other value is interpreted as the name of an attribute in the entity which triggers the rule and the placeholder is substituted by the value of that attribute.

All alias for simple event attributes or "complex" calculated values can be directly used in the placeholder with their name. And any of the original event attributes (with the special cases for id and type meaning entity ID and type, respectively) can be referred too.

This substitution can be used in the the following fields:

  • template, from, to and subject for email action
  • template, url, qs, headers, json for post action
  • template for sms action
  • template for twitter action
  • id, type, name, value, ìsPattern for update action

SMS action

Sends a SMS to a number set as an action parameter with the body of the message built from the template

 "action": {
        "type": "sms",
        "template": "Meter ${Meter} has pressure ${Pressure}.",
        "parameters": {
            "to": "123456789"
        }
    }

The field parameters include a field to with the number to send the message to.

The template and to fields perform attribute substitution.

email action

Sends an email to the recipient set in the action parameters, with the body mail build from the template field. A field to in parameters sets the recipient and a field fromsets the sender's email address. Also the subject of the email can be set in the field subject in parameters.

 "action": {
        "type": "email",
        "template": "Meter ${Meter} has pressure ${Pressure} (GEN RULE)",
        "parameters": {
            "to": "someone@telefonica.com",
            "from": "cep@system.org",
            "subject": "It's The End Of The World As We Know It (And I Feel Fine)"
        }
    }

The template, from, to and subject fields perform string substitution.

update attribute action

Updates one or more attributes of a given entity (in the Context Broker instance specified in the Perseo configuration). The parameters map includes the following fields:

  • id: optional, the id of the entity which attribute is to be updated (by default the id of the entity that triggers the rule is used, i.e. ${id})
  • type: optional, the type of the entity which attribute is to be updated (by default the type of the entity that triggers the rule is usedi.e. ${type})
  • isPattern: optional, false by default
  • attributes: mandatory, array of target attributes to update. Each element of the array must contain the fields
    • name: mandatory, attribute name to set
    • value: mandatory, attribute value to set
    • type: optional, type of the attribute to set. By default, not set (in which case, only the attribute value is changed).
  • trust: optional, trust token for getting an access token from Auth Server which can be used to get to a Context Broker behind a PEP.
"action":{
        "type":"update",
        "parameters":{
            "id":"${id}_mirror",
            "attributes": [
                {
                    "name":"abnormal",
                    "type":"boolean",
                    "value":"true"
                }
            ]
        }
    }

The name parameter cannot take id or type as a value, as that would refer to the entity's id and the entity's type (which are not updatable) and not to an attribute with any of those names. Trying to create such action will return an error.

The id, type, name, value, ìsPattern fields perform string substitution.

First time an update action using trust token is triggered, Perseo interacts with Keystone to get the temporal auth token corresponding to that trust token. This auth token is cached and used in every new update associated to the same action. Eventually, Perseo can receive a 401 Not Authorized due to auth token expiration. In that case, Perseo interacts again with Keystone to get a fresh auth token, then retries the update that causes the 401 (and the cache is refreshed with the new auth token for next updates).

It could happen (in theory) that a just got auth token also produce a 401 Not authorized, however this would be an abnormal situation: Perseo logs the problem with the update but doesn't try to get a new one from Keystone. Next time Perseo triggers the action, the process may repeat, i.e. first update attemp fails with 401, Perseo requests a fresh auth token to Keystone, the second update attemp fails with 401, Perseo logs the problem and doesn't retry again.

HTTP request action

Makes an HTTP request to an URL specified in url inside parameters, sending a body built from template. The parameters field can specify

  • method: optional, HTTP method to use, POST by default
  • url: mandatory, URL target of the HTTP method
  • headers: optional, an object with fields and values for the HTTP header
  • qs: optional, an object with fields and values to build the query string of the URL
  • json: optional, an object that will be sent as JSON. String substitution will be performed in the keys and values of the object's fields. If present, it overrides template from action
 "action":{
      "type":"post",
      "template":"BloodPressure is ${BloodPressure}",
      "parameters":{
         "url": "http://localhost:9182/${type}/${id}",
         "method": "PUT",
         "headers": {
            "Content-type": "text/plain",
            "X-${type}-pressure": "${BloodPressure}"
         },
         "qs": {
            "${id}": "${BloodPressure}"
         }
      }
   }

Note that you can encode a JSON in the template field:

 "action": {
        "type": "post",
        "template": "{\"meter\":\"${Meter}\", \"pressure\": ${Pressure}}",
        "parameters": {
            "url": "http://${target_host}:${target_port}/myapp/${id}",
            "headers": {
                        "Content-type": "application/json",
                        "X-${type}-pressure": "${BloodPressure}"
            },
            "qs": {
                        "${id}": "${BloodPressure}"
            }
        }
    }

or use the json parameter

 "action": {
        "type": "post",
        "parameters": {
            "url": "http://${target_host}:${target_port}/myapp/${id}",
            "headers": {
                        "Content-type": "application/json",
                        "X-${type}-pressure": "${BloodPressure}"
            },
            "qs": {
                        "${id}": "${BloodPressure}"
            },
            "json": {
               "meter": "${meter}",
               "${id}": "${type}",
               "pressure": "${pressure}"
            }
        }
    }

The template and url fields and both the field names and the field values of qs and headers and json perform string substitution.

twitter action

Updates the status of a twitter account, with the text build from the template field. The field parameters must contain the values for the consumer key and secret and the access token key and access token secret of the pre-provisioned application associated to the twitter user.

 "action": {
        "type": "twitter",
        "template": "Meter ${Meter} has pressure ${Pressure} (GEN RULE)",
        "parameters": {
          "consumer_key": "xvz1evFS4wEEPTGEFPHBog",
          "consumer_secret": "L8qq9PZyRg6ieKGEKhZolGC0vJWLw8iEJ88DRdyOg",
          "access_token_key": "xvz1evFS4wEEPTGEFPHBog",
          "access_token_secret": "L8qq9PZyRg6ieKGEKhZolGC0vJWLw8iEJ88DRdyOg"
        }
    }

The template field performs string substitution.

Metadata and object values

Metadata values can be accessed by adding the suffix __metadata__x to the attribute name, being x the name of the metadata attribute. This name can be used in the EPL text of the rule and in the parameters of the action which accept string substitution. If the value of the metadata item is an object itself, nested fields can be referred by additional suffixes beginning with double underscore and the hierarchy can be walked down by adding more suffixes, like __metadata__x__subf1__subf12.

For example: The metadata in an event/notice like

{
  "subscriptionId" : "51c04a21d714fb3b37d7d5a7",
  "originator" : "localhost",
  "contextResponses" : [
    {
      "contextElement" : {
        "attributes" : [
          {
            "name" : "BloodPressure",
            "type" : "centigrade",
            "value" : "2",
            "metadatas": [{
              "crs": {
                "value": {"system": "WGS84"}
              }]
            }
          },
		{
            "name" : "TimeInstant",
            "type" : "urn:x-ogc:def:trs:IDAS:1.0:ISO8601",
            "value" : "2014-04-29T13:18:05Z"
          }
        ],
        "type" : "BloodMeter",
        "isPattern" : "false",
        "id" : "bloodm1"
      },
      "statusCode" : {
        "code" : "200",
        "reasonPhrase" : "OK"
      }
    }
  ]
}

could be used by a rule so

{
    "name": "blood_rule_email_md",
    "text": "select *,\"blood_rule_email_md\" as ruleName, *,ev.BloodPressure? as Pression, ev.id? as Meter from pattern [every ev=iotEvent(cast(BloodPressure__metadata__crs__system?,String)=\"WGS84\" and type=\"BloodMeter\")]",
    "action": {
        "type": "email",
        "template": "Meter ${Meter} has pression ${Pression} (GEN RULE) and system is ${BloodPressure__metadata__crs__system}",
        "parameters": {
            "to": "someone@org.com",
            "from": "perseo_cep@telefonica.com",
            "subject": "MD VALUE: ${BloodPressure__metadata__crs__system}"
        }
    }
}

Generally, fields of attribute values which are objects themselves are accessible by adding to the name of the field a double underscore prefix, so an attribute x with fields a, b, c, will allow these fields to be referred as x__a, x__b and x__c.

Note: be aware of the difference between the key metadatas used in the context broker notificacions (v1), ending in s and the infix metadata, without the final s, used to access fields from EPL and actions.

Location fields

Fields with geolocation info with the formats recognized by NGSI v1, are parsed and generate two pairs of pseudo-attributes, the first pair is for the latitude and the longitude and the second pair is for the x and y UTMC coordinates for the point. These pseudo-attributes ease the use of the position in the EPL sentence of the rule. These derived attributes have the same name of the attribute with a suffix of __lat and __lon , and __x and __y respectively.

The formats are

So, a notification in the deprecated format like

{
   "subscriptionId":"57f73930e0e2c975a712b8fd",
   "originator":"localhost",
   "contextResponses":[
      {
         "contextElement":{
            "type":"Vehicle",
            "isPattern":"false",
            "id":"Car1",
            "attributes":[
               {
                  "name":"position",
                  "type":"coords",
                  "value":"40.418889, -3.691944",
                  "metadatas":[
                     {
                        "name":"location",
                        "type":"string",
                        "value":"WGS84"
                     }
                  ]
               }
            ]
         }
      }
   ]
}

will propagate to the core, (and so making available to the EPL sentence) the fields position__lat, position__lon , position__x, position__y

{  
   "noticeId":"169b0920-8edb-11e6-838d-0b633312661c",
   "id":"Car1",
   "type":"Vehicle",
   "isPattern":"false",
   "subservice":"/",
   "service":"unknownt",
   "position":"40.418889, -3.691944",
   "position__type":"coords",
   "position__metadata__location":"WGS84",
   "position__metadata__location__type":"string",
   "position__lat":40.418889,
   "position__lon":-3.691944,
   "position__x":657577.4234800448,
   "position__y":9591797.935076647
}

Analogously, a notification in "geopoint" format, like

{
   "subscriptionId":"57f73930e0e2c975a712b8fd",
   "originator":"localhost",
   "contextResponses":[
      {
         "contextElement":{
            "type":"Vehicle",
            "isPattern":"false",
            "id":"Car1",
            "attributes":[
               {
                  "name":"position",
                  "type":"geo:point",
                  "value":"40.418889, -3.691944"
               }
            ]
         },
         "statusCode":{
            "code":"200",
            "reasonPhrase":"OK"
         }
      }
   ]
}

will send to core an event with the fields position__lat, position__lon, position__x, position__y also

{  
   "noticeId":"7b8f1c50-8eda-11e6-838d-0b633312661c",
   "id":"Car1",
   "type":"Vehicle",
   "isPattern":"false",
   "subservice":"/",
   "service":"unknownt",
   "position":"40.418889, -3.691944",
   "position__type":"geo:point",
   "position__lat":40.418889,
   "position__lon":-3.691944,
   "position__x":657577.4234800448,
   "position__y":9591797.935076647

An example of rule taking advantage of these derived attributes could be:

{
    "name": "rule_distance",
    "text": "select *, \"rule_distance\" as ruleName from pattern [every ev=iotEvent(Math.pow((cast(cast(position__x?,String),float) - 618618.8286057833), 2) + Math.pow((cast(cast(position__y?,String),float) - 9764160.736945232), 2) < Math.pow(5e3,2))]",
    "action": {
        "type": "email",
        "template": "${id} (${type}) is at ${position__lat}, ${position__lon} (${position__x}, ${position__y})",
        "parameters": {
            "to": "someone@tid.es",
            "from": "system@iot.tid.es",
            "subject": "${id} is coming"
        }
    }
}

that will send an email when the entity with attribute position is less than 5 km far away from Cuenca. It uses the circle equation, (x - a)^2 + (y - b)^2 = d^2, being (a, b) 618618.8286057833 and 9764160.736945232 the UTMC coordinates of Cuenca and d the distance of 5 000 m.

Note: for long distances the precision of the computations and the distortion of the projection can introduce some degree of inaccuracy.

Time fields

Some attributes and metadata, supposed to contain a time in ISO8601 format, will generate a pseudo-attribute with the same name as the attribute (or metadata field) and a suffix "__ts", with the parsed value as milliseconds for Unix epoch. This value makes easier to write the EPL text which involves time comparisons. The fields (attribute or metadata) supposed to convey time information are

  • Fields named TimeInstant
  • Fields of type DateTime
  • Fields of type urn:x-ogc:def:trs:IDAS:1.0:ISO8601

Additionally, some derived pseudo-attributes are included also

  • x__day: the day of the month (1-31) for the specified date according to local time.
  • x__month: the month (1-12) in the specified date according to local time.
  • x__year: the year (4 digits) of the specified date according to local time.
  • x__hour: the hour (0-23) in the specified date according to local time.
  • x__minute: the minutes (0-59) in the specified date according to local time.
  • x__second: the seconds (0-59) in the specified date according to local time.
  • x__millisecond: the milliseconds (0-999) in the specified date according to local time.
  • x__dayUTC: the day of the month (1-31) in the specified date according to universal time.
  • x__monthUTC: the month (1-12) in the specified date according to universal time.
  • x__yearUTC: the year (4 digits) of the specified date according to universal time.
  • x__hourUTC: the hour (0-23) in the specified date according to universal time.
  • x__minuteUTC: the minutes (0-59) in the specified date according to universal time.
  • x__secondUTC: the seconds (0-59) in the specified date according to universal time.
  • x__millisecondUTC: the milliseconds (0-999) in the specified date according to universal time.

So, an incoming notification like

{
    "subscriptionId": "51c04a21d714fb3b37d7d5a7",
    "originator": "localhost",
    "contextResponses": [
        {
            "contextElement": {
                "attributes": [
                    {
                        "name": "TimeInstant",
                        "value": "2014-04-29T13:18:05Z"
                    },
                    {
                        "name": "birthdate",
                        "type": "urn:x-ogc:def:trs:IDAS:1.0:ISO8601",
                        "value": "2014-04-29T13:18:05Z"
                    },
                    {
                        "name": "hire",
                        "type": "DateTime",
                        "value": "2016-10-13T12:10:44.149Z"
                    },
                    {
                        "name": "role",
                        "value": "benevolent dictator for life",
                        "metadatas": [{
                            "name": "when",
                            "value": "2014-04-29T13:18:05Z",
                            "type": "DateTime"
                        }]
                    }
                ],
                "type": "employee",
                "isPattern": "false",
                "id": "John Doe"
            },
            "statusCode": {
                "code": "200",
                "reasonPhrase": "OK"
            }
        }
    ]
}

will send to core the "event"

{  
   "noticeId":"799635b0-914f-11e6-836b-bf1691c99768",
   "noticeTS":1476368120971,
   "id":"John Doe",
   "type":"employee",
   "isPattern":"false",
   "subservice":"/",
   "service":"unknownt",
   "TimeInstant":"2014-04-29T13:18:05Z",
   "TimeInstant__ts":1398777485000,
   "TimeInstant__day":29,
   "TimeInstant__month":4,
   "TimeInstant__year":2014,
   "TimeInstant__hour":15,
   "TimeInstant__minute":18,
   "TimeInstant__second":5,
   "TimeInstant__millisecond":0,
   "TimeInstant__dayUTC":29,
   "TimeInstant__monthUTC":4,
   "TimeInstant__yearUTC":2014,
   "TimeInstant__hourUTC":13,
   "TimeInstant__minuteUTC":18,
   "TimeInstant__secondUTC":5,
   "TimeInstant__millisecondUTC":0,
   "birthdate":"2014-04-29T13:18:05Z",
   "birthdate__type":"urn:x-ogc:def:trs:IDAS:1.0:ISO8601",
   "birthdate__ts":1398777485000,
   "birthdate__day":29,
   "birthdate__month":4,
   "birthdate__year":2014,
   "birthdate__hour":15,
   "birthdate__minute":18,
   "birthdate__second":5,
   "birthdate__millisecond":0,
   "birthdate__dayUTC":29,
   "birthdate__monthUTC":4,
   "birthdate__yearUTC":2014,
   "birthdate__hourUTC":13,
   "birthdate__minuteUTC":18,
   "birthdate__secondUTC":5,
   "birthdate__millisecondUTC":0,
   "hire":"2014-04-29T13:18:05Z",
   "hire__type":"DateTime",
   "hire__ts":1398777485000,
   "hire__day":29,
   "hire__month":4,
   "hire__year":2014,
   "hire__hour":15,
   "hire__minute":18,
   "hire__second":5,
   "hire__millisecond":0,
   "hire__dayUTC":29,
   "hire__monthUTC":4,
   "hire__yearUTC":2014,
   "hire__hourUTC":13,
   "hire__minuteUTC":18,
   "hire__secondUTC":5,
   "hire__millisecondUTC":0,
   "role":"benevolent dictator for life",
   "role__metadata__when":"2014-04-29T13:18:05Z",
   "role__metadata__when__type":"DateTime",
   "role__metadata__when__ts":1398777485000,
   "role__metadata__when__day":29,
   "role__metadata__when__month":4,
   "role__metadata__when__year":2014,
   "role__metadata__when__hour":15,
   "role__metadata__when__minute":18,
   "role__metadata__when__second":5,
   "role__metadata__when__millisecond":0,
   "role__metadata__when__dayUTC":29,
   "role__metadata__when__monthUTC":4,
   "role__metadata__when__yearUTC":2014,
   "role__metadata__when__hourUTC":13,
   "role__metadata__when__minuteUTC":18,
   "role__metadata__when__secondUTC":5,
   "role__metadata__when__millisecondUTC":0
}

A rule that will check if the employee has been hired in the last half hour, could be

{
    "name": "rule_time",
    "text": "select *, \"rule_time\" as ruleName from pattern [every ev=iotEvent(cast(cast(hire__ts?,String),float) > current_timestamp - 30*60*1000)]",
    "action": {
        "type": "email",
        "template": "So glad with our new ${role}, ${id}!",
        "parameters": {
            "to": "someone@tid.es",
            "from": "system@iot.tid.es",
            "subject": "Welcome ${id}!"
        }
    }
}