Properly subscribing to Tag Changes from an Expression

Okay, I'm stumped, or probably more likely beating my head against a wall because I should be going a different direction.

I have a resource which accepts an expression, to utilize this I have created an expression which will take the path to the resource and run the expression (Think runScript only not script). This all works except that I can't get it to update past the initial execution.

So my questions are:

  1. Is this a good approach, or should I be going more of a Named Query Binding route? If that's the case where do I need to look for a starting point to adding a Type of Binding?

  2. If this is a reasonable approach, where do I need to look to have an expression which updates on Tag and Property changes? I understand that I need a Change Listener of some sort, what I can't seem to find is what shape that needs to take. I have looked into a BoundTagExpression but I can't seem to get that to work.

I've used Expression Parsing? for a starting point.

The ultimate solution in that thread which @Kevin.Herron supplied is no longer valid as we no longer use SQLTags as of V8.

What I think, I need to do is use GatewayContext.getTagManager().subscribeAsync(), but, honestly I'm not sure.

I am interested in how I would go about adding a binding type either way, as I feel that might be a better user experience.

.subscribeAsync() is the right path to follow, but I see grief in your future if you try to re-implement expression bindings. Is there a reason these bindings cannot be set up in the designer? Can you elaborate on how this flows?

1 Like

What I am working towards is not intended to be a re-implementation of expression bindings, but an extension to them. The problem I'm trying to solve is when there are "generalized" expression bindings which perhaps only differ by the tag path used or property, but are used across multiple components.

So for instance you have a complex expression with many branches or what not, something like a status indicator for a motor or valve. With the exception of the tag/property which is being examined the expression is identical. Current work flow is to copy and paste bindings or retype them in some cases and then edit them to look at the correct tag/property.

So at this point, you create a resource and then use an expression binding with a special expression which calls the resource.

Hmm. An expression "macro" function. I will have to ponder this. The are two obvious hurdles:

  • "Registering" a "compiled" expression to a macro name, and

  • Accessing arguments to the macro within the macro.

The former might need a designer dialog to create project resources.

The latter could probably use a special function, arg() perhaps, that takes an argument index to look up the arg in a thread local.

Non-trivial, but cool idea.

This is currently what I'm working on, almost there, just a few more hurdles.

I believe I have addressed this, with no small amount of inspiration from your Automation Toolkit Module

Pretty sure this is par for the course.

1 Like

Okay, so I have perhaps gone down a different road in my navigation of this particular hurdle. Looking through the API, it looks like what I'm really after is implementing InteractionListener on my class, and then using the connect() method.

This is working, as far as the childInteractionUpdated() method is being called, however, what I am struggling to do now is actually update the value.

Am I missing something obvious?

When stepping through the code, I can see the correct tag value being read, and returned, however, the component value (at least in the designer, I haven't checked in a client yet but since I am doing this testing in Vision I would expect it to be very similar), never updates.

Sounds backwards.

The typical pattern is to receive an InteractionListener when your function's connect() method is called. Which you save and use to retrigger the expression containing your function. If appropriate. A function's .execute() method is called by its containing expression to actually perform a calculation.

Keep in mind that the kinds of functions that use a connected InteractionListener are the ones that either a) poll at some interval, or b) subscribe to some other event source for which new values require recomputing.

Also keep in mind that the .connect() operation does not happen to anything inside one of my iteration function's repeating subexpressions. Iteration is fundamentally incompatible with re-triggering. You probably should approach "macro" execution with the same limitation.

1 Like

Okay, I am back to this.

I believe I understand what you're trying to tell me, however, I suppose I am unsure of how to apply it.

The issue that I have, is that my function potentially only takes a single string parameter (the path to the resource). I was hoping to allow for the use of tag paths in the expression structure there.

All of this works when the binding is first fired. However, I can not get tag changes to cause a re-trigger of my function, because the argument in the expression is (correctly) a constant expression.

In other words, because the resource path doesn't change so the containing expression is never re-evaluated.

If I force the use of an argument for anything that needs to be dynamically updated, then everything works as expected, it just felt clunky to me to force a Tag to be passed in as an argument. Perhaps I am causing myself pain for something that isn't achievable as an extension.

You must parse your expression to identify the "changeable" items and subscribe to them. When the subscriptions deliver new values, you stash them in your function instance and tell the InteractionListener to trigger. Your code then gets asked to recompute itself and deliver the result to the caller.

Your code must be smart enough to notice when the expression itself changes, so can unsubscribe from things it no longer needs, and to unsubscribe from everything when the function is shut down.

I should repeat that my iteration functions explicitly disallow any nested subscription expressions (nested functions never receive the .connect() call).

I suspect you will choke on the need to track subscriptions per macro caller, not just per macro definition.