Using external definitions in real-time rules

This guide describes how to use cell-level .NET Core external event handlers in the real-time rules engine.

Differences between site- and cell-level external definitions

In SmartSpace 3.5 the real-time rules engine was introduced, allowing rules and event handlers to be executed at cell-level in a disk-independent host.

Since SmartSpace 3.6, the external definition API has made it possible to implement site-level event handlers using .NET Core.

In SmartSpace 3.7 the external definition API is extended to support cell-level .NET Core external event handlers running in the real-time rules engine.

Site-level and cell-level external event handlers are evaluated in different ways. The site-level event handlers are hosted in a service (the ‘External plugin host’) that is separate from the user data store; whereas the cell-level external event handlers are hosted together with the cell-level rules and event handlers in the Real-time rules engine service host (the ‘Cellular object property data’ service and the cell-level external event handlers are executed in the same transaction as the cell-level internal event handlers.

Installation

All support for cell-level external event handlers is installed at the same time as the site-level external event handlers, and there is no additional installation overhead.

For further information on installing the external definition API, see Installing .NET API.

Worked example

In this example we will look at creating a cellular event handler that works together with some features of ACS to create entries in the user data store when some underlying ACS events occur.

Defining a cellular external event handler

Any event handler is cellular if it either uses or sets a cellular property. In this example, we have a cellular property “acs interaction between ‘ACS Object’ and ‘ACS Object’”

and we define an external event handler “Find interactions from ACS” that uses and sets the cellular property. Because it refers to a cellular property, this event handler is automatically labeled as cellular by SmartSpace:

Code generation

The code generation for cellular external event handler plugins works in the same way as the site-level plugins (see External Definition API (ubisense.com)). In this case, the code generator creates the following C# source files:

These comprise the Accessor and KeyRow definitions for the referenced complex property, together with the definition of the type ‘UEnterprise.UserDefined’, which is the internal name for the published type ‘ACS Object’. They also comprise the Accessor and KeyRow for the internal ‘Contains’ relation, that provides an interface between the rules engine and the underlying spatial monitor.

How to use the internal ‘Contains’ relation

The Contains relation is a projection of the USpatial.Monitor.Contains relation, which holds all the requested interactions between objects in the given spatial cell. An interaction is requested if it has been referred to in a cellular rule by using the ‘contains’ operator, or if it is explicitly requested by code in the plugin, using some features packaged up in the class CellularUtils:

namespace Ubisense.UDMAPI{
    public static class CellularUtils
    {
        public static UObject GetProcessCell();
        public static void SetSpatialRequest(string property1, string property2, bool add = true);
        public static string UdmSpatialPropertyToRole(string property);
        public static void UpdateSpatialRules();
    }
}

The SetSpatialRequest operation is able to add (or remove) requests for new pairs of spatial properties to be pushed into the Contains relation; property1 denotes the container, and property2 denotes the contained property. The normal usage is to build up a set of pairs as a request, which is then acted on in response to the UpdateSpatialRules operation.

In our example program we will go through the existing list of monitor requests (i.e. all pairs of properties currently requested to be monitored by the spatial monitor) and add requests for every property in which the container includes the string ‘ACS::IdentPoint’, which will give us all interactions in which an ‘ACS Ident Zone’ contains another object:

using Requests = Ubisense.USpatial.MonitorRequests; 
namespace Ubisense.UDMAPI
{
    public class FindInteractionsFromACS : FindInteractionsFromAcsWrapper
    {
        public FindInteractionsFromACS ()
        {
            // Connect to the inheritance database (because we will use
            // Narrow in the event handler)
            UBase.Inheritance.Globalise();
 
            // Register an event handler for updates of the Contains relation
            Contains.update += Contains_update;
 
            // Request to be informed of all currently-monitored interactions
            // where an Ident Point is the container.
            const string ident_point = "ACS::IdentPoint";
 
            // Connect to the spatial monitor requests schema to find all
            // the relevant monitored interactions.
            using Requests.Schema requests = new Requests.Schema(false);
            requests.ConnectAsClient();
            var xact = requests.ReadTransaction();
 
            // Each monitored interaction that contains the ident point 
            // identifier in its container results in a request.
            foreach (var row in Requests.Relations.key_(xact))
                if (row.container_.ToString().Contains(ident_point))
                    CellularUtils.SetSpatialRequest(row.container_.ToString(),
                                                    row.contained_.ToString());
 
            // When all the requests are made, they are committed using this call
            CellularUtils.UpdateSpatialRules();
        }

Now, whenever an interaction begins (or ends) involving an ‘ACS Ident Zone’ as container, the Contains relation will be updated, so the Contains.update event will be triggered. In our event handler, we check that we are dealing with ACS Objects, and if so we assert the value in the cellular property that we defined earlier:

using Requests = Ubisense.USpatial.MonitorRequests; 
namespace Ubisense.UDMAPI
{
    public class FindInteractionsFromACS : FindInteractionsFromAcsWrapper
    {
        public FindInteractionsFromACS ()
        {
            // Connect to the inheritance database (because we will use
            // Narrow in the event handler)
            UBase.Inheritance.Globalise();
 
            // Register an event handler for updates of the Contains relation
            Contains.update += Contains_update;
 
            // Request to be informed of all currently-monitored interactions
            // where an Ident Point is the container.
            const string ident_point = "ACS::IdentPoint";
 
            // Connect to the spatial monitor requests schema to find all
            // the relevant monitored interactions.
            using Requests.Schema requests = new Requests.Schema(false);
            requests.ConnectAsClient();
            var xact = requests.ReadTransaction();
 
            // Each monitored interaction that contains the ident point 
            // identifier in its container results in a request.
            foreach (var row in Requests.Relations.key_(xact))
                if (row.container_.ToString().Contains(ident_point))
                    CellularUtils.SetSpatialRequest(row.container_.ToString(),
                                                    row.contained_.ToString());
 
            // When all the requests are made, they are committed using this call
            CellularUtils.UpdateSpatialRules();
        }

Combining external and internal event handlers

It is normally more convenient to use the built-in business rules language to develop simple application logic if possible, and there are no restrictions on mixing internal and external event handlers, so it makes sense to build applications from a combination of the two. In this example, we update a site-level assertion “’ACS Object’ has monitored interaction with ‘ACS Object’: Bool”, as controlled by a property “’ACS Object’ is monitored : Bool”:

This will ensure that, whenever an ident zone has the __ is monitored property set, all of its ACS-level interactions will result in assertions at site level.

How to build and run the plugin

The cellular plugins are built in the same way that site level plugins are built, using ‘dotnet publish’ to create a suitable .NET Core output.

All plugins (site-level and cell-level) are loaded from the same external plugin directory, and all the plugin host services (i.e. the site-level "External plugin host" service and the cell-level "Cellular object property data" service) lock all the plugins in the external plugin directory. Therefore, if you want to overwrite a plugin during development, it is necessary to stop all these services, then delete the plugin and replace it with an updated version, otherwise file locking will prevent the change from occurring.

Running the example

The example code is executed as part of a visualization package for various ACS zones. In the scenario shown below, there are three vehicles inside ident zones.

In the object browser, with no cellular data loaded, we can see that two of the zones are monitored, and so two of the zones are subject to interaction assertions:

If the relevant spatial cell is loaded, we see that all three of the ident zones have interactions (as expected), and monitoring the ident zone 0652 in addition results in a third assertion being created:

 

To understand the operation of the cellular real-time rules engine and the cellular external definitions, it is important to understand where the various data are stored and evaluated. In this case, although some of the relevant data is stored at site level, all the functionality is run at cell level:

  Site level Cell level

Storage

‘__ is monitored’ property

‘__ has monitored interaction __’ assertion (managed by assertion store and synchronized with user data model)

‘acs interaction between __ and __’ property

Evaluation

 

External event handler that uses ‘Contains’ to set the ‘acs interaction between __ and __’ property

Event handlers that use ‘__ is monitored’ and ‘acs interaction between __ and __’ to set the ‘__ has monitored interaction __’ assertion

For more details of how this works, and why it is structured in this way, see Ubisense real-time rules: concepts and configuration.