>>>>>>>>>>>>>>>>>>>>>>>>>>>> Example Tag Actor/Factory >>>>>>>>>>>>>>>>>>>>>>>>>>>> This is a simple example that adds a boolean configuration property to all atomic (normal) tags. It then reacts to that property being set to true, to observe value changes for the tag. >>>>>>>>>>>>>>>>>>>>>>>>>>>>> Registration >>>>>>>>>>>>>>>>>>>>>>>>>>>>> In our module setup, we would call: context.getTagManager().getConfigManager().registerActorFactory(new ExampleActorFactory()); >>>>>>>>>>>>>>>>>>>>>>>>>>>>> Class >>>>>>>>>>>>>>>>>>>>>>>>>>>>> package com.inductiveautomation.ignition.gateway.tags.actors.factories.example; import java.util.Set; import com.inductiveautomation.ignition.common.config.BasicDescriptiveProperty; import com.inductiveautomation.ignition.common.config.BasicProperty; import com.inductiveautomation.ignition.common.config.MutableConfigurationPropertyModel; import com.inductiveautomation.ignition.common.config.Property; import com.inductiveautomation.ignition.common.config.PropertySet; import com.inductiveautomation.ignition.common.config.VersionedPropertySet; import com.inductiveautomation.ignition.common.i18n.LocalizedString; import com.inductiveautomation.ignition.common.model.values.QualifiedValue; import com.inductiveautomation.ignition.common.tags.model.TagPath; import com.inductiveautomation.ignition.common.util.LoggerEx; import com.inductiveautomation.ignition.gateway.model.GatewayContext; import com.inductiveautomation.ignition.gateway.tags.evaluation.ActorClassification; import com.inductiveautomation.ignition.gateway.tags.evaluation.ConfigurationException; import com.inductiveautomation.ignition.gateway.tags.evaluation.ShutdownReason; import com.inductiveautomation.ignition.gateway.tags.evaluation.TagActor; import com.inductiveautomation.ignition.gateway.tags.evaluation.TagActorFactory; import com.inductiveautomation.ignition.gateway.tags.evaluation.nodes.NodeContext; /** * This example provides a Tag Actor that is managed based on the setting of a boolean property that it provides. * It keeps track of all tags that currently have the property set to "true". */ public class ExampleActorFactory implements TagActorFactory { private static final LoggerEx LOGGER = LoggerEx.newBuilder().build("tags.actors.example"); //Tags can only have 1 active actor per classification. Classification also dictates the position of the actor in //the list. We need a unique id (the system ids only go to 1200) and by using a large number, this one will be at //the end. This makes it an "observer", which means it should not change the value. private static final ActorClassification CLASSIFICATION = new ActorClassification("example", 200001); //This is our only configuration property. We'll register it on configuration model, which will make it editable //on the tag, and then we'll monitor it for changes by returning it from "getMonitoredProperties". //Note that in this example, we don't have a localization bundle available in the designer scope, so we can't really //use proper bundle keys. Instead, we use raw LocalizedStrings to still provide better category & display name //info. Remember that everything will actually be stored according to the property id. private static final Property ENABLE_MONITORING = new BasicDescriptiveProperty<>("example.monitor", LocalizedString.createRaw("Example Property"), LocalizedString.createRaw("Examle Category"), null, Boolean.class, false); private GatewayContext context; @Override public ActorClassification getActorClassification() { return CLASSIFICATION; } @Override public void startup(GatewayContext gwContext) { this.context = gwContext; LOGGER.infof("Example actor factory started up."); } @Override public void shutdown() { this.context = null; LOGGER.infof("Example actor factory shut down."); } @Override public void configureTagModel(MutableConfigurationPropertyModel model) { //We add this as a simple property available to all *atomic* tags. In other words, not applicable to //root UDT instances or Folders model.addDependantProperties(WellKnownTagProps.TagType, TagObjectType.AtomicTag, ENABLE_MONITORING); } @Override public Set> getMonitoredProperties() { return Set.of(ENABLE_MONITORING); } @Override public boolean isApplicable(PropertySet config) { //We could just check for the presence of our property. But by doing this, anything set to false will simply //shut down the previous actor. return config.getOrDefault(ENABLE_MONITORING); } @Override public TagActor create(NodeContext context, PropertySet config) throws ConfigurationException { LOGGER.infof("Creating example actor for tag '%s'", context.getPath()); return new ExampleActor(); } /** * This is what is created and runs for each tag. It is initialized, and then receives each value change through * processValue. */ protected class ExampleActor implements TagActor { TagPath thisPath; public ExampleActor() { } @Override public ActorClassification actorClassification() { return CLASSIFICATION; } @Override public void initialize(NodeContext context, PropertySet configuration) { thisPath = context.getPath(); LOGGER.infof("Example actor initialized for '%s'", thisPath); } @Override public void destroy(NodeContext context, ShutdownReason reason) { LOGGER.infof("Example actor destroyed for '%s'", context.getPath()); } @Override public boolean attemptConfiguration(NodeContext context, VersionedPropertySet config) { return false; } @Override public boolean onPathChanged(TagPath newPath) { //We could manage the path change here and return "True". By returning false, the system will simply //rebuild the actor. return false; } @Override public QualifiedValue processValue(QualifiedValue value) { //Remember that this is called as part of tag value processing, and should not take much time or block. //If you want to use the value in a way that will take more time, you should add it to your own queue of //data to process. LOGGER.infof("Processing value for path '%s', value=%s", thisPath, value); //We return the value unmodified. return value; } } }