Skip to content

The Support-Rulesengine (Experimental)

Overview

The Support-Rulesengine is an experimental mircoservice that allows you to use a rules-based approach to filter, compare and extract data at the edge and then send it to the cloud for further processing.

The Support-Rulesengine is designed to subscribe to new events from various Device Services via the Edge Xpert message bus. It will cache the latest event readings for each device, and this cache of readings will be the primary data source utilized in the rule evaluation.

Grule Rule Language

The implementation of Support-Rulesengine is based on Grule-Rule-Engine, allowing you to define your own rule in the format of Grule Rule Language(GRL):

rule <RuleName> <RuleDescription> {
    when
        <boolean expression>
    then
        <assignment or operation expression>
}
RuleName: specifies the identifier for a specific rule. The rule name must be unique in the entire Support-Rulesengine, and it must not contain white space.

RuleDescription: specifies the additional information to describes the purpose of the rule. The description must be enclosed in double quotes.

Boolean Expression: specifies the predicate expression that will be evaluated by the rule engine to identify if a specific rule's action should be triggered or not.

Assignment or Operation Expression: specifies the actions that will be triggered when the rule is evaluated to true. Actions can be multiple and each expression should be separated by the ; character.

Info

By default, the Support-Rulesengine will cache the latest reading of events being published to the Edge Xpert message bus via topic edgex/events/device/#. To evaluate these readings in a rule definition, you can use the format of <device name>.<resource name> to refer to the value of a reading. For example, the following rule evaluates if the value of resource FridgeTemperature from device FridgeTempSensor is greater than 8:

rule FridgeTempAbnormal "detect abnormal fridge temperature"{
  when
    FridgeTempSensor.FridgeTemperature > 8
  then
    Actions.PublishSensorFusionEvent("edgex/events/abnormal", "sensor_fusion_profile", "sensor_fusion_device", "sensor_fusion_source", "FridgeTempSensor.FridgeTemperature" );
    Retract("FridgeTempAbnormal");
}
Note that rule parsing errors may occur if your device name and resource name doesn't comply with following principles:
1. The name of the identifier must begin with a letter or an underscore _.
2. The names may contain the letters a-z or A-Z or digits 0-9 as well as the character _.
3. The name of the identifier should not start with a digit.

If it's not possible for your device name and resource name to comply with above principles, the Support-Rulesengine will provide some built-in functions in next experimental release to work around this limitation.

Built-in Functions

GRL allows you to specify function call in the rule definition, and a bunch of built-in functions have been defined to be used. In addition to these built-in functions as provided by as provided by Grule-Rule-Engine, IOTech also implements additional built-in functions as listed below:

Actions.PublishSensorFusionEvent

Actions.PublishSensorFusionEvent(publishTopic string, profileName string, deviceName string, sourceName string, deviceResources ...string)
Actions.PublishSensorFusionEvent will publish a new event to Edge Xpert message bus via specified mqtt topic. The new event will have specified profile name, device name, and source name. The new event will also contain the latest readings associated with the specified variadic device resources., which should be specified in the format of <device name>.<resource name>. For example, Actions.PublishSensorFusionEvent("edgex/events/abnormal", "sensor_fusion_profile", "sensor_fusion_device", "sensor_fusion_source", "RFIDScanner.ProductRFIDScanNumber", "BarcodeReader.ProductBarcode", "VisualInference.ClassId") will publish a new event with following values:

  • profile name: sensor_fusion_profile
  • device name: sensor_fusion_device
  • source name: sensor_fusion_source
  • three readings:
    • ProductRFIDScanNumber from device RFIDScanner
    • ProductBarcode from device BarcodeReader
    • ClassId from device VisualInference

Actions.SetDeviceResourceValue

Actions.SetDeviceResourceValue(deviceName string, resourceName string, value string)
Actions.SetDeviceResourceValue will set the value for specified device's resource. For example, Actions.SetDeviceResourceValue("BarcodeReader", "ProductBarcode", "10001") will set the value of resource ProductBarcode from device BarcodeReader to 10001.

Actions.SendNotification

Actions.SendNotification(content, description, severity, category string, labels ...string)
Actions.SendNotification will send a notification to Edge Xpert notification service.

The parameters of Actions.SendNotification are described below:

  • content: The message sent to the notification receivers
  • description: Human readable description explaining the reason for the notification or alert
  • severity: An enumeration string indicating the severity of the notification, valid values are MINOR, NORMAL and CRITICAL
  • category: A string categorizing the notification
  • labels: Array of associated means to label or tag a notification for better search and filtering

Default Configuration

Similarly to other EdgeX microservices, the Support-Rulesengine uses the same common configuration. By default, the Support-Rulesengine uses the following configuration:

[Writable]
LogLevel = "INFO"
  [Writable.InsecureSecrets]
    [Writable.InsecureSecrets.DB]
    path = "redisdb"
      [Writable.InsecureSecrets.DB.Secrets]
        username = ""
        password = ""
[Service]
HealthCheckInterval = "10s"
Host = "localhost"
Port = 59862
ServerBindAddr = "" # Leave blank so default to Host value unless different value is needed.
StartupMsg = "This is the Rules Engine Microservice"
MaxResultCount = 1024
MaxRequestSize = 0 # Not curently used. Defines the maximum size of http request body in bytes
RequestTimeout = "5s"
  [Service.CORSConfiguration]
  EnableCORS = false
  CORSAllowCredentials = false
  CORSAllowedOrigin = "https://localhost"
  CORSAllowedMethods = "GET, POST, PUT, PATCH, DELETE"
  CORSAllowedHeaders = "Authorization, Accept, Accept-Language, Content-Language, Content-Type, X-Correlation-ID"
  CORSExposeHeaders = "Cache-Control, Content-Language, Content-Length, Content-Type, Expires, Last-Modified, Pragma, X-Correlation-ID"
  CORSMaxAge = 3600

[Registry]
Host = "localhost"
Port = 8500
Type = "consul"

[Clients]
  [Clients.core-metadata]
  Protocol = "http"
  Host = "localhost"
  Port = 59881

  [Clients.core-command]
  Protocol = "http"
  Host = "localhost"
  Port = 59882

[Databases]
  [Databases.Primary]
  Host = "localhost"
  Name = "rulesengine"
  Port = 6379
  Timeout = 5000
  Type = "redisdb"

[MessageQueue]
Protocol = "mqtt"
Host = "localhost"
Port = 1883
Type = "mqtt"
AuthMode = "none"
SecretName = "mqtt-bus"
SubscribeEnabled = true
SubscribeTopic = "edgex/events/device/#"  # required for subscribing to Events from MessageBus
  [MessageQueue.Optional]
  # Default MQTT Specific options that need to be here to enable evnironment variable overrides of them
  # Client Identifiers
  ClientId ="support-rulesengine"
  # Connection information
  Qos          =  "0" # Quality of Sevice values are 0 (At most once), 1 (At least once) or 2 (Exactly once)
  KeepAlive    =  "10" # Seconds (must be 2 or greater)
  Retained     = "false"
  AutoReconnect  = "true"
  ConnectTimeout = "5" # Seconds
  # TLS configuration - Only used if Cert/Key file or Cert/Key PEMblock are specified
  SkipCertVerify = "false"

[SecretStore]
Type = "vault"
Protocol = "http"
Host = "localhost"
Port = 8200
Path = "support-rulesengine/"
TokenFile = "/tmp/edgex/secrets/support-rulesengine/secrets-token.json"
RootCaCertPath = ""
ServerName = ""
  [SecretStore.Authentication]
  AuthType = "X-Vault-Token"
  [SecretStore.RuntimeTokenProvider]
  Enabled = false
  Protocol = "https"
  Host = "localhost"
  Port = 59841
  TrustDomain = "edgexfoundry.org"
  EndpointSocket = "/tmp/edgex/secrets/spiffe/public/api.sock"
  RequiredSecrets = "redisdb"

Example

The example below uses Support-Rulesengine to check if the product barcode from a barcode reader and the product scan number from an RFID scanner have the same value. If the values are different, the Support-Rulesengine will publish a new event with these two readings to Edge Xpert message bus through edgex/events/abnormal topic.

  1. Launch Virtual Device Service to simulate two devices--the barcode reader and the RFID scanner:
    edgexpert up device-virtual 
    
  2. Prepare a profile to simulate the barcode reader as shown below, and then save the profile as profile.barcode.yaml:

    name: "Simulated-Barcode-Reader"
    manufacturer: "IOTech"
    model: "Device-Virtual-UnsignedInt"
    labels:
      - "simulator"
    description: "Simulated Barcode Reader for Intel Visual Inference Project"
    
    deviceResources:
      -
        name: "EnableRandomization_Barcode"
        isHidden: true
        description: "used to decide whether to re-generate a random value"
        properties:
          valueType: "Bool"
          readWrite: "W"
          defaultValue: "false"
      -
        name: "ProductBarcode"
        isHidden: false
        description: "Product Barcode in 5-digit unsigned integer value"
        properties:
          valueType: "Uint16"
          readWrite: "RW"
          minimum: "10001"
          maximum: "10003"
          defaultValue: "10001"
    
    deviceCommands:
      -
        name: "WriteProductBarcode"
        readWrite: "W"
        isHidden: false
        resourceOperations:
          - { deviceResource: "ProductBarcode" }
          - { deviceResource: "EnableRandomization_Barcode", defaultValue: "false" } 
    

    Info

    Note that the profile specifies a data range between 10001~10003 for ProductBarcode.

  3. Add profile.barcode.yaml into the Core Metadata:

    export METADATA_HOST=`docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' core-metadata`
    curl -X POST -F "file=@profile.barcode.yaml" http://$METADATA_HOST:59881/api/v2/deviceprofile/uploadfile
    

  4. Prepare a new device request payload to simulate the barcode reader as shown below, and then save the payload as device-barcode.json:

    [
      {
        "apiVersion": "v2",
        "device" : {
          "name" :"BarcodeReader",
          "description":"Simulated Barcode Reader",
          "adminState":"UNLOCKED",
          "operatingState":"UP",
          "protocols": {
            "other": {
              "Address": "device-virtual-uint-01",
              "Protocol": "300"
            }
          },
          "labels":[
            "intel"
          ],
          "serviceName":"device-virtual",
          "profileName":"Simulated-Barcode-Reader"
        }
      }
    ]
    

  5. Add device-barcode.json into the Core Metadata:

    export METADATA_HOST=`docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' core-metadata`
    curl -X POST http://$METADATA_HOST:59881/api/v2/device --data-binary "@device-barcode.json"
    

  6. Prepare a profile to simulate the RFID scanner as shown below, and then save the profile as profile.rfid.yaml:

    name: "Simulated-RFID-Scanner"
    manufacturer: "IOTech"
    model: "Device-Virtual-UnsignedInt"
    labels:
      - "simulator"
    description: "Simulated RFID Scanner for Intel Visual Inference Project"
    
    deviceResources:
      -
        name: "EnableRandomization_RFIDScanner"
        isHidden: true
        description: "used to decide whether to re-generate a random value"
        properties:
          valueType: "Bool"
          readWrite: "W"
          defaultValue: "false"
      -
        name: "ProductRFIDScanNumber"
        isHidden: false
        description: "Product Number in 5-digit unsigned integer value"
        properties:
          valueType: "Uint16"
          readWrite: "RW"
          minimum: "10001"
          maximum: "10003"
          defaultValue: "10001"
    
    deviceCommands:
      -
        name: "WriteProductRFIDScanNumber"
        readWrite: "W"
        isHidden: false
        resourceOperations:
          - { deviceResource: "ProductRFIDScanNumber" }
          - { deviceResource: "EnableRandomization_RFIDScanner", defaultValue: "false" }
    

    Info

    Note that the profile specifies a data range between 10001~10003 for ProductRFIDScanNumber.

  7. Add profile.rfid.yaml into the Core Metadata:

    export METADATA_HOST=`docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' core-metadata`
    curl -X POST -F "file=@profile.rfid.yaml" http://$METADATA_HOST:59881/api/v2/deviceprofile/uploadfile
    

  8. Prepare a new device request payload to simulate the RFID scanner as shown below, and then save the content as device-rfid.json:
    [
      {
        "apiVersion": "v2",
        "device" : {
          "name" :"RFIDScanner",
          "description":"Simulated RFID Scanner",
          "adminState":"UNLOCKED",
          "operatingState":"UP",
          "protocols": {
            "other": {
              "Address": "device-virtual-uint-01",
              "Protocol": "300"
            }
          },
          "labels":[
            "intel"
          ],
          "serviceName":"device-virtual",
          "profileName":"Simulated-RFID-Scanner"
        }
      }
    ]
    
  9. Add device-rfid.json into the Core Metadata:
    export METADATA_HOST=`docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' core-metadata`
    curl -X POST http://$METADATA_HOST:59881/api/v2/device --data-binary "@device-rfid.json"
    
  10. Launch Support-Rulesengine via Edge Xpert CLI utility:
    edgexpert up support-rulesengine 
    
  11. Prepare a rule definition as shown below, and then save the content as productMismatch.grl:

    rule ProductMismatch "Detect Product Mismatch" {
      when 
        RFIDScanner.ProductRFIDScanNumber != BarcodeReader.ProductBarcode
      then
        Actions.PublishSensorFusionEvent("edgex/events/abnormal", "sensor_fusion_profile", "sensor_fusion_device", "sensor_fusion_source", "RFIDScanner.ProductRFIDScanNumber", "BarcodeReader.ProductBarcode");
        Retract("ProductMismatch");
    }
    

    Info

    Above rule definition defines a rule ProductMismatch. The when section evaluates if the resource ProductRFIDScanNumber from the device RFIDScanner and the resource ProductBarcode from device BarcodeReader have different values. Note that two actions will be triggered: Actions.PublishSensorFusionEvent and Retract.

  12. Add productMismatch.grl into Support-Rulesengine through REST APIs:

    export RULESENGINE_HOST=`docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' support-rulesengine`
    curl -X POST http://$RULESENGINE_HOST:59862/api/v2/rule --data-binary "@productMismatch.grl"
    

  13. Now, we can send out simulated events to start the rule evaluation process:

    export COMMAND_HOST=`docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' core-command`
    curl -X GET http://$COMMAND_HOST:59882/api/v2/device/name/RFIDScanner/ProductRFIDScanNumber?ds-pushevent=yes
    curl -X GET http://$COMMAND_HOST:59882/api/v2/device/name/BarcodeReader/ProductBarcode?ds-pushevent=yes
    

    Note

    Please note that above commands will simulate two events: one for ProductRFIDScanNumber and the other for ProductBarcode and the actions of rule ProductMismatch will not be triggered when these two events have identical reading value. You may have to execute these commands several times to simulate more events with different reading values, so that the actions of ProductMismatch can be triggered.

  14. To monitor if the action has been triggered, we can launch mosquitto_sub to subscribe for the new events passed into the message bus through edgex/events/abnormal topic:

    mosquitto_sub -t edgex/events/abnormal
    
    Occasionally, if the resource ProductRFIDScanNumber from the device RFIDScanner and the resource ProductBarcode from device BarcodeReader have different value, a new event should be received as shown below:
    {"ReceivedTopic":"","CorrelationID":"","ApiVersion":"v2","RequestID":"","ErrorCode":0,"Payload":"eyJhcGlWZXJzaW9uIjoidjIiLCJyZXF1ZXN0SWQiOiIyY2EyYTU1Yy1iN2M5LTQyYTctYTIwMi1iMjZlNDRhMzVjM2IiLCJldmVudCI6eyJhcGlWZXJzaW9uIjoidjIiLCJpZCI6ImZiZjMxZDUyLWI2ZWMtNGI3Yi1hMWZhLThjNDVhMWRhYTBmYSIsImRldmljZU5hbWUiOiJzZW5zb3JfZnVzaW9uX2RldmljZSIsInByb2ZpbGVOYW1lIjoic2Vuc29yX2Z1c2lvbl9wcm9maWxlIiwic291cmNlTmFtZSI6InNlbnNvcl9mdXNpb25fc291cmNlIiwib3JpZ2luIjoxNjY4NjExNTg2NDI2NzY3NzY3LCJyZWFkaW5ncyI6W3siaWQiOiJlMzNmODllMC02NjI4LTRjNTMtOWI1Zi0yYjg4Zjg0NTIxNjgiLCJvcmlnaW4iOjE2Njg2MTE1ODQ0MDg4NTk4NzgsImRldmljZU5hbWUiOiJSRklEU2Nhbm5lciIsInJlc291cmNlTmFtZSI6IlByb2R1Y3RSRklEU2Nhbk51bWJlciIsInByb2ZpbGVOYW1lIjoiU2ltdWxhdGVkLVJGSUQtU2Nhbm5lciIsInZhbHVlVHlwZSI6IlVpbnQxNiIsInZhbHVlIjoiMTAwMDMifSx7ImlkIjoiMzkwZmJlOGQtNmNiOC00ZDVkLTk0NzUtYTA3ZmI5MTY2NjllIiwib3JpZ2luIjoxNjY4NjExNTg2NDI2MjQ4MDgwLCJkZXZpY2VOYW1lIjoiQmFyY29kZVJlYWRlciIsInJlc291cmNlTmFtZSI6IlByb2R1Y3RCYXJjb2RlIiwicHJvZmlsZU5hbWUiOiJTaW11bGF0ZWQtQmFyY29kZS1SZWFkZXIiLCJ2YWx1ZVR5cGUiOiJVaW50MTYiLCJ2YWx1ZSI6IjEwMDAxIn1dfX0=","ContentType":"application/json","QueryParams":{}}
    

  15. Once we decide to stop and remove the rule evaluation, we can remove ProductMismatch through REST APIs:
    export RULESENGINE_HOST=`docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' support-rulesengine`
    curl -X DELETE http://$RULESENGINE_HOST:59862/api/v2/rule/name/ProductMismatch