# Alarms and Events

In this tutorial, you will learn how to develop a simple edge module that sends alarms and events. We will also receive the alarm on the north side of the ABB Ability™ Platform through a hot path subscription to verify if our operation succeeded. It assumes that you are familiar with basic edge development concepts described in the document, Standard edge module operations. You might find it useful to get familiar with the following articles as well:

# Overview

Sending an alarm or an event is analogical to sending timeseries. There are however a few important differences that must be remembered. A quick comparison reveals that the topic used when sending alarms and events is similar topics_messages_out/type=(alarm|event). However, the message content is different. To see an example message structure please refer to the edge reference module on the Platform documentation site. An alarm, or an event, can pass multiple custom properties disallowed in time series messages. Keep in mind that the Platform delivers events, alarms and telemetry on separate channels.

# Prerequisites

# Triggering An Alarm

An alarm feature of the ABB Ability™ Platform is created for an easy way of reporting abnormal behavior from the device to the cloud. You can raise an alarm for things like: overheating, excessive load of your device, or any sensor read-out that you set as outside of a normal operational range. Ideologically an alarm suits a different purpose than telemetry messages, but it is digested by the Platform in a similar manner. In the next section of this tutorial we will subscribe to the service bus (hot path) to monitor the alarms. The same steps can be applied to read telemetry updates.

To trigger an alarm, we need to send an appropriate message to the topic constructed by concatenating the topics_messages_out environment variable with the /type=alarm string. We call it a property bag, suffixed topic.

The next step is to find out the proper json format for an alarm. The structure of the json object can be seen below in the sample payload that will be sent by the module compiled from the 02-events-alarms project:

{
   "objectId":"ccf1aa10-4401-4ce7-b5b1-71e181dc8354",
   "model":"abb.ability.device",
   "timestamp":"2019-02-13T15:54:22.8134682Z",
   "alarm":"TempToHigh",
   "value":{
    "value":"105",
    "active":"true"
   }
}

Notice that the root value property is an object type. It has a nested value property which is where you pass the telemetry connected to the alarm, but it also has an active property which is a custom property that is passed by the Platform without any semantics verification and without any computation being done. You can define as many properties in the root value object as you want, but remember that you are responsible for making sense out of the data that you pass. In our example, we are passing an active property to be able to deactivate the alarm after the parameters of the device come back to be within a normal operational range.

Let's see how the described functionality is used in our sample project. The program simulates the telemetry of a connected sensor. If the read-outs exceed a certain threshold value, an alarm will be raised. Because the implementation is similar to the sending telemetry module we will look only at the parts that are the core of the alarms operations.

The first class that we are going to look into is an AlarmMessage class that is used to produce the alarm date time objects(DTOs). By the usage of the [JsonProperty()] attributes, the JsonConvert.SerializeObject() method is able to serialize the objects into the json payload, seen below.

The alarm DTO class:

public class AlarmMessage<T>
    {
        public class AlarmMessageValue
        {
            public AlarmMessageValue(Func<T> valueGenerator, bool active)
            {
                Value = valueGenerator != null ? valueGenerator() : default(T);
                Active = active;
            }

            [JsonProperty("value", NullValueHandling = NullValueHandling.Ignore)]
            public T Value { private set; get; }

            [JsonProperty("active")]
            public bool Active { private set; get; }
        }

        public AlarmMessage(Guid deviceId, string alarmName, bool active, Func<T> valueGenerator = null)
        {
            ObjectId = deviceId.ToString();
            Model = "abb.ability.device";
            Timestamp = DateTime.UtcNow.ToString("o");
            Value = new AlarmMessageValue(valueGenerator, active);
            Alarm = alarmName;
        }

        [JsonProperty("objectId")]
        public string ObjectId { get; private set; }

        [JsonProperty("model")]
        public string Model { get; private set; }

        [JsonProperty("timestamp")]
        public string Timestamp { get; private set; }

        [JsonProperty("value")]
        public AlarmMessageValue Value { get; private set; }

        [JsonProperty("alarm")]
        public string Alarm { get; private set; }

        [JsonProperty("active")]
        public bool Active { get; private set; }
    }

The next important functionality contains an alarmsTopic variable initialization, serializing the AlarmMessage DTO and publishing the actual message. Below is the functionality for how to send the alarm in our module:

public async void SendAlarm(double Temp)
{
    var alarmsTopic = $"{Configuration.MessagesOutTopic}/type=alarm";

    var msg = JsonConvert.SerializeObject(new AlarmMessage<double>(_deviceId, "TempToHigh", true, () => Temp));
    await _mqttClient.PublishAsync(alarmsTopic, msg).ContinueWith((e) => Console.WriteLine($"Published message to topic '{alarmsTopic}': {msg}"));
}

Let's look into the telemetry simulation and logic. This is the place where we fire the previously described methods. If the simulated temperature exceeds ALARM_TEMPERATURE_TRIGGER_THRESHOLD the alarm is triggered by invoking SendAlarm(randTemp) function. If it drops below ALARM_TEMPERATURE_CANCEL_THRESHOLD it gets canceled. The alarmActive property stores the current state of the alarm so that we know whether we need to update the digital twin with the alarm state change.

public async override void StartSendingTelemetryAsync(dynamic parameter)
{
   Console.WriteLine("Starting events and alarms publishing...");
   var rand = new Random();

   while (true)
   {
       //Generate random temperature to simulate the sensor reading
       var randTemp = Math.Round(rand.NextDouble() * 150, 2);

       if (randTemp > ALARM_TEMPERATURE_TRIGGER_THRESHOLD)
       {
           SendAlarm(randTemp);
           alarmActive = true;
       } else if (randTemp < ALARM_TEMPERATURE_CANCEL_THRESHOLD && alarmActive)
       {
           CancelAlarm();
           alarmActive = false;
       }
       await Task.Delay(10000);
   }
}

Let's explore how we cancel that alarm with the CancelAlarm() implementation:

public async void CancelAlarm()
{
       var alarmsTopic = $"{Configuration.MessagesOutTopic}/type=alarm";

       var msg = JsonConvert.SerializeObject(new AlarmMessage<double>(_deviceId, "TempToHigh", false));
       await _mqttClient.PublishAsync(alarmsTopic, msg).ContinueWith((e) => Console.WriteLine($"Published message to topic '{alarmsTopic}': {msg}"));
}

In our implementation, we just sent the alarm again with the same alarm identifier (alarm field), but we set the active property to false. This is an example of how it is possible to define your own properties and use them for your specific use case. In our scenario, we wanted to be able to cancel the alarm after the device behaves normally again. On the north side of the Platform we apply our logic to interpret the field that we defined.

To summarize, for the alarm to be triggered, there needs to be a message being published on the topics_messages_out/type=alarm topic. That means that the edge module is responsible for figuring out when to fire the alarm. Due to all of the logic being applied on the data on the client premises directly on the edge device, we can limit the amount of the telemetry that is being pushed to the cloud which lowers the costs of ABB Ability™ Platform operations. The alarms are delivered to the client apps through the Instance API on a separate channel, so you can have a separate module that is responsible for the alarms handling without any filtering required. That gives you the ability to apply the "single responsibility principle" in your code.

# Sending Events

You can treat an alarm as a very specific form of an event. If we view alarms that way, then you will be familiar with the concepts of an event. However, don't get tricked into thinking you can just copy and paste the same code without applying any changes.

A trap ahead!

Sending an event requires a different topic. Use topics_messages_out/type=event instead.

A trap ahead!

Although similar, the event json doesn't have an alarm property anymore. For an identifier, use the event field.

Here is an example event DTO:

public class EventMessage<T>
    {
        public EventMessage(Guid deviceId, string eventName, Func<T> valueGenerator)
        {
            ObjectId = deviceId.ToString();
            Model = "abb.ability.device";
            Timestamp = DateTime.UtcNow.ToString("o");
            Value = valueGenerator();
            Event = eventName;
        }

        [JsonProperty("objectId")]
        public string ObjectId { get; private set; }

        [JsonProperty("model")]
        public string Model { get; private set; }

        [JsonProperty("timestamp")]
        public string Timestamp { get; private set; }

        [JsonProperty("value")]
        public T Value { get; private set; }

        [JsonProperty("event")]
        public string Event { get; private set; }
    }

Sending the event:

var eventsTopic = $"{Configuration.MessagesOutTopic}/type=event";
var msg = JsonConvert.SerializeObject(new EventMessage<double>(Guid.Parse(Configuration.ObjectId), "abb.ability.eventsModule.started", () => 0));
await _mqttClient.PublishAsync(eventsTopic, msg).ContinueWith((e) => Console.WriteLine($"Published message to topic '{eventsTopic}': {msg}"));

# Running the Sample Module

There are two ways to run this custom module. CST provides types for a given version of Ability Platform. If your CST contact has not already loaded these types into your Ability environment, these types can be manually uploaded to the environment. Once the types are loaded into the instance, any supported Edge setup can be used to run the Edge types provided by the tutorials.

Once you are familiar with the Edge, it is possible to build custom versions of the tutorials. Once a custom modules is created and the type(s) are loaded into an instance, any valid Edge can run the custom type.

# Receiving The Events and Alarms

If you wrote your module that sends alarms, it is useful to know how to check whether everything works. That way, you can verify the alarms and events are delivered to the Platform as expected. One way to do this is to use the hot path subscription API. The hot path subscription API endpoint can be perceived as a live feed of data meant to receive the data on the north side (cloud application end) of the Platform. If the operation succeeds, then the entire system is working correctly.

For this purpose, we can use a simple program described in this tutorial. Let's use the following configuration for out subscription configuration:

{
    "baseUrl": "https://<Instance API Host Name>.azurewebsites.net/api/v1/", // Fill in your platform name
    "endPoint": "Subscriptions", // Leave this to connect to Subscriptions endpoint
    "type": "alarms", // Set type of subscription. Available values are platformEvents, variables, alarms, events and corresponds to which service bus topic in which to setup the subscription.
    "subscriptionTTL": "60", // Duration for which the subscription will run in minutes (Min: 5)
    "subscriptionFilter": "objectId='ccf1aa10-4401-4ce7-b5b1-71e181dc8354' AND alarm='TempToHigh'", // Filter for hot path messages.
    "sasTokenTTL": "60" // Time that the sas token used for authentication to the platform will remain valid in minutes (Min: 5)
}

This is the example configuration from the subscriptions tutorial, but with the type field set to alarms and a filter to receive only the data that originated from your device. To check the events, simply set the type fields to events and prepare an analogical filter.

If your module is sending the time series data and your configuration is correct, you should receive similar output:

Creating subscription...
Subscription created.
Subscription client connecting to hot path telemetry...
Client connected.
Retrieving hot path data...
Received message: SequenceNumber:56013520365420545 Body:{
   "objectId":"ccf1aa10-4401-4ce7-b5b1-71e181dc8354",
   "model":"abb.ability.device",
   "timestamp":"2019-03-19T08:16:27.0051513Z",
   "alarm":"TempToHigh",
   "value":{
    "value":"105",
    "active":"true"
   }
}
Received message: SequenceNumber:26177172834091009 Body:{
   "objectId":"ccf1aa10-4401-4ce7-b5b1-71e181dc8354",
   "model":"abb.ability.device",
   "timestamp":"2019-03-19T08:16:27.0051513Z",
   "alarm":"TempToHigh",
   "value":{
    "value":"60",
    "active":"false"
   }
}

While troubleshooting, don't forget about the docker service logs command, especially if you will be using it against edge-Proxy, edge-Broker, and your own module.

Last updated: 1/10/2022, 11:05:26 AM
Feedback