Create Customizer with Property dropdown

I want the properties native to the target component, as well as the custom properties a user might have added. Here is what I have so far as a test:

public void printComponent(Dataset mappings){
        if (mappings != null) {
            for (int i = 0; i < mappings.getRowCount(); i++) {
                String tagPath  = mappings.getValueAt(i, 0).toString();
                String compPath = mappings.getValueAt(i, 1).toString();
                logger.info("Component: {}\n Tagpath: {}", view.getComponentByPath(compPath).toString(), tagPath);
                AbstractVisionComponent component = view.getComponentByPath(compPath);
                component.setPropertyValue("value", 1000);
            }
        }
    }

Ultimately I want the user to be able to pass in the component path and property name, then I dynamically set those values from the internals of the module.

I am running into a couple issues.

  1. Some components are PMI components and others are AbstractVisionComponents, meaning that not all components have a Java property setter that I can access.
  2. For some reason using component.setPropertyValue("value", integerVariable) does not work for a component that clearly has a 'value' property.

The component I am testing on is a native LED Display component. Any thoughts on how I can fix this? I am able to collect the components from the root container, I am just struggling with setting a property of a given name on that component.

Did you look at the DynamicPropertyProvider interface that Paul linked? If a component doesn't implement that, either directly or via inheritance, it doesn't support custom properties.

component.setPropertyValue() is a method of that interface, and is only for custom properties.

Any conventional NetBeans-compliant property needs to be written with its normal java setter method. Indirectly, perhaps, if using a helper class.

The complexity is hidden for you in Jython thanks to the PyComponentWrapper for custom properties/methods, and jython's native handling of java NetBeans-compliant getter/setter methods.

Outside of jython, you have to wire this stuff up yourself. (Except for bindings. Where IA has done all the hard work for you, as long as you structure your application to pull data into a component, not to push it.)

If you were following my advice with the client tag provider shim, this would all be moot. Did you abandon that approach?

I have not abandoned the idea. I had already implemented a method for retrieving historical values and wanted to finish what I started before trying something else. The client tag provider shim has some complications that I have not sorted out just yet, like

  1. if I can have multiple client tag providers at once
  2. how to manage inserting/deleting tags into the client shim so I don't bog the system with a duplicate of every tag
  3. How to give my component client context that can handle the creation and enablement/disablement of the client shim
  4. How to feed historical values into each client tag in an efficient/thread-safe manner
  1. Sure

  2. You cannot avoid making a wrapper for every subscribed tag.

  3. You cannot remove a shim. Once installed, "disabling" a shim means telling all of the subscription wrappers to simply pass through to the gateway tag provider.

  4. I would expect you to have a thread pool of your own to query history and feed it into the system at your virtual timestamp.

All of that makes sense except 2. Don't I have to dynamically decide what tags to be subscribed to? Or are you saying that every subscribed tag simply means every tag that is already pulling a value in my default tag provider?

No. The shim will receive all requests from anywhere in the client for tags from a given provider. Simple reads and writes would be intelligently handled (when simulating) or passed through (when back on real time). Subscriptions persist, so would need to decide what to do as any relevant state changes. That can only happen if every tag subscription gets wrapped.

1 Like

Can I 'subscribe' a client tag to my historical data? Would it even be considered a subscription if I am simply mass-writing tags with historical values? Another relevant question is about UDTs. Could a client tag provider handle UDT instances, or would I have to mimic the UDT instance with a file structure?

  1. Your shim would have to emulate realtime for the historical data.

  2. You wouldn't be mass-writing historical data. It would still be a subscription. Your shim would have to pace delivery appropriately.

  3. You shouldn't have to do anything special for UDTs.

I'll stop here. I'm not going to architect/write this code for you, and we are at that point.

2 Likes

I never expected you to write the code. I now have a working version that just writes directly to the component property and have moved on to experimenting with the tag provider. Thank you for all your help.

Update:

I have created the tag wrapper, scheduler, and client tag provider that does what I want. However, when I try to register the new client tag provider over the top of 'default' (using the same name as an attempt to override) it is successful, but in order to receive the new values in a component I have to refresh the binding on that component. Is there a way to programmatically refresh a binding?

That means you aren't wrapping subscriptions properly. The point of a subscription is that you can send new values whenever appropriate.

If a subscriptions value source changes, how is the subscription itself supposed to know that without refreshing? This is my 'subscribe' method in the wrapper:

   public CompletableFuture<Void> subscribe() {
        if (isSubscribed) {
            return CompletableFuture.completedFuture(null);
        }

        logger.info("Subscribing wrapper to default provider for tag: {}", tagPath);
        return defaultProvider.subscribeAsync(
            Collections.singletonList(tagPath),
            Collections.singletonList(this)
        ).thenRun(() -> {
            isSubscribed = true;
            logger.info("Successfully subscribed wrapper to default provider for tag: {}", tagPath);
        });
    }

"this" refers to the instance of the wrapper, which implements TagChangeListener.

Your wrapper should have been given a tag change listener to which to deliver notifications. Your wrapper should pass on notifications from the real tag provider when not simulating, and should generate any desired notifications when simulating.

1 Like

I have my program in a state where 'default' is being overwritten with my new provider and historical values are making it to the tags. However, because the realtime tag changes are always executing, both my historical tag changes and the realtime tag changes are being written into the tag at the same time. How can I differentiate between realtime tag change events and historical tag change events while simulating?

Your shim should be receiving real time tag changes all the time. Your shim should be generating historical events only when your simulation state says to. Your shim is entirely in control of this, and should be able to intelligently decide what events to pass on to the real consumer.

Ah I understand. I have been mixing up where I should override the tagChanged() method. I will try it from the provider and see how it goes.

No matter what I have tried, I am still required to refresh the binding in order to receive my newly created values into a component property. After reviewing the debug logs, I noticed this:

    09:57:47.790 [DesignerExecEngine-1] DEBUG tags.subscriptions -- Subscribing, paths= 
    [[default]Blink 1.browseValue, [default]Blink.browseValue, [default]Blink 2.browseValue, [default]Blink 3.browseValue, listeners=[Blink 1, Blink, Blink 2, Blink 3, default]

I also noticed this:

09:58:31.220 [AWT-EventQueue-0] DEBUG vision.binding.tag-binding -- [component=LED Display 15,property=value] Subscribing to "[default]Blink 1" (LED Display 15.value)
09:58:31.220 [AWT-EventQueue-0] DEBUG tags.subscriptions -- Subscribing, paths=[[default]Blink 1], listeners=[[default]Blink 1 --> LED Display 15.value]

This happens upon window load. Currently I am running my tests on [default]Blink 1. Even if I do initialize my tag subscriptions correctly, it all happens after the binding has been initialized between the existing 'default' provider and the component. The binding system has no reason to relinquish the binding between the component and the original 'default' tag provider (which still exists underneath the client provider I instantiate).

I know that my code works for processing realtime values when the simulation is running, because the component only receives events from my simulation. Then when I disable the simulation, the property value returns to displaying realtime values. But this ONLY WORKS if I first refresh the binding after activating the simulation.

So my question remains. Either I need to programmatically refresh the binding, initialize my simulation tag provider so it takes precedent automatically, or change my tagChanged override somehow. Here is my tagChanged override:

    @Override
    public void tagChanged(TagChangeEvent event) {
        QualifiedValue newValue = event.getValue();
        
        if (newValue == null) {
            logger.warn("Received null value in tagChanged for tag: {}", tagPath);
            return;
        }
        
        // Check if this is a historical or real-time value
        if (!processingHistoricalValue) {
            // This is a real-time value
            lastRealTimeValue = newValue;
            logger.debug("Received real-time value for tag {}: {}", tagPath, newValue);
            
            // Only notify listeners of real-time values when not in simulation mode
            if (!simMode) {
                logger.debug("Forwarding real-time value (not in simulation mode) for tag: {}", tagPath);
                notifyListeners(newValue);
            } else {
                logger.debug("Ignoring real-time value (in simulation mode) for tag: {}", tagPath);
            }
        } else {
            // This is a historical value from the presenter
            logger.debug("Received historical value for tag {}: {}", tagPath, newValue);
            if (simMode) {
                logger.debug("Forwarding historical value (in simulation mode) for tag: {}", tagPath);
                notifyListeners(newValue);
            } else {
                logger.debug("Ignoring historical value (not in simulation mode) for tag: {}", tagPath);
            }
        }
    }

Could I use something like public CompletableFuture<List<QualityCode>> reinitializeTagsAsync(List<TagPath> paths)?

That doesn't look at all like what I would expect for a subscription object. I would expect your subscription object to:

  • Handle tag changes from the real time system at all times, by capturing the last received value unconditionally, and calling the consumer's listener when not simulating. (That you have listeners, plural, is a big red flag that you are doing this wrong.)

  • Handle tag changes from your simulation system (via some method other than .tagChanged() that only your sim will call) by simply relaying them to the consumer's listener.

  • Handle transitions to and from simulation mode by sending the appropriate value to the consumer's listener (which is why you need to capture real time values all the time).

You mentioned the 'consumer' in the context of both the realtime provider and my shim provider. You also mentioned that the consumer has a singular listener. Can you elaborate on what you mean by 'consumer'?