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>
}
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");
}
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.
- Launch Virtual Device Service to simulate two devices--the barcode reader and the RFID scanner:
edgexpert up device-virtual
-
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
. -
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
-
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" } } ]
-
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"
-
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
. -
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
- 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" } } ]
- 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"
- Launch Support-Rulesengine via Edge Xpert CLI utility:
edgexpert up support-rulesengine
-
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
. Thewhen
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. -
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"
-
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 ofProductMismatch
can be triggered. -
To monitor if the action has been triggered, we can launch
mosquitto_sub
to subscribe for the new events passed into the message bus throughedgex/events/abnormal
topic: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:mosquitto_sub -t edgex/events/abnormal
{"ReceivedTopic":"","CorrelationID":"","ApiVersion":"v2","RequestID":"","ErrorCode":0,"Payload":"eyJhcGlWZXJzaW9uIjoidjIiLCJyZXF1ZXN0SWQiOiIyY2EyYTU1Yy1iN2M5LTQyYTctYTIwMi1iMjZlNDRhMzVjM2IiLCJldmVudCI6eyJhcGlWZXJzaW9uIjoidjIiLCJpZCI6ImZiZjMxZDUyLWI2ZWMtNGI3Yi1hMWZhLThjNDVhMWRhYTBmYSIsImRldmljZU5hbWUiOiJzZW5zb3JfZnVzaW9uX2RldmljZSIsInByb2ZpbGVOYW1lIjoic2Vuc29yX2Z1c2lvbl9wcm9maWxlIiwic291cmNlTmFtZSI6InNlbnNvcl9mdXNpb25fc291cmNlIiwib3JpZ2luIjoxNjY4NjExNTg2NDI2NzY3NzY3LCJyZWFkaW5ncyI6W3siaWQiOiJlMzNmODllMC02NjI4LTRjNTMtOWI1Zi0yYjg4Zjg0NTIxNjgiLCJvcmlnaW4iOjE2Njg2MTE1ODQ0MDg4NTk4NzgsImRldmljZU5hbWUiOiJSRklEU2Nhbm5lciIsInJlc291cmNlTmFtZSI6IlByb2R1Y3RSRklEU2Nhbk51bWJlciIsInByb2ZpbGVOYW1lIjoiU2ltdWxhdGVkLVJGSUQtU2Nhbm5lciIsInZhbHVlVHlwZSI6IlVpbnQxNiIsInZhbHVlIjoiMTAwMDMifSx7ImlkIjoiMzkwZmJlOGQtNmNiOC00ZDVkLTk0NzUtYTA3ZmI5MTY2NjllIiwib3JpZ2luIjoxNjY4NjExNTg2NDI2MjQ4MDgwLCJkZXZpY2VOYW1lIjoiQmFyY29kZVJlYWRlciIsInJlc291cmNlTmFtZSI6IlByb2R1Y3RCYXJjb2RlIiwicHJvZmlsZU5hbWUiOiJTaW11bGF0ZWQtQmFyY29kZS1SZWFkZXIiLCJ2YWx1ZVR5cGUiOiJVaW50MTYiLCJ2YWx1ZSI6IjEwMDAxIn1dfX0=","ContentType":"application/json","QueryParams":{}}
- 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