# 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
- The sample code
- .NET Core SDK
- Your favorite IDE or text editor
- .NET Core CLI tools
# 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.