Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change value of a property inside a SubElementCollection via HTTP #1011

Open
thevituu opened this issue Jan 15, 2025 · 4 comments
Open

Change value of a property inside a SubElementCollection via HTTP #1011

thevituu opened this issue Jan 15, 2025 · 4 comments

Comments

@thevituu
Copy link

Hello,

I was trying to change a property value of a property which is inside of a SubElementCollection. I was using the following REST call:
http://localhost:8080/api/v3.0/submodels/{submodel id}/submodel-elements/DigitalOutputs/$value

with the following body:
{ "QX0_0": true }

As a response I am getting:
"'SetSubmodelElementValueByPathRequest' not supported on this server"

Is it in any way possible to change a value of a property at the moment?

Also I was initially trying to change values with the built in OPC UA Server and utilizing the ValueChangeEvent. Changing the value automatically under specific event conditions seems to work, but they don't get changed inside of the OPC UA Server. I suppose this functionality is not implemented yet, am I right?

Thank you.

@mjacoby
Copy link
Member

mjacoby commented Jan 16, 2025

Unfortunately, there has been a bug which disabled updating elements via HTTP PATCH using valueOnly complete.
The issue has been fixed and you can try it out by either using the latest SNAPSHOT version, the latest docker image (:latest and don't forget to do a pull), or checkout the main branch and compile it yourself.

Regarding OPC UA, I am not 100% sure what you are asking/trying to do. You mention the ValueChangeEvent...does this mean you manually create these events and publish them on the message bus?

Simply updating values via OPC UA should totally work and has worked in the past. I did check it after the fix and this is working for me. However, I did not check before applying the fix. My assumption is that this was also affected by the same bug and therefore has been fixed with the latest update.
In case the problem still persist let me know.

@thevituu
Copy link
Author

thevituu commented Jan 17, 2025

Thank you for your answer. I was trying to run it inside my Java Application with the 1.3.0-SNAPSHOT starter. Apprently I am getting the same issue there.

Regarding OPC UA: I have an AAS that looks like the following:

AAS
-SubmodelA
-> SubmodelElementCollectionA
--> Property: Variable_A

-SubmodelB
-> SubmodelElementCollectionB
-->Property: Variable_B

What I want to do:
The value of Variable_B should always synchronize with Variable_A. For this I tried to implement a ValueChange Listener, which executes the variable change of Variable_B whenever Variable_A is being updated.
I was trying to do it as follows:

public class ChangeListener {

    private final Service service;

    public ChangeListener(Service service) throws MessageBusException {
        this.service = service;

        // Subscribe to ValueChangeEventMessage
        this.service.getMessageBus().subscribe(SubscriptionInfo.create(
                ValueChangeEventMessage.class,
                this::handleValueChange
        ));

        System.out.println("ChangeListener initialized and subscribed to MessageBus");
    }

    private void handleValueChange(ValueChangeEventMessage event) {
        try {
            // Print the event for debugging
            System.out.println("Received ValueChangeEventMessage: " + event.getNewValue());

            // Check if the event corresponds to Variable_A
            if (event.getElement() != null &&
                    event.getElement().getKeys().stream()
                            .anyMatch(key -> "Variable_A".equals(key.getValue()))) {

                PropertyValue newValue = (PropertyValue) event.getNewValue();

                // Print the detected value change
                System.out.println("Detected change in Variable_A: " + newValue);

                // Create Reference for IECVariables/Variable1/Value
                List<Key> keys = new ArrayList<>();
                // Key for the Submodel
                keys.add(new DefaultKey.Builder()
                        .type(KeyTypes.SUBMODEL)
                        .value("https://example.com/ids/sm/5503_3151_1052_3375") // Submodel ID
                        .build());

                // Key for SubElementCollection
                keys.add(new DefaultKey.Builder()
                        .type(KeyTypes.SUBMODEL_ELEMENT_COLLECTION)
                        .value("VariableColl") // SubmodelElementCollection ID-Short
                        .build());

                // Key for Property
                keys.add(new DefaultKey.Builder()
                        .type(KeyTypes.PROPERTY)
                        .value("Variable_B") // Property ID-Short
                        .build());

                Reference reference = new DefaultReference.Builder()
                        .type(ReferenceTypes.MODEL_REFERENCE)
                        .keys(keys)
                        .build();

                // 2) Retrieve the current SubmodelElement from persistence
                SubmodelElement existing = service.getPersistence().getSubmodelElement(
                        reference,
                        QueryModifier.DEFAULT
                );
                if (existing instanceof Property property) {
                    property.setValue(newValue.getValue().asString());
                    service.getPersistence().update(reference, property);

                    System.out.println("Updated 'IECVariables/VariableColl/Variable_B' to: " + newValue);
                    SubmodelElement updated = service.getPersistence()
                            .getSubmodelElement(reference, QueryModifier.DEFAULT);
                    System.out.println("AFTER UPDATE - property value in memory: "
                            + ((Property) updated).getValue());
                } else {
                    System.err.println("Referenced element is not a Property or not found!");
                }
            }
        } catch (Exception e) {
            System.err.println("Failed to process value change for Variable_A");
            e.printStackTrace();
        }
    }
}

The event gets recognized and the handle method also gets executed, but the update in the OPC UA Server never happens for Variable_B.

I also tried to change the value with AssetConnectionManager, but I think this would be a wrong approach. Because if I understand correctly, the AssetConnectionManager is for changing values of an asset that it is connected to (e.g. OPC UA Server of a PLC), is that correct?

I'd appreciate some help and hints to flaws in my implementation.

@mjacoby
Copy link
Member

mjacoby commented Jan 20, 2025

The reason your current code is not working is that you are missing to manually publish a ValueChange event after updating the value as those are not generated by the persistence in FA³ST. If you add the following code after your AFTER UPDATE log message your code works

service.getMessageBus().publish(ValueChangeEventMessage.builder()
        .element(reference)
        .oldValue(event.getOldValue())
        .newValue(event.getNewValue())
        .build());

There are other ways you can do this, e.g., using LambaAssetConnection which could look like this

private static final String SUBMODEL_ID = "https://example.com/ids/sm/5503_3151_1052_3375";
    private static final String ID_SHORT_COLLECTION = "VariableColl";
    private static final String ID_SHORT_A = "Variable_A";
    private static final String ID_SHORT_B = "Variable_B";
    private static final int QUEUE_SIZE = 5;

    private static final BlockingQueue<PropertyValue> queue = new ArrayBlockingQueue<>(QUEUE_SIZE);

    public static void main(String[] args) throws Exception {
        Service service = new Service(ServiceConfig.load(FaaastSyncProperties.class.getResourceAsStream("/config.json")));
        Reference referenceA = ReferenceBuilder.forSubmodel(SUBMODEL_ID, ID_SHORT_COLLECTION, ID_SHORT_A);
        Reference referenceB = ReferenceBuilder.forSubmodel(SUBMODEL_ID, ID_SHORT_COLLECTION, ID_SHORT_B);

        ExecutorService executorService = Executors.newSingleThreadExecutor();
        service.getMessageBus().subscribe(SubscriptionInfo.create(
                ValueChangeEventMessage.class,
                message -> {
                    try {
                        queue.put((PropertyValue) message.getNewValue());
                    }
                    catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        System.err.println("Failed to put value in queue: " + e.getMessage());
                    }
                },
                referenceA));

        service.getAssetConnectionManager().registerLambdaSubscriptionProvider(
                referenceB,
                LambdaSubscriptionProvider.builder()
                        .generate(lambda -> executorService.submit(() -> {
                            while (!Thread.currentThread().isInterrupted()) {
                                try {
                                    PropertyValue value = queue.take();
                                    lambda.newDataReceived(value);
                                }
                                catch (InterruptedException e) {
                                    Thread.currentThread().interrupt();
                                    System.err.println("Error taking from queue: " + e.getMessage());
                                }
                            }
                        }))
                        .build());

        service.start();
    }

Please be aware that there are multiple ways a property resp. its value can change and not all of them trigger a ValueChange event., e.g., when the whole element is replaced via PUT or any parent element is modified like someone updating the whole submodel using PUT or similar. To not trigger potentially thousands of messages each time a submodel is updated this way the ValueChange events are only triggered when a submodel element is created, deleted, or updated using PATCH.

@thevituu
Copy link
Author

thevituu commented Jan 21, 2025

Thanks a lot! The OPC UA thing works as expected now.

I tried changing values with HTTP now with the precompile 1.3.0 SNAPSHOT but it seems that it doesn't change the value.

This is the patch call I am executing:
https://localhost:443/api/v3.0/submodels/aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvc20vODAzMV80MTgwXzEwNTJfMjAxNw/submodel-elements/DigitalOutputs/$value

with the following body:

{
  "QX0_0": true
}

After sending the call, I am running the same API Call as GET to get the values, but apparently it didn't change, since I get the following response:

{
    "DigitalOutputs": {
        "QX0_2": false,
        "QX0_3": false,
        "QX0_0": false,
        "QX0_1": false
    }
}

Am I doing something wrong here?

EDIT:

I found my issue, I had to use the following Body in my PATCH request:

{
    "DigitalOutputs": {
        "QX0_0": true
    }
}

But when running the SNAPSHOT as a maven dependency, I am still getting this error:

{
    "messages": [
        {
            "messageType": "Error",
            "text": "'SetSubmodelElementValueByPathRequest' not supported on this server",
            "timestamp": "2025-01-21T12:10:35.182+01:00"
        }
    ]
}

EDIT 2:

After clean installing I get another error with exactly the same API call I used in the precompiled version:

{
    "messages": [
        {
            "messageType": "Exception",
            "text": "java.lang.IllegalStateException: found multiple request mapper matching HTTP method and URL (HTTP method: PATCH, url: submodels/aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvc20vODAzMV80MTgwXzEwNTJfMjAxNw/submodel-elements/DigitalOutputs/$value)",
            "timestamp": "2025-01-21T12:16:37.687+01:00"
        }
    ]
}

The following is my config for my Java App:

ServiceConfig config = ServiceConfig.builder()
                .core(CoreConfig.builder()
                        .requestHandlerThreadPoolSize(2)
                        .build())
                .persistence(PersistenceInMemoryConfig.builder()
                        .initialModelFile(new File(resource.getPath()))
                        .build())
                .endpoint(HttpEndpointConfig.builder().sni(true).ssl(true).port(8080)      
                        .build())
                .messageBus(MessageBusInternalConfig.builder().build())
                .fileStorage(FileStorageInMemoryConfig.builder().build())
                .build();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants