Alarming directly from a module

Can someone provide an example on how to use alarming within a module?

https://docs.inductiveautomation.com/display/SE/Working+with+Platform+Services

We want to be able to set an active alarm when a function call fails without having to create and/or update a tag every time there is an alarm.

Any help would be appreciated

Josh

Seems like you were most of the way there in this thread:

What happened? Where did you get stuck?

Thanks Kevin,

I can’t get the alarm to show as active and unacknowledged specifically through the [System]Gateway/Alarming/Active and Acked tag or through the Alarm Log.

So I can put it in the Journal as Active, but it doesn’t show it as an active alarm. I have been trying to get an answer on this for a while and since there was no activity on my original post I made a new one.

Josh

Can you post the current code you have?

Here is a collection of code I have tried to use. LIFT is the name of our product we are developing. The liftGatewayContext is the gatewayContext object for our gateway part of the module. It is probable that this is a combination of things and it won’t work in its current state. This is currently not being used in our project and hasn’t really been looked at in a few weeks.

public void createAlarm() {
        try {

            // Qualified Path
            var addr = InetAddress.getLocalHost();
            String hostname = addr.getHostName();
            QualifiedPath.Builder qualifiedPathBuilder = new QualifiedPath.Builder();
            qualifiedPathBuilder.setAlarm("Test Alarm");
            qualifiedPathBuilder.setMachine(hostname);
            qualifiedPathBuilder.setProvider("LIFT");
            qualifiedPathBuilder.setEvent("1");
            //qualifiedPathBuilder.setSource(source);
            //qualifiedPathBuilder.setUser(user);
            //qualifiedPathBuilder.setTag("testAlarm");
            QualifiedPath qualifiedPath = qualifiedPathBuilder.build();
            logger.info("Qualified Path " + qualifiedPath.toString());

            BasicPropertySet bsp = new BasicPropertySet();
            EventData eventData = new EventData();
            eventData.set(CommonAlarmProperties.Label, "LIFT Alarm Test");
            eventData.set(AlarmModeProperties.Mode, AlarmMode.Equality);
            eventData.set(AlarmModeProperties.SetpointA, 1.0);
            eventData.set(CommonAlarmProperties.IsSystemEvent, true);
            eventData.set(CommonAlarmProperties.EventValue, 1.0);
            BasicAlarmEvent alarmEvent = new BasicAlarmEvent(qualifiedPath, "LIFT", AlarmPriority.Low);
            alarmEvent.active(eventData);
            liftGatewayContext.getAlarmManager().getJournalManager().storeEvent(alarmEvent, AlarmStateTransition.Active);

            alarmEvent.acknowledge(eventData);
            liftGatewayContext.getAlarmManager().getJournalManager().storeEvent(alarmEvent, AlarmStateTransition.Ack);
            eventData.set(CommonAlarmProperties.EventValue, 0.0);
            alarmEvent.clear(eventData);
            liftGatewayContext.getAlarmManager().getJournalManager().storeEvent(alarmEvent, AlarmStateTransition.Clear);



            // Parent Properties
            BasicPropertySet basicPropertySet = new BasicPropertySet();

            // Alarm Definition
            BasicAlarmDefinition alarmDefinition = new BasicAlarmDefinition();
            alarmDefinition.set(CommonAlarmProperties.DisplayPath,"LIFT/Alarms");
            alarmDefinition.set(CommonAlarmProperties.Enabled, true);
            alarmDefinition.set(CommonAlarmProperties.IsSystemEvent, true);
            alarmDefinition.set(CommonAlarmProperties.Label, "Test");
            alarmDefinition.set(CommonAlarmProperties.Name, "LIFT Alarm Test");
            alarmDefinition.set(CommonAlarmProperties.Priority, AlarmPriority.Low);
            alarmDefinition.set(CommonAlarmProperties.Source, qualifiedPath);
            alarmDefinition.set(AlarmModeProperties.Mode, AlarmMode.Equality);
            alarmDefinition.set(AlarmModeProperties.SetpointA, 1.0);
            logger.info("Alarm Definition " + alarmDefinition.getName() + " " + alarmDefinition.getRawValueMap().toString());

            // Alarm Configuration
            BasicAlarmConfiguration alarmConfiguration = new BasicAlarmConfiguration();
            alarmConfiguration.add(alarmDefinition);
            logger.info("Alarm Configuration " + alarmConfiguration.get("LIFT Alarm Test").getProperties());

            // Expression Context
            ExpressionContext expressionContext = null;

            AlarmEvaluator alarmEvaluator = LiftGatewayContext.get().getGatewayContext().getAlarmManager().registerAlarm(qualifiedPath, basicPropertySet, alarmConfiguration, null);
            logger.info("Alarm Evaluator " + alarmEvaluator.getState().getEvaluationStatus().toString());

            // Evaluate
            BasicQualifiedValue basicQualifiedValue = new BasicQualifiedValue();
            basicQualifiedValue.setValue(1.0);
            basicQualifiedValue.setQuality(QualityCode.Good);
            basicQualifiedValue.setTimestamp(DateTime.now().toDate());

            alarmEvaluator.evaluate(basicQualifiedValue);
            logger.info("Alarm Evaluator " + alarmEvaluator.getState().getEvaluationStatus().toString());
            alarmEvaluator.release();
        }
        catch (Exception ex) {
            logger.error("Create Alarm Failed", ex);
        }
    }

Not sure what all that event creation and manual storing in the journal is for. I don't think that would be needed if the Alarm is properly created and then triggered/evaluated by Ignition.

@eric.hollering had it right in the original thread - all you should really need is to register an alarm via AlarmManager::registerAlarm with a configuration that would always cause the alarm to trip and then whenever you want to fire it just call AlarmEvaluator::evaluate with some value.

Do you mean that an alarm triggered by the method mentioned previously doesn't show up in the alarm status table?

Kevin,

When doing the above code examples, I can get the alarm in the journal, but not in the alarm status table and it doesn’t impact the System\Gateway\Alarming tags which we are using for some UI elements.

I think we are struggling with what needs to go into the registerAlarm.

An example of setting up a
QualifiedPath
PropertyValueSource
AlarmConfiguration
ExpressionParseContext
etc

The documentation doesn’t provide much in the way of an example for how to use registerAlarm.

  1. We have a class in our module
  2. We have functions in that class
  3. We would like to register an alarm for the class
  4. When an exception or error occurs, we would like to trigger an alarm.

That’s primarily what we are looking for.

Does that make sense?

Josh

I have confirmed the following example code works.

Create an AlarmDefinition:

private static class TestAlarmDefinition extends BasicAlarmDefinition {

    public TestAlarmDefinition() {
        UUID id = UUID.randomUUID();
        setName("MyAlarm/" + id);
        set(AlarmModeProperties.Mode, AlarmMode.AnyChange);
        set(AlarmModeProperties.OnEachEvaluation, Boolean.TRUE);
        set(CommonAlarmProperties.NotifyInitialEvent, Boolean.TRUE);
        set(CommonAlarmProperties.DisplayPath, id.toString());
        set(CommonAlarmProperties.Priority, AlarmPriority.High);
    }

}

Create the evaluator and trigger the alarm:

QualifiedPath path = QualifiedPath.of(
    WellKnownPathTypes.Source, "MyModule",
    WellKnownPathTypes.Alarm, "MyAlarm"
);
var definition = new TestAlarmDefinition();
var configuration = new BasicAlarmConfiguration(Collections.singletonList(definition));

AlarmEvaluator evaluator = getContext().getAlarmManager().registerAlarm(
    path,
    null,
    configuration,
    null
);

getContext().getScheduledExecutorService().scheduleAtFixedRate(
    () -> {
        LoggerFactory.getLogger("AlarmTest").info("Evaluating alarm...");

        evaluator.evaluate(new BasicQualifiedValue(Boolean.TRUE));
    },
    10, 10, TimeUnit.SECONDS
);

These show up in the alarm status table and the alarm system tags. Because it uses an “any change” alarm the event is always cleared, not active, but it is unacknowledged.

1 Like

Thanks @Kevin.Herron, we appreciate the example and are excited to give this a shot. I know it might seem a little obscure, but being able to leverage alarming decoupled from the tag structure has some pretty important implications for certain customer scenarios, and keeping it within the existing platform toolset will be a good win for us.

We have successfully implemented this after reviewing the example.

Thanks again.

1 Like

Make sure that you release the evaluator during your module shutdown or when you are otherwise done generating alarms, not immediately like in the previous code samples you provided.

We are very close, but noticed an odd problem. Can I get someone else’s thoughts?

The Alarm Log looks correct in regards to DisplayPath and Label

However, that is not translating to the Alarm Journal

Any input would be appreciated.

public void raiseAlarm(String code, String message, AlarmPriority priority, String displayPath, String label, String notes) {
        var alarmDefinition = new BasicAlarmDefinition();
        UUID id = UUID.randomUUID();
        alarmDefinition.setName(code + "/" + id);
        // AnyChange means this will fire whenever the evaluator's evaluate method is called.
        // Because there's no value source to evaluate against, the state will always be cleared.
        // If, in the future, we want to set an Active alarm state rather than cleared, we can use AlarmMode.Equality
        //   or similar with a setpoint property, and the active state can be driven by the value passed into the evaluate function.

        // For now, we've kept this simple, with the thinking that the alarm state would be cleared in these cases,
        // since the driving event (a code exception / processing error condition) will be fired and require a manual intervention.

        // It might be especially interesting to consider using the active state in a case where a failure may be able to
        // be resolved through automation.  For example, a failed message to do an inventory move could allow for automated retries,
        // and in the event that a retry succeeds, the alarm could clear.

        // Here's an example of an equality config, looking for a 1 as SetpointA
        alarmDefinition.set(AlarmModeProperties.Mode, AlarmMode.Equality);
        alarmDefinition.set(AlarmModeProperties.SetpointA, 1d);

        //alarmDefinition.set(AlarmModeProperties.Mode, AlarmMode.AnyChange);
        //alarmDefinition.set(AlarmModeProperties.OnEachEvaluation, Boolean.TRUE);
        alarmDefinition.set(CommonAlarmProperties.NotifyInitialEvent, Boolean.TRUE);
        alarmDefinition.set(CommonAlarmProperties.EventId, id);
        alarmDefinition.set(CommonAlarmProperties.Label, label);
        alarmDefinition.set(CommonAlarmProperties.DisplayPath, displayPath);
        alarmDefinition.set(CommonAlarmProperties.Priority, priority);
        alarmDefinition.set(CommonAlarmProperties.Notes, notes);

        QualifiedPath path = QualifiedPath.of(
                WellKnownPathTypes.Source, "LIFT Module",
                WellKnownPathTypes.Alarm, code
        );

        var configuration = new BasicAlarmConfiguration(Collections.singletonList(alarmDefinition));

        var evaluator = this.getGatewayContext().getAlarmManager().registerAlarm(
                path,
                null,
                configuration,
                null
        );

        evaluator.setAlarmObserver(this);

        evaluators.put(id, evaluator);

        evaluator.evaluate(new BasicQualifiedValue(Boolean.TRUE));
    }

You seem to be creating a new definition and registering a new evaluator every time you call raiseAlarm, which I assume is every time you want to generate an alarm, and that seems problematic to me and may be the cause of your issue.

Note that my example creates one definition and one evaluator and then re-uses it each time it generates a new alarm event using the evaluator.

Currently, that is true, but we are also releasing the evaluator on acknowledge as we are clearing the alarm as well. Does that still make this an issue?

/**
     * LIFT Alarm Transitioned State
     * @param alarmEvent - Alarm Event that occurred
     * @param alarmStateTransition - Current Alarm State
     */
    @Override
    public void alarmTransitioned(AlarmEvent alarmEvent, AlarmStateTransition alarmStateTransition) {
        if (alarmStateTransition == AlarmStateTransition.Ack) {
            logger.debug("Alarm (" + alarmEvent.getName() + ") Acknowledged");
            alarmEvent.clear(alarmEvent.getAckData());
            logger.debug("Alarm (" + alarmEvent.getName() + ") Cleared");
            // Get evaluator and remove from map
            UUID id = UUID.fromString(alarmEvent.getName());
            evaluators.get(id).release();
            evaluators.remove(id);
        }
    }

Make the changes and find out. You shouldn’t be releasing on ack either - release when your module shuts down or you are otherwise done ever firing that alarm. Each time you create a new definition you are defining a new alarm with a new UUID and I’m wondering if the journal doesn’t work because by the time it’s there viewing it you’ve unregistered and moved onto a new UUID.

Also, your additional config is probably showing up as “static” config to the alarm journal system, and therefore not being logged to the journal unless you specifically configured your alarm journal to do so.

So, after what Paul said, we enabled Static Config to be stored in the journal

We also changed the alarmTransitioned code to reevaluate the alarm before releasing it.

/**
     * LIFT Alarm Transitioned State
     * @param alarmEvent - Alarm Event that occurred
     * @param alarmStateTransition - Current Alarm State
     */
    @Override
    public void alarmTransitioned(AlarmEvent alarmEvent, AlarmStateTransition alarmStateTransition) {
        if (alarmStateTransition == AlarmStateTransition.Ack) {
            logger.debug("Alarm (" + alarmEvent.getName() + ") Acknowledged");
            //alarmEvent.clear(alarmEvent.getAckData());

            // Get evaluator and remove from map
            UUID id = UUID.fromString(alarmEvent.getName());
            evaluators.get(id).evaluate(new BasicQualifiedValue(Boolean.FALSE));
            logger.debug("Alarm (" + alarmEvent.getName() + ") Cleared");
            evaluators.get(id).release();
            logger.debug("Alarm (" + alarmEvent.getName() + ") Evaluator Released");
            evaluators.remove(id);
        }
    }

This is what the journal looks like now and it looks how I would expect it. Is there a better way to do this?